tuwien.auto.calimero.TestDeviceLogic Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of calimero-testnetwork Show documentation
Show all versions of calimero-testnetwork Show documentation
Test network with KNXnet/IP server and virtual subnet
The newest version!
/*
Calimero 2 - A library for KNX network access
Copyright (c) 2010, 2024 B. Malinowsky
This program 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 2 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under terms
of your choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module. An
independent module is a module which is not derived from or based on
this library. If you modify this library, you may extend this exception
to your version of the library, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from your
version.
*/
package tuwien.auto.calimero;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.datapoint.Datapoint;
import tuwien.auto.calimero.datapoint.DatapointMap;
import tuwien.auto.calimero.datapoint.StateDP;
import tuwien.auto.calimero.device.BaseKnxDevice;
import tuwien.auto.calimero.device.KnxDevice;
import tuwien.auto.calimero.device.KnxDeviceServiceLogic;
import tuwien.auto.calimero.device.LinkProcedure;
import tuwien.auto.calimero.device.ServiceResult;
import tuwien.auto.calimero.device.ios.InterfaceObject;
import tuwien.auto.calimero.device.ios.InterfaceObjectServer;
import tuwien.auto.calimero.device.ios.KnxPropertyException;
import tuwien.auto.calimero.dptxlator.DPT;
import tuwien.auto.calimero.dptxlator.DPTXlator;
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteFloat;
import tuwien.auto.calimero.dptxlator.DPTXlator2ByteUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
import tuwien.auto.calimero.dptxlator.DPTXlator4ByteFloat;
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
import tuwien.auto.calimero.dptxlator.DPTXlatorString;
import tuwien.auto.calimero.dptxlator.DptXlator16BitSet;
import tuwien.auto.calimero.dptxlator.TranslatorTypes;
import tuwien.auto.calimero.internal.Executor;
import tuwien.auto.calimero.knxnetip.KNXnetIPRouting;
import tuwien.auto.calimero.link.medium.RFSettings;
import tuwien.auto.calimero.mgmt.Description;
import tuwien.auto.calimero.mgmt.Destination;
import tuwien.auto.calimero.mgmt.ManagementClient.EraseCode;
import tuwien.auto.calimero.mgmt.ManagementClientImpl;
import tuwien.auto.calimero.mgmt.PropertyAccess;
import tuwien.auto.calimero.mgmt.PropertyAccess.PID;
import tuwien.auto.calimero.mgmt.TransportLayer;
/**
* Test device logic for KNX devices in our test network.
*/
class TestDeviceLogic extends KnxDeviceServiceLogic
{
private static final Logger logger = LoggerFactory.getLogger(TestDeviceLogic.class);
// PID.PROJECT_INSTALLATION_ID
private static final int defProjectInstallationId = 0;
// PID.ROUTING_MULTICAST_ADDRESS
private static final InetAddress defRoutingMulticast = KNXnetIPRouting.DefaultMulticast;
// PID.MAC_ADDRESS
private static final byte[] defMacAddress;
static {
byte[] mac = null;
try {
mac = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()).getHardwareAddress();
}
catch (SocketException | UnknownHostException | NullPointerException ignore) {}
defMacAddress = mac != null ? mac : new byte[6];
}
// Values used for service families DIB
// PID.KNXNETIP_DEVICE_CAPABILITIES
// Bits LSB to MSB: 0 Device Management, 1 Tunneling, 2 Routing, 3 Remote Logging,
// 4 Remote Configuration and Diagnosis, 5 Object Server
private static final int defDeviceCaps = 1 + 2 + 4;
// server friendly name, matches PID.FRIENDLY_NAME property
private static final String friendlyName = "KNX Test Device";
private final Map state = new HashMap<>();
private final List isResponder = new ArrayList<>();
TestDeviceLogic() throws KNXException
{
addDatapoint("0/0/7", DPTXlatorBoolean.DPT_SWITCH);
addDatapoint("0/1/0", "input trigger", DPTXlatorBoolean.DPT_BOOL);
addDatapoint("0/1/1", "G1 switch", DPTXlatorBoolean.DPT_BOOL);
addDatapoint("0/1/2", "G2 switch", DPTXlatorBoolean.DPT_BOOL);
addDatapoint("0/1/10", "switching input G2", DPTXlatorBoolean.DPT_BOOL);
addDatapoint("1/0/1", "Bool", DPTXlatorBoolean.DPT_BOOL);
addDatapoint("1/0/11", "Bool 2", DPTXlatorBoolean.DPT_ENABLE);
addDatapoint("1/0/111", "Bool 3", DPTXlatorBoolean.DPT_OCCUPANCY);
addDatapoint("1/0/2", DPTXlator3BitControlled.DPT_CONTROL_BLINDS);
addDatapoint("1/0/3", DPTXlator8BitUnsigned.DPT_SCALING);
addDatapoint("1/0/4", DPTXlator2ByteUnsigned.DPT_VALUE_2_UCOUNT);
addDatapoint("1/0/5", DPTXlatorString.DPT_STRING_8859_1);
state.put(new GroupAddress("1/0/5"), "Hello KNX!");
addDatapoint("1/0/6", DPTXlator2ByteFloat.DPT_RAIN_AMOUNT);
addDatapoint("1/0/7", DPTXlator4ByteFloat.DPT_ACCELERATION);
addDatapoint("1/0/200", DPTXlator2ByteUnsigned.DPT_ABSOLUTE_COLOR_TEMPERATURE);
addDatapoint("1/0/205", DptXlator16BitSet.DptRhccStatus);
addDatapoint("1/0/206", DptXlator16BitSet.DptMedia);
}
private void addDatapoint(final String address, final DPT dpt) throws KNXException {
addDatapoint(address, dpt.getDescription(), dpt);
}
private void addDatapoint(final String address, final String name, final DPT dpt) throws KNXException {
final StateDP dp = new StateDP(new GroupAddress(address), name, 0, dpt.getID());
getDatapointModel().add(dp);
final String s = TranslatorTypes.createTranslator(0, dp.getDPT()).getValue();
state.put(dp.getMainAddress(), s);
}
@Override
public void setDevice(final KnxDevice device)
{
super.setDevice(device);
for (int i = 0; i < 1000; i++)
writeMemory(i, new byte[] { (byte) i });
if (device.getAddress().equals(TestNetwork.programmableDevice))
setProgrammingMode(true);
if (device.getAddress().equals(TestNetwork.responderDevice))
isResponder.addAll(((DatapointMap) getDatapointModel()).getDatapoints());
// the rest here just sets some arbitrary values in interface objects required for testing
try {
final InterfaceObjectServer ios = device.getInterfaceObjectServer();
// set TP1 medium type
ios.setDescription(new Description(0, 0, PID.MEDIUM_TYPE, 0, 0, false, 0, 1, 3, 0), true);
ios.setProperty(0, PID.MEDIUM_TYPE, 1, 1, new byte[] { 0x1 });
// set device control property, used for checking verify mode
ios.setDescription(new Description(0, 0, PID.DEVICE_CONTROL, 0, 0, true, 0, 1, 3, 3), true);
ios.setProperty(0, PID.DEVICE_CONTROL, 1, 1, new byte[] { 0x0 });
final int last = device.getAddress().getDevice() + 1;
final byte[] serialNo = new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, (byte) last };
ios.setProperty(0, PID.SERIAL_NUMBER, 1, 1, serialNo);
ios.setDescription(new Description(0, 0, PID.SERIAL_NUMBER, 0, 0, false, 0, 1, 3, 0), true);
if (device.getDeviceLink().getKNXMedium() instanceof RFSettings) {
final var rfObject = ios.addInterfaceObject(InterfaceObject.RF_MEDIUM_OBJECT);
final int pidRfMultiType = 51;
ios.setProperty(rfObject.getIndex(), pidRfMultiType, 1, 1, (byte) 0);
}
}
catch (final RuntimeException e) {
e.printStackTrace();
}
final InterfaceObjectServer ios = device.getInterfaceObjectServer();
ios.addInterfaceObject(InterfaceObject.KNXNETIP_PARAMETER_OBJECT);
// create interface object list for property PID_IO_LIST
final InterfaceObject[] interfaceObjects = ios.getInterfaceObjects();
final int iosize = interfaceObjects.length;
final byte[] ioList = new byte[iosize * 2];
for (final InterfaceObject io : interfaceObjects) {
ioList[io.getIndex() * 2 + 1] = (byte) io.getType();
}
try {
ios.setProperty(0, 1, PID.IO_LIST, 1, iosize, ioList);
}
catch (final KnxPropertyException e1) {
e1.printStackTrace();
}
int idx = 2;
setProgramData(ios, idx, (byte) 3);
idx = 5;
setProgramData(ios, idx, (byte) 5);
idx = 3;
try {
ios.setProperty(idx, PropertyAccess.PID.LOAD_STATE_CONTROL, 1, 1, (byte) 1);
idx = 4;
ios.setProperty(idx, PropertyAccess.PID.LOAD_STATE_CONTROL, 1, 1, (byte) 4);
}
catch (final KnxPropertyException e) {
e.printStackTrace();
}
try {
initKNXnetIpParameterObject(ios, 1);
}
catch (final KnxPropertyException e) {
e.printStackTrace();
}
}
@Override
public DPTXlator requestDatapointValue(final Datapoint dp) throws KNXException
{
if (!isResponder.contains(dp))
return null;
final DPTXlator t = TranslatorTypes.createTranslator(0, dp.getDPT());
t.setValue(state.get(dp.getMainAddress()));
return t;
}
@Override
public void updateDatapointValue(final Datapoint ofDp, final DPTXlator update)
{
state.put(ofDp.getMainAddress(), update.getValue());
}
@Override
public ServiceResult readParameter(final int objectType, final int pid, final byte[] info) {
if (objectType != 0 || pid != 59)
return super.readParameter(objectType, pid, info);
final boolean broadcast = false; // dst.equals(GroupAddress.Broadcast); // XXX
final byte[] response = new byte[1];
response[0] = 0xa;
final int tmedium = device.getDeviceLink().getKNXMedium().timeFactor();
final int wait = broadcast ? new Random().nextInt(10 * tmedium) : 0;
logger.debug("add random wait time of " + wait + " ms before response");
try {
Thread.sleep(wait);
}
catch (final InterruptedException e) {
e.printStackTrace();
}
return ServiceResult.of(response);
}
@Override
public void writeParameter(final int objectType, final int pid, final byte[] info) {
if (LinkProcedure.isEnterConfigMode(objectType, pid, info)) {
final ManagementClientImpl mgmt = new ManagementClientImpl(device.getDeviceLink(),
((BaseKnxDevice) device).transportLayer()) {};
final Map groupObjects = new HashMap<>();
final int CC_Switch_OnOff = 1;
final int CC_Dimming_Ctrl = 5;
groupObjects.put(CC_Switch_OnOff, new GroupAddress(7, 3, 10));
groupObjects.put(CC_Dimming_Ctrl, new GroupAddress(7, 3, 11));
final var respondTo = mgmt.createDestination(new IndividualAddress(1), false);
final var linkProc = LinkProcedure.forSensor(mgmt, device.getAddress(), respondTo, false, 0xbeef,
groupObjects);
linkProc.setLinkFunction(this::onLinkResponse);
Executor.execute(linkProc, device.getAddress() + " Link Procedure Thread");
}
}
@Override
public ServiceResult readADC(final int channel, final int consecutiveReads)
{
return ServiceResult.of(0x100);
}
@Override
public ServiceResult authorize(final Destination remote, final byte[] key)
{
final byte[] validKey = new byte[] { 0x10, 0x20, 0x30, 0x40 };
final int levelValid = 2;
if (Arrays.equals(key, validKey)) {
final int currentLevel = levelValid;
return ServiceResult.of(currentLevel);
}
return super.authorize(remote, key);
}
@Override
public ServiceResult restart(final boolean masterReset, final EraseCode eraseCode, final int channel)
{
final var result = super.restart(masterReset, eraseCode, channel);
if (device.getAddress().equals(new IndividualAddress(1, 1, 4)))
setProgrammingMode(true);
return result;
}
private static final int NetworkParameterRes = 0b1111011011;
private static final int SystemNetworkParamResponse = 0b0111001001;
@Override
public ServiceResult management(final int svcType, final byte[] asdu, final KNXAddress dst,
final Destination respondTo, final TransportLayer tl)
{
if (svcType == NetworkParameterRes || svcType == SystemNetworkParamResponse)
return null;
return super.management(svcType, asdu, dst, respondTo, tl);
}
private int onLinkResponse(final int flags, final Map groupObjects)
{
logger.info("link response: flags " + flags + " and group objects " + groupObjects);
return 0;
}
// precondition: we have an IOS instance
private static void initKNXnetIpParameterObject(final InterfaceObjectServer ios, final int objectInstance)
throws KnxPropertyException
{
final int knxObject = 11;
// reset transmit counter to 0
// those two are 4 byte unsigned
ios.setProperty(knxObject, objectInstance, PID.MSG_TRANSMIT_TO_IP, 1, 1, new byte[4]);
ios.setProperty(knxObject, objectInstance, PID.MSG_TRANSMIT_TO_KNX, 1, 1, new byte[4]);
//
// set properties used in device DIB for search response during discovery
//
// friendly name property entry is an array of 30 characters
final byte[] data = new byte[30];
System.arraycopy(friendlyName.getBytes(StandardCharsets.ISO_8859_1), 0, data, 0, friendlyName.length());
ios.setProperty(knxObject, objectInstance, PID.FRIENDLY_NAME, 1, data.length, data);
ios.setProperty(knxObject, objectInstance, PID.PROJECT_INSTALLATION_ID, 1, 1,
bytesFromWord(defProjectInstallationId));
// server KNX device address, since we don't know about routing at this time
// address is always 0.0.0, but is updated in setRoutingConfiguration
final byte[] device = new IndividualAddress(0).toByteArray();
ios.setProperty(knxObject, objectInstance, PID.KNX_INDIVIDUAL_ADDRESS, 1, 1, device);
ios.setProperty(knxObject, objectInstance, PID.MAC_ADDRESS, 1, 1, defMacAddress);
// routing stuff
resetRoutingConfiguration(ios);
// ip and setup multicast
byte[] ip = new byte[4];
try {
ip = InetAddress.getLocalHost().getAddress();
}
catch (final UnknownHostException e) {}
ios.setProperty(knxObject, objectInstance, PID.CURRENT_IP_ADDRESS, 1, 1, ip);
ip[3] = (byte) (ip[3] - 1);
ios.setProperty(knxObject, objectInstance, PID.IP_ADDRESS, 1, 1, ip);
ios.setProperty(knxObject, objectInstance, PID.SUBNET_MASK, 1, 1, new byte[] { -1, -1, -1, 0 });
ios.setProperty(knxObject, objectInstance, PID.DEFAULT_GATEWAY, 1, 1, ip);
ios.setProperty(knxObject, objectInstance, PID.TTL, 1, 1, new byte[] { 9 });
try {
final byte[] a1 = new IndividualAddress("1.1.5").toByteArray();
final byte[] a2 = new IndividualAddress("1.1.6").toByteArray();
final byte[] a3 = new IndividualAddress("1.1.7").toByteArray();
final int idx = ios.getProperty(knxObject, 1, PID.OBJECT_INDEX, 1, 1)[0];
ios.setDescription(new Description(idx, knxObject, PID.ADDITIONAL_INDIVIDUAL_ADDRESSES, 0, 0, true, 0, 10, 3, 3), true);
ios.setProperty(knxObject, objectInstance, PID.ADDITIONAL_INDIVIDUAL_ADDRESSES, 1, 1, a1);
ios.setProperty(knxObject, objectInstance, PID.ADDITIONAL_INDIVIDUAL_ADDRESSES, 2, 1, a2);
ios.setProperty(knxObject, objectInstance, PID.ADDITIONAL_INDIVIDUAL_ADDRESSES, 3, 1, a3);
}
catch (final KNXFormatException e) {
e.printStackTrace();
}
ios.setProperty(knxObject, objectInstance, PID.SYSTEM_SETUP_MULTICAST_ADDRESS, 1, 1,
defRoutingMulticast.getAddress());
//
// set properties used in service families DIB for description
//
ios.setProperty(knxObject, objectInstance, PID.KNXNETIP_DEVICE_CAPABILITIES, 1, 1,
bytesFromWord(defDeviceCaps));
//
// set properties used in manufacturer data DIB for discovery self-description
//
// we don't indicate any capabilities here, since executing the respective tasks
// is either done in the gateway (and, therefore, the property is set by the
// gateway) or by the user, who has to care about it on its own
ios.setProperty(knxObject, objectInstance, PID.KNXNETIP_ROUTING_CAPABILITIES, 1, 1, (byte) 0);
ios.setProperty(knxObject, objectInstance, PID.KNXNETIP_DEVICE_STATE, 1, 1, (byte) 0);
ios.setProperty(knxObject, objectInstance, PID.IP_CAPABILITIES, 1, 1, (byte) 0);
ios.setProperty(knxObject, objectInstance, PID.IP_ASSIGNMENT_METHOD, 1, 1, (byte) 1);
ios.setProperty(knxObject, objectInstance, PID.CURRENT_IP_ASSIGNMENT_METHOD, 1, 1, (byte) 1);
}
private static void resetRoutingConfiguration(final InterfaceObjectServer ios)
{
// routing multicast shall be set 0 if no routing service offered
try {
ios.setProperty(InterfaceObject.KNXNETIP_PARAMETER_OBJECT, 1, PID.ROUTING_MULTICAST_ADDRESS, 1, 1,
new byte[4]);
}
catch (final KnxPropertyException e) {}
}
private static void setProgramData(final InterfaceObjectServer ios, final int idx, final byte value)
{
try {
ios.setProperty(idx, PropertyAccess.PID.PROGRAM_VERSION, 1, 1, value, value, value, value, value);
ios.setProperty(idx, PropertyAccess.PID.LOAD_STATE_CONTROL, 1, 1, value);
ios.setProperty(idx, PropertyAccess.PID.RUN_STATE_CONTROL, 1, 1, value);
ios.setProperty(idx, PropertyAccess.PID.ERROR_CODE, 1, 1, new byte[] { 8 });
}
catch (final KnxPropertyException e) {
e.printStackTrace();
}
}
private static byte[] bytesFromWord(final int word)
{
return new byte[] { (byte) (word >> 8), (byte) word };
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy