Java tutorial
/** * Copyright 2015 Smart Society Services B.V. * * Licensed 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 * * http://www.apache.org/licenses/LICENSE-2.0 */ package com.alliander.osgp.webdevicesimulator.service; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.security.PrivateKey; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.Resource; import org.apache.commons.codec.binary.Base64; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Minutes; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.alliander.osgp.oslp.Oslp; import com.alliander.osgp.oslp.Oslp.ConfirmRegisterDeviceResponse; import com.alliander.osgp.oslp.Oslp.DaliConfiguration; import com.alliander.osgp.oslp.Oslp.GetActualPowerUsageRequest; import com.alliander.osgp.oslp.Oslp.GetActualPowerUsageResponse; import com.alliander.osgp.oslp.Oslp.GetFirmwareVersionResponse; import com.alliander.osgp.oslp.Oslp.GetPowerUsageHistoryRequest; import com.alliander.osgp.oslp.Oslp.GetPowerUsageHistoryResponse; import com.alliander.osgp.oslp.Oslp.GetStatusResponse; import com.alliander.osgp.oslp.Oslp.HistoryTermType; import com.alliander.osgp.oslp.Oslp.IndexAddressMap; import com.alliander.osgp.oslp.Oslp.LightValue; import com.alliander.osgp.oslp.Oslp.LongTermIntervalType; import com.alliander.osgp.oslp.Oslp.Message; import com.alliander.osgp.oslp.Oslp.MeterType; import com.alliander.osgp.oslp.Oslp.PageInfo; import com.alliander.osgp.oslp.Oslp.PowerUsageData; import com.alliander.osgp.oslp.Oslp.PsldData; import com.alliander.osgp.oslp.Oslp.RelayConfiguration; import com.alliander.osgp.oslp.Oslp.RelayType; import com.alliander.osgp.oslp.Oslp.SetEventNotificationsRequest; import com.alliander.osgp.oslp.Oslp.SetEventNotificationsResponse; import com.alliander.osgp.oslp.Oslp.SetLightRequest; import com.alliander.osgp.oslp.Oslp.SetLightResponse; import com.alliander.osgp.oslp.Oslp.SetScheduleRequest; import com.alliander.osgp.oslp.Oslp.SetScheduleResponse; import com.alliander.osgp.oslp.Oslp.SsldData; import com.alliander.osgp.oslp.Oslp.StartSelfTestResponse; import com.alliander.osgp.oslp.Oslp.StopSelfTestResponse; import com.alliander.osgp.oslp.Oslp.UpdateFirmwareRequest; import com.alliander.osgp.oslp.Oslp.UpdateFirmwareResponse; import com.alliander.osgp.oslp.OslpEnvelope; import com.alliander.osgp.oslp.OslpUtils; import com.alliander.osgp.webdevicesimulator.application.services.DeviceManagementService; import com.alliander.osgp.webdevicesimulator.domain.entities.Device; import com.alliander.osgp.webdevicesimulator.domain.entities.DeviceOutputSetting; import com.alliander.osgp.webdevicesimulator.domain.entities.OslpLogItem; import com.alliander.osgp.webdevicesimulator.domain.repositories.OslpLogItemRepository; import com.alliander.osgp.webdevicesimulator.domain.valueobjects.EventNotificationToBeSent; import com.alliander.osgp.webdevicesimulator.domain.valueobjects.LightType; import com.alliander.osgp.webdevicesimulator.domain.valueobjects.LinkType; import com.alliander.osgp.webdevicesimulator.domain.valueobjects.OutputType; import com.alliander.osgp.webdevicesimulator.domain.valueobjects.ProtocolType; import com.alliander.osgp.webdevicesimulator.exceptions.DeviceSimulatorException; import com.google.protobuf.ByteString; public class OslpChannelHandler extends SimpleChannelHandler { private static DateTimeZone localTimeZone = DateTimeZone.forID("Europe/Paris"); private static final Logger LOGGER = LoggerFactory.getLogger(OslpChannelHandler.class); private static class Callback { private final CountDownLatch latch = new CountDownLatch(1); private OslpEnvelope response; private final int connectionTimeout; Callback(final int connectionTimeout) { this.connectionTimeout = connectionTimeout; } OslpEnvelope get(final String deviceIdentification) throws IOException, DeviceSimulatorException { try { if (!this.latch.await(this.connectionTimeout, TimeUnit.MILLISECONDS)) { LOGGER.warn("Failed to receive response from device {} within timelimit {} ms", deviceIdentification, this.connectionTimeout); throw new IOException( "Failed to receive response within timelimit " + this.connectionTimeout + " ms"); } LOGGER.info("Response received within {} ms", this.connectionTimeout); } catch (final InterruptedException e) { throw new DeviceSimulatorException("InterruptedException", e); } return this.response; } void handle(final OslpEnvelope response) { this.response = response; this.latch.countDown(); } } @Autowired private OslpLogItemRepository oslpLogItemRepository; @Autowired private PrivateKey privateKey; @Resource private String oslpSignatureProvider; @Resource private String oslpSignature; @Resource private int connectionTimeout; @Autowired private ClientBootstrap bootstrap; @Autowired private DeviceManagementService deviceManagementService; private final Lock lock = new ReentrantLock(); private final ConcurrentMap<Integer, Callback> callbacks = new ConcurrentHashMap<>(); @Autowired private Integer sequenceNumberWindow; @Autowired private Integer sequenceNumberMaximum; private final List<OutOfSequenceEvent> outOfSequenceList = new ArrayList<>(); @Autowired private Long responseDelayTime; @Autowired private Long reponseDelayRandomRange; private final Random random = new Random(); private static final int CUMALATIVE_BURNING_MINUTES = 600; private static int INITIAL_BURNING_MINUTES = 100000; public static class OutOfSequenceEvent { private final Long deviceId; private final String request; private final DateTime timestamp; public OutOfSequenceEvent(final Long deviceId, final String request, final DateTime timestamp) { this.deviceId = deviceId; this.request = request; this.timestamp = timestamp; } public Long getDeviceId() { return this.deviceId; } public String getRequest() { return this.request; } public DateTime getTimestamp() { return this.timestamp; } } /** * Get an OutOfSequenceEvent for given device id. The OutOfSequenceEvent * instance will be removed from the list, before the instance is returned. * * @param deviceId * The id of the device. * * @return An OutOfSequenceEvent instance, or null. */ public OutOfSequenceEvent hasOutOfSequenceEventForDevice(final Long deviceId) { for (final OutOfSequenceEvent outOfSequenceEvent : this.outOfSequenceList) { if (outOfSequenceEvent.getDeviceId().equals(deviceId)) { this.outOfSequenceList.remove(outOfSequenceEvent); return outOfSequenceEvent; } } return null; } public void setPrivateKey(final PrivateKey privateKey) { this.privateKey = privateKey; } public void setProvider(final String provider) { this.oslpSignatureProvider = provider; } public void setSignature(final String signature) { this.oslpSignature = signature; } public void setOslpLogItemRepository(final OslpLogItemRepository oslpLogItemRepository) { this.oslpLogItemRepository = oslpLogItemRepository; } public void setDeviceManagementService(final DeviceManagementService deviceManagementService) { this.deviceManagementService = deviceManagementService; } public ClientBootstrap getBootstrap() { return this.bootstrap; } public void setBootstrap(final ClientBootstrap bootstrap) { this.bootstrap = bootstrap; } @Override public void messageReceived(final ChannelHandlerContext ctx, final MessageEvent e) throws Exception { final OslpEnvelope message = (OslpEnvelope) e.getMessage(); this.oslpLogItemRepository.save(new OslpLogItem(message.getDeviceId(), this.getDeviceIdentificationFromMessage(message.getPayloadMessage()), true, message.getPayloadMessage())); if (message.isValid()) { if (this.isOslpResponse(message)) { LOGGER.info("Received OSLP Response (before callback): {}", message.getPayloadMessage()); // Lookup correct callback and call handle method final Integer channelId = e.getChannel().getId(); final Callback callback = this.callbacks.remove(channelId); if (callback == null) { LOGGER.warn("Callback for channel {} does not longer exist, dropping response.", channelId); return; } callback.handle(message); } else { LOGGER.info("Received OSLP Request: {}", message.getPayloadMessage().toString().split(" ")[0]); // Sequence number logic byte[] sequenceNumber = message.getSequenceNumber(); Integer number = -1; if (!(message.getPayloadMessage().hasRegisterDeviceRequest() || message.getPayloadMessage().hasConfirmRegisterDeviceRequest())) { // Convert byte array to integer number = this.convertByteArrayToInteger(sequenceNumber); // Wrap the number back to 0 if the limit is reached or // increment if (number >= this.sequenceNumberMaximum) { LOGGER.info( "wrapping sequence number back to 0, current sequence number: {} next sequence number: 0", number); number = 0; } else { LOGGER.info( "incrementing sequence number, current sequence number: {} next sequence number: {}", number, number + 1); number += 1; } // Convert integer back to byte array sequenceNumber = this.convertIntegerToByteArray(number); } final byte[] deviceId = message.getDeviceId(); // Build the OslpEnvelope with the incremented sequence number. final OslpEnvelope.Builder responseBuilder = new OslpEnvelope.Builder() .withSignature(this.oslpSignature).withProvider(this.oslpSignatureProvider) .withPrimaryKey(this.privateKey).withDeviceId(deviceId).withSequenceNumber(sequenceNumber); // Pass the incremented sequence number to the handleRequest() // function for checking. responseBuilder.withPayloadMessage(this.handleRequest(message, number)); final OslpEnvelope response = responseBuilder.build(); this.oslpLogItemRepository.save(new OslpLogItem(response.getDeviceId(), this.getDeviceIdentificationFromMessage(response.getPayloadMessage()), false, response.getPayloadMessage())); LOGGER.info("sending OSLP response with sequence number: {}", this.convertByteArrayToInteger(response.getSequenceNumber())); e.getChannel().write(response); LOGGER.info("Send OSLP Response: {}", response.getPayloadMessage().toString().split(" ")[0]); } } else { LOGGER.warn("Received message wasn't properly secured."); } } @Override public void channelOpen(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { LOGGER.info("Channel {} opened", e.getChannel().getId()); super.channelOpen(ctx, e); } @Override public void channelDisconnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { LOGGER.info("Channel {} disconnected", e.getChannel().getId()); super.channelDisconnected(ctx, e); } @Override public void channelClosed(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { LOGGER.info("Channel {} closed", e.getChannel().getId()); super.channelClosed(ctx, e); } @Override public void channelUnbound(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception { LOGGER.info("Channel {} unbound", e.getChannel().getId()); super.channelUnbound(ctx, e); } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final ExceptionEvent e) { if (this.isConnectionReset(e.getCause())) { LOGGER.info("Connection was (as expected) reset by the device."); } else { LOGGER.warn("Unexpected exception from downstream.", e.getCause()); } e.getChannel().close(); } private boolean isConnectionReset(final Throwable e) { return e instanceof IOException && e.getMessage() != null && e.getMessage().contains("Connection reset by peer"); } public OslpEnvelope send(final InetSocketAddress address, final OslpEnvelope request, final String deviceIdentification) throws IOException, DeviceSimulatorException { LOGGER.info("Sending OSLP request: {}", request.getPayloadMessage()); final Callback callback = new Callback(this.connectionTimeout); this.lock.lock(); // Open connection and send message ChannelFuture channelFuture = null; try { channelFuture = this.bootstrap.connect(address); channelFuture.awaitUninterruptibly(this.connectionTimeout, TimeUnit.MILLISECONDS); if (channelFuture.getChannel() != null && channelFuture.getChannel().isConnected()) { LOGGER.info("Connection established to: {}", address); } else { LOGGER.info("The connnection to the device {} is not successfull", deviceIdentification); LOGGER.warn("Unable to connect to: {}", address); throw new IOException("Unable to connect"); } this.callbacks.put(channelFuture.getChannel().getId(), callback); channelFuture.getChannel().write(request); } finally { this.lock.unlock(); } // wait for response and close connection try { final OslpEnvelope response = callback.get(deviceIdentification); LOGGER.info("Received OSLP response (after callback): {}", response.getPayloadMessage()); /* * Devices expect the channel to be closed if (and only if) the * platform initiated the conversation. If the device initiated the * conversation it needs to close the channel itself. */ channelFuture.getChannel().close(); return response; } catch (final IOException | DeviceSimulatorException e) { LOGGER.error("send exception", e); // Remove callback when exception has occurred this.callbacks.remove(channelFuture.getChannel().getId()); throw e; } } private boolean isOslpResponse(final OslpEnvelope envelope) { return envelope.getPayloadMessage().hasRegisterDeviceResponse() || envelope.getPayloadMessage().hasConfirmRegisterDeviceResponse() || envelope.getPayloadMessage().hasEventNotificationResponse(); } private void sleep(final Long sleepTime) { if (sleepTime == null || sleepTime == 0) { return; } try { LOGGER.info("Sleeping for {} milliseconds", sleepTime); Thread.sleep(sleepTime); } catch (final InterruptedException e) { LOGGER.info("InterruptedException", e); } } private Oslp.Message handleRequest(final OslpEnvelope message, final int sequenceNumber) throws DeviceSimulatorException, IOException, ParseException { final Oslp.Message request = message.getPayloadMessage(); // Create response message Oslp.Message response = null; final String deviceIdString = Base64.encodeBase64String(message.getDeviceId()); LOGGER.info("request received, sequenceNumber: {}", sequenceNumber); LOGGER.info("manufacturerId byte[0]: {} byte[1]: {}", message.getDeviceId()[0], message.getDeviceId()[1]); LOGGER.info("deviceId as BASE 64 STRING: {}", deviceIdString); // lookup correct device. final Device device = this.deviceManagementService.findDevice(deviceIdString); if (device == null) { throw new DeviceSimulatorException("device with id: " + deviceIdString + " is unknown"); } // Calculate expected sequence number final Integer expectedSequenceNumber = device.doGetNextSequence(); // Check sequence number if (Math.abs(expectedSequenceNumber - sequenceNumber) > this.sequenceNumberWindow) { this.outOfSequenceList.add( new OutOfSequenceEvent(device.getId(), message.getPayloadMessage().toString(), DateTime.now())); throw new DeviceSimulatorException("SequenceNumber incorrect for device: " + device.getDeviceIdentification() + " Expected: " + (expectedSequenceNumber == 0 ? this.sequenceNumberMaximum : expectedSequenceNumber - 1) + " Actual: " + (sequenceNumber == 0 ? this.sequenceNumberMaximum : sequenceNumber - 1) + " SequenceNumberWindow: " + this.sequenceNumberWindow + " Request: " + message.getPayloadMessage().toString()); } // If responseDelayTime (and optional responseDelayRandomRange) are set, // sleep for a little while if (this.responseDelayTime != null && this.reponseDelayRandomRange == null) { this.sleep(this.responseDelayTime); } else if (this.responseDelayTime != null && this.reponseDelayRandomRange != null) { final Long randomDelay = (long) (this.reponseDelayRandomRange * this.random.nextDouble()); this.sleep(this.responseDelayTime + randomDelay); } // Handle only expected messages if (request.hasStartSelfTestRequest()) { device.setLightOn(true); device.setSelftestActive(true); response = createStartSelfTestResponse(); } else if (request.hasStopSelfTestRequest()) { device.setLightOn(false); device.setSelftestActive(false); response = createStopSelfTestResponse(); } else if (request.hasSetLightRequest()) { handleSetLightRequest(device, request.getSetLightRequest()); response = createSetLightResponse(); } else if (request.hasSetEventNotificationsRequest()) { this.handleSetEventNotificationsRequest(device, request.getSetEventNotificationsRequest()); response = createSetEventNotificationsResponse(); } else if (request.hasUpdateFirmwareRequest()) { this.handleUpdateFirmwareRequest(device, request.getUpdateFirmwareRequest()); response = createUpdateFirmwareResponse(); } else if (request.hasGetFirmwareVersionRequest()) { response = createGetFirmwareVersionResponse(); } else if (request.hasSwitchFirmwareRequest()) { response = createSwitchFirmwareResponse(); } else if (request.hasUpdateDeviceSslCertificationRequest()) { response = createUpdateDeviceSslCertificationResponse(); } else if (request.hasSetDeviceVerificationKeyRequest()) { response = createSetDeviceVerificationKeyResponse(); } else if (request.hasSetScheduleRequest()) { this.handleSetScheduleRequest(device, request.getSetScheduleRequest()); response = createSetScheduleResponse(); } else if (request.hasSetConfigurationRequest()) { this.handleSetConfigurationRequest(device, request.getSetConfigurationRequest()); response = this.createSetConfigurationResponse(); } else if (request.hasGetConfigurationRequest()) { this.handleGetConfigurationRequest(device, request.getGetConfigurationRequest()); response = this.createGetConfigurationResponse(device); } else if (request.hasSwitchConfigurationRequest()) { response = createSwitchConfigurationResponse(); } else if (request.hasGetActualPowerUsageRequest()) { this.handleGetActualPowerUsageRequest(device, request.getGetActualPowerUsageRequest()); response = createGetActualPowerUsageResponse(); } else if (request.hasGetPowerUsageHistoryRequest()) { this.handleGetPowerUsageHistoryRequest(device, request.getGetPowerUsageHistoryRequest()); response = createGetPowerUsageHistoryWithDatesResponse(request.getGetPowerUsageHistoryRequest()); } else if (request.hasGetStatusRequest()) { response = createGetStatusResponse(device); } else if (request.hasResumeScheduleRequest()) { response = createResumeScheduleResponse(); } else if (request.hasSetRebootRequest()) { response = createSetRebootResponse(); } else if (request.hasSetTransitionRequest()) { this.handleSetTransitionRequest(device); response = createSetTransitionResponse(); } else if (request.hasConfirmRegisterDeviceRequest()) { response = createConfirmRegisterDeviceResponse( request.getConfirmRegisterDeviceRequest().getRandomDevice(), request.getConfirmRegisterDeviceRequest().getRandomPlatform()); } else { // Handle errors by logging LOGGER.error("Did not expect request, ignoring: " + request.toString()); } // Update device device.setSequenceNumber(expectedSequenceNumber); this.deviceManagementService.updateDevice(device); // Write log entry for response LOGGER.debug("Responding: " + response); return response; } private static Message createConfirmRegisterDeviceResponse(final int randomDevice, final int randomPlatform) { return Oslp.Message.newBuilder() .setConfirmRegisterDeviceResponse(ConfirmRegisterDeviceResponse.newBuilder() .setRandomDevice(randomDevice).setRandomPlatform(randomPlatform).setStatus(Oslp.Status.OK)) .build(); } private void handleSetScheduleRequest(final Device device, final SetScheduleRequest setScheduleRequest) { // Not yet implemented. LOGGER.info("handleSetScheduleRequest not yet implemented. Device: {}, number of schedule entries: {}", device.getDeviceIdentification(), setScheduleRequest.getSchedulesCount()); } private static Message createStartSelfTestResponse() throws IOException { return Oslp.Message.newBuilder() .setStartSelfTestResponse(StartSelfTestResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createStopSelfTestResponse() throws IOException { return Oslp.Message.newBuilder().setStopSelfTestResponse(StopSelfTestResponse.newBuilder() .setStatus(Oslp.Status.OK).setSelfTestResult(ByteString.copyFrom(new byte[] { 0 }))).build(); } private static Message createSetLightResponse() throws IOException { return Oslp.Message.newBuilder() .setSetLightResponse(SetLightResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createSetEventNotificationsResponse() { return Oslp.Message.newBuilder().setSetEventNotificationsResponse( SetEventNotificationsResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createUpdateFirmwareResponse() { return Oslp.Message.newBuilder() .setUpdateFirmwareResponse(UpdateFirmwareResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createGetFirmwareVersionResponse() { return Oslp.Message.newBuilder() .setGetFirmwareVersionResponse(GetFirmwareVersionResponse.newBuilder().setFirmwareVersion("R01")) .build(); } private static Message createSwitchFirmwareResponse() { return Oslp.Message.newBuilder() .setSwitchFirmwareResponse(Oslp.SwitchFirmwareResponse.newBuilder().setStatus(Oslp.Status.OK)) .build(); } private static Message createSetDeviceVerificationKeyResponse() { return Oslp.Message.newBuilder().setSetDeviceVerificationKeyResponse( Oslp.SetDeviceVerificationKeyResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createUpdateDeviceSslCertificationResponse() { return Oslp.Message.newBuilder().setUpdateDeviceSslCertificationResponse( Oslp.UpdateDeviceSslCertificationResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createSetScheduleResponse() { return Oslp.Message.newBuilder() .setSetScheduleResponse(SetScheduleResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createGetActualPowerUsageResponse() { // yyyyMMddhhmmss z final SimpleDateFormat utcTimeFormat = new SimpleDateFormat("yyyyMMddHHmmss"); utcTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC")); final Date currentDateTime = new Date(); final String utcTimestamp = utcTimeFormat.format(currentDateTime); @SuppressWarnings("deprecation") final int actualConsumedPower = currentDateTime.getMinutes(); return Oslp.Message.newBuilder() .setGetActualPowerUsageResponse(GetActualPowerUsageResponse.newBuilder() .setPowerUsageData(PowerUsageData.newBuilder().setRecordTime(utcTimestamp) .setMeterType(MeterType.P1).setTotalConsumedEnergy(actualConsumedPower * 2L) .setActualConsumedPower(actualConsumedPower) .setPsldData(PsldData.newBuilder().setTotalLightingHours(actualConsumedPower * 3)) .setSsldData(SsldData.newBuilder().setActualCurrent1(1).setActualCurrent2(2) .setActualCurrent3(3).setActualPower1(1).setActualPower2(2) .setActualPower3(3).setAveragePowerFactor1(1).setAveragePowerFactor2(2) .setAveragePowerFactor3(3) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 1 })) .setTotalLightingMinutes(480)) .addRelayData(Oslp.RelayData .newBuilder().setIndex(ByteString.copyFrom(new byte[] { 2 })) .setTotalLightingMinutes(480)) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 3 })) .setTotalLightingMinutes(480)) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 4 })) .setTotalLightingMinutes(480)))) .setStatus(Oslp.Status.OK)) .build(); } private static Message createGetPowerUsageHistoryWithDatesResponse( final GetPowerUsageHistoryRequest powerUsageHistoryRequest) throws ParseException { final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyyMMddHHmmss").withZoneUTC(); // 20140405 220000 final DateTime now = new DateTime(); final DateTime dateTimeFrom = formatter .parseDateTime(powerUsageHistoryRequest.getTimePeriod().getStartTime()); DateTime dateTimeUntil = formatter.parseDateTime(powerUsageHistoryRequest.getTimePeriod().getEndTime()); final int itemsPerPage = 2; final int intervalMinutes = powerUsageHistoryRequest.getTermType() == HistoryTermType.Short ? 60 : 1440; final int usagePerItem = powerUsageHistoryRequest.getTermType() == HistoryTermType.Short ? 2400 : 57600; // If from in the future, return emtpy list final List<PowerUsageData> powerUsageDataList = new ArrayList<PowerUsageData>(); if (dateTimeFrom.isAfter(now)) { return createUsageMessage(1, itemsPerPage, 1, powerUsageDataList); } // Ensure until date is not in future dateTimeUntil = correctUsageUntilDate(dateTimeUntil, powerUsageHistoryRequest.getTermType()); final int queryInterval = Minutes.minutesBetween(dateTimeFrom, dateTimeUntil).getMinutes(); final int totalNumberOfItems = queryInterval / intervalMinutes; final int numberOfPages = (int) Math.ceil((double) totalNumberOfItems / (double) itemsPerPage); // Determine page number int currentPageNumber; if (powerUsageHistoryRequest.getPage() == 0) { currentPageNumber = 1; } else { currentPageNumber = powerUsageHistoryRequest.getPage(); } int page = 1; int itemsToSkip = 0; while (currentPageNumber != page) { itemsToSkip += itemsPerPage; page++; } // Advance time to correct page starting point, last to first (like real // device) DateTime pageStartTime = dateTimeUntil.minusMinutes(intervalMinutes * itemsToSkip) .minusMinutes(intervalMinutes); final int itemsOnPage = Math.min(Math.abs(totalNumberOfItems - itemsToSkip), itemsPerPage); // Advance usage to start of page int totalUsage = (totalNumberOfItems * usagePerItem) - (usagePerItem * itemsToSkip); // Fill page with items for (int i = 0; i < itemsOnPage; i++) { final int range = (100) + 1; final int randomCumulativeMinutes = (int) (Math.random() * range) + 100; // Increase the meter final double random = usagePerItem - (usagePerItem / 50d * Math.random()); totalUsage -= random; // Add power usage item to response final PowerUsageData powerUsageData = PowerUsageData.newBuilder() .setRecordTime(pageStartTime.toString(formatter)).setMeterType(MeterType.P1) .setTotalConsumedEnergy(totalUsage).setActualConsumedPower((int) random) .setPsldData(PsldData.newBuilder().setTotalLightingHours((int) random * 3)) .setSsldData(SsldData.newBuilder().setActualCurrent1(10).setActualCurrent2(20) .setActualCurrent3(30).setActualPower1(10).setActualPower2(20).setActualPower3(30) .setAveragePowerFactor1(10).setAveragePowerFactor2(20).setAveragePowerFactor3(30) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 1 })) .setTotalLightingMinutes(INITIAL_BURNING_MINUTES - randomCumulativeMinutes)) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 2 })) .setTotalLightingMinutes(INITIAL_BURNING_MINUTES - randomCumulativeMinutes)) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 3 })) .setTotalLightingMinutes(INITIAL_BURNING_MINUTES - randomCumulativeMinutes)) .addRelayData(Oslp.RelayData.newBuilder() .setIndex(ByteString.copyFrom(new byte[] { 4 })) .setTotalLightingMinutes(INITIAL_BURNING_MINUTES - randomCumulativeMinutes))) .build(); powerUsageDataList.add(powerUsageData); pageStartTime = pageStartTime.minusMinutes(intervalMinutes); INITIAL_BURNING_MINUTES -= CUMALATIVE_BURNING_MINUTES; } return createUsageMessage(currentPageNumber, itemsPerPage, numberOfPages, powerUsageDataList); } private static DateTime correctUsageUntilDate(final DateTime dateTimeUntil, final HistoryTermType termType) { final DateTime now = new DateTime(); if (dateTimeUntil.isAfter(now)) { if (termType == HistoryTermType.Short) { return now.hourOfDay().roundCeilingCopy(); } else { return now.withZone(localTimeZone).dayOfWeek().roundCeilingCopy().withZone(DateTimeZone.UTC); } } return dateTimeUntil; } private static Message createUsageMessage(final int currentPageNumber, final int itemsPerPage, final int numberOfPages, final List<PowerUsageData> powerUsageDataList) { return Oslp.Message.newBuilder() .setGetPowerUsageHistoryResponse(GetPowerUsageHistoryResponse.newBuilder() .addAllPowerUsageData(powerUsageDataList) .setPageInfo(PageInfo.newBuilder().setCurrentPage(currentPageNumber) .setPageSize(itemsPerPage).setTotalPages(numberOfPages)) .setStatus(Oslp.Status.OK)) .build(); } private Message createSetConfigurationResponse() { return Oslp.Message.newBuilder() .setSetConfigurationResponse(Oslp.SetConfigurationResponse.newBuilder().setStatus(Oslp.Status.OK)) .build(); } private Message createGetConfigurationResponse(final Device device) { final DaliConfiguration.Builder daliConfiguration = DaliConfiguration.newBuilder() .addAddressMap(IndexAddressMap.newBuilder().setIndex(ByteString.copyFrom(new byte[] { 1 })) .setAddress(ByteString.copyFrom(new byte[] { 1 })).setRelayType(RelayType.RT_NOT_SET)) .setNumberOfLights(ByteString.copyFrom(new byte[] { 1 })); final Oslp.GetConfigurationResponse.Builder configuration = Oslp.GetConfigurationResponse.newBuilder(); try { configuration.setStatus(Oslp.Status.OK) .setPreferredLinkType(Enum.valueOf(Oslp.LinkType.class, device.getPreferredLinkType().name())) .setLightType(Enum.valueOf(Oslp.LightType.class, device.getLightType().name())) .setShortTermHistoryIntervalMinutes(15); if (device.getProtocol().equals(ProtocolType.OSLP.toString())) { // AME devices configuration.setMeterType(MeterType.P1); configuration.setLongTermHistoryIntervalType(LongTermIntervalType.DAYS); configuration.setLongTermHistoryInterval(1); } else { // ELSTER devices configuration.setMeterType(MeterType.MT_NOT_SET); configuration.setLongTermHistoryIntervalType(LongTermIntervalType.LT_INT_NOT_SET); configuration.setLongTermHistoryInterval(1); configuration.setTimeSyncFrequency(86400); configuration.setDeviceFixIpValue( ByteString.copyFrom(InetAddress.getByName("192.168.0.100").getAddress())); configuration.setNetMask(ByteString.copyFrom(InetAddress.getByName("255.255.255.0").getAddress())); configuration.setGateWay(ByteString.copyFrom(InetAddress.getByName("192.168.0.1").getAddress())); configuration.setIsDhcpEnabled(false); configuration.setCommunicationTimeout(30); configuration.setCommunicationNumberOfRetries(5); configuration.setCommunicationPauseTimeBetweenConnectionTrials(120); configuration .setOspgIpAddress(ByteString.copyFrom(InetAddress.getByName("168.63.97.65").getAddress())); configuration.setOsgpPortNumber(12122); configuration.setIsTestButtonEnabled(false); configuration.setIsAutomaticSummerTimingEnabled(false); configuration.setAstroGateSunRiseOffset(-15); configuration.setAstroGateSunSetOffset(15); configuration.addSwitchingDelay(1); configuration.addSwitchingDelay(2); configuration.addSwitchingDelay(3); configuration.addSwitchingDelay(4); configuration.addRelayLinking(Oslp.RelayMatrix.newBuilder() .setMasterRelayIndex(ByteString.copyFrom(new byte[] { 1 })).setMasterRelayOn(false) .setIndicesOfControlledRelaysOn(ByteString.copyFrom(new byte[] { 2, 3, 4 })) .setIndicesOfControlledRelaysOff(ByteString.copyFrom(new byte[] { 2, 3, 4 }))); configuration.setRelayRefreshing(false).setSummerTimeDetails("0360100") .setWinterTimeDetails("1060200"); } if (device.getDeviceType().equals(Device.PSLD_TYPE)) { configuration.setDaliConfiguration(daliConfiguration); } if (device.getDeviceType().equals(Device.SSLD_TYPE)) { configuration.setRelayConfiguration(createRelayConfiguration(device.getOutputSettings())); } return Oslp.Message.newBuilder().setGetConfigurationResponse(configuration).build(); } catch (final Exception e) { LOGGER.error("Unexpected UnknownHostException", e); return null; } } private static Message createSwitchConfigurationResponse() { return Oslp.Message.newBuilder().setSwitchConfigurationResponse( Oslp.SwitchConfigurationResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } /** * Create relay configuration based on stored configuration values. */ private static RelayConfiguration createRelayConfiguration(final List<DeviceOutputSetting> outputSettings) { final RelayConfiguration.Builder configuration = RelayConfiguration.newBuilder(); for (final DeviceOutputSetting dos : outputSettings) { final IndexAddressMap.Builder relayMap = IndexAddressMap.newBuilder() .setIndex(OslpUtils.integerToByteString(dos.getInternalId())) .setAddress(OslpUtils.integerToByteString(dos.getExternalId())); // Map device-simulator enum OutputType to OSLP enum RelayType if (dos.getOutputType() == OutputType.LIGHT) { relayMap.setRelayType(RelayType.LIGHT); } else if (dos.getOutputType() == OutputType.TARIFF) { relayMap.setRelayType(RelayType.TARIFF); } else { relayMap.setRelayType(RelayType.RT_NOT_SET); } configuration.addAddressMap(relayMap); } return configuration.build(); } private static Message createGetStatusResponse(final Device device) { final List<LightValue> outputValues = new ArrayList<>(); for (final DeviceOutputSetting dos : device.getOutputSettings()) { final LightValue.Builder lightValue = LightValue.newBuilder() .setIndex(OslpUtils.integerToByteString(dos.getInternalId())); if (dos.getOutputType().equals(OutputType.LIGHT)) { lightValue.setOn(device.isLightOn()); if (device.getDimValue() != null) { lightValue.setDimValue(OslpUtils.integerToByteString(device.getDimValue())); } // Real device specifies dimvalue 0 when off. if (!device.isLightOn()) { lightValue.setDimValue(OslpUtils.integerToByteString(0)); } } else if (dos.getOutputType().equals(OutputType.TARIFF)) { lightValue.setOn(device.isTariffOn()); } outputValues.add(lightValue.build()); } // Fallback in case output settings are not yet defined. if (outputValues.isEmpty()) { final LightValue.Builder lightValue = LightValue.newBuilder().setIndex(OslpUtils.integerToByteString(0)) .setOn(device.isLightOn()); if (device.getDimValue() != null) { lightValue.setDimValue(OslpUtils.integerToByteString(device.getDimValue())); } // Real device specifies dimvalue 0 when off. if (!device.isLightOn()) { lightValue.setDimValue(OslpUtils.integerToByteString(0)); } outputValues.add(lightValue.build()); } final Oslp.GetStatusResponse.Builder builder = GetStatusResponse.newBuilder(); builder.setStatus(Oslp.Status.OK); builder.addAllValue(outputValues); builder.setPreferredLinktype(Enum.valueOf(Oslp.LinkType.class, device.getPreferredLinkType().name())); builder.setActualLinktype(Enum.valueOf(Oslp.LinkType.class, device.getActualLinkType().name())); builder.setLightType(Enum.valueOf(Oslp.LightType.class, device.getLightType().name())); builder.setEventNotificationMask(device.getEventNotificationMask()); LOGGER.info("device.getProtocol(): {}", device.getProtocol()); LOGGER.info("ProtocolType.OSLP_ELSTER.name(): {}", ProtocolType.OSLP_ELSTER.name()); if (device.getProtocol().equals(ProtocolType.OSLP_ELSTER.toString())) { builder.setNumberOfOutputs(4); builder.setDcOutputVoltageMaximum(24000); builder.setDcOutputVoltageCurrent(24000); builder.setMaximumOutputPowerOnDcOutput(15000); builder.setSerialNumber( ByteString.copyFrom(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); builder.setMacAddress(ByteString.copyFrom(new byte[] { 1, 2, 3, 4, 5, 6 })); builder.setHardwareId("Hardware ID").setInternalFlashMemSize(1024); builder.setExternalFlashMemSize(2048).setLastInternalTestResultCode(0).setStartupCounter(42); builder.setBootLoaderVersion("1.1.1").setFirmwareVersion("2.8.5"); builder.setCurrentConfigurationBackUsed(ByteString.copyFrom(new byte[] { 0 })); builder.setName("ELS_DEV-SIM-DEVICE").setCurrentTime("20251231155959"); builder.setCurrentIp("127.0.0.1"); } return Oslp.Message.newBuilder().setGetStatusResponse(builder.build()).build(); } private static Message createResumeScheduleResponse() { return Oslp.Message.newBuilder() .setResumeScheduleResponse(Oslp.ResumeScheduleResponse.newBuilder().setStatus(Oslp.Status.OK)) .build(); } private static Message createSetRebootResponse() { return Oslp.Message.newBuilder() .setSetRebootResponse(Oslp.SetRebootResponse.newBuilder().setStatus(Oslp.Status.OK)).build(); } private static Message createSetTransitionResponse() { return Oslp.Message.newBuilder() .setSetTransitionResponse(Oslp.SetTransitionResponse.newBuilder().setStatus(Oslp.Status.OK)) .build(); } private static void handleSetLightRequest(final Device device, final SetLightRequest request) { // Device simulator will only use first light value, // other light values will be ignored final LightValue lightValue = request.getValues(0); device.setLightOn(lightValue.getOn()); if (lightValue.hasDimValue()) { final int dimValue = lightValue.getDimValue().byteAt(0); device.setDimValue(dimValue); } else { device.setDimValue(null); } } private void handleSetTransitionRequest(final Device device) { // Device simulator will only use first light value, // other light values will be ignored // reverse the light. device.setLightOn(!device.isLightOn()); final EventNotificationToBeSent eventNotificationToBeSent = new EventNotificationToBeSent(device.getId(), device.isLightOn()); this.deviceManagementService.getEventNotificationToBeSent().add(eventNotificationToBeSent); } private void handleSetEventNotificationsRequest(final Device device, final SetEventNotificationsRequest request) { device.setEventNotifications(request.getNotificationMask()); } private void handleUpdateFirmwareRequest(final Device device, final UpdateFirmwareRequest request) { // For now, do nothing, perhaps store firmware version, so that it can // be displayed ??? } private void handleSetConfigurationRequest(final Device device, final Oslp.SetConfigurationRequest setConfigurationRequest) { if (setConfigurationRequest.hasPreferredLinkType()) { device.setPreferredLinkType( Enum.valueOf(LinkType.class, setConfigurationRequest.getPreferredLinkType().name())); } if (setConfigurationRequest.hasLightType()) { device.setLightType(Enum.valueOf(LightType.class, setConfigurationRequest.getLightType().name())); } if (setConfigurationRequest.hasRelayConfiguration()) { final List<DeviceOutputSetting> outputSettings = new ArrayList<>(); for (final IndexAddressMap iam : setConfigurationRequest.getRelayConfiguration().getAddressMapList()) { final int index = iam.getIndex().byteAt(0); final int address = iam.getAddress().byteAt(0); final OutputType outputType = OutputType.valueOf(iam.getRelayType().name()); outputSettings.add(new DeviceOutputSetting(index, address, outputType)); } device.setOutputSettings(outputSettings); } } private void handleGetConfigurationRequest(final Device device, final Oslp.GetConfigurationRequest getConfigurationRequest) { // Do nothing for now. } private void handleGetActualPowerUsageRequest(final Device device, final GetActualPowerUsageRequest request) { // Do nothing for now. } private void handleGetPowerUsageHistoryRequest(final Device device, final GetPowerUsageHistoryRequest request) { // Do nothing for now. } private String getDeviceIdentificationFromMessage(final Oslp.Message message) { String deviceIdentification = ""; // Expand with other message types which contain a device // identification if needed. if (message.hasRegisterDeviceRequest()) { deviceIdentification = message.getRegisterDeviceRequest().getDeviceIdentification(); } return deviceIdentification; } private byte[] convertIntegerToByteArray(final Integer value) { // See: platform.service.SequenceNumberUtils final byte[] bytes = new byte[2]; bytes[0] = (byte) (value >>> 8); bytes[1] = (byte) (value >>> 0); LOGGER.info( "web-device-simulator.OslpChannelHandler.convertIntegerToByteArray() byte[0]: {} byte[1]: {} Integer value: {}", bytes[0], bytes[1], value); return bytes; } private Integer convertByteArrayToInteger(final byte[] array) { // See: platform.service.SequenceNumberUtils final Integer value = (array[0] & 0xFF) << 8 | (array[1] & 0xFF); LOGGER.info( "web-device-simulator.OslpChannelHandler.convertByteArrayToInteger() byte[0]: {} byte[1]: {} Integer value: {}", array[0], array[1], value); return value; } }