/* * Copyright 2011-16 Fraunhofer ISE * * This file is part of OpenMUC. * For more information visit http://www.openmuc.org * * OpenMUC is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenMUC is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenMUC. If not, see <http://www.gnu.org/licenses/>. * */ package org.openmuc.framework.driver.mbus; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; import org.openmuc.framework.config.ArgumentSyntaxException; import org.openmuc.framework.config.DeviceScanInfo; import org.openmuc.framework.config.DriverInfo; import org.openmuc.framework.config.ScanException; import org.openmuc.framework.config.ScanInterruptedException; import org.openmuc.framework.driver.spi.Connection; import org.openmuc.framework.driver.spi.ConnectionException; import org.openmuc.framework.driver.spi.DriverDeviceScanListener; import org.openmuc.framework.driver.spi.DriverService; import org.openmuc.jmbus.MBusSap; import org.openmuc.jmbus.ScanSecondaryAddress; import org.openmuc.jmbus.SecondaryAddress; import org.openmuc.jmbus.VariableDataStructure; import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component public class MBusDriver implements DriverService { private final static Logger logger = LoggerFactory.getLogger(MBusDriver.class); private final Map<String, MBusSerialInterface> interfaces = new HashMap<>(); private final static String ID = "mbus"; private final static String DESCRIPTION = "M-Bus (wired) is a protocol to read out meters."; private final static String DEVICE_ADDRESS = "Synopsis: <serial_port>:<mbus_address>\nExample for <serial_port>: /dev/ttyS0 (Unix), COM1 (Windows)\n The mbus_address can either be the primary address or the secondary address"; private final static String SETTINGS = "Synopsis: [<baud_rate>][:<timeout>]\nThe default baud rate is 2400. Default read timeout is 2500 ms. Example: 9600:t5000"; private final static String CHANNEL_ADDRESS = "Synopsis: [X]<dib>:<vib>\nThe DIB and VIB fields in hexadecimal form seperated by a collon. If the channel address starts with an X then the specific data record will be selected for readout before reading it."; private final static String DEVICE_SCAN_SETTINGS = "Synopsis: <serial_port>[:<baud_rate>]\nExamples for <serial_port>: /dev/ttyS0 (Unix), COM1 (Windows)"; // "Synopsis: <serial_port>[:<baud_rate>][:s]\nExamples for <serial_port>: /dev/ttyS0 (Unix), COM1 (Windows); s // forsecondary address scan."; private final static DriverInfo info = new DriverInfo(ID, DESCRIPTION, DEVICE_ADDRESS, SETTINGS, CHANNEL_ADDRESS, DEVICE_SCAN_SETTINGS); private boolean interruptScan; private final boolean scanSecondary = false; private int timeout = 2500; private int baudRate = 2400; @Override public DriverInfo getInfo() { return info; } @Override public void scanForDevices(String settings, DriverDeviceScanListener listener) throws UnsupportedOperationException, ArgumentSyntaxException, ScanException, ScanInterruptedException { interruptScan = false; String[] args = settings.split(":"); if (settings.isEmpty() || args.length > 2) { throw new ArgumentSyntaxException( "Less than one or more than two arguments in the settings are not allowed."); } setScanOptions(args); MBusSap mBusSap; if (!interfaces.containsKey(args[0])) { mBusSap = new MBusSap(args[0], baudRate); try { mBusSap.open(); } catch (IllegalArgumentException e) { throw new ArgumentSyntaxException(); } catch (IOException e) { throw new ScanException(e); } } else { mBusSap = interfaces.get(args[0]).getMBusSap(); } mBusSap.setTimeout(timeout); try { if (scanSecondary) { List<SecondaryAddress> addresses = ScanSecondaryAddress.scan(mBusSap, "ffffffff"); Iterator<SecondaryAddress> iterAddresses = addresses.iterator(); while (iterAddresses.hasNext()) { SecondaryAddress secondaryAddress = iterAddresses.next(); listener.deviceFound(new DeviceScanInfo(args[0] + ":" + secondaryAddress, "", getScanDescription(secondaryAddress))); } } else { VariableDataStructure dataStructure = null; for (int i = 0; i <= 250; i++) { if (interruptScan) { throw new ScanInterruptedException(); } if (i % 5 == 0) { listener.scanProgressUpdate(i * 100 / 250); } logger.debug("scanning for meter with primary address {}", i); try { dataStructure = mBusSap.read(i); } catch (TimeoutException e) { continue; } catch (IOException e) { throw new ScanException(e); } String description = ""; if (dataStructure != null) { SecondaryAddress secondaryAddress = dataStructure.getSecondaryAddress(); description = getScanDescription(secondaryAddress); } listener.deviceFound(new DeviceScanInfo(args[0] + ":" + i, "", description)); logger.debug("found meter: {}", i); } } } finally { mBusSap.close(); } } @Override public void interruptDeviceScan() throws UnsupportedOperationException { interruptScan = true; } @Override public Connection connect(String deviceAddress, String settings) throws ArgumentSyntaxException, ConnectionException { String[] deviceAddressTokens = deviceAddress.trim().split(":"); if (deviceAddressTokens.length != 2) { throw new ArgumentSyntaxException("The device address does not consist of two parameters."); } String serialPortName = deviceAddressTokens[0]; Integer mBusAddress; SecondaryAddress secondaryAddress = null; try { if (deviceAddressTokens[1].length() == 16) { mBusAddress = 0xfd; secondaryAddress = SecondaryAddress.getFromHexString(deviceAddressTokens[1]); } else { mBusAddress = Integer.decode(deviceAddressTokens[1]); } } catch (Exception e) { throw new ArgumentSyntaxException("Settings: mBusAddress (" + deviceAddressTokens[1] + ") is not a int nor a 16 sign long hexadecimal secondary address"); } MBusSerialInterface serialInterface; synchronized (this) { synchronized (interfaces) { serialInterface = interfaces.get(serialPortName); if (serialInterface == null) { parseDeviceSettings(settings); MBusSap mBusSap = new MBusSap(serialPortName, baudRate); try { mBusSap.open(); } catch (IOException e1) { throw new ConnectionException("Unable to bind local interface: " + deviceAddressTokens[0]); } mBusSap.setTimeout(timeout); serialInterface = new MBusSerialInterface(mBusSap, serialPortName, interfaces); } } synchronized (serialInterface) { try { serialInterface.getMBusSap().linkReset(mBusAddress); sleep(100); // for slow slaves if (secondaryAddress != null) { serialInterface.getMBusSap().selectComponent(secondaryAddress); sleep(100); } serialInterface.getMBusSap().read(mBusAddress); } catch (IOException e) { serialInterface.close(); throw new ConnectionException(e); } catch (TimeoutException e) { if (serialInterface.getDeviceCounter() == 0) { serialInterface.close(); } throw new ConnectionException(e); } serialInterface.increaseConnectionCounter(); } } return new MBusConnection(serialInterface, mBusAddress, secondaryAddress); } private void parseDeviceSettings(String settings) throws ArgumentSyntaxException { if (!settings.isEmpty()) { String[] settingArray = settings.split(":"); for (String setting : settingArray) { if (setting.matches("^[t,T][0-9]*")) { setting = setting.substring(1); timeout = parseInt(setting, "Settings: Timeout is not a parsable number."); } else if (setting.matches("^[0-9]*")) { baudRate = parseInt(setting, "Settings: Baudrate is not a parsable number."); } else { throw new ArgumentSyntaxException("Settings: Unknown settings parameter. [" + setting + "]"); } } } } private int parseInt(String setting, String errorMsg) throws ArgumentSyntaxException { int ret = 0; try { ret = Integer.parseInt(setting); } catch (NumberFormatException e) { throw new ArgumentSyntaxException(errorMsg + " [" + setting + "]"); } return ret; } private void sleep(long millisec) throws ConnectionException { try { Thread.sleep(millisec); } catch (InterruptedException e) { throw new ConnectionException(e); } } private String getScanDescription(SecondaryAddress secondaryAddress) { String description = secondaryAddress.getManufacturerId() + '_' + secondaryAddress.getDeviceType() + '_' + secondaryAddress.getVersion(); return description; } private void setScanOptions(String args[]) throws ArgumentSyntaxException { for (int i = 1; i < args.length; ++i) { // if (args[i] == "s") { // scanSecondary = true; // } // else { try { baudRate = Integer.parseInt(args[i]); } catch (NumberFormatException e) { throw new ArgumentSyntaxException("Argument number " + i + " is not an integer");// nor option s."); } // } } } }