/*
* 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.wmbus;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.openmuc.framework.config.ArgumentSyntaxException;
import org.openmuc.framework.data.DoubleValue;
import org.openmuc.framework.data.LongValue;
import org.openmuc.framework.data.Record;
import org.openmuc.framework.data.StringValue;
import org.openmuc.framework.data.Value;
import org.openmuc.framework.driver.spi.ChannelRecordContainer;
import org.openmuc.framework.driver.spi.Connection;
import org.openmuc.framework.driver.spi.ConnectionException;
import org.openmuc.framework.driver.spi.RecordsReceivedListener;
import org.openmuc.jmbus.Bcd;
import org.openmuc.jmbus.DataRecord;
import org.openmuc.jmbus.DecodingException;
import org.openmuc.jmbus.HexConverter;
import org.openmuc.jmbus.SecondaryAddress;
import org.openmuc.jmbus.VariableDataStructure;
import org.openmuc.jmbus.WMBusListener;
import org.openmuc.jmbus.WMBusMessage;
import org.openmuc.jmbus.WMBusMode;
import org.openmuc.jmbus.WMBusSap;
import org.openmuc.jmbus.WMBusSapAmber;
import org.openmuc.jmbus.WMBusSapRadioCrafts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class representing an MBus Connection.<br>
* This class will bind to the local com-interface.<br>
*
*/
public class WMBusSerialInterface {
private final static Logger logger = LoggerFactory.getLogger(WMBusSerialInterface.class);
private final static Map<String, WMBusSerialInterface> interfaces = new HashMap<>();
private final HashMap<Integer, WMBusConnection> connectionsBySecondaryAddress = new HashMap<>();
RecordsReceivedListener listener;
private final WMBusSap wMBusSap;
private final String serialPortName;
private final String transceiverString;
private final String modeString;
public class Receiver implements WMBusListener {
@Override
public void discardedBytes(byte[] bytes) {
if (logger.isDebugEnabled()) {
logger.debug("received bytes that will be discarded: " + HexConverter.toHexString(bytes));
}
}
@Override
public void newMessage(WMBusMessage message) {
try {
message.decode();
} catch (DecodingException e) {
if (logger.isDebugEnabled()) {
logger.debug("Unable to decode header of received message: " + message, e);
}
return;
}
synchronized (this) {
WMBusConnection connection = connectionsBySecondaryAddress
.get(message.getSecondaryAddress().getHashCode());
if (connection == null) {
if (logger.isTraceEnabled()) {
logger.trace("WMBus: connection is null, from device: {} with HashCode: {}",
message.getSecondaryAddress().getDeviceId().toString(),
message.getSecondaryAddress().getHashCode());
}
return;
}
List<ChannelRecordContainer> channelContainers = connection.getContainersToListenFor();
if (channelContainers.size() == 0) {
if (logger.isTraceEnabled()) {
logger.trace("WMBus: channelContainers.size == 0, from device: "
+ message.getSecondaryAddress().getDeviceId().toString());
}
return;
}
VariableDataStructure variableDataStructure = message.getVariableDataResponse();
try {
variableDataStructure.decode();
} catch (DecodingException e) {
if (logger.isWarnEnabled()) {
logger.warn("Unable to decode header of variable data response or received message: {}",
message, e);
}
return;
}
List<DataRecord> dataRecords = message.getVariableDataResponse().getDataRecords();
String[] dibvibs = new String[dataRecords.size()];
int i = 0;
for (DataRecord dataRecord : dataRecords) {
dibvibs[i++] = HexConverter.toShortHexString(dataRecord.getDib()) + ':'
+ HexConverter.toShortHexString(dataRecord.getVib());
}
List<ChannelRecordContainer> containersReceived = new ArrayList<>();
long timestamp = System.currentTimeMillis();
for (ChannelRecordContainer container : channelContainers) {
i = 0;
for (DataRecord dataRecord : dataRecords) {
if (dibvibs[i++].equalsIgnoreCase(container.getChannelAddress())) {
Value value = null;
switch (dataRecord.getDataValueType()) {
case DATE:
value = new StringValue(((Date) dataRecord.getDataValue()).toString());
container.setRecord(new Record(value, timestamp));
break;
case STRING:
value = new StringValue((String) dataRecord.getDataValue());
container.setRecord(new Record(value, timestamp));
break;
case DOUBLE:
value = new DoubleValue(dataRecord.getScaledDataValue());
container.setRecord(new Record(value, timestamp));
break;
case LONG:
if (dataRecord.getMultiplierExponent() == 0) {
value = new LongValue((Long) dataRecord.getDataValue());
container.setRecord(new Record(value, timestamp));
}
else {
value = new DoubleValue(dataRecord.getScaledDataValue());
container.setRecord(new Record(value, timestamp));
}
break;
case BCD:
if (dataRecord.getMultiplierExponent() == 0) {
value = new LongValue(((Bcd) dataRecord.getDataValue()).longValue());
container.setRecord(new Record(value, timestamp));
}
else {
value = new DoubleValue(((Bcd) dataRecord.getDataValue()).longValue()
* Math.pow(10, dataRecord.getMultiplierExponent()));
container.setRecord(new Record(value, timestamp));
}
break;
case NONE:
if (logger.isWarnEnabled()) {
logger.warn("Received data record with <dib>:<vib> = " + dibvibs[i]
+ " has value type NONE.");
}
continue;
}
if (logger.isTraceEnabled()) {
logger.trace("WMBus: Value from channel {}",
container.getChannel().getId() + " is:" + value.toString());
}
containersReceived.add(container);
break;
}
}
}
listener.newRecords(containersReceived);
}
}
@Override
public void stoppedListening(IOException e) {
WMBusSerialInterface.this.stoppedListening();
}
}
public static WMBusSerialInterface getInstance(String serialPortName, String transceiverString, String modeString)
throws ConnectionException, ArgumentSyntaxException {
WMBusSerialInterface serialInterface;
synchronized (interfaces) {
serialInterface = interfaces.get(serialPortName);
if (serialInterface != null) {
if (!serialInterface.modeString.equals(modeString)
|| !serialInterface.transceiverString.equals(transceiverString)) {
throw new ConnectionException(
"Connections serial interface is already in use with a different transceiver or mode");
}
}
else {
serialInterface = new WMBusSerialInterface(serialPortName, transceiverString, modeString);
interfaces.put(serialPortName, serialInterface);
}
}
return serialInterface;
}
private WMBusSerialInterface(String serialPortName, String transceiverString, String modeString)
throws ArgumentSyntaxException, ConnectionException {
this.serialPortName = serialPortName;
this.transceiverString = transceiverString;
this.modeString = modeString;
WMBusMode mode = null;
if (modeString.equalsIgnoreCase("s")) {
mode = WMBusMode.S;
}
else if (modeString.equalsIgnoreCase("t")) {
mode = WMBusMode.T;
}
else {
throw new ArgumentSyntaxException(
"The wireless M-Bus mode is not correctly specified in the device's parameters string. Should be S or T but is: "
+ modeString);
}
if (transceiverString.equals("amber")) {
wMBusSap = new WMBusSapAmber(serialPortName, mode, new Receiver());
}
else if (transceiverString.equals("rc")) {
wMBusSap = new WMBusSapRadioCrafts(serialPortName, mode, new Receiver());
}
else {
throw new ArgumentSyntaxException(
"The type of transceiver is not correctly specified in the device's parameters string. Should be amber or rc but is: "
+ transceiverString);
}
try {
wMBusSap.open();
} catch (IOException e) {
throw new ConnectionException("Failed to open serial interface", e);
}
}
public void connectionClosedIndication(SecondaryAddress secondaryAddress) {
connectionsBySecondaryAddress.remove(secondaryAddress.getHashCode());
if (connectionsBySecondaryAddress.size() == 0) {
close();
}
}
public void close() {
synchronized (interfaces) {
wMBusSap.close();
interfaces.remove(serialPortName);
}
}
public Connection connect(SecondaryAddress secondaryAddress, String keyString) throws ArgumentSyntaxException {
WMBusConnection connection = new WMBusConnection(wMBusSap, secondaryAddress, keyString, this);
if (logger.isTraceEnabled()) {
logger.trace("WMBus: connect device with ID " + secondaryAddress.getDeviceId().toString() + " and HashCode "
+ secondaryAddress.getHashCode());
}
connectionsBySecondaryAddress.put(secondaryAddress.getHashCode(), connection);
return connection;
}
public void stoppedListening() {
synchronized (interfaces) {
interfaces.remove(serialPortName);
}
synchronized (this) {
for (WMBusConnection connection : connectionsBySecondaryAddress.values()) {
listener.connectionInterrupted("wmbus", connection);
}
connectionsBySecondaryAddress.clear();
}
}
}