/* * 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.modbus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ModbusChannel { private final static Logger logger = LoggerFactory.getLogger(ModbusDriver.class); /** Contains values to define the access method of the channel */ public static enum EAccess { READ, WRITE } /** A Parameter of the channel address */ public static final int IGNORE_UNIT_ID = -1; /** A Parameter of the channel address */ private final int UNITID = 0; /** A Parameter of the channel address */ private final int PRIMARYTABLE = 1; /** A Parameter of the channel address */ private final int ADDRESS = 2; /** A Parameter of the channel address */ private final int DATATYPE = 3; /** Start address to read or write from */ private int startAddress; /** Number of registers/coils to be read or written */ private int count; /** Used to determine the register/coil count */ private EDatatype datatype; /** Used to determine the appropriate transaction method */ private EFunctionCode functionCode; /** Specifies whether the channel should be read or written */ private EAccess accessFlag; /** */ private EPrimaryTable primaryTable; private String channelAddress; /** * Is needed when the target device is behind a gateway/bridge which connects Modbus TCP with Modbus+ or Modbus * Serial. Note: Some devices requires the unitId even if they are in a Modbus TCP Network and have their own IP. * "Like when a device ties its Ethernet and RS-485 ports together and broadcasts whatever shows up on one side, * onto the other if the packet isn't for themselves, but isn't "just a bridge"." */ private int unitId; public ModbusChannel(String channelAddress, EAccess accessFlag) { channelAddress = channelAddress.toLowerCase(); String[] addressParams = decomposeAddress(channelAddress); if (addressParams != null && checkAddressParams(addressParams)) { this.channelAddress = channelAddress; setUnitId(addressParams[UNITID]); setPrimaryTable(addressParams[PRIMARYTABLE]); setStartAddress(addressParams[ADDRESS]); setDatatype(addressParams[DATATYPE]); setCount(addressParams[DATATYPE]); setAccessFlag(accessFlag); setFunctionCode(); } else { throw new RuntimeException("Address initialization faild! Invalid parameters used: " + channelAddress); } } public void update(EAccess access) { setAccessFlag(access); setFunctionCode(); } private String[] decomposeAddress(String channelAddress) { String[] param = new String[4]; String[] addressParams = channelAddress.toLowerCase().split(":"); if (addressParams.length == 3) { param[UNITID] = ""; param[PRIMARYTABLE] = addressParams[0]; param[ADDRESS] = addressParams[1]; param[DATATYPE] = addressParams[2]; } else if (addressParams.length == 4) { param[UNITID] = addressParams[0]; param[PRIMARYTABLE] = addressParams[1]; param[ADDRESS] = addressParams[2]; param[DATATYPE] = addressParams[3]; } else { return null; } return param; } private boolean checkAddressParams(String[] params) { boolean returnValue = false; if ((params[UNITID].matches("\\d+?") || params[UNITID].equals("")) && EPrimaryTable.isValidValue(params[PRIMARYTABLE]) && params[ADDRESS].matches("\\d+?") && EDatatype.isValidDatatype(params[DATATYPE])) { returnValue = true; } return returnValue; } private void setFunctionCode() { if (accessFlag.equals(EAccess.READ)) { setFunctionCodeForReading(); } else { setFunctionCodeForWriting(); } } /** * Matches data type with function code * * @throws Exception */ private void setFunctionCodeForReading() { switch (datatype) { case BOOLEAN: if (primaryTable.equals(EPrimaryTable.COILS)) { functionCode = EFunctionCode.FC_01_READ_COILS; } else if (primaryTable.equals(EPrimaryTable.DISCRETE_INPUTS)) { functionCode = EFunctionCode.FC_02_READ_DISCRETE_INPUTS; } else { invalidReadAddressParameterCombination(); } break; case SHORT: case INT: case FLOAT: case DOUBLE: case LONG: case BYTE_HIGH: case BYTE_LOW: case BYTEARRAY: if (primaryTable.equals(EPrimaryTable.HOLDING_REGISTERS)) { functionCode = EFunctionCode.FC_03_READ_HOLDING_REGISTERS; } else if (primaryTable.equals(EPrimaryTable.INPUT_REGISTERS)) { functionCode = EFunctionCode.FC_04_READ_INPUT_REGISTERS; } else { invalidReadAddressParameterCombination(); } break; default: throw new RuntimeException("read: Datatype " + datatype.toString() + " not supported yet!"); } } private void setFunctionCodeForWriting() { switch (datatype) { case BOOLEAN: if (primaryTable.equals(EPrimaryTable.COILS)) { functionCode = EFunctionCode.FC_05_WRITE_SINGLE_COIL; } else { invalidWriteAddressParameterCombination(); } break; case BYTE_HIGH: case BYTE_LOW: case SHORT: if (primaryTable.equals(EPrimaryTable.HOLDING_REGISTERS)) { functionCode = EFunctionCode.FC_06_WRITE_SINGLE_REGISTER; } else { invalidWriteAddressParameterCombination(); } break; case INT: case FLOAT: case DOUBLE: case LONG: case BYTEARRAY: if (primaryTable.equals(EPrimaryTable.HOLDING_REGISTERS)) { functionCode = EFunctionCode.FC_16_WRITE_MULTIPLE_REGISTERS; } else { invalidWriteAddressParameterCombination(); } break; default: throw new RuntimeException("write: Datatype " + datatype.toString() + " not supported yet!"); } } private void invalidWriteAddressParameterCombination() { throw new RuntimeException("Invalid channel address parameter combination for writing. \n Datatype: " + datatype.toString().toUpperCase() + " PrimaryTable: " + primaryTable.toString().toUpperCase()); } private void invalidReadAddressParameterCombination() { throw new RuntimeException("Invalid channel address parameter combination for reading. \n Datatype: " + datatype.toString().toUpperCase() + " PrimaryTable: " + primaryTable.toString().toUpperCase()); } private void setStartAddress(String startAddress) { this.startAddress = Integer.parseInt(startAddress); } private void setDatatype(String datatype) { this.datatype = EDatatype.getEnumFromString(datatype); } private void setUnitId(String unitId) { if (unitId.equals("")) { this.unitId = IGNORE_UNIT_ID; } else { this.unitId = Integer.parseInt(unitId); } } private void setPrimaryTable(String primaryTable) { this.primaryTable = EPrimaryTable.getEnumfromString(primaryTable); } public EPrimaryTable getPrimaryTable() { return primaryTable; } private void setCount(String addressParamDatatyp) { if (datatype.equals(EDatatype.BYTEARRAY)) { // TODO check syntax first? bytearray[n] // special handling of the BYTEARRAY datatyp String[] datatypParts = addressParamDatatyp.split("\\[|\\]"); // split string either at [ or ] if (datatypParts.length == 2) { count = Integer.parseInt(datatypParts[1]); } } else { // all other datatyps count = datatype.getRegisterCount(); } } private void setAccessFlag(EAccess accessFlag) { this.accessFlag = accessFlag; } public int getStartAddress() { return startAddress; } public int getCount() { return count; } public EDatatype getDatatype() { return datatype; } public EFunctionCode getFunctionCode() { return functionCode; } public EAccess getAccessFlag() { return accessFlag; } public int getUnitId() { return unitId; } public String getChannelAddress() { return channelAddress; } @Override public String toString() { return "channeladdress: " + unitId + ":" + primaryTable.toString() + ":" + startAddress + ":" + datatype.toString(); } }