/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.plc4x.simulator.server.cbus.protocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.plc4x.java.cbus.readwrite.*;
import org.apache.plc4x.simulator.model.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CBusServerAdapter extends ChannelInboundHandlerAdapter {
private static final List<Byte> AVAILABLE_UNITS = Arrays.asList((byte) 3, (byte) 23, (byte) 48);
private static final Logger LOGGER = LoggerFactory.getLogger(CBusServerAdapter.class);
private Context context;
private static final RequestContext requestContext = new RequestContext(false);
private static boolean connect;
private static boolean smart;
private static boolean idmon;
private static boolean exstat;
private static boolean monitor;
private static boolean monall;
private static boolean pun;
private static boolean pcn;
private static boolean srchk;
private static byte monitorApplicationAddress1;
private static byte monitorApplicationAddress2;
private static CBusOptions cBusOptions;
private final Lock outputLock = new ReentrantLock();
private ScheduledFuture<?> salMonitorFuture;
private ScheduledFuture<?> mmiMonitorFuture;
public CBusServerAdapter(Context context) {
LOGGER.info("Creating adapter with context {}", context);
this.context = context;
cBusOptions = new CBusOptions(connect, smart, idmon, exstat, monitor, monall, pun, pcn, srchk);
}
private static void buildCBusOptions() {
LOGGER.info("Updating options {}", cBusOptions);
cBusOptions = new CBusOptions(connect, smart, idmon, exstat, monitor, monall, pun, pcn, srchk);
LOGGER.info("Updated options {}", cBusOptions);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (salMonitorFuture != null)
salMonitorFuture.cancel(false);
if (mmiMonitorFuture != null)
mmiMonitorFuture.cancel(false);
super.channelInactive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!(msg instanceof CBusMessage)) {
return;
}
try {
outputLock.lock();
syncChannelRead(ctx, msg);
} finally {
ctx.flush();
outputLock.unlock();
}
}
private void syncChannelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
LOGGER.debug("Working with cBusOptions\n{}", cBusOptions);
// Serial is slow
TimeUnit.MILLISECONDS.sleep(100);
if (!smart && !connect) {
// In this mode every message will be echoed
LOGGER.info("Sending echo");
ctx.write(msg);
}
CBusMessage packet = (CBusMessage) msg;
if (packet instanceof CBusMessageToClient) {
LOGGER.info("Message to client not supported\n{}", packet);
return;
}
CBusMessageToServer cBusMessageToServer = (CBusMessageToServer) packet;
Request request = cBusMessageToServer.getRequest();
if (request instanceof RequestEmpty || request instanceof RequestNull) {
LOGGER.debug("Ignoring\n{}", request);
} else if (request instanceof RequestDirectCommandAccess) {
handleDirectCommandAccess(ctx, (RequestDirectCommandAccess) request);
} else if (request instanceof RequestCommand) {
handleRequestCommand(ctx, (RequestCommand) request);
} else if (request instanceof RequestObsolete) {
RequestObsolete requestObsolete = (RequestObsolete) request;
LOGGER.info("Handling RequestObsolete\n{}", requestObsolete);
// TODO: handle this
} else if (request instanceof RequestReset) {
if (smart || connect) {
// On reset, we need to send the echo if we had not sent it above
LOGGER.info("Sending echo");
ctx.write(msg);
}
LOGGER.info("Handling RequestReset\n{}", request);
handleReset();
} else if (request instanceof RequestSmartConnectShortcut) {
handleSmartConnect((RequestSmartConnectShortcut) request);
}
}
private void handleDirectCommandAccess(ChannelHandlerContext ctx, RequestDirectCommandAccess requestDirectCommandAccess) {
CALData calData = requestDirectCommandAccess.getCalData();
LOGGER.info("Handling RequestDirectCommandAccess\n{}", requestDirectCommandAccess);
handleCalData(ctx, calData, requestDirectCommandAccess.getAlpha());
}
private void handleRequestCommand(ChannelHandlerContext ctx, RequestCommand requestCommand) {
LOGGER.info("Handling RequestCommand\n{}", requestCommand);
CBusCommand cbusCommand = requestCommand.getCbusCommand();
LOGGER.info("Handling CBusCommand\n{}", cbusCommand);
if (cbusCommand instanceof CBusCommandPointToPoint) {
CBusCommandPointToPoint cBusCommandPointToPoint = (CBusCommandPointToPoint) cbusCommand;
CBusPointToPointCommand command = cBusCommandPointToPoint.getCommand();
UnitAddress unitAddress = null;
if (command instanceof CBusPointToPointCommandIndirect) {
CBusPointToPointCommandIndirect cBusPointToPointCommandIndirect = (CBusPointToPointCommandIndirect) command;
// TODO: handle bridgeAddress
// TODO: handle networkRoute
unitAddress = cBusPointToPointCommandIndirect.getUnitAddress();
}
if (command instanceof CBusPointToPointCommandDirect) {
CBusPointToPointCommandDirect cBusPointToPointCommandDirect = (CBusPointToPointCommandDirect) command;
unitAddress = cBusPointToPointCommandDirect.getUnitAddress();
}
if (unitAddress == null) {
throw new IllegalStateException("Unit address should be set at this point");
}
boolean knownUnit = AVAILABLE_UNITS.contains(unitAddress.getAddress());
if (!knownUnit) {
LOGGER.warn("{} not a known unit", unitAddress);
ReplyOrConfirmation replyOrConfirmation = new ServerErrorReply((byte) 0x0, cBusOptions, requestContext);
CBusMessageToClient cBusMessageToClient = new CBusMessageToClient(replyOrConfirmation, requestContext, cBusOptions);
ctx.writeAndFlush(cBusMessageToClient);
return;
}
CALData calData = command.getCalData();
handleCalData(ctx, calData, requestCommand.getAlpha());
return;
} else if (cbusCommand instanceof CBusCommandPointToMultiPoint) {
CBusCommandPointToMultiPoint cBusCommandPointToMultiPoint = (CBusCommandPointToMultiPoint) cbusCommand;
CBusPointToMultiPointCommand command = cBusCommandPointToMultiPoint.getCommand();
if (command instanceof CBusPointToMultiPointCommandStatus) {
CBusPointToMultiPointCommandStatus cBusPointToMultiPointCommandStatus = (CBusPointToMultiPointCommandStatus) command;
StatusRequest statusRequest = cBusPointToMultiPointCommandStatus.getStatusRequest();
if (statusRequest instanceof StatusRequestBinaryState) {
StatusRequestBinaryState statusRequestBinaryState = (StatusRequestBinaryState) statusRequest;
LOGGER.info("Handling StatusRequestBinaryState\n{}", statusRequestBinaryState);
handleStatusRequestBinary(ctx, requestCommand, statusRequestBinaryState.getApplication());
}
if (statusRequest instanceof StatusRequestBinaryStateDeprecated) {
StatusRequestBinaryStateDeprecated statusRequestBinaryStateDeprecated = (StatusRequestBinaryStateDeprecated) statusRequest;
LOGGER.info("Handling StatusRequestBinaryStateDeprecated\n{}", statusRequestBinaryStateDeprecated);
handleStatusRequestBinary(ctx, requestCommand, statusRequestBinaryStateDeprecated.getApplication());
return;
}
if (statusRequest instanceof StatusRequestLevel) {
StatusRequestLevel statusRequestLevel = (StatusRequestLevel) statusRequest;
handleStatusRequestLevel(ctx, requestCommand, statusRequestLevel);
return;
}
throw new IllegalStateException();
}
if (command instanceof CBusPointToMultiPointCommandNormal) {
CBusPointToMultiPointCommandNormal cBusPointToMultiPointCommandNormal = (CBusPointToMultiPointCommandNormal) command;
LOGGER.info("Handling CBusPointToMultiPointCommandNormal\n{}", cBusPointToMultiPointCommandNormal);
// TODO: handle this
return;
}
// TODO: handle this
return;
} else if (cbusCommand instanceof CBusCommandPointToPointToMultiPoint) {
CBusCommandPointToPointToMultiPoint cBusCommandPointToPointToMultiPoint = (CBusCommandPointToPointToMultiPoint) cbusCommand;
LOGGER.info("Handling CBusCommandPointToPointToMultiPoint\n{}", cBusCommandPointToPointToMultiPoint);
// TODO: handle this
return;
} else if (cbusCommand instanceof CBusCommandDeviceManagement) {
CBusCommandDeviceManagement cBusCommandDeviceManagement = (CBusCommandDeviceManagement) cbusCommand;
LOGGER.info("Handling CBusCommandDeviceManagement\n{}", cBusCommandDeviceManagement);
// TODO: handle this
return;
}
Alpha alpha = requestCommand.getAlpha();
if (alpha != null) {
Confirmation confirmation = new Confirmation(alpha, null, ConfirmationType.NOT_TRANSMITTED_CORRUPTION);
ReplyOrConfirmationConfirmation replyOrConfirmationConfirmation = new ReplyOrConfirmationConfirmation(alpha.getCharacter(), confirmation, null, cBusOptions, requestContext);
CBusMessage response = new CBusMessageToClient(replyOrConfirmationConfirmation, requestContext, cBusOptions);
LOGGER.info("Send response\n{}", response);
ctx.writeAndFlush(response);
}
}
private static void handleStatusRequestLevel(ChannelHandlerContext ctx, RequestCommand requestCommand, StatusRequestLevel statusRequestLevel) {
StatusCoding coding = StatusCoding.LEVEL_BY_THIS_SERIAL_INTERFACE;
// TODO: map actual values from simulator
byte blockStart = statusRequestLevel.getStartingGroupAddressLabel();
List<LevelInformation> levelInformations = Collections.singletonList(new LevelInformationNormal(0x5555, LevelInformationNibblePair.Value_F, LevelInformationNibblePair.Value_F));
CALData calData = new CALDataStatusExtended(CALCommandTypeContainer.CALCommandReply_4Bytes, null, coding, statusRequestLevel.getApplication(), blockStart, null, levelInformations, requestContext);
CALReply calReply;
if (exstat) {
calReply = new CALReplyLong((byte) 0x0, calData, (byte) 0x0, new UnitAddress((byte) 0x04), null, new SerialInterfaceAddress((byte) 0x02), (byte) 0x0, null, cBusOptions, requestContext);
} else {
calReply = new CALReplyShort((byte) 0x0, calData, cBusOptions, requestContext);
}
CBusMessage response = createCBusMessageForReply(requestCommand.getAlpha(), calReply, cBusOptions);
LOGGER.info("Send level status response\n{}", response);
ctx.writeAndFlush(response);
}
private void handleStatusRequestBinary(ChannelHandlerContext ctx, RequestCommand requestCommand, ApplicationIdContainer application) {
if (application == ApplicationIdContainer.NETWORK_CONTROL) {
LOGGER.info("Handling installation MMI Request");
sendInstallationMMIResponse(ctx, requestCommand.getAlpha());
return;
}
List<StatusByte> statusBytes = new LinkedList<>();
// TODO: map actual values from simulator
for (int i = 0; i < 22; i++) {
statusBytes.add(new StatusByte(GAVState.ON, GAVState.ERROR, GAVState.OFF, GAVState.DOES_NOT_EXIST));
}
LOGGER.info("Send binary status response");
sendStatusBytes(ctx, "First parts {}", application, (byte) 0x0, statusBytes, requestCommand.getAlpha(), cBusOptions);
}
private void handleCalData(ChannelHandlerContext ctx, CALData calData, Alpha alpha) {
if (calData instanceof CALDataGetStatus) {
// TODO: implement me
} else if (calData instanceof CALDataIdentify) {
handleCalDataIdentify(ctx, (CALDataIdentify) calData, alpha);
} else if (calData instanceof CALDataRecall) {
// TODO: implement me
} else if (calData instanceof CALDataReset) {
CALDataReset calDataReset = (CALDataReset) calData;
if (smart || connect) {
// On reset, we need to send the echo if we had not sent it above
LOGGER.info("Sending echo");
ctx.write(calDataReset);
}
LOGGER.info("Handling CALDataReset\n{}", calDataReset);
handleReset();
} else if (calData instanceof CALDataWrite) {
CALDataWrite calDataWrite = (CALDataWrite) calData;
Runnable acknowledger = () -> {
CALDataAcknowledge calDataAcknowledge = new CALDataAcknowledge(CALCommandTypeContainer.CALCommandAcknowledge, null, calDataWrite.getParamNo(), (short) 0x0, requestContext);
CALReplyShort calReply = new CALReplyShort((byte) 0x0, calDataAcknowledge, cBusOptions, requestContext);
EncodedReplyCALReply encodedReply = new EncodedReplyCALReply((byte) 0x0, calReply, cBusOptions, requestContext);
ReplyEncodedReply replyEncodedReply = new ReplyEncodedReply((byte) 0x0, encodedReply, null, cBusOptions, requestContext);
ReplyOrConfirmation replyOrConfirmation = new ReplyOrConfirmationReply((byte) 0x0, replyEncodedReply, new ResponseTermination(), cBusOptions, requestContext);
if (alpha != null) {
replyOrConfirmation = new ReplyOrConfirmationConfirmation((byte) 0x0, new Confirmation(alpha, null, ConfirmationType.CONFIRMATION_SUCCESSFUL), replyOrConfirmation, cBusOptions, requestContext);
}
CBusMessageToClient cBusMessageToClient = new CBusMessageToClient(replyOrConfirmation, requestContext, cBusOptions);
LOGGER.info("Sending ack\n{}", cBusMessageToClient);
ctx.writeAndFlush(cBusMessageToClient);
};
switch (calDataWrite.getParamNo().getParameterType()) {
case APPLICATION_ADDRESS_1:
ApplicationAddress1 applicationAddress1 = ((ParameterValueApplicationAddress1) calDataWrite.getParameterValue()).getValue();
monitorApplicationAddress1 = applicationAddress1.getAddress();
acknowledger.run();
return;
case APPLICATION_ADDRESS_2:
ApplicationAddress2 applicationAddress2 = ((ParameterValueApplicationAddress2) calDataWrite.getParameterValue()).getValue();
monitorApplicationAddress2 = applicationAddress2.getAddress();
acknowledger.run();
return;
case INTERFACE_OPTIONS_1:
InterfaceOptions1 interfaceOptions1 = ((ParameterValueInterfaceOptions1) calDataWrite.getParameterValue()).getValue();
idmon = interfaceOptions1.getIdmon();
monitor = interfaceOptions1.getMonitor();
if (monitor) startMMIMonitor(ctx);
else stopMMIMonitor();
smart = interfaceOptions1.getSmart();
srchk = interfaceOptions1.getSrchk();
// TODO: add support for xonxoff
// xonxoff = interfaceOptions1.getXonXoff();
connect = interfaceOptions1.getConnect();
if (connect) startSALMonitor(ctx);
else stopSALMonitor();
buildCBusOptions();
acknowledger.run();
return;
case INTERFACE_OPTIONS_2:
InterfaceOptions2 interfaceOptions2 = ((ParameterValueInterfaceOptions2) calDataWrite.getParameterValue()).getValue();
// TODO: add support for burden
// burden = interfaceOptions2.getBurden();
// TODO: add support for clockgen
// clockgen = interfaceOptions2.getClockGen();
buildCBusOptions();
acknowledger.run();
return;
case INTERFACE_OPTIONS_3:
InterfaceOptions3 interfaceOptions3Value = ((ParameterValueInterfaceOptions3) calDataWrite.getParameterValue()).getValue();
boolean oldExstat = exstat;
exstat = interfaceOptions3Value.getExstat();
if (oldExstat != exstat) {
LOGGER.info("Restarting monitors");
stopMMIMonitor();
startMMIMonitor(ctx);
stopSALMonitor();
startSALMonitor(ctx);
}
pun = interfaceOptions3Value.getPun();
// TODO: add support for localsal
// localsal = interfaceOptions3Value.getLocalSal();
pcn = interfaceOptions3Value.getPcn();
buildCBusOptions();
acknowledger.run();
return;
case BAUD_RATE_SELECTOR:
BaudRateSelector baudRateSelector = ((ParameterValueBaudRateSelector) calDataWrite.getParameterValue()).getValue();
// TODO: add support for baudrate
// baudrate = baudRateSelector.getValue();
buildCBusOptions();
acknowledger.run();
return;
case INTERFACE_OPTIONS_1_POWER_UP_SETTINGS:
InterfaceOptions1 interfaceOptions1PowerUpSettings = ((ParameterValueInterfaceOptions1PowerUpSettings) calDataWrite.getParameterValue()).getValue().getInterfaceOptions1();
idmon = interfaceOptions1PowerUpSettings.getIdmon();
monitor = interfaceOptions1PowerUpSettings.getMonitor();
if (monitor) startMMIMonitor(ctx);
else stopMMIMonitor();
smart = interfaceOptions1PowerUpSettings.getSmart();
srchk = interfaceOptions1PowerUpSettings.getSrchk();
// TODO: add support for xonxoff
// xonxoff = interfaceOptions1PowerUpSettings.getXonXoff();
connect = interfaceOptions1PowerUpSettings.getConnect();
if (connect) startSALMonitor(ctx);
else stopSALMonitor();
buildCBusOptions();
acknowledger.run();
return;
case CUSTOM_MANUFACTURER:
// TODO: handle other param typed
acknowledger.run();
return;
case SERIAL_NUMBER:
// TODO: handle other param typed
acknowledger.run();
return;
case CUSTOM_TYPE:
// TODO: handle other param typed
acknowledger.run();
return;
default:
throw new IllegalStateException("Unmapped type");
}
} else {
throw new IllegalStateException("Unmapped type: " + calData.getClass());
}
}
private static CBusMessage createCBusMessageForReply(Alpha alpha, CALReply calReply, CBusOptions cBusOptions) {
EncodedReply encodedReply = new EncodedReplyCALReply((byte) 0x0, calReply, CBusServerAdapter.cBusOptions, CBusServerAdapter.requestContext);
ReplyEncodedReply replyEncodedReply = new ReplyEncodedReply((byte) 0xC0, encodedReply, null, CBusServerAdapter.cBusOptions, CBusServerAdapter.requestContext);
ReplyOrConfirmation replyOrConfirmation = new ReplyOrConfirmationReply((byte) 0xFF, replyEncodedReply, new ResponseTermination(), CBusServerAdapter.cBusOptions, CBusServerAdapter.requestContext);
if (alpha != null) {
Confirmation confirmation = new Confirmation(alpha, null, ConfirmationType.CONFIRMATION_SUCCESSFUL);
replyOrConfirmation = new ReplyOrConfirmationConfirmation(alpha.getCharacter(), confirmation, replyOrConfirmation, CBusServerAdapter.cBusOptions, CBusServerAdapter.requestContext);
}
return new CBusMessageToClient(replyOrConfirmation, requestContext, cBusOptions);
}
private static void sendInstallationMMIResponse(ChannelHandlerContext ctx, Alpha alpha) {
LOGGER.info("Send installation MMIs");
sendMMIs(ctx, ApplicationIdContainer.NETWORK_CONTROL, alpha, cBusOptions);
}
private static void sendMonitoredMMIs(ChannelHandlerContext ctx) {
LOGGER.info("Send installation MMIs");
// TODO: for whatever reason those are not send with a crc
CBusOptions cBusOptions = new CBusOptions(connect, smart, idmon, exstat, monitor, monall, pun, pcn, false);
sendMMIs(ctx, ApplicationIdContainer.LIGHTING_38, null, cBusOptions);
}
private static void sendMMIs(ChannelHandlerContext ctx, ApplicationIdContainer application, Alpha alpha, CBusOptions cBusOptions) {
{
byte blockStart = 0x0;
List<StatusByte> unitStatusBytes = new LinkedList<>();
for (int i = blockStart; i <= 88 - 4; i = i + 4) {
LOGGER.debug("Handling units 0-88 {},{},{},{}", i, (i + 1), (i + 2), (i + 3));
addStatusBytesAtOffset(unitStatusBytes, i);
}
LOGGER.debug("Produced {}, status bytes which equates to {} status", unitStatusBytes.size(), unitStatusBytes.size() * 4);
sendStatusBytes(ctx, "Sending second part {}", application, blockStart, unitStatusBytes, alpha, cBusOptions);
}
{
byte blockStart = 88;
List<StatusByte> unitStatusBytes = new LinkedList<>();
for (int i = 88; i <= 88 + 88 - 4; i = i + 4) {
LOGGER.debug("Handling units 88-176 {},{},{},{}", i, (i + 1), (i + 2), (i + 3));
addStatusBytesAtOffset(unitStatusBytes, i);
}
LOGGER.debug("Produced {}, status bytes which equates to {} status", unitStatusBytes.size(), unitStatusBytes.size() * 4);
sendStatusBytes(ctx, "Sending second part {}", application, blockStart, unitStatusBytes, null, cBusOptions);
}
{
byte blockStart = (byte) 176;
List<StatusByte> unitStatusBytes = new LinkedList<>();
for (int i = 176; i <= 176 + 80 - 4; i = i + 4) {
LOGGER.debug("Handling units 176-256 {},{},{},{}", i, (i + 1), (i + 2), (i + 3));
addStatusBytesAtOffset(unitStatusBytes, i);
}
LOGGER.debug("Produced {}, status bytes which equates to {} status", unitStatusBytes.size(), unitStatusBytes.size() * 4);
sendStatusBytes(ctx, "Sending third part {}", application, blockStart, unitStatusBytes, null, cBusOptions);
}
}
private static void addStatusBytesAtOffset(List<StatusByte> unitStatusBytes, int i) {
unitStatusBytes.add(
new StatusByte(
AVAILABLE_UNITS.contains((byte) (i + 3)) ? GAVState.ON : GAVState.DOES_NOT_EXIST,
AVAILABLE_UNITS.contains((byte) (i + 2)) ? GAVState.ON : GAVState.DOES_NOT_EXIST,
AVAILABLE_UNITS.contains((byte) (i + 1)) ? GAVState.ON : GAVState.DOES_NOT_EXIST,
AVAILABLE_UNITS.contains((byte) (i)) ? GAVState.ON : GAVState.DOES_NOT_EXIST
)
);
}
private static void sendStatusBytes(ChannelHandlerContext ctx, String logMessage, ApplicationIdContainer application, byte blockStart, List<StatusByte> unitStatusBytes, Alpha alpha, CBusOptions cBusOptions) {
int numberOfStatusBytes = unitStatusBytes.size();
CALReply calReply;
CALCommandTypeContainer commandTypeContainer = null;
if (cBusOptions.getExstat()) {
for (CALCommandTypeContainer calCommandTypeContainerElement : CALCommandTypeContainer.values()) {
if (calCommandTypeContainerElement.getCommandType() != CALCommandType.STATUS_EXTENDED) {
continue;
}
if (calCommandTypeContainerElement.getNumBytes() + 3 == numberOfStatusBytes) {
commandTypeContainer = calCommandTypeContainerElement;
break;
}
}
CALData calData = new CALDataStatusExtended(commandTypeContainer, null, StatusCoding.BINARY_BY_THIS_SERIAL_INTERFACE, application, blockStart, unitStatusBytes, null, requestContext);
// TODO: do we use a random unit or a fixed or do we need it as parameter
int randomElementIndex = ThreadLocalRandom.current().nextInt(AVAILABLE_UNITS.size()) % AVAILABLE_UNITS.size();
byte randomUnit = AVAILABLE_UNITS.get(randomElementIndex);
calReply = new CALReplyLong((byte) 0x86, calData, 0x00, new UnitAddress(randomUnit), null, new SerialInterfaceAddress((byte) 0x02), (byte) 0x00, null, cBusOptions, requestContext);
} else {
for (CALCommandTypeContainer calCommandTypeContainerElement : CALCommandTypeContainer.values()) {
if (calCommandTypeContainerElement.getCommandType() != CALCommandType.STATUS) {
continue;
}
if (calCommandTypeContainerElement.getNumBytes() + 3 == numberOfStatusBytes) {
commandTypeContainer = calCommandTypeContainerElement;
break;
}
}
CALData calData = new CALDataStatus(commandTypeContainer, null, application, (byte) 0x00, unitStatusBytes, requestContext);
calReply = new CALReplyShort((byte) 0x0, calData, cBusOptions, requestContext);
}
CBusMessage response = createCBusMessageForReply(alpha, calReply, cBusOptions);
LOGGER.debug(logMessage, response);
ctx.writeAndFlush(response);
}
private void handleCalDataIdentify(ChannelHandlerContext ctx, CALDataIdentify calDataIdentify, Alpha alpha) {
short numBytes;
IdentifyReplyCommand identifyReplyCommand;
switch (calDataIdentify.getAttribute()) {
case Manufacturer:
numBytes = 0x08;
identifyReplyCommand = new IdentifyReplyCommandManufacturer("Apache ", numBytes);
break;
case Type:
numBytes = 0x08;
identifyReplyCommand = new IdentifyReplyCommandType("plc4x-si", numBytes);
break;
case FirmwareVersion:
numBytes = 0x08;
identifyReplyCommand = new IdentifyReplyCommandFirmwareVersion(" 0.09 ", numBytes);
break;
case Summary:
numBytes = 0x09;
identifyReplyCommand = new IdentifyReplyCommandSummary("NOIDEA", (byte) 0xAF, "0900", numBytes);
break;
case ExtendedDiagnosticSummary:
numBytes = 0x0C;
identifyReplyCommand = new IdentifyReplyCommandExtendedDiagnosticSummary(ApplicationIdContainer.FREE_USAGE_01, ApplicationIdContainer.FREE_USAGE_0F, (byte) 0x0, 0x0, 4711L, (byte) 0x13, false, false, false, true, false, false, false, false, false, false, false, false, false, numBytes);
break;
case NetworkTerminalLevels:
numBytes = 0x0C;
identifyReplyCommand = new IdentifyReplyCommandNetworkTerminalLevels(new byte[]{0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13}, numBytes);
break;
case TerminalLevel:
numBytes = 0x0C;
identifyReplyCommand = new IdentifyReplyCommandTerminalLevels(new byte[]{0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13}, numBytes);
break;
case NetworkVoltage:
numBytes = 0x05;
identifyReplyCommand = new IdentifyReplyCommandNetworkVoltage("48", "7", numBytes);
break;
case GAVValuesCurrent:
numBytes = 0x10;
identifyReplyCommand = new IdentifyReplyCommandGAVValuesCurrent(new byte[]{
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
}, numBytes);
break;
case GAVValuesStored:
numBytes = 0x10;
identifyReplyCommand = new IdentifyReplyCommandGAVValuesStored(new byte[]{
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
}, numBytes);
break;
case GAVPhysicalAddresses:
numBytes = 0x10;
identifyReplyCommand = new IdentifyReplyCommandGAVPhysicalAddresses(new byte[]{
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
}, numBytes);
break;
case LogicalAssignment:
numBytes = 0x0E;
identifyReplyCommand = new IdentifyReplyCommandLogicalAssignment(Collections.singletonList(new LogicAssignment(false, true, true, true, true, true)), numBytes);
break;
case Delays:
numBytes = 0x0F;
identifyReplyCommand = new IdentifyReplyCommandDelays(new byte[]{0x3}, (byte) 0x13, numBytes);
break;
case MinimumLevels:
numBytes = 0x0E;
identifyReplyCommand = new IdentifyReplyCommandMinimumLevels(new byte[]{0x3}, numBytes);
break;
case MaximumLevels:
numBytes = 0x0F;
identifyReplyCommand = new IdentifyReplyCommandMaximumLevels(new byte[]{0xF}, numBytes);
break;
case CurrentSenseLevels:
numBytes = 0x10;
identifyReplyCommand = new IdentifyReplyCommandCurrentSenseLevels(new byte[]{0xF}, numBytes);
break;
case OutputUnitSummary:
numBytes = 0x12;
identifyReplyCommand = new IdentifyReplyCommandOutputUnitSummary(new IdentifyReplyCommandUnitSummary(false, false, false, false, false, false, false, false), (byte) 0x4, (byte) 0x4, (short) 45, numBytes);
break;
case DSIStatus:
numBytes = 0x12;
identifyReplyCommand = new IdentifyReplyCommandDSIStatus(ChannelStatus.OK, ChannelStatus.OK, ChannelStatus.OK, ChannelStatus.OK, ChannelStatus.OK, ChannelStatus.OK, ChannelStatus.OK, ChannelStatus.OK, UnitStatus.OK, (byte) 0x34, numBytes);
break;
default:
throw new IllegalStateException("unmapped type " + calDataIdentify.getAttribute());
}
CALData calData = new CALDataIdentifyReply(getReplyCommandType(numBytes + 1), null, calDataIdentify.getAttribute(), identifyReplyCommand, requestContext);
CALReply calReply;
if (exstat) {
calReply = new CALReplyLong((byte) 0x0, calData, (byte) 0x0, new UnitAddress((byte) 0x0), null, new SerialInterfaceAddress((byte) 0x02), (byte) 0x0, null, cBusOptions, requestContext);
} else {
calReply = new CALReplyShort((byte) 0x0, calData, cBusOptions, requestContext);
}
CBusMessage response = createCBusMessageForReply(alpha, calReply, cBusOptions);
LOGGER.info("Send identify response\n{}", response);
ctx.writeAndFlush(response);
}
private static void handleSmartConnect(RequestSmartConnectShortcut requestSmartConnectShortcut) {
LOGGER.info("Handling RequestSmartConnectShortcut\n{}", requestSmartConnectShortcut);
smart = true;
connect = true;
buildCBusOptions();
}
private void handleReset() {
connect = false;
smart = false;
idmon = false;
exstat = false;
monitor = false;
monall = false;
pun = false;
pcn = false;
srchk = false;
stopSALMonitor();
stopMMIMonitor();
}
private void startSALMonitor(ChannelHandlerContext ctx) {
if (salMonitorFuture != null) {
LOGGER.debug("SAL Monitor already running");
return;
}
LOGGER.info("Starting monitor");
salMonitorFuture = ctx.executor().scheduleAtFixedRate(() -> {
if (monitorApplicationAddress1 != 0x38 && monitorApplicationAddress2 != 0x38 && monitorApplicationAddress1 != (byte) 0xFF && monitorApplicationAddress2 != (byte) 0xFF) {
LOGGER.debug("Filtered because monitor application address 1 {} monitor application address 1 {}", monitorApplicationAddress1, monitorApplicationAddress2);
return;
}
try {
int randomElementIndex = ThreadLocalRandom.current().nextInt(AVAILABLE_UNITS.size()) % AVAILABLE_UNITS.size();
byte randomUnit = AVAILABLE_UNITS.get(randomElementIndex);
outputLock.lock();
MonitoredSAL monitoredSAL;
SALData salData = createLightingData();
if (cBusOptions.getExstat()) {
monitoredSAL = new MonitoredSALLongFormSmartMode((byte) 0x05, (byte) 0x00, new UnitAddress(randomUnit), null, ApplicationIdContainer.LIGHTING_38, (byte) 0x00, null, salData, cBusOptions);
} else {
monitoredSAL = new MonitoredSALShortFormBasicMode((byte) 0x0, (byte) 0x0, (short) 0x0, (short) 0x0, (byte) 0x0, ApplicationIdContainer.LIGHTING_38, salData, cBusOptions);
}
EncodedReply encodedReply = new MonitoredSALReply((byte) 0x0, monitoredSAL, cBusOptions, requestContext);
Reply reply = new ReplyEncodedReply((byte) 0x0, encodedReply, null, cBusOptions, requestContext);
ReplyOrConfirmation replyOrConfirmation = new ReplyOrConfirmationReply((byte) 0x00, reply, new ResponseTermination(), cBusOptions, requestContext);
CBusMessage message = new CBusMessageToClient(replyOrConfirmation, requestContext, cBusOptions);
LOGGER.info("[SAL Monitor] Sending out\n{}", message);
ctx.writeAndFlush(message);
} finally {
outputLock.unlock();
}
}, 5, 5, TimeUnit.SECONDS);
}
private static SALData createLightingData() {
LightingData lightingData;
double random = Math.random();
if (random < 0.25) {
lightingData = new LightingDataOn(LightingCommandTypeContainer.LightingCommandOn, (byte) 0xAF);
} else if (random > 0.25 && random < 0.5) {
lightingData = new LightingDataOff(LightingCommandTypeContainer.LightingCommandOff, (byte) 0xAF);
} else if (random > 0.5 && random < 0.75) {
lightingData = new LightingDataRampToLevel(LightingCommandTypeContainer.LightingCommandRampToLevel_20Second, (byte) 0xAF, (byte) 0xE0);
} else {
lightingData = new LightingDataTerminateRamp(LightingCommandTypeContainer.LightingCommandTerminateRamp, (byte) 0xAF);
}
return new SALDataLighting(null, lightingData);
}
private void stopSALMonitor() {
if (salMonitorFuture == null) {
return;
}
LOGGER.info("Stopping SAL monitor");
salMonitorFuture.cancel(false);
salMonitorFuture = null;
}
private void startMMIMonitor(ChannelHandlerContext ctx) {
if (mmiMonitorFuture != null) {
LOGGER.debug("MMI Monitor already running");
return;
}
LOGGER.info("Starting MMI monitor");
mmiMonitorFuture = ctx.executor().scheduleAtFixedRate(() -> {
try {
outputLock.lock();
LOGGER.info("[MMI Monitor] Sending out infos");
sendMonitoredMMIs(ctx);
} finally {
outputLock.unlock();
}
}, 5, 5, TimeUnit.SECONDS);
}
private void stopMMIMonitor() {
if (mmiMonitorFuture == null) {
return;
}
LOGGER.info("Stopping monitor");
mmiMonitorFuture.cancel(false);
mmiMonitorFuture = null;
}
private CALCommandTypeContainer getReplyCommandType(int numBytes) {
for (CALCommandTypeContainer value : CALCommandTypeContainer.values()) {
if (value.getCommandType() == CALCommandType.REPLY && value.getNumBytes() == numBytes) {
return value;
}
}
throw new IllegalArgumentException("No reply type for " + numBytes);
}
}
↑ V6066 The type of object passed as argument is incompatible with the type of collection: Byte, Integer.
↑ V6067 Two or more case-branches perform the same actions.
↑ V6066 The type of object passed as argument is incompatible with the type of collection: Byte, Integer.
↑ V6021 Variable 'interfaceOptions2' is not used.
↑ V6021 Variable 'baudRateSelector' is not used.
↑ V6066 The type of object passed as argument is incompatible with the type of collection: Byte, Integer.
↑ V6066 The type of object passed as argument is incompatible with the type of collection: Byte, Integer.