All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.wizard.simulation.LedIo24Simulator Maven / Gradle / Ivy

There is a newer version: 2.0.26
Show newest version
package org.bidib.wizard.simulation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.BidibPort;
import org.bidib.jbidibc.messages.Feature;
import org.bidib.jbidibc.messages.LcConfigX;
import org.bidib.jbidibc.messages.enums.InputPortEnum;
import org.bidib.jbidibc.messages.enums.LcOutputType;
import org.bidib.jbidibc.messages.enums.LightPortEnum;
import org.bidib.jbidibc.messages.enums.PortConfigStatus;
import org.bidib.jbidibc.messages.enums.PortModelEnum;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.message.AccessoryParaGetMessage;
import org.bidib.jbidibc.messages.message.AccessoryParaResponse;
import org.bidib.jbidibc.messages.message.AccessorySetMessage;
import org.bidib.jbidibc.messages.message.AccessoryStateResponse;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.LcConfigXGetMessage;
import org.bidib.jbidibc.messages.message.LcConfigXResponse;
import org.bidib.jbidibc.messages.message.LcConfigXSetMessage;
import org.bidib.jbidibc.messages.message.LcKeyMessage;
import org.bidib.jbidibc.messages.message.LcKeyResponse;
import org.bidib.jbidibc.messages.message.LcNotAvailableResponse;
import org.bidib.jbidibc.messages.message.LcOutputMessage;
import org.bidib.jbidibc.messages.message.LcPortQueryAllMessage;
import org.bidib.jbidibc.messages.message.LcPortQueryMessage;
import org.bidib.jbidibc.messages.message.LcStatResponse;
import org.bidib.jbidibc.messages.port.BytePortConfigValue;
import org.bidib.jbidibc.messages.port.PortConfigUtils;
import org.bidib.jbidibc.messages.port.PortConfigValue;
import org.bidib.jbidibc.messages.port.ReconfigPortConfigValue;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.simulation.SimulationBidibMessageProcessor;
import org.bidib.jbidibc.simulation.SwitchingFunctionsNode;
import org.bidib.jbidibc.simulation.annotation.BidibNodeSimulator;
import org.bidib.jbidibc.simulation.annotation.BidibNodeSimulators;
import org.bidib.jbidibc.simulation.nodes.FlatBacklightPortType;
import org.bidib.jbidibc.simulation.nodes.FlatInputPortType;
import org.bidib.jbidibc.simulation.nodes.FlatLightPortType;
import org.bidib.jbidibc.simulation.nodes.FlatPortType;
import org.bidib.jbidibc.simulation.nodes.FlatServoPortType;
import org.bidib.jbidibc.simulation.nodes.FlatSwitchPortType;
import org.bidib.jbidibc.simulation.nodes.InputPortParamsType;
import org.bidib.jbidibc.simulation.nodes.InputPortType;
import org.bidib.jbidibc.simulation.nodes.LightPortParamsType;
import org.bidib.jbidibc.simulation.nodes.LightPortType;
import org.bidib.jbidibc.simulation.nodes.PortParamsType;
import org.bidib.jbidibc.simulation.nodes.PortType;
import org.bidib.wizard.model.ports.GenericPort;
import org.bidib.wizard.model.ports.InputPort;
import org.bidib.wizard.model.ports.LightPort;
import org.bidib.wizard.model.status.InputPortStatus;
import org.bidib.wizard.model.status.LightPortStatus;
import org.bidib.wizard.model.status.SwitchPortStatus;
import org.bidib.wizard.simulation.events.InputPortStatusEvent;
import org.bidib.wizard.simulation.events.LightPortStatusEvent;
import org.bidib.wizard.simulation.ports.GenericSimulationPort;
import org.bushe.swing.event.EventBus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@BidibNodeSimulators({ @BidibNodeSimulator(vid = "13", pid = "126"), @BidibNodeSimulator(vid = "13", pid = "127") })
public class LedIo24Simulator extends LightControlSimulator implements SwitchingFunctionsNode {

    private static final Logger LOGGER = LoggerFactory.getLogger(LedIo24Simulator.class);

    private static final String SIMULATION_PANEL_CLASS = "org.bidib.wizard.simulation.client.view.panel.LedIo24Panel";

    private final Map genericPorts = new HashMap();

    protected final ScheduledExecutorService inputWorker = Executors.newScheduledThreadPool(1);

    protected final ScheduledExecutorService simulationWorker = Executors.newScheduledThreadPool(1);

    public LedIo24Simulator(byte[] nodeAddress, long uniqueId, boolean autoAddFeature,
        SimulationBidibMessageProcessor messageReceiver, final BidibRequestFactory bidibRequestFactory) {
        super(nodeAddress, uniqueId, autoAddFeature, messageReceiver, bidibRequestFactory);
    }

    @Override
    public void postConstruct() {
        super.postConstruct();
        Feature feature = getFeature(BidibLibrary.FEATURE_CTRL_PORT_FLAT_MODEL);
        if (feature != null && MapUtils.isEmpty(genericPorts)) {
            LOGGER.info("The current simulator has the flat model configured. Prepare the generic ports.");

            int numPorts = feature.getValue();

            LOGGER.info("Create generic ports, numPorts: {}", numPorts);

            for (int portNumber = 0; portNumber < numPorts; portNumber++) {
                GenericSimulationPort port = new GenericSimulationPort(portNumber);
                port.setPortValue(250);

                port
                    .setPortStatus(portNumber % 2 == 0 ? InputPortStatus.ON.getType().getType()
                        : InputPortStatus.OFF.getType().getType());

                int mask =
                    (1 << BidibLibrary.BIDIB_PORTTYPE_INPUT | 1 << BidibLibrary.BIDIB_PORTTYPE_LIGHT
                        | 1 << BidibLibrary.BIDIB_PORTTYPE_SERVO);

                port.setCurrentPortType(LcOutputType.INPUTPORT, mask);

                genericPorts.put(portNumber, port);
            }
        }
        else {
            LOGGER.info("Skip init the ports because the ports are available already.");

            if (MapUtils.isNotEmpty(genericPorts)) {
                LOGGER.info("Set the feature FEATURE_CTRL_PORT_FLAT_MODEL to value: {}", genericPorts.size());
                features.add(new Feature(BidibLibrary.FEATURE_CTRL_PORT_FLAT_MODEL, genericPorts.size()));
            }
        }
    }

    @Override
    protected void prepareFeatures() {
        LOGGER.info("Prepare the features.");
        super.prepareFeatures();

        features.remove(new Feature(BidibLibrary.FEATURE_ACCESSORY_MACROMAPPED, 2));
        features.add(new Feature(BidibLibrary.FEATURE_ACCESSORY_MACROMAPPED, 8));
    }

    @Override
    protected void prepareCVs() {
        super.prepareCVs();

        configurationVariables.put("String 8", "TEST result");
    }

    @Override
    public void start() {
        LOGGER.info("Start the simulator for address: {}", getAddress());

        super.start();

        if (!MapUtils.isEmpty(genericPorts)) {
            int index = 0;

            // prepare the light ports
            for (int portNum = 0; portNum < lightPortCount; portNum++) {
                GenericSimulationPort genericPort = genericPorts.get(portNum + index);

                final ReconfigPortConfigValue reconfigPortConfigValue =
                    (ReconfigPortConfigValue) genericPort.getPortConfigX().get(BidibLibrary.BIDIB_PCFG_RECONFIG);

                genericPort.setCurrentPortType(LcOutputType.LIGHTPORT, reconfigPortConfigValue.getPortMap());
                // genericPort.setCurrentPortType(LcOutputType.LIGHTPORT, 0x8002);

                if (portNum == 0) {
                    genericPort.setInactive(true);
                }

                Map> values = new LinkedHashMap<>();

                values
                    .put(BidibLibrary.BIDIB_PCFG_RECONFIG,
                        new ReconfigPortConfigValue(reconfigPortConfigValue.getValue()));
                // values.put(BidibLibrary.BIDIB_PCFG_RECONFIG, new ReconfigPortConfigValue(Integer.valueOf(0x800201)));
                // values.putAll(genericPort.getPortConfigX());

                LightPort lightPort = lightPorts.get(portNum);
                if (lightPort != null && lightPort.getConfigStatus() == PortConfigStatus.CONFIG_PASSED) {
                    LOGGER.info("Do not initialize the port because the configuration is set already: {}", genericPort);

                    values
                        .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF,
                            new BytePortConfigValue(ByteUtils.getLowByte(lightPort.getPwmMin())));
                    values
                        .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON,
                            new BytePortConfigValue(ByteUtils.getLowByte(lightPort.getPwmMax())));
                    values
                        .put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN,
                            new BytePortConfigValue(ByteUtils.getLowByte(lightPort.getDimMin())));
                    values
                        .put(BidibLibrary.BIDIB_PCFG_DIMM_UP,
                            new BytePortConfigValue(ByteUtils.getLowByte(lightPort.getDimMax())));
                }
                else {

                    values
                        .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF,
                            new BytePortConfigValue(Byte.valueOf((byte) 0x00)));
                    values
                        .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON, new BytePortConfigValue(Byte.valueOf((byte) 0xFF)));
                    values.put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN, new BytePortConfigValue(Byte.valueOf((byte) 0x0F)));
                    values.put(BidibLibrary.BIDIB_PCFG_DIMM_UP, new BytePortConfigValue(Byte.valueOf((byte) 0x3F)));

                    // TODO RGB
                    // values.put(BidibLibrary.BIDIB_PCFG_RGB, new RgbPortConfigValue(Integer.valueOf(3)));
                }

                values.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L, new BytePortConfigValue(ByteUtils.getLowByte(20)));
                values.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H, new BytePortConfigValue(ByteUtils.getLowByte(150)));
                values.put(BidibLibrary.BIDIB_PCFG_SERVO_SPEED, new BytePortConfigValue(ByteUtils.getLowByte(4)));

                genericPort.setPortConfigX(values);

                lightPort = new LightPort(genericPort);
                lightPorts.put(portNum, lightPort);
            }

            // TODO this is a hack for the OneControl
            index = lightPortCount;

            // prepare the input ports
            LOGGER.info("Set the port type for input ports, index: {}", index);
            for (int portNum = 0; portNum < inputPortCount; portNum++) {
                GenericSimulationPort genericPort = genericPorts.get(portNum + index);

                final ReconfigPortConfigValue reconfigPortConfigValue =
                    (ReconfigPortConfigValue) genericPort.getPortConfigX().get(BidibLibrary.BIDIB_PCFG_RECONFIG);

                genericPort.setCurrentPortType(LcOutputType.INPUTPORT, reconfigPortConfigValue.getPortMap());

                // genericPort.setCurrentPortType(LcOutputType.INPUTPORT, 0x8002);

                // input ports do support remapping
                Map> values = new LinkedHashMap<>();
                values
                    .put(BidibLibrary.BIDIB_PCFG_RECONFIG,
                        new ReconfigPortConfigValue(reconfigPortConfigValue.getValue()));
                // values.put(BidibLibrary.BIDIB_PCFG_RECONFIG, new ReconfigPortConfigValue(Integer.valueOf(0x80020F)));

                InputPort inputPort = inputPorts.get(portNum);
                if (inputPort != null && inputPort.getConfigStatus() == PortConfigStatus.CONFIG_PASSED) {
                    LOGGER.info("Do not initialize the port because the configuration is set already: {}", genericPort);

                    values
                        .put(BidibLibrary.BIDIB_PCFG_INPUT_CTRL,
                            new BytePortConfigValue(inputPort.getInputBehaviour().getType()));
                    values
                        .put(BidibLibrary.BIDIB_PCFG_TICKS,
                            new BytePortConfigValue(ByteUtils.getLowByte(inputPort.getTicks())));
                }
                else {
                    values.put(BidibLibrary.BIDIB_PCFG_INPUT_CTRL, new BytePortConfigValue(Byte.valueOf((byte) 0x00)));
                    values.put(BidibLibrary.BIDIB_PCFG_TICKS, new BytePortConfigValue(Byte.valueOf((byte) 0x0F)));
                }

                values.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L, new BytePortConfigValue(ByteUtils.getLowByte(20)));
                values.put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H, new BytePortConfigValue(ByteUtils.getLowByte(150)));
                values.put(BidibLibrary.BIDIB_PCFG_SERVO_SPEED, new BytePortConfigValue(ByteUtils.getLowByte(4)));

                genericPort.setPortConfigX(values);

                inputPort = new InputPort(genericPort);
                inputPorts.put(portNum, inputPort);
            }

        }
    }

    @Override
    public void stop() {
        LOGGER.info("Stop the LED-IO-24 simulator.");

        if (futureTriggerInputMessage != null) {
            LOGGER.info("Stop the input message trigger.");
            boolean success = futureTriggerInputMessage.cancel(false);
            LOGGER.info("Stopped the input message trigger, success: {}", success);

            futureTriggerInputMessage = null;
        }

        super.stop();
    }

    @Override
    public String getSimulationPanelClass() {
        return SIMULATION_PANEL_CLASS;
    }

    private ScheduledFuture futureTriggerInputMessage;

    @Override
    protected void processSysEnableRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the sysEnable request: {}", bidibMessage);
        super.processSysEnableRequest(bidibMessage);

        // use executor to send response
        if (futureTriggerInputMessage == null) {

            LOGGER.info("Schedule the input message trigger");

            int interval = 15;

            futureTriggerInputMessage = inputWorker.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    LOGGER.info("Trigger input response");
                    // try {
                    // triggerInputResponse();
                    // }
                    // catch (Exception ex) {
                    // LOGGER.warn("Trigger the input response failed.", ex);
                    // }
                    LOGGER.info("Trigger input response has finished.");
                }
            }, 15, interval, TimeUnit.SECONDS);
        }
    }

    @Override
    protected void processSysDisableRequest(BidibMessageInterface bidibMessage) {
        super.processSysDisableRequest(bidibMessage);

        if (futureTriggerInputMessage != null) {
            LOGGER.info("Stop the input message trigger.");
            boolean success = futureTriggerInputMessage.cancel(false);
            LOGGER.info("Stopped the input message trigger, success: {}", success);

            futureTriggerInputMessage = null;
        }

    }

    protected void triggerInputResponse() {

        int maxPortId = 1;

        Random generator = new Random();
        int portNum = generator.nextInt(maxPortId);

        portNum += 24;

        LOGGER.info("Trigger change input status, current portNum: {}", portNum);
        try {
            changeInputPortStatus(portNum);
        }
        catch (Exception ex) {
            LOGGER.warn("Publish input status failed for portNum: {}", portNum, ex);
        }
    }

    @Override
    protected byte[] processLcConfigXSetRequest(BidibMessageInterface bidibMessage) {

        LOGGER.info("Process the LcConfigXSet request: {}", bidibMessage);
        byte[] response = null;

        if (getFlatPortModelPortCount() > 0) {
            try {
                LcConfigXSetMessage lcConfigXSetMessage = (LcConfigXSetMessage) bidibMessage;
                int outputNumber = lcConfigXSetMessage.getPortNumber(getPortModel());
                LcOutputType outputType = lcConfigXSetMessage.getPortType(getPortModel());

                GenericPort port = null;
                port = genericPorts.get(Integer.valueOf(outputNumber));
                LOGGER.info("Set LcConfig for output number: {}, port: {}", outputNumber, port);

                BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, outputNumber);

                if (port != null) {

                    // the problem here is that the BIDIB_PCFG_RECONFIG overwrites the supported port types
                    LcConfigX lcConfigX = lcConfigXSetMessage.getLcConfigX(messageLogger);
                    ReconfigPortConfigValue reconfig =
                        (ReconfigPortConfigValue) lcConfigX.getPortConfig().get(BidibLibrary.BIDIB_PCFG_RECONFIG);
                    if (reconfig != null) {
                        // we must keep the supported port types
                        Integer supportedPortTypes = port.getSupportedPortTypes();
                        if (supportedPortTypes != null) {
                            ReconfigPortConfigValue newReconfig =
                                new ReconfigPortConfigValue(reconfig.getCurrentOutputType().getType(),
                                    supportedPortTypes);
                            LOGGER.info("Prepared BIDIB_PCFG_RECONFIG to replace: {}", newReconfig);
                            lcConfigX.getPortConfig().put(BidibLibrary.BIDIB_PCFG_RECONFIG, newReconfig);
                        }
                    }
                    // set the current port configuration
                    port.setPortConfigX(lcConfigX.getPortConfig());

                    Map> updatedValues = port.getPortConfigX();
                    LOGGER.info("Return config of port: {}", port);
                    lcConfigX = new LcConfigX(bidibPort, updatedValues);

                    byte[] content = LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel());

                    // TODO remove this test
                    if (outputNumber == 5) {
                        content = new byte[] { 0x00, 0x05, 0x00, 0x0C };
                    }

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(), content);
                    response = lcConfigXResponse.getContent();
                }
                else {
                    LOGGER.warn("No port assigned!");
                    LcNotAvailableResponse magicResponse =
                        new LcNotAvailableResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort);
                    response = magicResponse.getContent();
                }
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcConfigX response failed.", ex);
            }
        }
        else {
            response = super.processLcConfigXSetRequest(bidibMessage);
        }
        return response;
    }

    @Override
    protected byte[] processLcConfigXGetRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the LcConfigXGet request: {}", bidibMessage);
        byte[] response = null;

        if (getFlatPortModelPortCount() > 0) {

            try {
                LcConfigXGetMessage lcConfigXGetMessage = (LcConfigXGetMessage) bidibMessage;
                int outputNumber = lcConfigXGetMessage.getPortNumber(getPortModel());
                LcOutputType outputType = lcConfigXGetMessage.getPortType(getPortModel());

                GenericPort port = null;

                port = genericPorts.get(Integer.valueOf(outputNumber));

                BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, outputNumber);

                if (port != null) {
                    Map> values = port.getPortConfigX();

                    // TODO remove this test
                    if (outputNumber == 2) {
                        values.put(BidibLibrary.BIDIB_PCFG_NONE, new BytePortConfigValue((byte) 12));
                    }

                    LOGGER.info("Return config of port: {}", port);
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    response = lcConfigXResponse.getContent();
                }
                else {
                    LOGGER.warn("No port available with port number: {}", outputNumber);
                    LcNotAvailableResponse notAvailableResponse =
                        new LcNotAvailableResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort);
                    response = notAvailableResponse.getContent();
                }
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcConfigX response failed.", ex);
            }
        }
        else {
            response = super.processLcConfigXGetRequest(bidibMessage);
        }
        return response;
    }

    @Override
    protected void processLcConfigXGetAllRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the LcConfigXGetAll request: {}", bidibMessage);

        if (getFlatPortModelPortCount() > 0) {
            LcOutputType outputType = LcOutputType.SWITCHPORT;

            Map> values = new LinkedHashMap<>();

            try {
                for (GenericPort port : genericPorts.values()) {
                    int portNumber = port.getPortNumber();
                    LOGGER.info("Prepare lcConfigXResponse for port number: {}", portNumber);
                    values.clear();

                    // if (portNumber >= 0 && portNumber < lightPortCount) { // 24 light ports
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_RECONFIG,
                    // new ReconfigPortConfigValue(Integer.valueOf(0x800201)));
                    //
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF,
                    // new BytePortConfigValue(Byte.valueOf((byte) 0x00)));
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON,
                    // new BytePortConfigValue(Byte.valueOf((byte) 0xFF)));
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN, new BytePortConfigValue(Byte.valueOf((byte) 0x0F)));
                    // values.put(BidibLibrary.BIDIB_PCFG_DIMM_UP, new BytePortConfigValue(Byte.valueOf((byte) 0x3F)));
                    //
                    // // TODO RGB
                    // // values.put(BidibLibrary.BIDIB_PCFG_RGB, new RgbPortConfigValue(Integer.valueOf(3)));
                    //
                    // }
                    // else { // input ports do not support remapping
                    //
                    // int currentPortType = ByteUtils.getInt(port.getCurrentPortType().getType());
                    //
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_RECONFIG,
                    // // be careful, the constructor is different ...
                    // new ReconfigPortConfigValue(currentPortType, 0x8000));
                    //
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_INPUT_CTRL,
                    // new BytePortConfigValue(Byte.valueOf((byte) 0x00)));
                    // values.put(BidibLibrary.BIDIB_PCFG_TICKS, new BytePortConfigValue(Byte.valueOf((byte) 0x00)));
                    // }

                    values.putAll(port.getPortConfigX());

                    // TODO remove this test
                    // if (portNumber == 6) {
                    // values.put(BidibLibrary.BIDIB_PCFG_NONE, new BytePortConfigValue((byte) 12));
                    // }

                    // if (port.isInactive()) {
                    // values
                    // .put(BidibLibrary.BIDIB_PCFG_ERR_PORT_INACTIVE,
                    // new BytePortConfigValue(Byte.valueOf((byte) 0x00)));
                    // }

                    LOGGER.info("Return config of port: {}", port);
                    BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, portNumber);
                    LcConfigX lcConfigX = new LcConfigX(bidibPort, values);

                    LcConfigXResponse lcConfigXResponse =
                        new LcConfigXResponse(bidibMessage.getAddr(), getNextSendNum(),
                            LcConfigX.getCodedPortConfig(null, lcConfigX, getPortModel()));
                    byte[] response = lcConfigXResponse.getContent();

                    LOGGER.info("Prepared lcConfigXResponse: {}", ByteUtils.bytesToHex(response));
                    sendSpontanousResponse(response);
                }
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcConfigXResponse response failed.", ex);
            }
        }
        else {
            super.processLcConfigXGetAllRequest(bidibMessage);
        }
    }

    @Override
    protected byte[] processLcPortQueryRequest(BidibMessageInterface bidibMessage) {

        LOGGER.info("Process the LcOutputQuery request: {}", bidibMessage);
        byte[] response = null;

        if (getFlatPortModelPortCount() > 0) {
            byte portState = 0;

            try {
                LcPortQueryMessage lcOutputQueryMessage = (LcPortQueryMessage) bidibMessage;
                LcOutputType outputType = lcOutputQueryMessage.getPortType(getPortModel());
                int outputNumber = lcOutputQueryMessage.getPortNumber(getPortModel());

                GenericPort port = genericPorts.get(Integer.valueOf(outputNumber));

                BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, outputNumber);

                if (port != null) {
                    portState = ByteUtils.getLowByte(0xFF);
                }
                else {
                    LOGGER.warn("No port available with portNumber: {}", outputNumber);
                }
                LcStatResponse lcStatResponse =
                    new LcStatResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort, portState);
                response = lcStatResponse.getContent();
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcStat response failed.", ex);
            }
        }
        else {
            response = super.processLcPortQueryRequest(bidibMessage);
        }
        return response;
    }

    @Override
    protected byte[] processLcPortQueryAllRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the PortQueryAll request: {}", bidibMessage);

        if (getFlatPortModelPortCount() > 0) {
            byte[] response = null;
            try {
                LcPortQueryAllMessage portQueryAllMessage = (LcPortQueryAllMessage) bidibMessage;
                int portRangeFrom = portQueryAllMessage.getPortRangeFrom(getPortModel());
                int portRangeTo = portQueryAllMessage.getPortRangeTo(getPortModel());

                int portTypeMask = portQueryAllMessage.getPortTypeMask();

                LOGGER
                    .info("Query all port states, portRangeFrom: {}, portRangeTo: {}, portModel: {}, portTypeMask: {}",
                        portRangeFrom, portRangeTo, getPortModel(), portTypeMask);

                for (GenericPort genericPort : genericPorts.values()) {
                    LcOutputType currentPortType = genericPort.getCurrentPortType();

                    if (PortConfigUtils.isSupportsPortType(currentPortType, portTypeMask)
                        && (genericPort.getPortNumber() >= portRangeFrom
                            && genericPort.getPortNumber() < portRangeTo)) {
                        try {
                            byte portStatus = genericPort.getPortStatus();
                            publishPortState(bidibMessage.getAddr(), currentPortType, genericPort.getPortNumber(),
                                portStatus);
                        }
                        catch (Exception ex) {
                            LOGGER.warn("Publish port state failed for port: {}", genericPort, ex);
                        }
                    }
                    else {
                        LOGGER.info("Skip current port that is out of port range or wrong port type: {}", genericPort);
                    }
                }

                LOGGER.info("Send the terminating LC_NA message.");
                publishLcNaResponse(bidibMessage.getAddr());

            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcStat response failed.", ex);
            }

            return response;
        }
        else {
            LOGGER
                .info(
                    "The LedIo24Simulator is configured as type-oriented port model! Delegate evaluation to LightControlSimulator.");
            return super.processLcPortQueryAllRequest(bidibMessage);
        }
    }

    @Override
    protected byte[] processLcKeyQueryRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the LcKeyQuery request: {}", bidibMessage);
        byte[] response = null;

        if (getFlatPortModelPortCount() > 0) {
            byte keyState = 0;
            GenericSimulationPort port = null;
            try {
                LcKeyMessage lcKeyMessage = (LcKeyMessage) bidibMessage;
                // int portNumber = lcKeyMessage.getKeyNumber();
                int portNumber = lcKeyMessage.getBidibPort().getPortNumber(PortModelEnum.type);

                port = genericPorts.get(Integer.valueOf(portNumber));

                if (port != null) {
                    keyState = port.getPortStatus();
                }
                else {
                    LOGGER.warn("No port available with portNumber: {}", portNumber);
                }

                LcKeyResponse lcKeyResponse =
                    new LcKeyResponse(bidibMessage.getAddr(), getNextSendNum(), ByteUtils.getLowByte(portNumber),
                        keyState);
                response = lcKeyResponse.getContent();
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcKey response failed.", ex);
            }

            if (port != null) {
                publishInputPortChange(port);
            }
        }
        else {
            response = super.processLcKeyQueryRequest(bidibMessage);
        }
        return response;
    }

    @Override
    protected byte[] processLcOutputRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the LcOutput request: {}", bidibMessage);
        byte[] response = null;

        if (getFlatPortModelPortCount() > 0) {

            GenericSimulationPort port = null;
            try {
                LcOutputMessage lcOutputMessage = (LcOutputMessage) bidibMessage;
                LcOutputType outputType = lcOutputMessage.getOutputType(getPortModel());
                int portNumber = lcOutputMessage.getOutputNumber(getPortModel());
                byte outputStatus = lcOutputMessage.getOutputStatus();

                port = genericPorts.get(Integer.valueOf(portNumber));

                BidibPort bidibPort = prepareBidibPort(getPortModel(), outputType, portNumber);

                if (port != null) {
                    port.setPortStatus(outputStatus);

                    LcStatResponse lcStatResponse =
                        new LcStatResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort,
                            lcOutputMessage.getOutputStatus());
                    response = lcStatResponse.getContent();
                }
                else {
                    LcNotAvailableResponse lcNotAvailableResponse =
                        new LcNotAvailableResponse(bidibMessage.getAddr(), getNextSendNum(), bidibPort);
                    response = lcNotAvailableResponse.getContent();
                }

                if (port != null) {
                    switch (port.getCurrentPortType()) {
                        case LIGHTPORT:
                            publishLightPortChange(port);
                            break;
                        default:
                            break;
                    }
                }
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcStat response failed.", ex);
            }
        }
        return response;
    }

    private void publishLightPortChange(GenericSimulationPort port) {

        LightPortStatus status = LightPortStatus.valueOf(LightPortEnum.valueOf(port.getPortStatus()));

        LOGGER.info("The lightport status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus.publish(new LightPortStatusEvent(NodeUtils.formatAddress(nodeAddress), port.getPortNumber(), status));
    }

    @Override
    protected byte[] processAccessorySetRequest(final BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the AccessorySet request: {}", bidibMessage);
        byte[] response = null;

        try {
            AccessorySetMessage accessorySetMessage = (AccessorySetMessage) bidibMessage;
            int accessoryNumber = accessorySetMessage.getAccessoryNumber();
            int aspect = accessorySetMessage.getAspect();

            byte[] value = new byte[] { 0, 0, 0 };

            if (accessoryNumber == 1 && aspect == 1) {
                LOGGER.warn("Adding simulated error to accessory state response");

                value = new byte[] { 2, 1, 20 };

                final AccessoryResponseHandler callable =
                    new AccessoryResponseHandler(bidibMessage.getAddr(), accessoryNumber, aspect);

                ScheduledFuture future =
                    simulationWorker.scheduleWithFixedDelay(callable, 1500, 1500, TimeUnit.MILLISECONDS);
                callable.setFuture(future);
            }

            // send accessory state response
            AccessoryStateResponse accessoryStateResponse =
                new AccessoryStateResponse(bidibMessage.getAddr(), getNextSendNum(),
                    ByteUtils.getLowByte(accessoryNumber), ByteUtils.getLowByte(aspect), value);
            response = accessoryStateResponse.getContent();
        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create AccessoryState response failed.", ex);
        }
        return response;
    }

    private final class AccessoryResponseHandler implements Runnable {

        private List accessoryResponseList = new ArrayList<>();

        private final byte[] nodeAddress;

        private ScheduledFuture future;

        public AccessoryResponseHandler(byte[] nodeAddress, int accessoryNumber, int aspect) {

            this.nodeAddress = nodeAddress;

            // add the remaining response values
            accessoryResponseList.add(accessoryNumber + "," + aspect + ",2,128,7");
            accessoryResponseList.add(accessoryNumber + ",255,2,129,7");
            accessoryResponseList.add(accessoryNumber + ",255,2,0,0");
        }

        public void setFuture(ScheduledFuture future) {
            LOGGER.info("Set the future: {}", future);
            this.future = future;
        }

        @Override
        public void run() {
            LOGGER.info("The AccessoryResponseHandler is executed.");
            if (!accessoryResponseList.isEmpty()) {
                String accessoryResponse = accessoryResponseList.remove(0);
                if (StringUtils.isNotBlank(accessoryResponse)) {
                    LOGGER.info("Send response: {}", accessoryResponse);
                    // parse
                    String[] parts = accessoryResponse.split(",");
                    byte[] value = new byte[parts.length];
                    int index = 0;
                    for (String part : parts) {
                        int val = Integer.parseInt(part);
                        value[index] = ByteUtils.getLowByte(val);
                        index++;
                    }

                    try {
                        AccessoryStateResponse accessoryStateResponse =
                            new AccessoryStateResponse(nodeAddress, 0, /* accessoryNumber */value[0],
                                /* aspect */value[1], ByteUtils.subArray(value, 2));
                        byte[] response = accessoryStateResponse.getContent();
                        LOGGER.info("Prepared accessoryStateResponse: {}", accessoryStateResponse);
                        LedIo24Simulator.this.sendSpontanousResponse(response);
                    }
                    catch (ProtocolException ex) {
                        LOGGER.warn("Create AccessoryState response failed.", ex);
                    }
                }
            }

            if (accessoryResponseList.isEmpty()) {
                LOGGER.info("No more entries in accessoryResponseList. Cancel the future.");

                if (future != null) {

                    try {
                        future.cancel(false);
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Cancel the future failed.", ex);
                    }

                }
            }
        }
    }

    // //////
    @Override
    protected byte[] processAccessoryParaGetRequest(BidibMessageInterface bidibMessage) {
        LOGGER.info("Process the AccessoryParaGet request: {}", bidibMessage);
        byte[] response = null;

        try {
            AccessoryParaGetMessage accessoryParaGetMessage = (AccessoryParaGetMessage) bidibMessage;
            int accessoryNumber = accessoryParaGetMessage.getAccessoryNumber();
            int paraNumber = accessoryParaGetMessage.getParaNumber();

            if (paraNumber == BidibLibrary.BIDIB_ACCESSORY_SWITCH_TIME) {

                LOGGER.info("The param BIDIB_ACCESSORY_SWITCH_TIME is known for accessory 0 and 1!");

                // TODO provide the correct data here ...
                byte[] value = null;

                switch (accessoryNumber) {
                    case 0:
                        value = new byte[] { ByteUtils.getLowByte(0x87) };
                        break;
                    case 1:
                        value = new byte[] { ByteUtils.getLowByte(0x77) };
                        break;
                    default:
                        value = new byte[] { ByteUtils.getLowByte(paraNumber) };
                        paraNumber = BidibLibrary.BIDIB_ACCESSORY_PARA_NOTEXIST;
                        break;
                }

                AccessoryParaResponse accessoryParaResponse =
                    new AccessoryParaResponse(bidibMessage.getAddr(), getNextSendNum(),
                        ByteUtils.getLowByte(accessoryNumber), ByteUtils.getLowByte(paraNumber), value);
                response = accessoryParaResponse.getContent();
            }
            else {
                response = super.processAccessoryParaGetRequest(bidibMessage);
            }

        }
        catch (ProtocolException ex) {
            LOGGER.warn("Create AccessoryPara response failed.", ex);
        }
        return response;
    }

    //////////

    private void publishInputPortChange(GenericSimulationPort port) {

        InputPortStatus status = InputPortStatus.valueOf(InputPortEnum.valueOf(port.getPortStatus()));

        LOGGER.info("The inputport status has changed, notify the listeners, nodeAddress: {}", nodeAddress);
        EventBus.publish(new InputPortStatusEvent(NodeUtils.formatAddress(nodeAddress), port.getPortNumber(), status));
    }

    @Override
    protected void changeInputPortStatus(int portNum) {

        GenericSimulationPort port = genericPorts.get(portNum);
        if (port != null) {

            InputPortStatus portStatus = InputPortStatus.valueOf(InputPortEnum.valueOf(port.getPortStatus()));

            switch (portStatus) {
                case OFF:
                    port.setPortStatus(InputPortStatus.ON.getType().getType());
                    break;
                default:
                    port.setPortStatus(InputPortStatus.OFF.getType().getType());
                    break;
            }

            try {
                BidibPort bidibPort = prepareBidibPort(getPortModel(), LcOutputType.INPUTPORT, portNum);

                LcStatResponse lcStatResponse =
                    new LcStatResponse(nodeAddress, getNextSendNum(), bidibPort, port.getPortStatus());
                byte[] response = lcStatResponse.getContent();

                sendSpontanousResponse(response);
            }
            catch (ProtocolException ex) {
                LOGGER.warn("Create LcStat response failed.", ex);
            }

            publishInputPortChange(port);

        }
        else {
            LOGGER.warn("The requested input port is not available: {}", portNum);
        }
    }

    @Override
    public void setPortsConfig(FlatPortType flatPortType) {
        if (flatPortType == null) {
            return;
        }

        LOGGER.info("Set the ports config: {}", flatPortType);

        if (CollectionUtils.isNotEmpty(flatPortType.getBACKLIGHTOrLIGHTOrINPUT())) {
            for (PortParamsType flatPortParamsType : flatPortType.getBACKLIGHTOrLIGHTOrINPUT()) {

                if (flatPortParamsType instanceof FlatInputPortType) {
                    // add input port
                    prepareInutPortFromConfig((FlatInputPortType) flatPortParamsType);
                }
                else if (flatPortParamsType instanceof FlatLightPortType) {
                    // add light port
                    prepareLightPortFromConfig((FlatLightPortType) flatPortParamsType);
                }
                else if (flatPortParamsType instanceof FlatBacklightPortType) {
                    // add backlight port
                    prepareBacklightPortFromConfig((FlatBacklightPortType) flatPortParamsType);
                }
                else if (flatPortParamsType instanceof FlatServoPortType) {
                    // add servo port
                    prepareServoPortFromConfig((FlatServoPortType) flatPortParamsType);
                }
                else if (flatPortParamsType instanceof FlatSwitchPortType) {
                    // add switch port
                    prepareSwitchPortFromConfig((FlatSwitchPortType) flatPortParamsType);
                }
                else {
                    LOGGER.warn("Unsupported flatPortParamsType detected: {}", flatPortParamsType);
                }
            }
        }

    }

    @Override
    public void setPortsConfig(PortType portType) {
        if (portType == null) {
            return;
        }

        LOGGER.info("Set the ports config: {}", portType);

        if (portType instanceof LightPortType) {

            LightPortType lightPortType = (LightPortType) portType;
            lightPortCount = lightPortType.getCount();
            LOGGER.info("Total number of lightports: {}", lightPortCount);

            lightPorts.clear();

            // prepare initial lightports
            for (int portId = 0; portId < lightPortCount; portId++) {

                LightPort lightPort = new LightPort();
                lightPort.setId(portId);

                // add the unconfigured lightport
                lightPorts.put(portId, lightPort);
            }

            prepareLightPortFromConfig(lightPortType);

        }
        else if (portType instanceof InputPortType) {

            InputPortType inputPortType = (InputPortType) portType;

            inputPortCount = inputPortType.getCount();
            inputPortOffset = (inputPortType.getOffset() != null ? inputPortType.getOffset() : lightPortCount);

            LOGGER.info("Configured number of input ports: {}", inputPortCount);

            // prepare initial lightports
            for (int portId = 0; portId < inputPortCount; portId++) {

                InputPort inputPort = new InputPort();
                inputPort.setId(portId);

                // add the unconfigured inputport
                inputPorts.put(portId, inputPort);
            }

            prepareInputPortFromConfig(inputPortType);
        }
    }

    protected void prepareLightPortFromConfig(final LightPortType lightPortType) {

        // overwrite configured ports
        if (CollectionUtils.isNotEmpty(lightPortType.getPort())) {

            // evaluate the configured ports
            for (LightPortParamsType portParams : lightPortType.getPort()) {
                LightPort lightPort = new LightPort();
                lightPort.setId(portParams.getPortId());
                lightPort.setDimMin(portParams.getDimSlopeDown());
                lightPort.setDimMax(portParams.getDimSlopeUp());
                lightPort.setPwmMax(portParams.getIntensityOn());
                lightPort.setPwmMin(portParams.getIntensityOff());
                if (portParams.getRgbValue() != null) {
                    try {
                        Integer rgbValue = Integer.parseInt(portParams.getRgbValue(), 16);
                        lightPort.setRgbValue(rgbValue);
                    }
                    catch (Exception ex) {
                        LOGGER.warn("Parse RGB value failed: {}", portParams.getRgbValue(), ex);
                        lightPort.setRgbValue(null);
                    }
                }
                if (portParams.getTransitionTime() != null) {
                    lightPort.setTransitionTime(portParams.getTransitionTime());
                }

                lightPort.setConfigStatus(PortConfigStatus.CONFIG_PASSED);

                lightPort.setStatus(LightPortStatus.OFF);

                LOGGER.info("Add configured port: {}", lightPort);

                // add the configured port
                lightPorts.put(lightPort.getId(), lightPort);
            }

            // use the number of configured ports
            lightPortCount = lightPorts.size();
        }
    }

    protected void prepareBacklightPortFromConfig(final FlatBacklightPortType lightPortType) {

        // TODO overwrite configured ports
        LOGGER.warn("prepareBacklightPortFromConfig is not implemented yet!");
    }

    protected void prepareServoPortFromConfig(final FlatServoPortType servoPortType) {

        // overwrite configured ports

        int portNumber = servoPortType.getPortId();
        GenericSimulationPort servoPort = new GenericSimulationPort(portNumber);

        int portTypeMap = getPortTypeMap(servoPortType.getPortTypeMap());

        servoPort.setCurrentPortType(LcOutputType.SERVOPORT, portTypeMap);

        Map> portConfig = new LinkedHashMap<>();
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_SERVO_SPEED,
                new BytePortConfigValue(ByteUtils.getLowByte(servoPortType.getSpeed())));
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_L,
                new BytePortConfigValue(ByteUtils.getLowByte(servoPortType.getAdjustLow())));
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_SERVO_ADJ_H,
                new BytePortConfigValue(ByteUtils.getLowByte(servoPortType.getAdjustHigh())));

        servoPort.setPortConfigX(portConfig);

        servoPort.setConfigStatus(PortConfigStatus.CONFIG_PASSED);

        servoPort.setPortValue(servoPortType.getValue());

        LOGGER.info("Add configured serbvo port: {}", servoPort);

        // add the configured port
        genericPorts.put(servoPort.getPortNumber(), servoPort);
    }

    protected void prepareSwitchPortFromConfig(final FlatSwitchPortType switchPortType) {

        // overwrite configured ports

        int portNumber = switchPortType.getPortId();
        GenericSimulationPort switchPort = new GenericSimulationPort(portNumber);

        int portTypeMap = getPortTypeMap(switchPortType.getPortTypeMap());

        switchPort.setCurrentPortType(LcOutputType.SWITCHPORT, portTypeMap);

        Map> portConfig = new LinkedHashMap<>();
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_TICKS,
                new BytePortConfigValue(ByteUtils.getLowByte(switchPortType.getTicks())));

        switchPort.setPortConfigX(portConfig);

        switchPort.setConfigStatus(PortConfigStatus.CONFIG_PASSED);

        switchPort.setPortStatus(SwitchPortStatus.ON.getType().getType());

        LOGGER.info("Add configured switch port: {}", switchPort);

        // add the configured port
        genericPorts.put(switchPort.getPortNumber(), switchPort);
    }

    protected void prepareLightPortFromConfig(final FlatLightPortType lightPortType) {

        // overwrite configured ports

        int portNumber = lightPortType.getPortId();
        GenericSimulationPort lightPort = new GenericSimulationPort(portNumber);

        int portTypeMap = getPortTypeMap(lightPortType.getPortTypeMap());
        lightPort.setCurrentPortType(LcOutputType.LIGHTPORT, portTypeMap);

        lightPort.setPortNumber(lightPortType.getPortId());

        Map> portConfig = new LinkedHashMap<>();

        portConfig
            .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_OFF,
                new BytePortConfigValue(ByteUtils.getLowByte(lightPortType.getIntensityOff())));
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_LEVEL_PORT_ON,
                new BytePortConfigValue(ByteUtils.getLowByte(lightPortType.getIntensityOn())));
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_DIMM_DOWN,
                new BytePortConfigValue(ByteUtils.getLowByte(lightPortType.getDimSlopeDown())));
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_DIMM_UP,
                new BytePortConfigValue(ByteUtils.getLowByte(lightPortType.getDimSlopeUp())));

        // TODO RGB
        // values.put(BidibLibrary.BIDIB_PCFG_RGB, new RgbPortConfigValue(Integer.valueOf(3)));

        if (lightPortType.getTransitionTime() != null) {
            portConfig
                .put(BidibLibrary.BIDIB_PCFG_TRANSITION_TIME,
                    new BytePortConfigValue(ByteUtils.getLowByte(lightPortType.getTransitionTime())));
        }
        lightPort.setPortConfigX(portConfig);

        lightPort.setConfigStatus(PortConfigStatus.CONFIG_PASSED);

        lightPort.setPortStatus(LightPortStatus.OFF.getType().getType());

        LOGGER.info("Add configured light port: {}", lightPort);

        // add the configured port
        genericPorts.put(lightPort.getPortNumber(), lightPort);

    }

    protected void prepareInutPortFromConfig(final FlatInputPortType inputPortType) {

        int portNumber = inputPortType.getPortId();
        GenericSimulationPort inputPort = new GenericSimulationPort(portNumber);

        int portTypeMap = getPortTypeMap(inputPortType.getPortTypeMap());

        inputPort.setCurrentPortType(LcOutputType.INPUTPORT, portTypeMap);

        Map> portConfig = new LinkedHashMap<>();
        portConfig
            .put(BidibLibrary.BIDIB_PCFG_TICKS,
                new BytePortConfigValue(ByteUtils.getLowByte(inputPortType.getTicks())));

        inputPort.setPortConfigX(portConfig);

        inputPort.setConfigStatus(PortConfigStatus.CONFIG_PASSED);

        inputPort.setPortStatus(InputPortStatus.ON.getType().getType());

        LOGGER.info("Add configured input port: {}", inputPort);

        // add the configured port
        genericPorts.put(inputPort.getPortNumber(), inputPort);
    }

    protected void prepareInputPortFromConfig(final InputPortType inputPortType) {

        // overwrite configured ports
        if (CollectionUtils.isNotEmpty(inputPortType.getPort())) {

            // evaluate the configured ports
            for (InputPortParamsType portParams : inputPortType.getPort()) {
                InputPort inputPort = new InputPort();
                inputPort.setId(portParams.getPortId());

                inputPort.setSwitchOffTime(portParams.getTicks());

                inputPort.setConfigStatus(PortConfigStatus.CONFIG_PASSED);

                LOGGER.info("Add configured port: {}", inputPort);

                // add the configured port
                inputPorts.put(inputPort.getId(), inputPort);
            }

            // use the number of configured ports
            inputPortCount = inputPorts.size();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy