Java tutorial
/** * Copyright (c) 2010-2014, openHAB.org and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.proserv.internal; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.UnsupportedEncodingException; import org.openhab.config.core.ConfigDispatcher; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.types.Command; import org.openhab.core.types.State; import java.math.BigDecimal; import java.math.RoundingMode; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Dictionary; import java.util.Enumeration; import java.util.Map; import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.openhab.binding.proserv.ProservBindingProvider; import org.openhab.binding.proserv.ProservBindingProvider; import org.openhab.binding.proserv.internal.ProservCronJobs; import org.openhab.binding.proserv.internal.ProservCronJobs.CronJob; import org.openhab.core.binding.AbstractActiveBinding; import org.openhab.io.net.exec.ExecUtil; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.openhab.action.mail.internal.Mail; //import org.openhab.action.nma.internal.NotifyMyAndroid; /** * The proServ binding connects to a proServ device with the * {@link ProservConnector} and read the internal state array every minute. With * the state array each binding will be updated. * * @author JEKA * @since 1.0.0 */ @SuppressWarnings("unused") public class ProservBinding extends AbstractActiveBinding<ProservBindingProvider> implements ManagedService { private static final Logger logger = LoggerFactory.getLogger(ProservBinding.class); private static ProservConnector connector = null; ProservXConnect proservXConnect = new ProservXConnect(); /** Default refresh interval (currently 65 sec) */ private long refreshInterval = 65000L; /* The IP address to connect to */ static String ip; private static int port = 80; private static String mailTo = ""; private static String shellRestartCommand = ""; private static String mailSubject = ""; private static String mailContent = ""; private static Boolean previousEmailTrigger[][] = new Boolean[18][16]; private static String language = null; private static String oldLanguage = null; private static String chartItemRefreshHour = null; private static String chartItemRefreshDay = null; private static String chartItemRefreshWeek = null; private static String chartItemRefreshMonth = null; private static String chartItemRefreshYear = null; private static boolean bIsFirstRefresh = true; static boolean bLogHashTees = false; private static boolean bHasReadHashTeesOnce = false; private static ProservData proservData = null; private static ProservCronJobs proservCronJobs = new ProservCronJobs(); public void deactivate() { connector.stopMonitor(); if (connector != null) { connector.disconnect(); } connector = null; } public void activate() { super.activate(); setProperlyConfigured(true); } private boolean isSupportedLanguage(String language) { if (language.equals("en") || language.equals("de") || language.equals("fr")) { return true; } if (!language.equals("xx")) { logger.error("Unsupported language: {}", language); } return false; } /** * {@inheritDoc} */ @SuppressWarnings("rawtypes") public void updated(Dictionary config) throws ConfigurationException { if (config != null) { boolean needsRefresh = false; String ip = (String) config.get("ip"); String portString = (String) config.get("port"); ProservBinding.mailTo = (String) config.get("mailto"); ProservBinding.language = (String) config.get("language"); if (ProservBinding.language != null) { if (!isSupportedLanguage(ProservBinding.language)) ProservBinding.language = "en"; } else { logger.error("Mising config proserv:language"); ProservBinding.language = "en"; ProservData.writeConfigData("proserv:language", ProservBinding.language); } if (ProservBinding.oldLanguage == null) needsRefresh = true; else if (ProservBinding.oldLanguage.compareTo(ProservBinding.language) != 0) needsRefresh = true; ProservBinding.oldLanguage = ProservBinding.language; ProservBinding.shellRestartCommand = (String) config.get("shellRestartCommand"); ProservBinding.chartItemRefreshHour = (String) config.get("chartItemRefreshHour"); ProservBinding.chartItemRefreshDay = (String) config.get("chartItemRefreshDay"); ProservBinding.chartItemRefreshWeek = (String) config.get("chartItemRefreshWeek"); ProservBinding.chartItemRefreshMonth = (String) config.get("chartItemRefreshMonth"); ProservBinding.chartItemRefreshYear = (String) config.get("chartItemRefreshYear"); if (ProservBinding.chartItemRefreshHour != null) if (Integer.parseInt(ProservBinding.chartItemRefreshHour) < 5000) ProservBinding.chartItemRefreshHour = "5000"; if (ProservBinding.chartItemRefreshDay != null) if (Integer.parseInt(ProservBinding.chartItemRefreshDay) < 5000) ProservBinding.chartItemRefreshDay = "5000"; if (ProservBinding.chartItemRefreshWeek != null) if (Integer.parseInt(ProservBinding.chartItemRefreshWeek) < 5000) ProservBinding.chartItemRefreshWeek = "5000"; if (ProservBinding.chartItemRefreshMonth != null) if (Integer.parseInt(ProservBinding.chartItemRefreshMonth) < 5000) ProservBinding.chartItemRefreshMonth = "5000"; if (ProservBinding.chartItemRefreshYear != null) if (Integer.parseInt(ProservBinding.chartItemRefreshYear) < 5000) ProservBinding.chartItemRefreshYear = "5000"; ProservBinding.bLogHashTees = Boolean.parseBoolean((String) config.get("logHashTees")); int portTmp = 80; if (StringUtils.isNotBlank(portString)) { portTmp = (int) Long.parseLong(portString); } if ((StringUtils.isNotBlank(ip) && !ip.equals(ProservBinding.ip)) || portTmp != ProservBinding.port) { // only do something if the ip or port has changed needsRefresh = true; logger.debug("needsRefresh = true"); ProservBinding.ip = ip; ProservBinding.port = portTmp; String refreshIntervalString = (String) config.get("refresh"); if (StringUtils.isNotBlank(refreshIntervalString)) { refreshInterval = Long.parseLong(refreshIntervalString); } setProperlyConfigured(true); } // Load language strings Reader reader = null; String filename = ProservBinding.language + ".map"; try { String path = ConfigDispatcher.getConfigFolder() + File.separator + "transform" + File.separator + filename; Properties properties = new Properties(); reader = new FileReader(path); properties.load(reader); ProservBinding.mailSubject = properties.getProperty("MAIL-SUBJECT"); ProservBinding.mailContent = properties.getProperty("MAIL-CONTENT"); } catch (Throwable e) { String message = "opening file '" + filename + "' throws exception"; logger.error(message, e); } finally { IOUtils.closeQuietly(reader); } if (proservData != null && needsRefresh == true) { logger.debug("proServ force a reload of configdata!"); proservData.refresh = true; // force a reload of configdata //execute(); } } } private boolean isInverted(int dataPointID) { for (int x = 0; x < 18; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 2; z++) { if (proservData.getFunctionDataPoint(x, y, z) == dataPointID) { if (proservData.getFunctionStateIsInverted(x, y)) { logger.debug("isInverted: x:{}, y:{}, z:{}, dataPointID={}", x, y, x, dataPointID); return true; } return false; } } } } return false; } private byte getDef(int dataPointID) { for (int x = 0; x < 18; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 2; z++) { if (proservData.getFunctionDataPoint(x, y, z) == dataPointID) { return proservData.getFunctionDefs(x, y); } } } } return 0; } @Override protected void internalReceiveUpdate(String itemName, State newState) { logger.debug("proServ received UPDATE for itemName:{}, newState:{}", itemName, newState.toString()); // updated values from rules if (itemName.contains("dpID")) { String dpID = itemName.substring(itemName.indexOf("dpID") + "dpID".length()); short dataPoint = Short.parseShort(dpID); short offset = 0; byte state = 0; int scheduleType = 0; if (proservCronJobs.cronJobs.containsKey(itemName) == true) { scheduleType = proservCronJobs.cronJobs.get(itemName).scheduleType; if (scheduleType == 0 || scheduleType == 4 || scheduleType == 5 || scheduleType == 6) { state = newState.toString().equals("ON") ? (byte) 1 : (byte) 0; logger.debug("internalReceiveUpdate state={}", state); if (isInverted(dataPoint)) { state = newState.toString().equals("ON") ? (byte) 0 : (byte) 1; logger.debug("internalReceiveUpdate isInverted state={}", state); } } else if (scheduleType == 1) { state = newState.toString().equals("ON") ? (byte) 1 : (byte) 0; offset = 4; } else if (scheduleType == 2) { state = newState.toString().equals("ON") ? (byte) 1 : (byte) 3; //0x44 ON=COMFORT=1, OFF=NIGHT=3 offset = 3; } else if (scheduleType == 3) { state = getDef(dataPoint); } else { logger.error("ERROR: internalReceiveUpdate unsupported scheduleType!!!!!"); return; } } else { logger.error("ERROR: internalReceiveUpdate could not find datapoint !!!!!"); return; } ProservConnector con = new ProservConnector(ip, port); try { con.connect(); boolean skipSetDataPoint = false; if (scheduleType == 2) { // check if dp+3 is 2 (absent) or 4 (freeze protection), if so do not send byte[] dataValue = con.getDataPointValue((short) (dataPoint + offset), (short) 1); if (dataValue != null) { if (dataValue[0] == 2 || dataValue[0] == 4) skipSetDataPoint = true; } } logger.debug( "internalReceiveUpdate state={}, dataPoint={}, offset={}, skipSetDataPoint={}, scheduleType={}", state, dataPoint, offset, skipSetDataPoint, scheduleType); if (!skipSetDataPoint) { if (false == con.setDataPointValue((short) (dataPoint + offset), state)) { logger.error( "internalReceiveUpdate con.setDataPointValue attempt 1 failed, re-try again immediately"); if (false == con.setDataPointValue((short) (dataPoint + offset), state)) { logger.error( "internalReceiveUpdate con.setDataPointValue attempt 2 failed, sleep 100 ms and re-try again"); try { Thread.sleep(100); } catch (InterruptedException ie) { } if (false == con.setDataPointValue((short) (dataPoint + offset), state)) { logger.error( "++++++++++++++++++++++++++++++++internalReceiveUpdate con.setDataPointValue attempt 3 failed"); } } } } } catch (NullPointerException e) { logger.error("internalReceiveUpdate NullPointerException"); } catch (UnsupportedEncodingException e) { logger.error("internalReceiveUpdate UnsupportedEncodingException"); } catch (UnknownHostException e) { logger.error("internalReceiveUpdate the given hostname '{}' : port'{}' of the proServ is unknown", ip, port); con = null; } catch (IOException e) { logger.warn( "internalReceiveUpdate couldn't establish network connection [host '{}' : port'{}'] error:'{}'", ip, port, e); con = null; } catch (Exception e) { logger.warn("internalReceiveUpdate Exception error:{}", e); } finally { logger.debug("internalReceiveUpdate reached finally"); if (con != null) { con.disconnect(); } } } } @Override public synchronized void internalReceiveCommand(String itemName, Command command) { logger.debug("proServ received command for itemName:{}, command:{}", itemName, command.toString()); String pathLogsDir = ConfigDispatcher.getConfigFolder() + File.separator + ".." + File.separator + "logs"; Path pathBackupRrd = FileSystems.getDefault().getPath(pathLogsDir + File.separator + "BackupRrd.zip") .toAbsolutePath(); Path pathZippedCsvFiles = FileSystems.getDefault() .getPath(pathLogsDir + File.separator + "ZippedCsvFiles.zip").toAbsolutePath(); if (itemName.equals("ProservTest") && command.toString().equals("START")) { eventPublisher.postUpdate(itemName, new StringType("PROCESSING")); try { Thread.sleep(3000); } catch (InterruptedException ie) { } //eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); eventPublisher.postUpdate(itemName, new StringType( "FAILED:Please see the logfile for details. This is a very long message which includes a log gfile path : C:\\Users\\jeka\\Documents\\OpenHAB\\source\\openhab-fork\\distribution\\openhabhome\\logs")); } //http://localhost:8080/CMD?ProservBackupResetRrd=START //http://localhost:8080/CMD?ProservBackupRrd=START if (itemName.equals("ProservBackupResetRrd") && command.toString().equals("START") || itemName.equals("ProservBackupRrd") && command.toString().equals("START")) { try { // save a copy of all rrd files in BackupRrd.zip Date now = new Date(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String zipFolderName = simpleDateFormat.format(now); ProservLogfileProvider proservLogfileProvider = new ProservLogfileProvider(); File directory = new File(ConfigDispatcher.getConfigFolder() + File.separator + ".." + File.separator + "etc" + File.separator + "rrd4j"); for (File f : directory.listFiles()) { if (f.getName().startsWith("itemProServLog")) { proservLogfileProvider.createZip(pathBackupRrd, f.toPath(), zipFolderName + "/" + f.getName()); } } // clean up garbage from java.nio File directoryLogs = new File( ConfigDispatcher.getConfigFolder() + File.separator + ".." + File.separator + "logs"); for (File f : directoryLogs.listFiles()) if (f.getName().startsWith("zipfstmp")) f.delete(); // delete all rrd files if (itemName.equals("ProservBackupResetRrd")) { int failedToDeleteFiles = 0; for (File f : directory.listFiles()) { if (f.getName().startsWith("itemProServLog")) { int count = 0; while (!f.delete()) { System.gc(); Thread.sleep(300); if (++count > 10) { failedToDeleteFiles++; break; } } } } if (failedToDeleteFiles > 0) { eventPublisher.postUpdate(itemName, new StringType( "FAILED:Failed to delete all history data files, but the backup is done. Number of files that couldn't be deleted: " + new Integer(failedToDeleteFiles).toString())); } else { eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); } } else { eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); } } catch (Throwable e) { logger.error("ProservBackupResetRrd exception: {}", e.toString()); eventPublisher.postUpdate(itemName, new StringType("FAILED:" + e.toString())); } } //http://localhost:8080/CMD?ProservSendRrdBackup=START else if (itemName.equals("ProservSendRrdBackup") && command.toString().equals("START")) { File f = new File(pathBackupRrd.toString()); if (f.exists() && !f.isDirectory()) { if (Mail.sendMail(mailTo, mailSubject, mailContent, pathBackupRrd.toUri().toString())) { eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:" + Mail.getLastError())); } } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:The history data file is missing, please save history data!")); } } //http://localhost:8080/CMD?ProservRestart=START else if (itemName.equals("ProservRestart") && command.toString().equals("START")) { try { ExecUtil.executeCommandLine(shellRestartCommand); eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); } catch (Throwable e) { eventPublisher.postUpdate(itemName, new StringType("FAILED:" + e.toString())); } } //http://localhost:8080/CMD?ProservSendCsvFiles=START else if (itemName.equals("ProservSendCsvFiles") && command.toString().equals("START")) { File f = new File(pathZippedCsvFiles.toString()); if (mailTo.isEmpty()) { eventPublisher.postUpdate(itemName, new StringType("FAILED:The email address is missing, please configure the email address!")); } else if (f.exists() && !f.isDirectory()) { if (Mail.sendMail(mailTo, mailSubject, mailContent, pathZippedCsvFiles.toUri().toString())) { eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:" + Mail.getLastError())); } } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:The CSV data file is missing, please export csv data file!")); } } //http://localhost:8080/CMD?ProservLanguage=de else if (itemName.equals("ProservLanguage")) { boolean retVal = true; if (isSupportedLanguage(command.toString())) { ProservBinding.language = command.toString(); ProservData.updateLangDirJsFile(language); retVal = ProservData.writeConfigData("proserv:language", ProservBinding.language); } if (retVal == false) eventPublisher.postUpdate(itemName, new StringType("FAILED:Failed to save the language setting. Please try later!")); else eventPublisher.postUpdate(itemName, new StringType(ProservBinding.language)); } //http://localhost:8080/CMD?ProservEmail=aa@bb.cc else if (itemName.equals("ProservEmail")) { if (command.toString().equals("?")) { eventPublisher.postUpdate(itemName, new StringType(ProservBinding.mailTo)); } else { ProservBinding.mailTo = command.toString(); if (ProservData.writeConfigData("proserv:mailto", ProservBinding.mailTo)) { eventPublisher.postUpdate(itemName, new StringType(ProservBinding.mailTo)); } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:Failed to save the new setting, please try later!")); } // Test Android notifications: //NotifyMyAndroid.notifyMyAndroid("The event", "the message"); } } //http://localhost:8080/CMD?ProservIP=192.168.2.1 else if (itemName.equals("ProservIP")) { if (command.toString().equals("?")) { eventPublisher.postUpdate(itemName, new StringType(ProservBinding.ip)); } else { ProservBinding.ip = command.toString(); ProservData.updateProservxJsonForRealknx(ProservBinding.ip); if (ProservData.writeConfigData("proserv:ip", ProservBinding.ip)) { eventPublisher.postUpdate(itemName, new StringType(ProservBinding.ip)); } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:Failed to save the new setting, please try later!")); } } } //http://localhost:8080/CMD?ProservCronJobs=DPxx:true:0:0 0 8 ? * 2-6:0 0 21 ? * 1,7;DPyy:true:1:0 0 8 ? * 2-6:0 0 21 ? * 1,7; else if (itemName.equals("ProservCronJobs")) { if (proservData == null) { eventPublisher.postUpdate(itemName, new StringType( "FAILED: unable to communicate with proserv, please check the communcation link (e.g the IP address)!")); return; } if (proservCronJobs.add(command.toString())) { proservData.updateSchedulerHtmlFile(proservCronJobs); proservData.updateProservRulesFile(proservCronJobs); eventPublisher.postUpdate(itemName, new StringType("SUCCESS")); } else { eventPublisher.postUpdate(itemName, new StringType("FAILED:Failed to save the new setting, please try later!")); } } else { // http://192.168.1.13:8081/CMD?dpID772=OFF // GET after to test result: http://192.168.1.13:8081/rest/items/dpID772/state State s = command.toString().equals("ON") ? OnOffType.ON : OnOffType.OFF; internalReceiveUpdate(itemName, s); } } private static String padRight(String s, int n) { return String.format("%1$-" + n + "s", s); } private void sendAlertMail(String s) { if (!mailTo.isEmpty()) { // Load language strings Reader reader = null; String mailSubject = "xAlert"; String mailContent = "xThis is an alert mail triggered by :"; String filename = ProservBinding.language + ".map"; try { String path = ConfigDispatcher.getConfigFolder() + File.separator + "transform" + File.separator + filename; Properties properties = new Properties(); reader = new FileReader(path); properties.load(reader); mailSubject = properties.getProperty("MAIL-SUBJECT-ALERT"); mailContent = properties.getProperty("MAIL-CONTENT-ALERT") + " " + s; } catch (Throwable e) { String message = "opening file '" + filename + "' throws exception"; logger.error(message, e); } finally { IOUtils.closeQuietly(reader); } Mail.sendMail(mailTo, mailSubject, mailContent); } else { logger.warn("proserv:mailto adress is not configured"); } } public synchronized void updateSendEmail(int x, int y, byte[] dataValue) { int startDatapoint = (48 * x) + (y * 3) + 1; proservData.setFunctionDataPoint(startDatapoint, x, y, 0); switch ((int) proservData.getFunctionCodes(x, y) & 0xFF) { case 0x31: { boolean bCurrent = proservData.parse1ByteBooleanValue(dataValue[0]); //logger.debug("updateSendEmail: dataValue[0]:{}, bCurrent:{}, x:{}, y:{}", dataValue[0], bCurrent, x, y); if (proservData.getFunctionStateIsInverted(x, y)) { bCurrent = !bCurrent; //logger.debug("updateSendEmail: isInverted bCurrent:{}, x:{}, y:{}", bCurrent, x, y); } if (previousEmailTrigger[x][y] != null && previousEmailTrigger[x][y] == false && bCurrent == true && proservData.getFunctionIsEmailTrigger(x, y)) { logger.debug("updateSendEmail: ---Sending!--- previousEmailTrigger[{}][{}]:{}, bCurrent:{}", x, y, previousEmailTrigger[x][y], bCurrent); sendAlertMail(proservData.getFunctionDescription(x, y)); } else { logger.debug("updateSendEmail: set previousEmailTrigger[{}][{}] = {} to new value:{}", x, y, previousEmailTrigger[x][y], bCurrent); } previousEmailTrigger[x][y] = bCurrent; } break; default: logger.debug("proServ binding, unhandled functioncode 0x{}", Integer.toHexString(((int) proservData.getFunctionCodes(x, y) & 0xFF))); } } // The postUpdateSingleValueFunction function takes one function value, identified by x, y & z // (z signifies actual or setpoint) and a single value as input and post update on the event bus. // It is called from the the Monitor thread when the proServ notifies for a value change. public void postUpdateSingleValueFunction(int x, int y, int z, byte[] dataValue) { int startDatapoint = (48 * x) + (y * 3) + 1; int Id = proservData.getFunctionMapId(x, y, z); switch ((int) proservData.getFunctionCodes(x, y) & 0xFF) { case 0x01: case 0x02: case 0x04: case 0x05: { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } break; case 0x11: case 0x12: case 0x13: { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); if (proservData.getFunctionStateIsInverted(x, y)) b = !b; eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } break; case 0x21: case 0x31: { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); if (proservData.getFunctionStateIsInverted(x, y)) b = !b; eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } break; case 0x26: case 0x34: { float f = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } break; case 0x38: { float f = proservData.parse4ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } break; case 0x32: case 0x91: { int i = proservData.parse1BytePercentValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(i)); } break; case 0x33: case 0x92: { int i = proservData.parse1ByteUnsignedValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(i)); } break; case 0x94: { float f = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } break; case 0x35: case 0x95: { long uint32 = proservData.parse4ByteUnsignedValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(uint32)); } break; case 0x36: case 0x96: { long int32 = proservData.parse4ByteSignedValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(int32)); } break; case 0x97: { float f = proservData.parse4ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } break; default: logger.debug("proServ binding, unhandled functioncode 0x{}", Integer.toHexString(((int) proservData.getFunctionCodes(x, y) & 0xFF))); } shortDelayBetweenBusEvents(); } // The postUpdateFunction function takes one function value, x & y // and a buffer with 3 data values as input and post update on the event bus. // It is called from the the polling thread. // The postUpdateFunction also fill the proservData functionDataPoint values which are later used // for lookup in the monitor thread. That is the async value update will only work after one successful data poll. public void postUpdateFunction(int x, int y, byte[] dataValue) { int startDatapoint = (48 * x) + (y * 3) + 1; int Id = proservData.getFunctionMapId(x, y, 0); int IdPreset = proservData.getFunctionMapId(x, y, 1); proservData.setFunctionDataPoint(startDatapoint, x, y, 0); switch ((int) proservData.getFunctionCodes(x, y) & 0xFF) { case 0x01: case 0x02: case 0x04: case 0x05: { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } break; case 0x11: case 0x12: case 0x13: { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); if (proservData.getFunctionStateIsInverted(x, y)) b = !b; eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } break; case 0x21: case 0x31: { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); if (proservData.getFunctionStateIsInverted(x, y)) b = !b; eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } break; case 0x26: case 0x34: { float f = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } break; case 0x38: { float f = proservData.parse4ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } break; case 0x32: case 0x91: { if (proservData.getFunctionLogThis(x, y, 0)) { int i = proservData.parse1BytePercentValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(i)); } if (proservData.getFunctionLogThis(x, y, 1)) { shortDelayBetweenBusEvents(); proservData.setFunctionDataPoint(startDatapoint + 2, x, y, 1); int preset = proservData.parse1BytePercentValue(dataValue[2]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(preset)); } } break; case 0x33: case 0x92: { if (proservData.getFunctionLogThis(x, y, 0)) { int i = proservData.parse1ByteUnsignedValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(i)); } if (proservData.getFunctionLogThis(x, y, 1)) { shortDelayBetweenBusEvents(); proservData.setFunctionDataPoint(startDatapoint + 2, x, y, 1); int preset = proservData.parse1ByteUnsignedValue(dataValue[2]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(preset)); } } break; case 0x94: { if (proservData.getFunctionLogThis(x, y, 0)) { float f = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } if (proservData.getFunctionLogThis(x, y, 1)) { shortDelayBetweenBusEvents(); proservData.setFunctionDataPoint(startDatapoint + 2, x, y, 1); float f = proservData.parse2ByteFloatValue(dataValue, 4); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } } break; case 0x35: case 0x95: { if (proservData.getFunctionLogThis(x, y, 0)) { long uint32 = proservData.parse4ByteUnsignedValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(uint32)); } if (proservData.getFunctionLogThis(x, y, 1)) { shortDelayBetweenBusEvents(); proservData.setFunctionDataPoint(startDatapoint + 2, x, y, 1); long uint32Preset = proservData.parse4ByteUnsignedValue(dataValue, 8); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(uint32Preset)); } } break; case 0x36: case 0x96: { if (proservData.getFunctionLogThis(x, y, 0)) { long int32 = proservData.parse4ByteSignedValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(int32)); } if (proservData.getFunctionLogThis(x, y, 1)) { shortDelayBetweenBusEvents(); proservData.setFunctionDataPoint(startDatapoint + 2, x, y, 1); long int32Preset = proservData.parse4ByteSignedValue(dataValue, 8); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(int32Preset)); } } break; case 0x97: { if (proservData.getFunctionLogThis(x, y, 0)) { float f = proservData.parse4ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } if (proservData.getFunctionLogThis(x, y, 1)) { shortDelayBetweenBusEvents(); proservData.setFunctionDataPoint(startDatapoint + 2, x, y, 1); float f = proservData.parse4ByteFloatValue(dataValue, 8); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); } } break; default: logger.debug("proServ binding, unhandled functioncode 0x{}", Integer.toHexString(((int) proservData.getFunctionCodes(x, y) & 0xFF))); } shortDelayBetweenBusEvents(); } // The postUpdateSingleValueHeating function takes one heating value, x (z signifies actual or setpoint) // and a single value as input and post update on the event bus. // It is called from the the Monitor thread when the proServ notifies for a value change. public void postUpdateSingleValueHeating(int x, int z, byte[] dataValue) { int Id = proservData.getHeatingMapId(x, z); int startDatapoint = 865 + x * 5; switch ((int) (proservData.getHeatingCodes(x) & 0xFF)) { case 0x41: case 0x42: case 0x43: case 0x44: float f = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f).setScale(2, RoundingMode.HALF_EVEN))); break; default: logger.debug("proServ binding, unhandled heatingCode {}", Integer.toHexString(((int) proservData.getHeatingCodes(x) & 0xFF))); } } // The postUpdateHeating function takes one heating value, x // and a buffer with 3 data values as input and post update on the event bus. // It is called from the the polling thread. // The postUpdateHeating also fill the proservData heatingDataPoint values which are later used // for lookup in the monitor thread. That is the async value update will only work after one successful data poll. public void postUpdateHeating(int x, byte[] dataValue) { int IdActual = proservData.getHeatingMapId(x, 0); int IdPreset = proservData.getHeatingMapId(x, 1); int startDatapoint = 865 + x * 5; switch ((int) (proservData.getHeatingCodes(x) & 0xFF)) { case 0x41: case 0x42: case 0x43: case 0x44: proservData.setHeatingDataPoint(startDatapoint, x, 0); float f0 = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdActual), new DecimalType(new BigDecimal(f0).setScale(2, RoundingMode.HALF_EVEN))); proservData.setHeatingDataPoint(startDatapoint + 2, x, 1); float f1 = proservData.parse2ByteFloatValue(dataValue, 4); shortDelayBetweenBusEvents(); eventPublisher.postUpdate("itemProServLog" + Integer.toString(IdPreset), new DecimalType(new BigDecimal(f1).setScale(2, RoundingMode.HALF_EVEN))); /* logger.info("{}{}: {}{}: {}", padRight(proservData.getHeatingDescription(x), 20), padRight(proservData.getStringProservLang(0), 5), padRight(Float.toString(f0), 10), padRight(proservData.getStringProservLang(1), 5), padRight(Float.toString(f1), 10)); */ break; default: logger.debug("proServ binding, unhandled heatingCode {}", Integer.toHexString(((int) proservData.getHeatingCodes(x) & 0xFF))); } } // The postUpdateWeather function is called from the the polling and the Monitor thread. public void postUpdateWeather(byte[] dataValue, int i) { int Id = proservData.getWeatherStationMapId(i); if (Id != 0) { if (i >= 0 && i <= 4) { float f0 = proservData.parse2ByteFloatValue(dataValue, 0); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), new DecimalType(new BigDecimal(f0).setScale(2, RoundingMode.HALF_EVEN))); } else if (i == 5) { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); eventPublisher.postUpdate("itemProServLog" + Integer.toString(Id), b ? OnOffType.ON : OnOffType.OFF); } } } @Override public synchronized void execute() { logger.debug("proServ binding refresh cycle starts!"); proservXConnect.handleProservConnectServer(); try { if (connector == null && ip.isEmpty()) { ProservData.updateProservDefaultSitemapFiles(language); ProservData.updateLangDirJsFile(language); if (ProservDiscovery.search()) { ProservBinding.ip = ProservDiscovery.getProservIP(); ProservData.writeConfigData("proserv:ip", ProservBinding.ip); ProservData.updateProservxJsonForRealknx(ProservBinding.ip); } } if (connector == null && !ip.isEmpty()) { connector = new ProservConnector(ip, port); } if (connector != null) { connector.connect(); if (proservData == null || proservData.refresh == true) { if (proservData != null) { logger.debug("proservData.refresh == {}", proservData.refresh); } proservData = null; proservData = new ProservData(chartItemRefreshHour, chartItemRefreshDay, chartItemRefreshWeek, chartItemRefreshMonth, chartItemRefreshYear, language); ProservData.updateLangDirJsFile(language); byte[] proservAllConfigValues = getConfigValues(); if (proservAllConfigValues == null) { logger.debug("proServ getConfigValues failed try again"); proservAllConfigValues = getConfigValues(); // try again.. } if (proservAllConfigValues != null) { proservData.parseRawConfigData(proservAllConfigValues); handleCronJobs(); logger.debug("Before createFiles"); createFiles(); logger.debug("After createFiles"); connector.startMonitor(this.eventPublisher, ProservBinding.proservData, this); } else { logger.debug("proServ getConfigValues failed twice in a row, try next refresh cycle!"); proservData.refresh = true; // force a reload of configdata } } } else { logger.debug("proServ binding refresh skipped connector is NULL"); } if (proservData != null) { if (bIsFirstRefresh) { logger.debug("proServ binding SKIP FIRST refresh cycle !"); bIsFirstRefresh = false; return; } // function 1-1 .. function 18-16 for (int x = 0; x < 18; x++) { for (int y = 0; y < 16; y++) { if (proservData.getFunctionLogThis(x, y, 0) || proservData.getFunctionLogThis(x, y, 1) || proservData.getFunctionIsEmailTrigger(x, y)) { int startDatapoint = (48 * x) + (y * 3) + 1; int numberOfDatapoints = 3; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) numberOfDatapoints); if (dataValue != null) { if (proservData.getFunctionLogThis(x, y, 0) || proservData.getFunctionLogThis(x, y, 1)) { postUpdateFunction(x, y, dataValue); } if (proservData.getFunctionIsEmailTrigger(x, y)) { updateSendEmail(x, y, dataValue); } } } else if (proservData.getFunctionIsTimer(x, y)) { proservData.setFunctionDataPoint((48 * x) + (y * 3) + 1, x, y, 0); } } } // heating 1-18 for (int x = 0; x < 18; x++) { if (proservData.getHeatingLogThis(x)) { int startDatapoint = 865 + x * 5; int numberOfDatapoints = 5; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) numberOfDatapoints); if (dataValue != null) { postUpdateHeating(x, dataValue); } } } // weatherStation if (proservData.getWeatherStationBrigtnessEastIsEnabled()) { int startDatapoint = 991; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) 1); if (dataValue != null) { postUpdateWeather(dataValue, 0); } } if (proservData.getWeatherStationBrigtnessSouthIsEnabled()) { int startDatapoint = 992; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) 1); if (dataValue != null) { postUpdateWeather(dataValue, 1); } } if (proservData.getWeatherStationBrigtnessWestIsEnabled()) { int startDatapoint = 993; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) 1); if (dataValue != null) { postUpdateWeather(dataValue, 2); } } if (proservData.getWeatherStationWindSpeedIsEnabled()) { int startDatapoint = 994; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) 1); if (dataValue != null) { postUpdateWeather(dataValue, 3); } } if (proservData.getWeatherStationOutdoorTempIsEnabled()) { int startDatapoint = 995; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) 1); if (dataValue != null) { postUpdateWeather(dataValue, 4); } } if (proservData.getWeatherStationRainIsEnabled()) { int startDatapoint = 996; byte[] dataValue = connector.getDataPointValue((short) startDatapoint, (short) 1); if (dataValue != null) { postUpdateWeather(dataValue, 5); } } if (ProservBinding.bLogHashTees == true) { // read the values of the #t data points once to get a start value if (!bHasReadHashTeesOnce) { logger.info("start read #t data points"); for (Map.Entry<String, CronJob> entry : proservCronJobs.cronJobs.entrySet()) { int dataPoint = Integer.parseInt(entry.getValue().dataPointID.substring(4)); byte[] dataValue = connector.getDataPointValue((short) dataPoint, (short) 1); if (dataValue != null) { boolean b = proservData.parse1ByteBooleanValue(dataValue[0]); eventPublisher.postUpdate(entry.getValue().dataPointID, b ? OnOffType.ON : OnOffType.OFF); } } bHasReadHashTeesOnce = true; } } logger.debug("proServ binding refresh cycle completed"); } else { logger.debug("proServ binding refresh skipped proservdata is NULL"); } } catch (NullPointerException e) { logger.warn("proServ NullPointerException"); } catch (UnsupportedEncodingException e) { logger.warn("proServ UnsupportedEncodingException"); } catch (UnknownHostException e) { logger.warn("proServ the given hostname '{}' : port'{}' of the proServ is unknown", ip, port); connector = null; } catch (IOException e) { logger.warn("proServ couldn't establish network connection [host '{}' : port'{}'] error:'{}'", ip, port, e); connector = null; } catch (Exception e) { logger.warn("proServ Exception in execute error:{}", e); } finally { logger.debug("proServ binding refresh cycle reached finally"); if (connector != null) { connector.disconnect(); } } } private void createFiles() { proservData.updateProservMapFile(); proservData.updateProservItemFile(proservCronJobs); proservData.updateProservSitemapFile(); proservData.updateProservSitemapClassicFile(); proservData.updateRrd4jPersistFile(proservCronJobs); proservData.updateDb4oPersistFile(proservCronJobs); proservData.updateSonosRulesFile(ProservBinding.ip); // generate html file and rules file based on the proservData and cronjobs proservData.updateSchedulerHtmlFile(proservCronJobs); proservData.updateProservRulesFile(proservCronJobs); ProservData.updateProservxJsonForRealknx(ProservBinding.ip); proservData.updateObjectsJsonForRealknx(); } private void handleCronJobs() { // find all #t-datapoints defined in proserv, then merge those with old existing (persisted) cronjob definitions for (int x = 0; x < 18; x++) { for (int y = 0; y < 16; y++) { if (proservData.getFunctionIsTimer(x, y)) { String zoneName = proservData.getZoneName(x); String dataPointName = proservData.getFunctionDescription(x, y); int scheduleType = 0; String cron2 = null; if ((int) (proservData.getFunctionCodes(x, y) & 0xFF) == 0x23) { scheduleType = 3; cron2 = ""; } else if (((int) (proservData.getFunctionCodes(x, y) & 0xFF) >= 0x11) && ((int) (proservData.getFunctionCodes(x, y) & 0xFF) <= 0x13)) { if (proservData.getFunctionUnits(x, y).equals("0")) scheduleType = 4; else if (proservData.getFunctionUnits(x, y).equals("1")) scheduleType = 5; else if (proservData.getFunctionUnits(x, y).equals("2")) scheduleType = 6; } proservCronJobs .add(proservCronJobs.new CronJob("dpID" + Integer.toString((48 * x) + (y * 3) + 1), scheduleType, zoneName, dataPointName, false, null, false, cron2)); } } } for (int x = 0; x < 18; x++) { if (proservData.getHeatingIsTimer(x)) { String zoneName = proservData.getZoneName(x); String dataPointName = proservData.getHeatingDescription(x); int scheduleType = 0; switch ((int) (proservData.getHeatingCodes(x) & 0xFF)) { case 0x42: scheduleType = 1; break; case 0x43: case 0x44: scheduleType = 2; break; } proservCronJobs.add(proservCronJobs.new CronJob("dpID" + Integer.toString(865 + (5 * x)), scheduleType, zoneName, dataPointName, false, null, false, null)); } } proservCronJobs.mergeOldJobs(); proservCronJobs.saveJobs(); } private void shortDelayBetweenBusEvents() { try { Thread.sleep(100); } catch (InterruptedException ie) { // Handle the exception } } private byte[] getConfigValues() { byte[] proservAllConfigValues = null; try { short PROSERV_MEMORY_LENGTH = 19194; short NUMBER_OF_BYTES_IN_CHUNK = 500; proservAllConfigValues = new byte[PROSERV_MEMORY_LENGTH]; // read a chunk of values short startByte = 1; for (int chunkId = 0;; chunkId++) { short numberOfBytesToRead = NUMBER_OF_BYTES_IN_CHUNK; if (startByte + NUMBER_OF_BYTES_IN_CHUNK >= PROSERV_MEMORY_LENGTH) numberOfBytesToRead = (short) (PROSERV_MEMORY_LENGTH - startByte); byte[] proServValues = null; for (int attempt = 0; attempt < 3; attempt++) { proServValues = connector.getParameterBytes(startByte, numberOfBytesToRead); if (proServValues != null) break; } if (proServValues == null) { if (startByte > 16000) { numberOfBytesToRead = 100; proServValues = connector.getParameterBytes(startByte, numberOfBytesToRead); if (proServValues == null) { logger.info("proServ getConfigValues failed at index[{}]", startByte); return null; } } else { logger.info("proServ getConfigValues failed at index[{}]", startByte); return null; } } int offset = chunkId * NUMBER_OF_BYTES_IN_CHUNK; startByte += NUMBER_OF_BYTES_IN_CHUNK; for (int i = 0; i < numberOfBytesToRead; i++) { proservAllConfigValues[offset + i] = proServValues[i]; } if (numberOfBytesToRead != NUMBER_OF_BYTES_IN_CHUNK) break; } logger.debug("proServ succesfully loaded all config values"); } finally { } return proservAllConfigValues; } @Override protected long getRefreshInterval() { return refreshInterval; } @Override protected String getName() { return "proServ Refresh Service"; } @Override public void addBindingProvider(ProservBindingProvider provider) { super.addBindingProvider(provider); setProperlyConfigured(true); } }