commit 46ab002721b84f9d684c5d70e19c6862515de103 Author: Harshan Liyanage Date: Thu Dec 8 16:40:18 2016 +0530 Resolved conflicts diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..c2c197b1b --- /dev/null +++ b/pom.xml @@ -0,0 +1,237 @@ + + + + + 4.0.0 + + + virtual-fire-alarm-plugin + org.wso2.carbon.devicemgt-plugins + 3.0.3-SNAPSHOT + ../pom.xml + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.impl + WSO2 Carbon - IoT Server VirtualFireAlarm Agent + WSO2 Carbon - VirtualFireAlarm Device Agent Implementation + http://wso2.org + + + + + org.apache.maven.plugins + maven-compiler-plugin + + UTF-8 + ${wso2.maven.compiler.source} + ${wso2.maven.compiler.target} + + 2.3.2 + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.Bootstrap + + + + + jar-with-dependencies + + wso2-firealarm-virtual-agent + false + + + + make-assembly + + package + + + single + + + + + + + + + + + + + log4j + log4j + ${log4j.version} + + + + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + ${paho.mqtt.version} + + + + + org.igniterealtime.smack.wso2 + smack + ${smack.wso2.version} + + + org.igniterealtime.smack.wso2 + smackx + ${smackx.wso2.version} + + + + + org.bouncycastle.wso2 + bcprov-jdk15on + ${bcprov.wso2.version} + + + org.bouncycastle.wso2 + bcpkix-jdk15on + ${bcpkix.wso2.version} + + + + + com.google.code.jscep.wso2 + jscep + ${jscep.version} + + + + commons-codec + commons-codec + + + + commons-lang + commons-lang + ${commons-lang.version} + + + + commons-logging + commons-logging + ${common-logging.version} + + + + commons-io + commons-io + ${commons.io} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + + org.json.wso2 + json + + + + commons-configuration + commons-configuration + 1.10 + + + + + + + + + wso2-nexus + WSO2 internal Repository + http://maven.wso2.org/nexus/content/groups/wso2-public/ + + true + daily + ignore + + + + wso2-maven2-repository + http://dist.wso2.org/maven2 + + + + + + + 1.7 + 1.7 + + + 8.1.3.v20120416 + + + 1.0.2 + + + 3.0.4.wso2v1 + 3.0.4.wso2v1 + + + 1.49.wso2v1 + 1.49.wso2v1 + + + 2.0.2.wso2v2 + + + 1.2.17 + 1.2 + 2.4 + 1.7 + 2.6 + + + 1.7.13 + + + diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/Bootstrap.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/Bootstrap.java new file mode 100644 index 000000000..63d4b2595 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/Bootstrap.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent; + +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; + +public class Bootstrap { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); + System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "info"); + System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); + System.setProperty("org.apache.commons.logging.simplelog.dateTimeFormat", "HH:mm:ss"); + AgentManager.getInstance().init(); + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/http/FireAlarmHTTPCommunicator.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/http/FireAlarmHTTPCommunicator.java new file mode 100644 index 000000000..98dbc6dc7 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/http/FireAlarmHTTPCommunicator.java @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.http; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.http.HTTPTransportHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.ProtocolException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmHTTPCommunicator extends HTTPTransportHandler { + private static final Log log = LogFactory.getLog(FireAlarmHTTPCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private ScheduledFuture connectorServiceHandler; + + public FireAlarmHTTPCommunicator() { + super(); + } + + public FireAlarmHTTPCommunicator(int port) { + super(port); + } + + public FireAlarmHTTPCommunicator(int port, int reconnectionInterval) { + super(port, reconnectionInterval); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + public void connect() { + Runnable connect = new Runnable() { + public void run() { + if (!isConnected()) { + try { + processIncomingMessage(); + server.start(); + registerThisDevice(); + publishDeviceData(); + log.info("HTTP Server started at port: " + port); + + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn("Unable to 'START' HTTP server. Will retry after " + + timeoutInterval / 1000 + " seconds."); + } + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connect, 0, timeoutInterval, + TimeUnit.MILLISECONDS); + } + + + @Override + public void processIncomingMessage() { + server.setHandler(new AbstractHandler() { + public void handle(String s, Request request, HttpServletRequest + httpServletRequest, + HttpServletResponse httpServletResponse) + throws IOException, ServletException { + httpServletResponse.setContentType("text/html;charset=utf-8"); + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + request.setHandled(true); + + AgentManager agentManager = AgentManager.getInstance(); + String pathContext = request.getPathInfo(); + String separator = File.separator; + + if (pathContext.toUpperCase().contains( + separator + AgentConstants.TEMPERATURE_CONTROL)) { + httpServletResponse.getWriter().println( + agentManager.getTemperature()); + + } else if (pathContext.toUpperCase().contains( + separator + AgentConstants.HUMIDITY_CONTROL)) { + httpServletResponse.getWriter().println( + agentManager.getHumidity()); + + } else if (pathContext.toUpperCase().contains( + separator + AgentConstants.BULB_CONTROL)) { + String[] pathVariables = pathContext.split(separator); + + if (pathVariables.length != 3) { + httpServletResponse.getWriter().println( + "Invalid BULB-control received by the device. Need to be in " + + "'{host}:{port}/BULB/{ON|OFF}' format."); + return; + } + + String switchState = pathVariables[2]; + + if (switchState == null) { + httpServletResponse.getWriter().println( + "Please specify switch-status of the BULB."); + } else { + boolean status = switchState.toUpperCase().equals( + AgentConstants.CONTROL_ON); + agentManager.changeAlarmStatus(status); + httpServletResponse.getWriter().println("Bulb is " + (status ? + AgentConstants.CONTROL_ON : AgentConstants.CONTROL_OFF)); + } + } else { + httpServletResponse.getWriter().println( + "Invalid control command received by the device."); + } + } + }); + } + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + final String deviceOwner = agentManager.getAgentConfigs().getDeviceOwner(); + final String deviceID = agentManager.getAgentConfigs().getDeviceId(); + boolean simulationMode = false; + int duration = 2 * 60; + int frequency = 5; + + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + + String pushDataPayload = String.format(AgentConstants.PUSH_DATA_PAYLOAD, deviceOwner, + deviceID, (agentManager.getDeviceIP() + ":" + port), + agentManager.getTemperature()); + executeDataPush(pushDataPayload); + } + }; + + if (!simulationMode) { + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, + publishInterval, + TimeUnit.SECONDS); + } else { + String pushDataPayload = String.format(AgentConstants.PUSH_SIMULATION_DATA_PAYLOAD, deviceOwner, + deviceID, (agentManager.getDeviceIP() + ":" + port), + agentManager.getTemperature(), true, duration, frequency); + executeDataPush(pushDataPayload); + + } + } + + + private void executeDataPush(String pushDataPayload) { + AgentManager agentManager = AgentManager.getInstance(); + String pushDataEndPointURL = agentManager.getPushDataAPIEP(); + HttpURLConnection httpConnection; + int responseCode = -1; + + try { + httpConnection = TransportUtils.getHttpConnection(agentManager.getPushDataAPIEP()); + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty("Authorization", + "Bearer " + agentManager.getAgentConfigs().getAuthToken()); + httpConnection.setRequestProperty("Content-Type", AgentConstants.APPLICATION_JSON); + + httpConnection.setDoOutput(true); + DataOutputStream dataOutPutWriter = new DataOutputStream(httpConnection.getOutputStream()); + dataOutPutWriter.writeBytes(pushDataPayload); + dataOutPutWriter.flush(); + dataOutPutWriter.close(); + + responseCode = httpConnection.getResponseCode(); + httpConnection.disconnect(); + + log.info(AgentConstants.LOG_APPENDER + "Message - '" + pushDataPayload + + "' was published to server at: " + httpConnection.getURL()); + + } catch (ProtocolException exception) { + String errorMsg = + "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for:" + pushDataEndPointURL; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + + } catch (IOException exception) { + String errorMsg = + "An IO error occurred whilst trying to get the response code from: " + + pushDataEndPointURL + " for a " + AgentConstants.HTTP_POST + " method."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + + } catch (TransportHandlerException exception) { + log.error(AgentConstants.LOG_APPENDER + + "Error encountered whilst trying to create HTTP-Connection to IoT-Server EP at: " + + pushDataEndPointURL); + } + + if (responseCode == HttpStatus.CONFLICT_409 || + responseCode == HttpStatus.PRECONDITION_FAILED_412) { + log.warn(AgentConstants.LOG_APPENDER + + "DeviceIP is being Re-Registered due to Push-Data failure with response code: " + + responseCode); + registerThisDevice(); + + } else if (responseCode != HttpStatus.NO_CONTENT_204) { + if (log.isDebugEnabled()) { + log.error(AgentConstants.LOG_APPENDER + "Status Code: " + responseCode + + " encountered whilst trying to Push-Device-Data to IoT Server at: " + + agentManager.getPushDataAPIEP()); + } + agentManager.updateAgentStatus(AgentConstants.SERVER_NOT_RESPONDING); + } + + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "Push-Data call with payload - " + pushDataPayload + + ", to IoT Server returned status " + responseCode); + } + } + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + try { + dataPushServiceHandler.cancel(true); + connectorServiceHandler.cancel(true); + closeConnection(); + } catch (Exception e) { + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + "Unable to 'STOP' HTTP server at port: " + port); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error( + AgentConstants.LOG_APPENDER + "HTTP-Termination: Thread Sleep Interrupt Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage(Object message, String... messageParams) { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + + public void registerThisDevice() { + final AgentManager agentManager = AgentManager.getInstance(); + agentManager.updateAgentStatus("Registering..."); + + final Runnable ipRegistration = new Runnable() { + @Override + public void run() { + while (isConnected()) { + try { + int responseCode = registerDeviceIP( + agentManager.getAgentConfigs().getDeviceOwner(), + agentManager.getAgentConfigs().getDeviceId()); + + if (responseCode == HttpStatus.OK_200) { + agentManager.updateAgentStatus(AgentConstants.REGISTERED); + break; + } else { + log.error(AgentConstants.LOG_APPENDER + + "Device Registration with IoT Server at:" + " " + + agentManager.getIpRegistrationEP() + + " failed with response - '" + responseCode + ":" + + HttpStatus.getMessage(responseCode) + "'"); + agentManager.updateAgentStatus(AgentConstants.RETRYING_TO_REGISTER); + } + } catch (AgentCoreOperationException exception) { + log.error(AgentConstants.LOG_APPENDER + + "Error encountered whilst trying to register the " + + "Device's IP at: " + + agentManager.getIpRegistrationEP() + + ".\nCheck whether the network-interface provided is " + + "accurate"); + agentManager.updateAgentStatus(AgentConstants.REGISTRATION_FAILED); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "Device Registration: Thread Sleep Interrupt Exception"); + } + } + } + }; + + Thread ipRegisterThread = new Thread(ipRegistration); + ipRegisterThread.setDaemon(true); + ipRegisterThread.start(); + } + + + /** + * This method calls the "Register-API" of the IoT Server in order to register the device's IP + * against its ID. + * + * @param deviceOwner the owner of the device by whose name the agent was downloaded. + * (Read from configuration file) + * @param deviceID the deviceId that is auto-generated whilst downloading the agent. + * (Read from configuration file) + * @return the status code of the HTTP-Post call to the Register-API of the IoT-Server + * @throws AgentCoreOperationException if any errors occur when an HTTPConnection session is + * created + */ + private int registerDeviceIP(String deviceOwner, String deviceID) + throws AgentCoreOperationException { + int responseCode = -1; + final AgentManager agentManager = AgentManager.getInstance(); + + String networkInterface = agentManager.getNetworkInterface(); + String deviceIPAddress = getDeviceIP(networkInterface); + + if (deviceIPAddress == null) { + throw new AgentCoreOperationException( + "An IP address could not be retrieved for the selected network interface - '" + + networkInterface + "."); + } + + agentManager.setDeviceIP(deviceIPAddress); + log.info(AgentConstants.LOG_APPENDER + "Device IP Address: " + deviceIPAddress); + + String deviceIPRegistrationEP = agentManager.getIpRegistrationEP(); + String registerEndpointURLString = + deviceIPRegistrationEP + File.separator + deviceOwner + File.separator + deviceID + + File.separator + deviceIPAddress + File.separator + port; + + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "DeviceIP Registration EndPoint: " + + registerEndpointURLString); + } + + HttpURLConnection httpConnection; + try { + httpConnection = TransportUtils.getHttpConnection(registerEndpointURLString); + } catch (TransportHandlerException e) { + String errorMsg = + "Protocol specific error occurred when trying to fetch an HTTPConnection to:" + + " " + registerEndpointURLString; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(); + } + + try { + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty("Authorization", "Bearer " + + agentManager.getAgentConfigs().getAuthToken()); + httpConnection.setDoOutput(true); + responseCode = httpConnection.getResponseCode(); + + } catch (ProtocolException exception) { + String errorMsg = "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for:" + registerEndpointURLString; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + + } catch (IOException exception) { + String errorMsg = "An IO error occurred whilst trying to get the response code from:" + + " " + registerEndpointURLString + " for a " + AgentConstants.HTTP_POST + " method."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + } + + log.info(AgentConstants.LOG_APPENDER + "DeviceIP - " + deviceIPAddress + + ", registration with IoT Server at : " + + agentManager.getAgentConfigs().getHTTPS_ServerEndpoint() + + " returned status " + + responseCode); + + return responseCode; + } + + /*------------------------------------------------------------------------------------------*/ + /* Utility methods relevant to creating and sending HTTP requests to the Iot-Server */ + /*------------------------------------------------------------------------------------------*/ + + /** + * This method is used to get the IP of the device in which the agent is run on. + * + * @return the IP Address of the device + * @throws AgentCoreOperationException if any errors occur whilst trying to get the IP address + */ + private String getDeviceIP() throws AgentCoreOperationException { + try { + return Inet4Address.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + String errorMsg = "Error encountered whilst trying to get the device IP address."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + } + + /** + * This is an overloaded method that fetches the public IPv4 address of the given network + * interface + * + * @param networkInterfaceName the network-interface of whose IPv4 address is to be retrieved + * @return the IP Address iof the device + * @throws AgentCoreOperationException if any errors occur whilst trying to get details of the + * given network interface + */ + private String getDeviceIP(String networkInterfaceName) throws + AgentCoreOperationException { + String ipAddress = null; + try { + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + for (; interfaceIPAddresses.hasMoreElements(); ) { + InetAddress ip = interfaceIPAddresses.nextElement(); + ipAddress = ip.getHostAddress(); + if (log.isDebugEnabled()) { + log.debug(AgentConstants.LOG_APPENDER + "IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + return ipAddress; + } + } + } catch (SocketException | NullPointerException exception) { + String errorMsg = + "Error encountered whilst trying to get IP Addresses of the network interface: " + + networkInterfaceName + + ".\nPlease check whether the name of the network interface used is correct"; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, exception); + } + + return ipAddress; + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/mqtt/FireAlarmMQTTCommunicator.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/mqtt/FireAlarmMQTTCommunicator.java new file mode 100644 index 000000000..5b24783e7 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/mqtt/FireAlarmMQTTCommunicator.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.mqtt.MQTTTransportHandler; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +//TODO:: Lincence header, comments and SPECIFIC class name since its not generic +public class FireAlarmMQTTCommunicator extends MQTTTransportHandler { + + private static final Log log = LogFactory.getLog(FireAlarmMQTTCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private static final String DEFAULT_PASSWORD = ""; + + public FireAlarmMQTTCommunicator(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic) { + super(deviceOwner, deviceType, mqttBrokerEndPoint, subscribeTopic); + } + + @SuppressWarnings("unused") + public FireAlarmMQTTCommunicator(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, + int intervalInMillis) { + super(deviceOwner, deviceType, mqttBrokerEndPoint, subscribeTopic, intervalInMillis); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + //TODO:: Terminate logs with a period + //TODO: Need to print exceptions + @Override + public void connect() { + final AgentManager agentManager = AgentManager.getInstance(); + Runnable connector = new Runnable() { + public void run() { + while (!isConnected()) { + try { + connectToQueue(agentManager.getAgentConfigs().getAuthToken(), DEFAULT_PASSWORD); + agentManager.updateAgentStatus("Connected to MQTT Queue"); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Connection to MQTT Broker at: " + mqttBrokerEndPoint + + " failed.\n Will retry in " + timeoutInterval + " milli-seconds."); + + if (e.getCause() != null && e.getCause() instanceof MqttSecurityException) { + refreshOAuthToken((MqttSecurityException) e.getCause()); + } + } + + try { + if (isConnected()) { + subscribeToQueue(); + agentManager.updateAgentStatus("Subscribed to MQTT Queue"); + publishDeviceData(); + } + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Subscription to MQTT Broker at: " + + mqttBrokerEndPoint + " failed"); + agentManager.updateAgentStatus("Subscription to broker failed."); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException ex) { + log.error(AgentConstants.LOG_APPENDER + "MQTT: Connect-Thread Sleep Interrupt Exception."); + } + } + } + }; + + Thread connectorThread = new Thread(connector); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + private void refreshOAuthToken(final MqttSecurityException exception) { + Runnable tokenRefresher = new Runnable() { + public void run() { + String authenticationMethod = AgentUtilOperations.getAuthenticationMethod(); + + try { + if (exception.getReasonCode() == MqttSecurityException.REASON_CODE_FAILED_AUTHENTICATION && + authenticationMethod.equals(AgentConstants.TOKEN_AUTHENTICATION_METHOD)) { + AgentUtilOperations.refreshOAuthToken(); + } + } catch (AgentCoreOperationException e1) { + log.error(AgentConstants.LOG_APPENDER + "Token Refresh Attempt Failed. " + e1); + } + } + }; + + Thread connectorThread = new Thread(tokenRefresher); + connectorThread.setDaemon(true); + connectorThread.start(); + } + + @Override + public void processIncomingMessage(MqttMessage message, String... messageParams) { + final AgentManager agentManager = AgentManager.getInstance(); + String tenantDomain = agentManager.getAgentConfigs().getTenantDomain(); + String deviceOwner = agentManager.getAgentConfigs().getDeviceOwner(); + String deviceID = agentManager.getAgentConfigs().getDeviceId(); + String receivedMessage; + String replyMessage; + String securePayLoad; + + try { + receivedMessage = AgentUtilOperations.extractMessageFromPayload(message.toString()); + log.info(AgentConstants.LOG_APPENDER + "Message [" + receivedMessage + "] was received"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Could not extract message from payload.", e); + return; + } + + + String[] controlSignal = receivedMessage.split(":"); + // message- ":" format.(ex: "BULB:ON", "TEMPERATURE", "HUMIDITY") + + try { + switch (controlSignal[0].toUpperCase()) { + case AgentConstants.BULB_CONTROL: + boolean stateToSwitch = controlSignal[1].equals(AgentConstants.CONTROL_ON); + agentManager.changeAlarmStatus(stateToSwitch); + log.info(AgentConstants.LOG_APPENDER + "Bulb was switched to state: '" + controlSignal[1] + "'"); + break; + + case AgentConstants.TEMPERATURE_CONTROL: + int currentTemperature = agentManager.getTemperature(); + + String replyTemperature = "Current temperature was read as: '" + currentTemperature + "C'"; + log.info(AgentConstants.LOG_APPENDER + replyTemperature); + + String tempPublishTopic = String.format(AgentConstants.MQTT_PUBLISH_TOPIC, tenantDomain, deviceID); + + replyMessage = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + publishToQueue(tempPublishTopic, securePayLoad); + break; + + case AgentConstants.HUMIDITY_CONTROL: + int currentHumidity = agentManager.getHumidity(); + + String replyHumidity = "Current humidity was read as: '" + currentHumidity + "%'"; + log.info(AgentConstants.LOG_APPENDER + replyHumidity); + + String humidPublishTopic = String.format( + AgentConstants.MQTT_PUBLISH_TOPIC, tenantDomain, deviceID); + + replyMessage = AgentConstants.HUMIDITY_CONTROL + ":" + currentHumidity; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + publishToQueue(humidPublishTopic, securePayLoad); + break; + + default: + log.warn(AgentConstants.LOG_APPENDER + "'" + controlSignal[0] + + "' is invalid and not-supported for this device-type"); + break; + } + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } catch (TransportHandlerException e) { + log.error(AgentConstants.LOG_APPENDER + + "MQTT - Publishing, reply message to the MQTT Queue at: " + + agentManager.getAgentConfigs().getMqttBrokerEndpoint() + " failed"); + } + + } + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + int currentTemperature = agentManager.getTemperature(); + String message = "PUBLISHER:" + AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + + try { + String payLoad = AgentUtilOperations.prepareSecurePayLoad(message); + + MqttMessage pushMessage = new MqttMessage(); + pushMessage.setPayload(payLoad.getBytes(StandardCharsets.UTF_8)); + pushMessage.setQos(DEFAULT_MQTT_QUALITY_OF_SERVICE); + pushMessage.setRetained(false); + + String topic = String.format(AgentConstants.MQTT_PUBLISH_TOPIC, + agentManager.getAgentConfigs().getTenantDomain(), + agentManager.getAgentConfigs().getDeviceId()); + + publishToQueue(topic, pushMessage); + log.info(AgentConstants.LOG_APPENDER + "Message: '" + message + "' published to MQTT Queue at [" + + agentManager.getAgentConfigs().getMqttBrokerEndpoint() + "] under topic [" + + topic + "]"); + + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Data Publish attempt to topic - [" + + AgentConstants.MQTT_PUBLISH_TOPIC + "] failed for payload [" + message + "]"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } + } + }; + + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, publishInterval, + TimeUnit.SECONDS); + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + while (isConnected()) { + + if (dataPushServiceHandler != null) { + dataPushServiceHandler.cancel(true); + } + + try { + closeConnection(); + + } catch (MqttException e) { + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' MQTT connection at broker at: " + + mqttBrokerEndPoint); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + + "MQTT-Terminator: Thread Sleep Interrupt Exception"); + } + } + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + @Override + public void processIncomingMessage() { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/xmpp/FireAlarmXMPPCommunicator.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/xmpp/FireAlarmXMPPCommunicator.java new file mode 100644 index 000000000..abb8d7965 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/communication/xmpp/FireAlarmXMPPCommunicator.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.packet.Message; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConfiguration; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.xmpp.XMPPTransportHandler; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class FireAlarmXMPPCommunicator extends XMPPTransportHandler { + + private static final Log log = LogFactory.getLog(FireAlarmXMPPCommunicator.class); + + private ScheduledExecutorService service = Executors.newScheduledThreadPool(2); + private ScheduledFuture dataPushServiceHandler; + private ScheduledFuture connectorServiceHandler; + + private String username; + private String password; + private String resource; + private String xmppAdminJID; + private String xmppDeviceJID; + + public FireAlarmXMPPCommunicator(String server) { + super(server); + } + + public FireAlarmXMPPCommunicator(String server, int port) { + super(server, port); + } + + public FireAlarmXMPPCommunicator(String server, int port, int timeout) { + super(server, port, timeout); + } + + public ScheduledFuture getDataPushServiceHandler() { + return dataPushServiceHandler; + } + + @Override + public void connect() { + final AgentManager agentManager = AgentManager.getInstance(); + username = agentManager.getAgentConfigs().getDeviceId(); + password = agentManager.getAgentConfigs().getAuthToken(); + xmppDeviceJID = username + "@" + agentManager.getAgentConfigs().getXmppServerName(); + xmppAdminJID = agentManager.getAgentConfigs().getServerJID(); + + Runnable connect = new Runnable() { + public void run() { + if (!isConnected()) { + try { + connectToServer(); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Connection to XMPP server at: " + server + " failed"); + } + + try { + loginToServer(username, password, resource); + agentManager.updateAgentStatus("Connected to XMPP Server"); + setMessageFilterAndListener(xmppAdminJID, xmppDeviceJID, true); + publishDeviceData(); + } catch (TransportHandlerException e) { + log.warn(AgentConstants.LOG_APPENDER + "Login to XMPP server at: " + server + " failed"); + agentManager.updateAgentStatus("No XMPP Account for Device"); + } + } + } + }; + + connectorServiceHandler = service.scheduleAtFixedRate(connect, 0, timeoutInterval, TimeUnit.MILLISECONDS); + } + + /** + * This is an abstract method used for post processing the received XMPP-message. This + * method will be implemented as per requirement at the time of creating an object of this + * class. + * + * @param xmppMessage the xmpp message received by the listener. + */ + @Override + public void processIncomingMessage(Message xmppMessage, String... messageParams) { + final AgentManager agentManager = AgentManager.getInstance(); + String from = xmppMessage.getFrom(); + String message = xmppMessage.getBody(); + String receivedMessage; + String replyMessage; + String securePayLoad; + + try { + receivedMessage = AgentUtilOperations.extractMessageFromPayload(message); + log.info(AgentConstants.LOG_APPENDER + "Message [" + receivedMessage + "] was received"); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Could not extract message from payload.", e); + return; + } + + String[] controlSignal = receivedMessage.split(":"); + //message- ":" format. (ex: "BULB:ON", "TEMPERATURE", "HUMIDITY") + + try { + switch (controlSignal[0].toUpperCase()) { + case AgentConstants.BULB_CONTROL: + if (controlSignal.length != 2) { + replyMessage = "BULB controls need to be in the form - 'BULB:{ON|OFF}'"; + log.warn(replyMessage); + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + } + + agentManager.changeAlarmStatus(controlSignal[1].equals(AgentConstants.CONTROL_ON)); + log.info(AgentConstants.LOG_APPENDER + "Bulb was switched to state: '" + controlSignal[1] + "'"); + break; + + case AgentConstants.TEMPERATURE_CONTROL: + int currentTemperature = agentManager.getTemperature(); + + String replyTemperature = + "The current temperature was read to be: '" + currentTemperature + + "C'"; + log.info(AgentConstants.LOG_APPENDER + replyTemperature); + + replyMessage = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + + case AgentConstants.HUMIDITY_CONTROL: + int currentHumidity = agentManager.getHumidity(); + + String replyHumidity = "The current humidity was read to be: '" + currentHumidity + "%'"; + log.info(AgentConstants.LOG_APPENDER + replyHumidity); + + replyMessage = AgentConstants.HUMIDITY_CONTROL + ":" + currentHumidity; + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-REPLY"); + break; + + default: + replyMessage = "'" + controlSignal[0] + "' is invalid and not-supported for this device-type"; + log.warn(replyMessage); + securePayLoad = AgentUtilOperations.prepareSecurePayLoad(replyMessage); + sendXMPPMessage(xmppAdminJID, securePayLoad, "CONTROL-ERROR"); + break; + } + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed", e); + } + + } + + + @Override + public void publishDeviceData() { + final AgentManager agentManager = AgentManager.getInstance(); + int publishInterval = agentManager.getPushInterval(); + + Runnable pushDataRunnable = new Runnable() { + @Override + public void run() { + Message xmppMessage = new Message(); + + try { + int currentTemperature = agentManager.getTemperature(); + + String message = AgentConstants.TEMPERATURE_CONTROL + ":" + currentTemperature; + String payLoad = AgentUtilOperations.prepareSecurePayLoad(message); + + xmppMessage.setTo(xmppAdminJID); + xmppMessage.setSubject(agentManager.getAgentConfigs().getTenantDomain()); + xmppMessage.setBody(payLoad); + xmppMessage.setType(Message.Type.chat); + + sendXMPPMessage(xmppAdminJID, xmppMessage); + log.info(AgentConstants.LOG_APPENDER + "Message: '" + message + "' sent to XMPP JID - " + + "[" + xmppAdminJID + "] under subject [" + xmppMessage.getSubject() + "]."); + } catch (AgentCoreOperationException e) { + log.warn(AgentConstants.LOG_APPENDER + "Preparing Secure payload failed for XMPP JID - " + + "[" + xmppAdminJID + "] with subject - [" + xmppMessage.getSubject() + "]."); + } + } + }; + + dataPushServiceHandler = service.scheduleAtFixedRate(pushDataRunnable, publishInterval, + publishInterval, TimeUnit.SECONDS); + } + + + @Override + public void disconnect() { + Runnable stopConnection = new Runnable() { + public void run() { + + if (dataPushServiceHandler != null) { + dataPushServiceHandler.cancel(true); + } + + if (connectorServiceHandler != null) { + connectorServiceHandler.cancel(true); + } + + while (isConnected()) { + closeConnection(); + + if (log.isDebugEnabled()) { + log.warn(AgentConstants.LOG_APPENDER + + "Unable to 'STOP' connection to XMPP server at: " + server); + } + + try { + Thread.sleep(timeoutInterval); + } catch (InterruptedException e1) { + log.error(AgentConstants.LOG_APPENDER + "XMPP-Terminator: Thread Sleep Interrupt Exception"); + } + + } + } + }; + + Thread terminatorThread = new Thread(stopConnection); + terminatorThread.setDaemon(true); + terminatorThread.start(); + } + + + @Override + public void processIncomingMessage() { + + } + + @Override + public void publishDeviceData(String... publishData) { + + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConfiguration.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConfiguration.java new file mode 100644 index 000000000..d5db8993c --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConfiguration.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +/** + * A Configuration class that holds all the Agent specific details that are read from the + * 'deviceConfig.properties' file. This file is generated by the IoT-Server at the time of + * downloading the device agent from the IoT-Server. + */ +public class AgentConfiguration { + private String tenantDomain; + private String deviceOwner; + private String deviceId; + private String deviceName; + private String controllerContext; + private String scepContext; + private String HTTPS_ServerEndpoint; + private String HTTP_ServerEndpoint; + private String apimGatewayEndpoint; + private String mqttBrokerEndpoint; + private String xmppServerEndpoint; + private String apiApplicationKey; + private String authMethod; + private String authToken; + private String refreshToken; + private int dataPushInterval; + private String xmppServerName; + private String serverJID; + + public String getTenantDomain() { + return tenantDomain; + } + + public void setTenantDomain(String tenantDomain) { + this.tenantDomain = tenantDomain; + } + + public String getDeviceOwner() { + return deviceOwner; + } + + public void setDeviceOwner(String deviceOwner) { + this.deviceOwner = deviceOwner; + } + + public String getDeviceId() { + return deviceId; + } + + public String getServerJID() { + return serverJID; + } + + public void setServerJID(String serverJID) { + this.serverJID = serverJID; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getControllerContext() { + return controllerContext; + } + + public void setControllerContext(String controllerContext) { + this.controllerContext = controllerContext; + } + + public String getHTTPS_ServerEndpoint() { + return HTTPS_ServerEndpoint; + } + + public void setHTTPS_ServerEndpoint(String HTTPS_ServerEndpoint) { + this.HTTPS_ServerEndpoint = HTTPS_ServerEndpoint; + } + + public String getHTTP_ServerEndpoint() { + return HTTP_ServerEndpoint; + } + + public void setHTTP_ServerEndpoint(String HTTP_ServerEndpoint) { + this.HTTP_ServerEndpoint = HTTP_ServerEndpoint; + } + + public String getApimGatewayEndpoint() { + return apimGatewayEndpoint; + } + + public void setApimGatewayEndpoint(String apimGatewayEndpoint) { + this.apimGatewayEndpoint = apimGatewayEndpoint; + } + + public String getMqttBrokerEndpoint() { + return mqttBrokerEndpoint; + } + + public void setMqttBrokerEndpoint(String mqttBrokerEndpoint) { + this.mqttBrokerEndpoint = mqttBrokerEndpoint; + } + + public String getXmppServerEndpoint() { + return xmppServerEndpoint; + } + + public void setXmppServerEndpoint(String xmppServerEndpoint) { + this.xmppServerEndpoint = xmppServerEndpoint; + } + + public String getApiApplicationKey() { + return apiApplicationKey; + } + + public void setApiApplicationKey(String apiApplicationKey) { + this.apiApplicationKey = apiApplicationKey; + } + + public String getAuthMethod() { + return authMethod; + } + + public void setAuthMethod(String authMethod) { + this.authMethod = authMethod; + } + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public int getDataPushInterval() { + return dataPushInterval; + } + + public void setDataPushInterval(int dataPushInterval) { + this.dataPushInterval = dataPushInterval; + } + + public String getScepContext() { + return scepContext; + } + + public void setScepContext(String scepContext) { + this.scepContext = scepContext; + } + + public String getXmppServerName() { + return xmppServerName; + } + + public void setXmppServerName(String xmppServerName) { + this.xmppServerName = xmppServerName; + } +} + + diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConstants.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConstants.java new file mode 100644 index 000000000..566e5cfc0 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentConstants.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +public class AgentConstants { + public static final String DEVICE_TYPE = "virtual_firealarm"; + public static final String LOG_APPENDER = "AGENT_LOG:: "; + public static final String PROPERTIES_FILE_PATH = ""; + public static final int DEFAULT_RETRY_THREAD_INTERVAL = 5000; // time in millis + public static final String TOKEN_AUTHENTICATION_METHOD = "token"; + /* --------------------------------------------------------------------------------------- + IoT-Server specific information + --------------------------------------------------------------------------------------- */ + public static final String DEVICE_ENROLLMENT_API_EP = "/scep"; + public static final String DEVICE_REGISTER_API_EP = "/register"; + public static final String DEVICE_PUSH_TEMPERATURE_API_EP = "/temperature"; + public static final String PUSH_DATA_PAYLOAD = + "{\"owner\":\"%s\",\"deviceId\":\"%s\",\"reply\":\"%s\",\"value\":\"%s\"}"; + + public static final String PUSH_SIMULATION_DATA_PAYLOAD = + "{\"owner\":\"%s\",\"deviceId\":\"%s\",\"reply\":\"%s\",\"value\":\"%s\",\"isSimulated\":\"%s\"," + + "\"duration\":\"%s\",\"frequency\":\"%s\"}"; + + public static final String DEVICE_DETAILS_PAGE_EP = "/devicemgt/device/%s?id=%s"; + public static final String DEVICE_ANALYTICS_PAGE_URL = + "/devicemgt/device/virtual_firealarm/analytics?deviceId=%s&deviceName=%s"; + + /* --------------------------------------------------------------------------------------- + HTTP Connection specific information for communicating with IoT-Server + --------------------------------------------------------------------------------------- */ + public static final String HTTP_POST = "POST"; + public static final String HTTP_GET = "GET"; + public static final String AUTHORIZATION_HEADER = "Authorization"; + public static final String CONTENT_TYPE_HEADER = "Content-Type"; + public static final String APPLICATION_JSON = "application/json"; + public static final String X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + public static final String REGISTERED = "Registered"; + public static final String NOT_REGISTERED = "Not-Registered"; + public static final String REGISTRATION_FAILED = "Registration Failed"; + public static final String RETRYING_TO_REGISTER = "Registration Failed. Re-trying.."; + public static final String SERVER_NOT_RESPONDING = "Server not responding.."; + + /* --------------------------------------------------------------------------------------- + MQTT Connection specific information + --------------------------------------------------------------------------------------- */ + public static final int DEFAULT_MQTT_RECONNECTION_INTERVAL = 2; // time in seconds + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0; + public static final String MQTT_SUBSCRIBE_TOPIC = "%s/" + DEVICE_TYPE + "/%s"; + public static final String MQTT_PUBLISH_TOPIC = "%s/" + DEVICE_TYPE + "/%s/publisher"; + + /* --------------------------------------------------------------------------------------- + Device/Agent specific properties to be read from the 'deviceConfig.properties' file + --------------------------------------------------------------------------------------- */ + public static final String AGENT_PROPERTIES_FILE_NAME = "deviceConfig.properties"; + public static final String TENANT_DOMAIN = "tenantDomain"; + public static final String DEVICE_OWNER_PROPERTY = "owner"; + public static final String DEVICE_ID_PROPERTY = "deviceId"; + public static final String SERVER_JID_PROPERTY = "server-jid"; + public static final String DEVICE_NAME_PROPERTY = "device-name"; + public static final String DEVICE_CONTROLLER_CONTEXT_PROPERTY = "controller-context"; + public static final String DEVICE_SCEP_CONTEXT_PROPERTY = "scep-context"; + public static final String SERVER_HTTPS_EP_PROPERTY = "https-ep"; + public static final String SERVER_HTTP_EP_PROPERTY = "http-ep"; + public static final String APIM_GATEWAY_EP_PROPERTY = "apim-ep"; + public static final String MQTT_BROKER_EP_PROPERTY = "mqtt-ep"; + public static final String XMPP_SERVER_EP_PROPERTY = "xmpp-ep"; + public static final String XMPP_SERVER_NAME_PROPERTY = "xmpp-server-name"; + public static final String API_APPLICATION_KEY = "application-key"; + public static final String AUTH_METHOD_PROPERTY = "auth-method"; + public static final String AUTH_TOKEN_PROPERTY = "auth-token"; + public static final String REFRESH_TOKEN_PROPERTY = "refresh-token"; + public static final String NETWORK_INTERFACE_PROPERTY = "network-interface"; + public static final String PUSH_INTERVAL_PROPERTY = "push-interval"; + /* --------------------------------------------------------------------------------------- + Default values for the Device/Agent specific configurations listed above + --------------------------------------------------------------------------------------- */ + public static final String DEFAULT_NETWORK_INTERFACE = "en0"; + public static final int DEFAULT_DATA_PUBLISH_INTERVAL = 15; // seconds + public static final String DEFAULT_PROTOCOL = "MQTT"; + /* --------------------------------------------------------------------------------------- + Control Signal specific constants to match the request context + --------------------------------------------------------------------------------------- */ + public static final String BULB_CONTROL = "BULB"; + public static final String TEMPERATURE_CONTROL = "TEMPERATURE"; + public static final String POLICY_SIGNAL = "POLICY"; + public static final String HUMIDITY_CONTROL = "HUMIDITY"; + public static final String CONTROL_ON = "ON"; + public static final String CONTROL_OFF = "OFF"; + public static final String AUDIO_FILE_NAME = "fireAlarmSound.mid"; + /* --------------------------------------------------------------------------------------- + Communication protocol specific Strings + --------------------------------------------------------------------------------------- */ + public static final String TCP_PREFIX = "tcp://"; + public static final String HTTP_PREFIX = "http://"; + public static final String HTTPS_PREFIX = "https://"; + public static final String HTTP_PROTOCOL = "HTTP"; + public static final String MQTT_PROTOCOL = "MQTT"; + public static final String XMPP_PROTOCOL = "XMPP"; + public static final String PROTOCOL_PROPERTY = "Protocol"; + public static final String HOST_PROPERTY = "Host"; + public static final String PORT_PROPERTY = "Port"; + + /* --------------------------------------------------------------------------------------- + Keystore specific strings for the device trustStore + --------------------------------------------------------------------------------------- */ + public static final String DEVICE_KEYSTORE_TYPE = "JKS"; + public static final String DEVICE_KEYSTORE = "virtual_firealarm.jks"; + public static final String DEVICE_KEYSTORE_PASSWORD = "wso2@virtual_firealarm"; + public static final String DEVICE_PRIVATE_KEY_ALIAS = "virtual_firealarm_key"; + public static final String DEVICE_CERT_ALIAS = "virtual_firealarm_cert"; + public static final String SERVER_CA_CERT_ALIAS = "ca_iotServer"; +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentManager.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentManager.java new file mode 100644 index 000000000..2e7634054 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentManager.java @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.http.FireAlarmHTTPCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.mqtt.FireAlarmMQTTCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.communication.xmpp.FireAlarmXMPPCommunicator; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.enrollment.EnrollmentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.VirtualHardwareManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AgentManager { + + private static final Log log = LogFactory.getLog(AgentManager.class); + private static AgentManager agentManager; + private String rootPath = ""; + + private boolean deviceReady = false; + private boolean isAlarmOn = false; + + private String deviceMgtControlUrl, deviceMgtAnalyticUrl; + private String deviceName, agentStatus; + + private int pushInterval; // seconds + private String prevProtocol, protocol; + + private String networkInterface; + private List interfaceList, protocolList; + private Map agentCommunicator; + + private AgentConfiguration agentConfigs; + + private String deviceIP; + private String enrollmentEP; + private String ipRegistrationEP; + private String pushDataAPIEP; + + private AgentManager() { + } + + public static AgentManager getInstance() { + if (agentManager == null) { + agentManager = new AgentManager(); + } + return agentManager; + } + + public void init() { + + agentCommunicator = new HashMap<>(); + // Read IoT-Server specific configurations from the 'deviceConfig.properties' file + try { + this.agentConfigs = AgentUtilOperations.readIoTServerConfigs(); + } catch (AgentCoreOperationException e) { + log.error("Reading device configuration from configuration file failed:\n"); + log.error(e); + System.exit(0); + } + + // Initialise IoT-Server URL endpoints from the configuration read from file + AgentUtilOperations.initializeServerEndPoints(); + // Set the hostNameVerifier to the APIM-Server IPAddress to enable HTTPS handshake + AgentUtilOperations.setHTTPSConfigurations(); + + String analyticsPageContext = String.format(AgentConstants.DEVICE_ANALYTICS_PAGE_URL, + agentConfigs.getDeviceId(), + agentConfigs.getDeviceName()); + + String controlPageContext = String.format(AgentConstants.DEVICE_DETAILS_PAGE_EP, + AgentConstants.DEVICE_TYPE, + agentConfigs.getDeviceId()); + + this.deviceMgtAnalyticUrl = agentConfigs.getHTTPS_ServerEndpoint() + analyticsPageContext; + this.deviceMgtControlUrl = agentConfigs.getHTTPS_ServerEndpoint() + controlPageContext; + + this.agentStatus = AgentConstants.NOT_REGISTERED; + this.deviceName = this.agentConfigs.getDeviceName(); + + this.pushInterval = this.agentConfigs.getDataPushInterval(); + this.networkInterface = AgentConstants.DEFAULT_NETWORK_INTERFACE; + + this.protocol = AgentConstants.DEFAULT_PROTOCOL; + this.prevProtocol = protocol; + + Map xmppIPPortMap; + try { + xmppIPPortMap = TransportUtils.getHostAndPort(agentConfigs.getXmppServerEndpoint()); + String xmppServer = xmppIPPortMap.get("Host"); + int xmppPort = Integer.parseInt(xmppIPPortMap.get("Port")); + + TransportHandler xmppCommunicator = new FireAlarmXMPPCommunicator(xmppServer, xmppPort); + agentCommunicator.put(AgentConstants.XMPP_PROTOCOL, xmppCommunicator); + + } catch (TransportHandlerException e) { + log.error("XMPP Endpoint String - " + agentConfigs.getXmppServerEndpoint() + + ", provided in the configuration file is invalid."); + } + String mqttTopic = String.format(AgentConstants.MQTT_SUBSCRIBE_TOPIC, agentConfigs.getTenantDomain(), + agentConfigs.getDeviceId()); + +// TransportHandler httpCommunicator = new FireAlarmHTTPCommunicator(); + TransportHandler mqttCommunicator = new FireAlarmMQTTCommunicator(agentConfigs.getDeviceOwner(), + agentConfigs.getDeviceId(), + agentConfigs.getMqttBrokerEndpoint(), + mqttTopic); + +// agentCommunicator.put(AgentConstants.HTTP_PROTOCOL, httpCommunicator); + agentCommunicator.put(AgentConstants.MQTT_PROTOCOL, mqttCommunicator); + + try { + interfaceList = new ArrayList<>(TransportUtils.getInterfaceIPMap().keySet()); + protocolList = new ArrayList<>(agentCommunicator.keySet()); + } catch (TransportHandlerException e) { + log.error("An error occurred whilst retrieving all NetworkInterface-IP mappings"); + } + + //Initializing hardware at that point + //AgentManger.setDeviceReady() method should invoked from hardware after initialization + VirtualHardwareManager.getInstance().init(); + + //Wait till hardware get ready + while (!deviceReady) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + log.info(AgentConstants.LOG_APPENDER + "Sleep error in 'device ready-flag' checking thread"); + } + } + + try { + if (!EnrollmentManager.getInstance().isEnrolled()) { + EnrollmentManager.getInstance().beginEnrollmentFlow(); + } + } catch (AgentCoreOperationException e) { + log.error("Device Enrollment Failed:\n"); + log.error(e); + System.exit(0); + } + + //Start agent communication + agentCommunicator.get(protocol).connect(); + } + + private void switchCommunicator(String stopProtocol, String startProtocol) { + agentCommunicator.get(stopProtocol).disconnect(); + + while (agentCommunicator.get(stopProtocol).isConnected()) { + try { + Thread.sleep(250); + } catch (InterruptedException e) { + log.info(AgentConstants.LOG_APPENDER + + "Sleep error in 'Switch-Communicator' Thread's shutdown wait."); + } + } + + agentCommunicator.get(startProtocol).connect(); + } + + public void setInterface(int interfaceId) { + if (interfaceId != -1) { + String newInterface = interfaceList.get(interfaceId); + + if (!newInterface.equals(networkInterface)) { + networkInterface = newInterface; + + if (protocol.equals(AgentConstants.HTTP_PROTOCOL) && !protocol.equals( + prevProtocol)) { + switchCommunicator(prevProtocol, protocol); + } + } + } + } + + public void setProtocol(int protocolId) { + if (protocolId != -1) { + String newProtocol = protocolList.get(protocolId); + + if (!protocol.equals(newProtocol)) { + prevProtocol = protocol; + protocol = newProtocol; + switchCommunicator(prevProtocol, protocol); + } + } + } + + public void changeAlarmStatus(boolean isOn) { + VirtualHardwareManager.getInstance().changeAlarmStatus(isOn); + isAlarmOn = isOn; + } + + public void updateAgentStatus(String status) { + this.agentStatus = status; + } + + /*------------------------------------------------------------------------------------------*/ + /* Getter and Setter Methods for the private variables */ + /*------------------------------------------------------------------------------------------*/ + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + public String getRootPath() { + return rootPath; + } + + public void setDeviceReady(boolean deviceReady) { + this.deviceReady = deviceReady; + } + + public String getDeviceMgtControlUrl() { + return deviceMgtControlUrl; + } + + public String getDeviceMgtAnalyticUrl() { + return deviceMgtAnalyticUrl; + } + + public AgentConfiguration getAgentConfigs() { + return agentConfigs; + } + + public String getDeviceIP() { + return deviceIP; + } + + public void setDeviceIP(String deviceIP) { + this.deviceIP = deviceIP; + } + + public String getEnrollmentEP() { + return enrollmentEP; + } + + public void setEnrollmentEP(String enrollmentEP) { + this.enrollmentEP = enrollmentEP; + } + + public String getIpRegistrationEP() { + return ipRegistrationEP; + } + + public void setIpRegistrationEP(String ipRegistrationEP) { + this.ipRegistrationEP = ipRegistrationEP; + } + + public String getPushDataAPIEP() { + return pushDataAPIEP; + } + + public void setPushDataAPIEP(String pushDataAPIEP) { + this.pushDataAPIEP = pushDataAPIEP; + } + + public String getDeviceName() { + return deviceName; + } + + public String getNetworkInterface() { + return networkInterface; + } + + public String getAgentStatus() { + return agentStatus; + } + + public int getPushInterval() { + return pushInterval; + } + + public void setPushInterval(int pushInterval) { + this.pushInterval = pushInterval; + TransportHandler transportHandler = agentCommunicator.get(protocol); + + switch (protocol) { + case AgentConstants.HTTP_PROTOCOL: + ((FireAlarmHTTPCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + case AgentConstants.MQTT_PROTOCOL: + ((FireAlarmMQTTCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + case AgentConstants.XMPP_PROTOCOL: + ((FireAlarmXMPPCommunicator) transportHandler).getDataPushServiceHandler() + .cancel(true); + break; + default: + log.warn("Unknown protocol " + protocol); + } + transportHandler.publishDeviceData(); + + if (log.isDebugEnabled()) { + log.debug("The Data Publish Interval was changed to: " + pushInterval); + } + } + + public List getInterfaceList() { + return interfaceList; + } + + public List getProtocolList() { + return protocolList; + } + + /** + * Get temperature reading from device + * + * @return Temperature + */ + public int getTemperature() { + return VirtualHardwareManager.getInstance().getTemperature(); + } + + /** + * Get humidity reading from device + * + * @return Humidity + */ + public int getHumidity() { + return VirtualHardwareManager.getInstance().getHumidity(); + } + + public boolean isAlarmOn() { + return isAlarmOn; + } +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentUtilOperations.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentUtilOperations.java new file mode 100644 index 000000000..60c3941b8 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/core/AgentUtilOperations.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.http.HttpStatus; +import org.json.JSONObject; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.enrollment.EnrollmentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.CommunicationUtils; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.ProtocolException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Properties; + +/** + * This class contains all the core operations of the FireAlarm agent that are common to both + * Virtual and Real Scenarios. These operations include, connecting to and subscribing to an MQTT + * queue and to a XMPP Server. Pushing temperature data to the IoT-Server at timely intervals. + * Reading device specific configuration from a configs file etc.... + */ +public class AgentUtilOperations { + + private static final Log log = LogFactory.getLog(AgentUtilOperations.class); + private static final String JSON_MESSAGE_KEY = "Msg"; + private static final String JSON_SIGNATURE_KEY = "Sig"; + private static final String JSON_SERIAL_KEY = "SerialNumber"; + + /** + * This method reads the agent specific configurations for the device from the + * "deviceConfigs.properties" file found at /repository/conf folder. + * If the properties file is not found in the specified path, then the configuration values + * are set to the default ones in the 'AgentConstants' class. + * + * @return an object of type 'AgentConfiguration' which contains all the necessary + * configuration attributes + */ + public static AgentConfiguration readIoTServerConfigs() throws AgentCoreOperationException { + AgentManager agentManager = AgentManager.getInstance(); + AgentConfiguration iotServerConfigs = new AgentConfiguration(); + Properties properties = new Properties(); + InputStream propertiesInputStream = null; + String propertiesFileName = AgentConstants.AGENT_PROPERTIES_FILE_NAME; + String rootPath = ""; + + try { + ClassLoader loader = AgentUtilOperations.class.getClassLoader(); + URL path = loader.getResource(propertiesFileName); + + if (path != null) { + log.info(AgentConstants.LOG_APPENDER + path); + rootPath = path.getPath().replace("wso2-firealarm-virtual-agent.jar!/deviceConfig.properties", "") + .replace("jar:", "").replace("file:", ""); + + rootPath = URLDecoder.decode(rootPath, StandardCharsets.UTF_8.toString()); + agentManager.setRootPath(rootPath); + + String deviceConfigFilePath = rootPath + AgentConstants.AGENT_PROPERTIES_FILE_NAME; + propertiesInputStream = new FileInputStream(deviceConfigFilePath); + + //load a properties file from class path, inside static method + properties.load(propertiesInputStream); + + iotServerConfigs.setTenantDomain(properties.getProperty( + AgentConstants.TENANT_DOMAIN)); + iotServerConfigs.setDeviceOwner(properties.getProperty( + AgentConstants.DEVICE_OWNER_PROPERTY)); + iotServerConfigs.setDeviceId(properties.getProperty( + AgentConstants.DEVICE_ID_PROPERTY)); + iotServerConfigs.setServerJID(properties.getProperty( + AgentConstants.SERVER_JID_PROPERTY)); + iotServerConfigs.setDeviceName(properties.getProperty( + AgentConstants.DEVICE_NAME_PROPERTY)); + iotServerConfigs.setControllerContext(properties.getProperty( + AgentConstants.DEVICE_CONTROLLER_CONTEXT_PROPERTY)); + iotServerConfigs.setScepContext(properties.getProperty( + AgentConstants.DEVICE_SCEP_CONTEXT_PROPERTY)); + iotServerConfigs.setHTTPS_ServerEndpoint(properties.getProperty( + AgentConstants.SERVER_HTTPS_EP_PROPERTY)); + iotServerConfigs.setHTTP_ServerEndpoint(properties.getProperty( + AgentConstants.SERVER_HTTP_EP_PROPERTY)); + iotServerConfigs.setApimGatewayEndpoint(properties.getProperty( + AgentConstants.APIM_GATEWAY_EP_PROPERTY)); + iotServerConfigs.setMqttBrokerEndpoint(properties.getProperty( + AgentConstants.MQTT_BROKER_EP_PROPERTY)); + iotServerConfigs.setXmppServerEndpoint(properties.getProperty( + AgentConstants.XMPP_SERVER_EP_PROPERTY)); + iotServerConfigs.setXmppServerName(properties.getProperty( + AgentConstants.XMPP_SERVER_NAME_PROPERTY)); + iotServerConfigs.setApiApplicationKey(properties.getProperty( + AgentConstants.API_APPLICATION_KEY)); + iotServerConfigs.setAuthMethod(properties.getProperty( + AgentConstants.AUTH_METHOD_PROPERTY)); + iotServerConfigs.setAuthToken(properties.getProperty( + AgentConstants.AUTH_TOKEN_PROPERTY)); + iotServerConfigs.setRefreshToken(properties.getProperty( + AgentConstants.REFRESH_TOKEN_PROPERTY)); + iotServerConfigs.setDataPushInterval(Integer.parseInt(properties.getProperty( + AgentConstants.PUSH_INTERVAL_PROPERTY))); + + log.info(AgentConstants.LOG_APPENDER + "Tenant Domain: " + + iotServerConfigs.getTenantDomain()); + log.info(AgentConstants.LOG_APPENDER + "Device Owner: " + + iotServerConfigs.getDeviceOwner()); + log.info(AgentConstants.LOG_APPENDER + "Device ID: " + iotServerConfigs.getDeviceId()); + log.info(AgentConstants.LOG_APPENDER + "Device Name: " + + iotServerConfigs.getDeviceName()); + log.info(AgentConstants.LOG_APPENDER + "Device Controller Context: " + + iotServerConfigs.getControllerContext()); + log.info(AgentConstants.LOG_APPENDER + "IoT Server HTTPS EndPoint: " + + iotServerConfigs.getHTTPS_ServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "IoT Server HTTP EndPoint: " + + iotServerConfigs.getHTTP_ServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "API-Manager Gateway EndPoint: " + + iotServerConfigs.getApimGatewayEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "MQTT Broker EndPoint: " + + iotServerConfigs.getMqttBrokerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "XMPP Server EndPoint: " + + iotServerConfigs.getXmppServerEndpoint()); + log.info(AgentConstants.LOG_APPENDER + "Authentication Method: " + + iotServerConfigs.getAuthMethod()); + log.info(AgentConstants.LOG_APPENDER + "Base64Encoded API Application Key: " + + iotServerConfigs.getApiApplicationKey()); + log.info(AgentConstants.LOG_APPENDER + "Authentication Token: " + + iotServerConfigs.getAuthToken()); + log.info(AgentConstants.LOG_APPENDER + "Refresh Token: " + + iotServerConfigs.getRefreshToken()); + log.info(AgentConstants.LOG_APPENDER + "Data Push Interval: " + + iotServerConfigs.getDataPushInterval()); + log.info(AgentConstants.LOG_APPENDER + "XMPP Server Name: " + + iotServerConfigs.getXmppServerName()); + } else { + throw new AgentCoreOperationException( + "Failed to load path of resource [" + propertiesFileName + "] from this classpath."); + } + } catch (FileNotFoundException ex) { + String errorMsg = "[" + propertiesFileName + "] file not found at: " + rootPath; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg); + + } catch (IOException ex) { + String errorMsg = "Error occurred whilst trying to fetch [" + propertiesFileName + "] from: " + + AgentConstants.PROPERTIES_FILE_PATH; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg); + } finally { + if (propertiesInputStream != null) { + try { + propertiesInputStream.close(); + } catch (IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "Error occurred whilst trying to close InputStream resource used to read the '" + + propertiesFileName + "' file"); + } + } + } + return iotServerConfigs; + } + + /** + * This method constructs the URLs for each of the API Endpoints called by the device agent + * Ex: Register API, Push-Data API + */ + public static void initializeServerEndPoints() { + AgentManager agentManager = AgentManager.getInstance(); + String serverSecureEndpoint = agentManager.getAgentConfigs().getHTTPS_ServerEndpoint(); + String serverUnSecureEndpoint = agentManager.getAgentConfigs().getHTTP_ServerEndpoint(); + String backEndContext = agentManager.getAgentConfigs().getControllerContext(); + String scepBackEndContext = agentManager.getAgentConfigs().getScepContext(); + + String deviceControllerAPIEndpoint = serverSecureEndpoint + backEndContext; + + String deviceEnrollmentEndpoint = + serverUnSecureEndpoint + scepBackEndContext + AgentConstants.DEVICE_ENROLLMENT_API_EP; + agentManager.setEnrollmentEP(deviceEnrollmentEndpoint); + + String registerEndpointURL = + deviceControllerAPIEndpoint + AgentConstants.DEVICE_REGISTER_API_EP; + agentManager.setIpRegistrationEP(registerEndpointURL); + + String pushDataEndPointURL = + deviceControllerAPIEndpoint + AgentConstants.DEVICE_PUSH_TEMPERATURE_API_EP; + agentManager.setPushDataAPIEP(pushDataEndPointURL); + + log.info(AgentConstants.LOG_APPENDER + "IoT Server's Device Controller API Endpoint: " + + deviceControllerAPIEndpoint); + log.info(AgentConstants.LOG_APPENDER + "Device Enrollment EndPoint: " + + registerEndpointURL); + log.info(AgentConstants.LOG_APPENDER + "DeviceIP Registration EndPoint: " + + registerEndpointURL); + log.info(AgentConstants.LOG_APPENDER + "Push-Data API EndPoint: " + pushDataEndPointURL); + } + + public static void setHTTPSConfigurations() { + String apimEndpoint = AgentManager.getInstance().getAgentConfigs().getApimGatewayEndpoint(); + System.setProperty("javax.net.ssl.trustStore", AgentConstants.DEVICE_KEYSTORE); + System.setProperty("javax.net.ssl.trustStorePassword", AgentConstants.DEVICE_KEYSTORE_PASSWORD); + + try { + final String apimHost = TransportUtils.getHostAndPort(apimEndpoint).get(AgentConstants.HOST_PROPERTY); + + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return hostname.equals(apimHost); + } + }); + } catch (TransportHandlerException e) { + log.error(AgentConstants.LOG_APPENDER + + "Failed to set HTTPS HostNameVerifier to the APIMServer-Host using the APIM-Endpoint " + + "string [" + apimEndpoint + "]."); + log.error(AgentConstants.LOG_APPENDER + e); + } + } + + public static String prepareSecurePayLoad(String message) throws AgentCoreOperationException { + PrivateKey devicePrivateKey = EnrollmentManager.getInstance().getPrivateKey(); + String encodedMessage = Base64.encodeBase64String(message.getBytes()); + String signedPayload; + try { + signedPayload = CommunicationUtils.signMessage(encodedMessage, devicePrivateKey); + } catch (TransportHandlerException e) { + String errorMsg = "Error occurred whilst trying to sign encrypted message of: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + JSONObject jsonPayload = new JSONObject(); + jsonPayload.put(JSON_MESSAGE_KEY, encodedMessage); + jsonPayload.put(JSON_SIGNATURE_KEY, signedPayload); + //below statements are temporary fix. + jsonPayload.put(JSON_SERIAL_KEY, EnrollmentManager.getInstance().getSCEPCertificate().getSerialNumber()); + return jsonPayload.toString(); + } + + public static String extractMessageFromPayload(String message) throws AgentCoreOperationException { + String actualMessage; + + PublicKey serverPublicKey = EnrollmentManager.getInstance().getServerPublicKey(); + JSONObject jsonPayload = new JSONObject(message); + Object encodedMessage = jsonPayload.get(JSON_MESSAGE_KEY); + Object signedPayload = jsonPayload.get(JSON_SIGNATURE_KEY); + boolean verification; + + if (encodedMessage != null && signedPayload != null) { + try { + verification = CommunicationUtils.verifySignature( + encodedMessage.toString(), signedPayload.toString(), serverPublicKey); + } catch (TransportHandlerException e) { + String errorMsg = + "Error occurred whilst trying to verify signature on received message: [" + message + "]"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + } else { + String errorMsg = "The received message is in an INVALID format. " + + "Need to be JSON - {\"Msg\":\"\", \"Sig\":\"\"}."; + throw new AgentCoreOperationException(errorMsg); + } + if (verification) { + actualMessage = new String(Base64.decodeBase64(encodedMessage.toString()), StandardCharsets.UTF_8); + } else { + String errorMsg = "Could not verify payload signature. The message was not signed by a valid client"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg); + } + return actualMessage; + } + + public static String getAuthenticationMethod() { + String authMethod = AgentManager.getInstance().getAgentConfigs().getAuthMethod(); + switch (authMethod) { + case AgentConstants.TOKEN_AUTHENTICATION_METHOD: + return AgentConstants.TOKEN_AUTHENTICATION_METHOD; + default: + return ""; + } + } + + public static void refreshOAuthToken() throws AgentCoreOperationException { + + AgentManager agentManager = AgentManager.getInstance(); + String tokenEndpoint = agentManager.getAgentConfigs().getApimGatewayEndpoint(); + tokenEndpoint = tokenEndpoint + APIManagerTokenUtils.TOKEN_ENDPOINT; + + HttpURLConnection httpConnection = null; + BufferedReader connectionBuffer = null; + String requestPayload; + String dataFromBuffer; + StringBuilder responseMessage = new StringBuilder(); + + try { + String refreshToken = agentManager.getAgentConfigs().getRefreshToken(); + String applicationScope = "device_type_" + AgentConstants.DEVICE_TYPE + + " device_" + agentManager.getAgentConfigs().getDeviceId(); + + requestPayload = APIManagerTokenUtils.GRANT_TYPE + "=" + APIManagerTokenUtils.REFRESH_TOKEN + "&" + + APIManagerTokenUtils.REFRESH_TOKEN + "=" + refreshToken + "&" + + APIManagerTokenUtils.SCOPE + "=" + applicationScope; + + httpConnection = TransportUtils.getHttpConnection(tokenEndpoint); + httpConnection.setRequestMethod(AgentConstants.HTTP_POST); + httpConnection.setRequestProperty(AgentConstants.AUTHORIZATION_HEADER, + "Basic " + agentManager.getAgentConfigs().getApiApplicationKey()); + httpConnection.setRequestProperty(AgentConstants.CONTENT_TYPE_HEADER, AgentConstants.X_WWW_FORM_URLENCODED); + httpConnection.setDoOutput(true); + + DataOutputStream dataOutPutWriter = new DataOutputStream(httpConnection.getOutputStream()); + dataOutPutWriter.writeBytes(requestPayload); + dataOutPutWriter.flush(); + dataOutPutWriter.close(); + + log.info(AgentConstants.LOG_APPENDER + "Request to refresh OAuth token was sent to [" + + httpConnection.getURL() + "] with payload [" + requestPayload + "]."); + log.info(AgentConstants.LOG_APPENDER + "Response [" + httpConnection.getResponseCode() + ":" + + httpConnection.getResponseMessage() + "] was received for token refresh attempt."); + + if (httpConnection.getResponseCode() == HttpStatus.OK_200) { + connectionBuffer = new BufferedReader(new InputStreamReader(httpConnection.getInputStream())); + while ((dataFromBuffer = connectionBuffer.readLine()) != null) { + responseMessage.append(dataFromBuffer); + } + + log.info(AgentConstants.LOG_APPENDER + + "Response " + responseMessage + " was received for the token refresh call."); + updateExistingTokens(responseMessage.toString()); + } else if (httpConnection.getResponseCode() == HttpStatus.BAD_REQUEST_400) { + log.error(AgentConstants.LOG_APPENDER + + "Token refresh call returned with a [400 Bad Request].\nThe refresh-token has " + + "probably expired.\nPlease contact System-Admin to get a valid refresh-token."); + } else { + log.warn(AgentConstants.LOG_APPENDER + "There was an issue with refreshing the Access Token."); + } + + } catch (TransportHandlerException e) { + throw new AgentCoreOperationException(e); + } catch (ProtocolException e) { + String errorMsg = "Protocol specific error occurred when trying to set method to " + + AgentConstants.HTTP_POST + " for endpoint at: " + tokenEndpoint; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + + } catch (IOException e) { + String errorMsg = "An IO error occurred whilst trying to get the response code from: " + tokenEndpoint + + " for a HTTP " + AgentConstants.HTTP_POST + " call."; + log.error(AgentConstants.LOG_APPENDER + errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } finally { + if (connectionBuffer != null) { + try { + connectionBuffer.close(); + } catch (IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "Error encounter whilst attempting to close buffer to connection at: " + + tokenEndpoint); + } + } + + if (httpConnection != null) { + httpConnection.disconnect(); + } + } + } + + private static void updateExistingTokens(String responseFromTokenEP) throws AgentCoreOperationException { + JSONObject jsonTokenObject = new JSONObject(responseFromTokenEP); + String newAccessToken = jsonTokenObject.get(APIManagerTokenUtils.ACCESS_TOKEN).toString(); + String newRefreshToken = jsonTokenObject.get(APIManagerTokenUtils.REFRESH_TOKEN).toString(); + + if (newAccessToken == null || newRefreshToken == null) { + String msg = + "Neither Access-Token nor Refresh-Token was found in the response [" + responseFromTokenEP + "]."; + log.error(AgentConstants.LOG_APPENDER + msg); + throw new AgentCoreOperationException(msg); + } + + AgentManager.getInstance().getAgentConfigs().setAuthToken(newAccessToken); + AgentManager.getInstance().getAgentConfigs().setRefreshToken(newRefreshToken); + String deviceConfigFilePath = + AgentManager.getInstance().getRootPath() + AgentConstants.AGENT_PROPERTIES_FILE_NAME; + + try { + PropertiesConfiguration propertyFileConfiguration = new PropertiesConfiguration(deviceConfigFilePath); + propertyFileConfiguration.setProperty(AgentConstants.AUTH_TOKEN_PROPERTY, newAccessToken); + propertyFileConfiguration.setProperty(AgentConstants.REFRESH_TOKEN_PROPERTY, newRefreshToken); + propertyFileConfiguration.save(); + } catch (ConfigurationException e) { + String msg = "Error occurred whilst trying to update the [" + AgentConstants.AGENT_PROPERTIES_FILE_NAME + + "] at: " + deviceConfigFilePath + " will the new tokens."; + log.error(AgentConstants.LOG_APPENDER + msg); + throw new AgentCoreOperationException(msg); + } + } + + private class APIManagerTokenUtils { + public static final String TOKEN_ENDPOINT = "/oauth2/token"; + public static final String GRANT_TYPE = "grant_type"; + public static final String ACCESS_TOKEN = "access_token"; + public static final String REFRESH_TOKEN = "refresh_token"; + public static final String SCOPE = "scope"; + } + +} + diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/enrollment/EnrollmentManager.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/enrollment/EnrollmentManager.java new file mode 100644 index 000000000..6059483bc --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/enrollment/EnrollmentManager.java @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.enrollment; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.X500NameBuilder; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.bouncycastle.pkcs.PKCS10CertificationRequest; +import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; +import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; +import org.jscep.client.Client; +import org.jscep.client.ClientException; +import org.jscep.client.EnrollmentResponse; +import org.jscep.client.verification.CertificateVerifier; +import org.jscep.client.verification.OptimisticCertificateVerifier; +import org.jscep.transaction.TransactionException; +import org.jscep.transport.response.Capabilities; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception.AgentCoreOperationException; +import sun.security.x509.X509CertImpl; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Security; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertStore; +import java.security.cert.CertStoreException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Date; + +/** + * This class controls the entire SCEP enrolment process of the client. It is a singleton for any single client which + * has the agent code running in it. The main functionality of this class includes generating a Private-Public Key + * Pair for the enrollment flow, creating the Certificate-Sign-Request using the generated Public-Key to send to the + * SEP server, Contacting the SCEP server to receive the Signed Certificate and requesting for the server's public + * key for encrypting the payloads. + * The provider for all Cryptographic functions used in this class are "BouncyCastle" and the Asymmetric-Key pair + * algorithm used is "RSA" with a key size of 2048. The signature algorithm used is "SHA1withRSA". + * This class also holds the "SCEPUrl" (Server Url read from the configs file), the Private-Public Keys of the + * client, Signed SCEP certificate and the server's public certificate. + */ + +//TODO: Need to save cert and keys to file after initial enrollment... +public class EnrollmentManager { + private static final Log log = LogFactory.getLog(EnrollmentManager.class); + private static EnrollmentManager enrollmentManager; + + private static final String KEY_PAIR_ALGORITHM = "RSA"; + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALG = "SHA1withRSA"; + private static final String CERT_IS_CA_EXTENSION = "is_ca"; + private static final int KEY_SIZE = 2048; + + // Seed to our PRNG. Make sure this is initialised randomly, NOT LIKE THIS + private static final byte[] SEED = ")(*&^%$#@!".getBytes(); + private static final int CERT_VALIDITY = 730; + + // URL of our SCEP server + private String SCEPUrl; + private PrivateKey privateKey; + private PublicKey publicKey; + private PublicKey serverPublicKey; + private X509Certificate SCEPCertificate; + private boolean isEnrolled = false; + + + /** + * Constructor of the EnrollmentManager. Initializes the SCEPUrl as read from the configuration file by the + * AgentManager. + */ + private EnrollmentManager() { + this.SCEPUrl = AgentManager.getInstance().getEnrollmentEP(); + setEnrollmentStatus(); + } + + /** + * Method to return the current singleton instance of the EnrollmentManager. + * + * @return the current singleton instance if available and if not initializes a new instance and returns it. + */ + public static EnrollmentManager getInstance() { + if (enrollmentManager == null) { + enrollmentManager = new EnrollmentManager(); + } + return enrollmentManager; + } + + + public void setEnrollmentStatus() { + KeyStore keyStore; + + try { + keyStore = KeyStore.getInstance(AgentConstants.DEVICE_KEYSTORE_TYPE); + keyStore.load(new FileInputStream(AgentConstants.DEVICE_KEYSTORE), + AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray()); + + this.isEnrolled = (keyStore.containsAlias(AgentConstants.DEVICE_CERT_ALIAS) && + keyStore.containsAlias(AgentConstants.DEVICE_PRIVATE_KEY_ALIAS) && + keyStore.containsAlias(AgentConstants.SERVER_CA_CERT_ALIAS)); + + } catch (KeyStoreException e) { + log.error(AgentConstants.LOG_APPENDER + "An error occurred whilst accessing the device KeyStore '" + + AgentConstants.DEVICE_KEYSTORE + "' with keystore type [" + + AgentConstants.DEVICE_KEYSTORE_TYPE + "] to ensure enrollment status."); + log.error(AgentConstants.LOG_APPENDER + e); + log.warn(AgentConstants.LOG_APPENDER + "Device will be re-enrolled."); + return; + } catch (CertificateException | NoSuchAlgorithmException e) { + log.error(AgentConstants.LOG_APPENDER + "An error occurred whilst trying to [load] the device KeyStore '" + + AgentConstants.DEVICE_KEYSTORE + "'."); + log.error(AgentConstants.LOG_APPENDER + e); + log.warn(AgentConstants.LOG_APPENDER + "Device will be re-enrolled."); + return; + } catch (IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "An error occurred whilst trying to load input stream with the keystore file: " + + AgentConstants.DEVICE_KEYSTORE); + log.error(AgentConstants.LOG_APPENDER + e); + log.warn(AgentConstants.LOG_APPENDER + "Device will be re-enrolled."); + return; + } + + try { + if (this.isEnrolled) { + this.SCEPCertificate = (X509Certificate) keyStore.getCertificate(AgentConstants.DEVICE_CERT_ALIAS); + this.privateKey = (PrivateKey) keyStore.getKey(AgentConstants.DEVICE_PRIVATE_KEY_ALIAS, + AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray()); + this.publicKey = SCEPCertificate.getPublicKey(); + + X509Certificate serverCACert = (X509Certificate) keyStore.getCertificate( + AgentConstants.SERVER_CA_CERT_ALIAS); + this.serverPublicKey = serverCACert.getPublicKey(); + log.info(AgentConstants.LOG_APPENDER + + "Device has already been enrolled. Hence, loaded certificate information from device" + + " trust-store."); + } + } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException e) { + log.error(AgentConstants.LOG_APPENDER + "An error occurred whilst accessing the device KeyStore '" + + AgentConstants.DEVICE_KEYSTORE + "' to ensure enrollment status."); + log.error(AgentConstants.LOG_APPENDER + e); + log.warn(AgentConstants.LOG_APPENDER + "Device will be re-enrolled."); + this.isEnrolled = false; + } + } + + /** + * Method to control the entire enrollment flow. This method calls the method to create the Private-Public Key + * Pair, calls the specific method to generate the Certificate-Sign-Request, creates a one time self signed + * certificate to present to the SCEP server with the initial CSR, calls the specific method to connect to the + * SCEP Server and to get the SCEP Certificate and also calls the method that requests the SCEP Server for its + * PublicKey for future payload encryption. + * + * @throws AgentCoreOperationException if the private method generateCertSignRequest() fails with an error or if + * there is an error creating a self-sign certificate to present to the + * server (whilst trying to get the CSR signed) + */ + public void beginEnrollmentFlow() throws AgentCoreOperationException { + Security.addProvider(new BouncyCastleProvider()); + + KeyPair keyPair = generateKeyPair(); + this.privateKey = keyPair.getPrivate(); + this.publicKey = keyPair.getPublic(); + + if (log.isDebugEnabled()) { + log.info(AgentConstants.LOG_APPENDER + "DevicePrivateKey:\n[\n" + privateKey + "\n]\n"); + log.info(AgentConstants.LOG_APPENDER + "DevicePublicKey:\n[\n" + publicKey + "\n]\n"); + } + + PKCS10CertificationRequest certSignRequest = generateCertSignRequest(); + + /** + * ----------------------------------------------------------------------------------------------- + * Generate an ephemeral self-signed certificate. This is needed to present to the CA in the SCEP request. + * In the future, add proper EKU and attributes in the request. The CA does NOT have to honour any of this. + * ----------------------------------------------------------------------------------------------- + */ + X500Name issuer = new X500Name("CN=Temporary Issuer"); + BigInteger serial = new BigInteger(32, new SecureRandom()); + Date fromDate = new Date(); + Date toDate = new Date(System.currentTimeMillis() + (CERT_VALIDITY * 86400000L)); + + // Build the self-signed cert using BC, sign it with our private key (self-signed) + X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuer, serial, fromDate, toDate, + certSignRequest.getSubject(), + certSignRequest.getSubjectPublicKeyInfo()); + ContentSigner sigGen; + X509Certificate tmpCert; + + try { + sigGen = new JcaContentSignerBuilder(SIGNATURE_ALG).setProvider(PROVIDER).build(keyPair.getPrivate()); + tmpCert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certBuilder.build(sigGen)); + } catch (OperatorCreationException e) { + String errorMsg = "Error occurred whilst creating a ContentSigner for the Temp-Self-Signed Certificate."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertificateException e) { + String errorMsg = "Error occurred whilst trying to create Temp-Self-Signed Certificate."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + /** + * ----------------------------------------------------------------------------------------------- + */ + + this.SCEPCertificate = getSignedCertificateFromServer(tmpCert, certSignRequest); + this.serverPublicKey = initPublicKeyOfServer(); + + storeCertificateToStore(AgentConstants.DEVICE_CERT_ALIAS, SCEPCertificate); + storeKeyToKeyStore(AgentConstants.DEVICE_PRIVATE_KEY_ALIAS, this.privateKey, SCEPCertificate); + + if (log.isDebugEnabled()) { + log.info(AgentConstants.LOG_APPENDER + + "SCEPCertificate, DevicePrivateKey, ServerPublicKey was saved to device keystore [" + + AgentConstants.DEVICE_KEYSTORE + "]"); + log.info(AgentConstants.LOG_APPENDER + "TemporaryCertPublicKey:\n[\n" + tmpCert.getPublicKey() + "\n]\n"); + log.info(AgentConstants.LOG_APPENDER + "ServerPublicKey:\n[\n" + serverPublicKey + "\n]\n"); + } + } + + private void storeCertificateToStore(String alias, Certificate certificate) { + KeyStore keyStore; + try { + keyStore = KeyStore.getInstance(AgentConstants.DEVICE_KEYSTORE_TYPE); + keyStore.load(new FileInputStream(AgentConstants.DEVICE_KEYSTORE), + AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray()); + + keyStore.setCertificateEntry(alias, certificate); + keyStore.store(new FileOutputStream(AgentConstants.DEVICE_KEYSTORE), + AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray()); + + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "An error occurred whilst trying to store the Certificate received from the SCEP " + + "Enrollment."); + log.error(AgentConstants.LOG_APPENDER + e); + log.warn(AgentConstants.LOG_APPENDER + + "SCEP Certificate was not stored in the keystore; " + + "Hence the device will be re-enrolled during next restart."); + } + } + + + private void storeKeyToKeyStore(String alias, Key cryptoKey, Certificate certInCertChain) { + KeyStore keyStore; + try { + keyStore = KeyStore.getInstance(AgentConstants.DEVICE_KEYSTORE_TYPE); + keyStore.load(new FileInputStream(AgentConstants.DEVICE_KEYSTORE), + AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray()); + + Certificate[] certChain = new Certificate[1]; + certChain[0] = certInCertChain; + + keyStore.setKeyEntry(alias, cryptoKey, AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray(), certChain); + keyStore.store(new FileOutputStream(AgentConstants.DEVICE_KEYSTORE), + AgentConstants.DEVICE_KEYSTORE_PASSWORD.toCharArray()); + + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + log.error(AgentConstants.LOG_APPENDER + + "An error occurred whilst trying to store the key with alias " + + "[" + alias + "] in the device keystore."); + log.error(AgentConstants.LOG_APPENDER + e); + log.warn(AgentConstants.LOG_APPENDER + + "Key [" + alias + "] was not stored in the keystore; " + + "Hence the device will be re-enrolled during next restart."); + } + } + + /** + * This method creates the Public-Private Key pair for the current client. + * + * @return the generated KeyPair object + * @throws AgentCoreOperationException when the given Security Provider does not exist or the Algorithmn used to + * generate the key pair is invalid. + */ + private KeyPair generateKeyPair() throws AgentCoreOperationException { + + // Generate key pair + KeyPairGenerator keyPairGenerator; + try { + keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM, PROVIDER); + keyPairGenerator.initialize(KEY_SIZE, new SecureRandom(SEED)); + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm [" + KEY_PAIR_ALGORITHM + "] provided for KeyPairGenerator is invalid."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (NoSuchProviderException e) { + String errorMsg = "Provider [" + PROVIDER + "] provided for KeyPairGenerator does not exist."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return keyPairGenerator.genKeyPair(); + } + + + /** + * This method creates the PKCS10 Certificate Sign Request which is to be sent to the SCEP Server using the + * generated PublicKey of the client. The certificate parameters used here are the ones from the AgentManager + * which are the values read from the configurations file. + * + * @return the PKCS10CertificationRequest object created using the client specific configs and the generated + * PublicKey + * @throws AgentCoreOperationException if an error occurs when creating a content signer to sign the CSR. + */ + private PKCS10CertificationRequest generateCertSignRequest() throws AgentCoreOperationException { + // Build the CN for the cert that's being requested. + X500NameBuilder nameBld = new X500NameBuilder(BCStyle.INSTANCE); + nameBld.addRDN(BCStyle.CN, AgentManager.getInstance().getAgentConfigs().getTenantDomain()); + nameBld.addRDN(BCStyle.O, AgentManager.getInstance().getAgentConfigs().getDeviceOwner()); + nameBld.addRDN(BCStyle.OU, AgentManager.getInstance().getAgentConfigs().getDeviceOwner()); + nameBld.addRDN(BCStyle.UNIQUE_IDENTIFIER, AgentManager.getInstance().getAgentConfigs().getDeviceId()); + nameBld.addRDN(BCStyle.SERIALNUMBER, AgentManager.getInstance().getAgentConfigs().getDeviceId()); + X500Name principal = nameBld.build(); + + JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALG).setProvider(PROVIDER); + ContentSigner contentSigner; + + try { + contentSigner = contentSignerBuilder.build(this.privateKey); + } catch (OperatorCreationException e) { + String errorMsg = "Could not create content signer with private key."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + // Generate the certificate signing request (csr = PKCS10) + PKCS10CertificationRequestBuilder reqBuilder = new JcaPKCS10CertificationRequestBuilder(principal, + this.publicKey); + return reqBuilder.build(contentSigner); + } + + + /** + * This method connects to the SCEP Server to fetch the signed SCEP Certificate. + * + * @param tempCert the temporary self-signed certificate of the client required for the initial CSR + * request against the SCEP Server. + * @param certSignRequest the PKCS10 Certificate-Sign-Request that is to be sent to the SCEP Server. + * @return the SCEP-Certificate for the client signed by the SCEP-Server. + * @throws AgentCoreOperationException if the SCEPUrl is invalid or if the flow of sending the CSR and getting + * the signed certificate fails or if the signed certificate cannot be + * retrieved from the reply from the server. + */ + private X509Certificate getSignedCertificateFromServer(X509Certificate tempCert, + PKCS10CertificationRequest certSignRequest) + throws AgentCoreOperationException { + + X509Certificate signedSCEPCertificate = null; + URL url; + EnrollmentResponse enrolResponse; + CertStore certStore; + + try { + // The URL where we are going to request our cert from + url = new URL(this.SCEPUrl); + + /* // This is called when we get the certificate for our CSR signed by CA + // Implement this handler to check the CA cert in prod. We can do cert pinning here + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated + methods, choose Tools | Templates. + } + };*/ + + // Implement verification of the CA cert. VERIFY the CA + CertificateVerifier ocv = new OptimisticCertificateVerifier(); + + // Instantiate our SCEP client + Client scepClient = new Client(url, ocv); + + // Submit our cert for signing. SCEP server should allow the client to specify + // the SCEP CA to issue the request against, if there are multiple CAs + enrolResponse = scepClient.enrol(tempCert, this.privateKey, certSignRequest); + + // Verify we got what we want, and just print out the cert. + certStore = enrolResponse.getCertStore(); + + for (Certificate x509Certificate : certStore.getCertificates(null)) { + if (log.isDebugEnabled()) { + log.debug(x509Certificate.toString()); + } + signedSCEPCertificate = (X509Certificate) x509Certificate; + } + + } catch (MalformedURLException ex) { + String errorMsg = "Could not create valid URL from given SCEP URI: " + SCEPUrl; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, ex); + } catch (TransactionException | ClientException e) { + String errorMsg = "Enrollment process to SCEP Server at: " + SCEPUrl + " failed."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertStoreException e) { + String errorMsg = "Could not retrieve [Signed-Certificate] from the response message from SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return signedSCEPCertificate; + } + + + /** + * Gets the Public Key of the SCEP-Server and initializes it for later use. This method contacts the SCEP Server + * and fetches its CA Cert and extracts the Public Key of the server from the received reply. + * + * @return the public key of the SCEP Server which is to be used to encrypt pyloads. + * @throws AgentCoreOperationException if the SCEPUrl is invalid or if the flow of sending the CSR and getting + * the signed certificate fails or if the signed certificate cannot be + * retrieved from the reply from the server. + */ + private PublicKey initPublicKeyOfServer() throws AgentCoreOperationException { + URL url; + CertStore certStore; + PublicKey serverCertPublicKey = null; + + try { + // The URL where we are going to request our cert from + url = new URL(this.SCEPUrl); + + /* // This is called when we get the certificate for our CSR signed by CA + // Implement this handler to check the CA cert in prod. We can do cert pinning here + CallbackHandler cb = new CallbackHandler() { + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated + methods, choose Tools | Templates. + } + };*/ + + // Implement verification of the CA cert. VERIFY the CA + CertificateVerifier ocv = new OptimisticCertificateVerifier(); + + // Instantiate our SCEP client + Client scepClient = new Client(url, ocv); + + // Get the CA capabilities. Should return SHA1withRSA for strongest hash and sig. Returns MD5. + if (log.isDebugEnabled()) { + Capabilities cap = scepClient.getCaCapabilities(); + log.debug(String.format( + "\nStrongestCipher: %s,\nStrongestMessageDigest: %s,\nStrongestSignatureAlgorithm: %s," + + "\nIsRenewalSupported: %s,\nIsRolloverSupported: %s", + cap.getStrongestCipher(), cap.getStrongestMessageDigest(), cap.getStrongestSignatureAlgorithm(), + cap.isRenewalSupported(), cap.isRolloverSupported())); + } + + certStore = scepClient.getCaCertificate(); + + for (Certificate cert : certStore.getCertificates(null)) { + if (cert instanceof X509Certificate) { + if (log.isDebugEnabled()) { + log.debug(((X509Certificate) cert).getIssuerDN().getName()); + } + + // I have chosen the CA cert based on its BasicConstraintExtension "is_ca" being set to "true" + // This is because the returned keystore may contain many certificates including RAs. + if (((Boolean) ((X509CertImpl) cert).getBasicConstraintsExtension().get(CERT_IS_CA_EXTENSION))) { + serverCertPublicKey = cert.getPublicKey(); + storeCertificateToStore(AgentConstants.SERVER_CA_CERT_ALIAS, cert); + } + } + } + + } catch (MalformedURLException ex) { + String errorMsg = "Could not create valid URL from given SCEP URI: " + SCEPUrl; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, ex); + } catch (ClientException e) { + String errorMsg = "Could not retrieve [Server-Certificate] from the SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (CertStoreException e) { + String errorMsg = "Could not retrieve [Server-Certificates] from the response message from SCEP-Server."; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } catch (IOException e) { + String errorMsg = "Error occurred whilst trying to get property ['is_ca'] from the retreived Certificates"; + log.error(errorMsg); + throw new AgentCoreOperationException(errorMsg, e); + } + + return serverCertPublicKey; + } + + /** + * Gets the Public-Key of the client. + * + * @return the public key of the client. + */ + public PublicKey getPublicKey() { + return publicKey; + } + + /** + * Gets the Private-Key of the client. + * + * @return the private key of the client. + */ + public PrivateKey getPrivateKey() { + return privateKey; + } + + /** + * Gets the SCEP-Certificate of the client. + * + * @return the SCEP Certificate of the client. + */ + public X509Certificate getSCEPCertificate() { + return SCEPCertificate; + } + + /** + * Gets the Public-Key of the Server. + * + * @return the pubic key of the server. + */ + public PublicKey getServerPublicKey() { + return serverPublicKey; + } + + /** + * Checks whether the device has already been enrolled with the SCEP Server. + * + * @return the enrollment status; 'TRUE' if already enrolled else 'FALSE'. + */ + public boolean isEnrolled() { + return isEnrolled; + } +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/exception/AgentCoreOperationException.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/exception/AgentCoreOperationException.java new file mode 100644 index 000000000..fac321ef9 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/exception/AgentCoreOperationException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.exception; + + +public class AgentCoreOperationException extends Exception{ + private static final long serialVersionUID = 2736466230451105710L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public AgentCoreOperationException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public AgentCoreOperationException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public AgentCoreOperationException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public AgentCoreOperationException() { + super(); + } + + public AgentCoreOperationException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/CommunicationUtils.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/CommunicationUtils.java new file mode 100644 index 000000000..bb445a3d9 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/CommunicationUtils.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; + +/** + * This is a utility class which contains methods common to the communication process of a client and the server. The + * methods include encryption/decryption of payloads and signing/verification of payloads received and to be sent. + */ +public class CommunicationUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + // The Signature Algorithm used. + private static final String SIGNATURE_ALG = "SHA1withRSA"; + // The Encryption Algorithm and the Padding used. + private static final String CIPHER_PADDING = "RSA/ECB/PKCS1Padding"; + + + /** + * Encrypts the message with the key that's passed in. + * + * @param message the message to be encrypted. + * @param encryptionKey the key to use for the encryption of the message. + * @return the encrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String encryptMessage(String message, Key encryptionKey) throws TransportHandlerException { + Cipher encrypter; + byte[] cipherData; + + try { + encrypter = Cipher.getInstance(CIPHER_PADDING); + encrypter.init(Cipher.ENCRYPT_MODE, encryptionKey); + cipherData = encrypter.doFinal(message.getBytes(StandardCharsets.UTF_8)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + encryptionKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return Base64.encodeBase64String(cipherData); + } + +///TODO:: Exception needs to change according to the common package + /** + * Signed a given message using the PrivateKey that's passes in. + * + * @param message the message to be signed. Ideally some encrypted payload. + * @param signatureKey the PrivateKey with which the message is to be signed. + * @return the Base64Encoded String of the signed payload. + * @throws TransportHandlerException if some error occurs with the signing process which may be related to the + * signature algorithm used or the key used for signing. + */ + public static String signMessage(String message, PrivateKey signatureKey) throws TransportHandlerException { + + Signature signature; + String signedEncodedString; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initSign(signatureKey); + signature.update(Base64.decodeBase64(message)); + + byte[] signatureBytes = signature.sign(); + signedEncodedString = Base64.encodeBase64String(signatureBytes); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + signatureKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return signedEncodedString; + } + + + /** + * Verifies some signed-data against the a Public-Key to ensure that it was produced by the holder of the + * corresponding Private Key. + * + * @param data the actual payoad which was signed by some Private Key. + * @param signedData the signed data produced by signing the payload using a Private Key. + * @param verificationKey the corresponding Public Key which is an exact pair of the Private-Key with we expect + * the data to be signed by. + * @return true if the signed data verifies to be signed by the corresponding Private Key. + * @throws TransportHandlerException if some error occurs with the verification process which may be related to + * the signature algorithm used or the key used for signing. + */ + public static boolean verifySignature(String data, String signedData, PublicKey verificationKey) + throws TransportHandlerException { + + Signature signature; + boolean verified; + + try { + signature = Signature.getInstance(SIGNATURE_ALG); + signature.initVerify(verificationKey); + signature.update(Base64.decodeBase64(data)); + + verified = signature.verify(Base64.decodeBase64(signedData)); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = + "Algorithm not found exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature exception occurred for Signature instance of [" + SIGNATURE_ALG + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for signatureKey \n[\n" + verificationKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return verified; + } + + + /** + * Encrypts the message with the key that's passed in. + * + * @param encryptedMessage the encrypted message that is supposed to be decrypted. + * @param decryptKey the key to use in the decryption process. + * @return the decrypted message in String format. + * @throws TransportHandlerException if an error occurs with the encryption flow which can be due to Padding + * issues, encryption key being invalid or the algorithm used is unrecognizable. + */ + public static String decryptMessage(String encryptedMessage, Key decryptKey) throws TransportHandlerException { + + Cipher decrypter; + String decryptedMessage; + + try { + + decrypter = Cipher.getInstance(CIPHER_PADDING); + decrypter.init(Cipher.DECRYPT_MODE, decryptKey); + decryptedMessage = new String(decrypter.doFinal(Base64.decodeBase64(encryptedMessage)), + StandardCharsets.UTF_8); + + } catch (NoSuchAlgorithmException e) { + String errorMsg = "Algorithm not found exception occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (NoSuchPaddingException e) { + String errorMsg = "No Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "InvalidKey exception occurred for encryptionKey \n[\n" + decryptKey + "\n]\n"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (BadPaddingException e) { + String errorMsg = "Bad Padding error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IllegalBlockSizeException e) { + String errorMsg = "Illegal blockSize error occurred for Cipher instance of [" + CIPHER_PADDING + "]"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } + + return decryptedMessage; + } +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandler.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandler.java new file mode 100644 index 000000000..fa45cdbb0 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +/** + * This interface consists of the core functionality related to the transport between any device and the server. The + * interface is an abstraction, regardless of the underlying protocol used for the transport. Implementation of this + * interface by any class that caters a specific protocol (ex: HTTP, XMPP, MQTT, CoAP) would ideally have methods + * specific to the protocol used for communication and thees methods that implement the logic related to the devices + * using the protocol. + * + * @param a message type specific to the protocol implemented + */ +public interface TransportHandler { + int DEFAULT_TIMEOUT_INTERVAL = 5000; // millis ~ 10 sec + + void connect(); + + boolean isConnected(); + + //TODO:: Any errors needs to be thrown ahead + void processIncomingMessage(T message, String... messageParams); + + void processIncomingMessage(); + + void publishDeviceData(String... publishData); + + void publishDeviceData(); + + void disconnect(); +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandlerException.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandlerException.java new file mode 100644 index 000000000..9ae69a998 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportHandlerException.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +public class TransportHandlerException extends Exception { + private static final long serialVersionUID = 2736466230451105440L; + + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public TransportHandlerException(String msg, Exception nestedEx) { + super(msg, nestedEx); + setErrorMessage(msg); + } + + public TransportHandlerException(String message, Throwable cause) { + super(message, cause); + setErrorMessage(message); + } + + public TransportHandlerException(String msg) { + super(msg); + setErrorMessage(msg); + } + + public TransportHandlerException() { + super(); + } + + public TransportHandlerException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportUtils.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportUtils.java new file mode 100644 index 000000000..11ebc04bf --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/TransportUtils.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.DatagramSocket; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.SocketException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class TransportUtils { + private static final Log log = LogFactory.getLog(TransportUtils.class); + + public static final int MIN_PORT_NUMBER = 9000; + public static final int MAX_PORT_NUMBER = 11000; + + /** + * Given a server endpoint as a String, this method splits it into Protocol, Host and Port + * + * @param ipString a network endpoint in the format - '://:' + * @return a map with keys "Protocol", "Host" & "Port" for the related values from the ipString + * @throws TransportHandlerException + */ + public static Map getHostAndPort(String ipString) + throws TransportHandlerException { + Map ipPortMap = new HashMap(); + String[] ipPortArray = ipString.split(":"); + + if (ipPortArray.length != 3) { + String errorMsg = + "The IP String - '" + ipString + + "' is invalid. It needs to be in format '://:'."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg); + } + + ipPortMap.put(AgentConstants.PROTOCOL_PROPERTY, ipPortArray[0]); + ipPortMap.put(AgentConstants.HOST_PROPERTY, ipPortArray[1].replace("/", "")); + ipPortMap.put(AgentConstants.PORT_PROPERTY, ipPortArray[2]); + return ipPortMap; + } + + /** + * This method validates whether a specific IP Address is of IPv4 type + * + * @param ipAddress the IP Address which needs to be validated + * @return true if it is of IPv4 type and false otherwise + */ + public static boolean validateIPv4(String ipAddress) { + try { + if (ipAddress == null || ipAddress.isEmpty()) { + return false; + } + + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) { + return false; + } + + for (String s : parts) { + int i = Integer.parseInt(s); + if ((i < 0) || (i > 255)) { + return false; + } + } + return !ipAddress.endsWith("."); + + } catch (NumberFormatException nfe) { + log.warn("The IP Address: " + ipAddress + " could not be validated against IPv4-style"); + return false; + } + } + + + public static Map getInterfaceIPMap() throws TransportHandlerException { + + Map interfaceToIPMap = new HashMap(); + Enumeration networkInterfaces; + String networkInterfaceName = ""; + String ipAddress; + + try { + networkInterfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the list of network-interfaces"; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + try { + for (; networkInterfaces.hasMoreElements(); ) { + networkInterfaceName = networkInterfaces.nextElement().getName(); + + if (log.isDebugEnabled()) { + log.debug("Network Interface: " + networkInterfaceName); + log.debug("------------------------------------------"); + } + + Enumeration interfaceIPAddresses = NetworkInterface.getByName( + networkInterfaceName).getInetAddresses(); + + for (; interfaceIPAddresses.hasMoreElements(); ) { + ipAddress = interfaceIPAddresses.nextElement().getHostAddress(); + + if (log.isDebugEnabled()) { + log.debug("IP Address: " + ipAddress); + } + + if (TransportUtils.validateIPv4(ipAddress)) { + interfaceToIPMap.put(networkInterfaceName, ipAddress); + } + } + + if (log.isDebugEnabled()) { + log.debug("------------------------------------------"); + } + } + } catch (SocketException exception) { + String errorMsg = + "Error encountered whilst trying to get the IP Addresses of the network " + + "interface: " + networkInterfaceName; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + return interfaceToIPMap; + } + + + /** + * Attempts to find a free port between the MIN_PORT_NUMBER(9000) and MAX_PORT_NUMBER(11000). + * Tries 'RANDOMLY picked' port numbers between this range up-until "randomAttempts" number of + * times. If still fails, then tries each port in descending order from the MAX_PORT_NUMBER + * whilst skipping already attempted ones via random selection. + * + * @param randomAttempts no of times to TEST port numbers picked randomly over the given range + * @return an available/free port + */ + public static synchronized int getAvailablePort(int randomAttempts) { + ArrayList failedPorts = new ArrayList(randomAttempts); + + Random randomNum = new Random(); + int randomPort = MAX_PORT_NUMBER; + + while (randomAttempts > 0) { + randomPort = randomNum.nextInt(MAX_PORT_NUMBER - MIN_PORT_NUMBER) + MIN_PORT_NUMBER; + + if (checkIfPortAvailable(randomPort)) { + return randomPort; + } + failedPorts.add(randomPort); + randomAttempts--; + } + + randomPort = MAX_PORT_NUMBER; + + while (true) { + if (!failedPorts.contains(randomPort) && checkIfPortAvailable(randomPort)) { + return randomPort; + } + randomPort--; + } + } + + + private static boolean checkIfPortAvailable(int port) { + ServerSocket tcpSocket = null; + DatagramSocket udpSocket = null; + + try { + tcpSocket = new ServerSocket(port); + tcpSocket.setReuseAddress(true); + + udpSocket = new DatagramSocket(port); + udpSocket.setReuseAddress(true); + return true; + } catch (IOException ex) { + // denotes the port is in use + } finally { + if (tcpSocket != null) { + try { + tcpSocket.close(); + } catch (IOException e) { + /* not to be thrown */ + } + } + + if (udpSocket != null) { + udpSocket.close(); + } + } + + return false; + } + + + /** + * This is a utility method that creates and returns a HTTP connection object. + * + * @param urlString the URL pattern to which the connection needs to be created + * @return an HTTPConnection object which cn be used to send HTTP requests + * @throws TransportHandlerException if errors occur when creating the HTTP connection with + * the given URL string + */ + public static HttpURLConnection getHttpConnection(String urlString) throws + TransportHandlerException { + URL connectionUrl; + HttpURLConnection httpConnection; + + try { + connectionUrl = new URL(urlString); + httpConnection = (HttpURLConnection) connectionUrl.openConnection(); + } catch (MalformedURLException e) { + String errorMsg = "Error occured whilst trying to form HTTP-URL from string: " + urlString; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, e); + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying to open a connection to: " + urlString; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + return httpConnection; + } + + /** + * This is a utility method that reads and returns the response from a HTTP connection + * + * @param httpConnection the connection from which a response is expected + * @return the response (as a string) from the given HTTP connection + * @throws TransportHandlerException if any errors occur whilst reading the response from + * the connection stream + */ + public static String readResponseFromHttpRequest(HttpURLConnection httpConnection) + throws TransportHandlerException { + BufferedReader bufferedReader; + try { + bufferedReader = new BufferedReader(new InputStreamReader( + httpConnection.getInputStream(), StandardCharsets.UTF_8)); + } catch (IOException exception) { + String errorMsg = "There is an issue with connecting the reader to the input stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + + String responseLine; + StringBuilder completeResponse = new StringBuilder(); + + try { + while ((responseLine = bufferedReader.readLine()) != null) { + completeResponse.append(responseLine); + } + } catch (IOException exception) { + String errorMsg = "Error occured whilst trying read from the connection stream at: " + + httpConnection.getURL(); + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, exception); + } + try { + bufferedReader.close(); + } catch (IOException exception) { + log.error("Could not succesfully close the bufferedReader to the connection at: " + httpConnection.getURL()); + } + return completeResponse.toString(); + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/http/HTTPTransportHandler.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/http/HTTPTransportHandler.java new file mode 100644 index 000000000..c771eb8ed --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/http/HTTPTransportHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.http; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.jetty.server.Server; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportUtils; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + */ +public abstract class HTTPTransportHandler implements TransportHandler { + private static final Log log = LogFactory.getLog(HTTPTransportHandler.class); + + protected Server server; + protected int port; + protected int timeoutInterval; + + protected HTTPTransportHandler() { + this.port = TransportUtils.getAvailablePort(10); + this.server = new Server(port); + timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + } + + protected HTTPTransportHandler(int port) { + this.port = port; + this.server = new Server(this.port); + timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + } + + protected HTTPTransportHandler(int port, int timeoutInterval) { + this.port = port; + this.server = new Server(this.port); + this.timeoutInterval = timeoutInterval; + } + + public void setTimeoutInterval(int timeoutInterval) { + this.timeoutInterval = timeoutInterval; + } + + /** + * Checks whether the HTTP server is up and listening for incoming requests. + * + * @return true if the server is up & listening for requests, else false. + */ + public boolean isConnected() { + return server.isStarted(); + } + + + protected void incrementPort() { + this.port = this.port + 1; + server = new Server(port); + } + + /** + * Shuts-down the HTTP Server. + */ + public void closeConnection() throws Exception { + if (server != null && isConnected()) { + server.stop(); + } + } + + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/mqtt/MQTTTransportHandler.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/mqtt/MQTTTransportHandler.java new file mode 100644 index 000000000..f7c6864c1 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/mqtt/MQTTTransportHandler.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.mqtt; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.MqttSecurityException; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

+ * This class contains the Device-Management specific implementation for all the MQTT functionality. This includes + * connecting to a MQTT Broker & subscribing to the appropriate MQTT-topic, action plan upon losing connection or + * successfully delivering a message to the broker and processing incoming messages. Makes use of the 'Paho-MQTT' + * library provided by Eclipse Org. + */ +public abstract class MQTTTransportHandler + implements MqttCallback, TransportHandler { + private static final Log log = LogFactory.getLog(MQTTTransportHandler.class); + public static final int DEFAULT_MQTT_QUALITY_OF_SERVICE = 0; + + private MqttClient client; + private String clientId; + private MqttConnectOptions options; + private String clientWillTopic; + + protected String mqttBrokerEndPoint; + protected int timeoutInterval; + protected String subscribeTopic; + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device + * and the MQTT Broker URL and the topic to subscribe. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, + String subscribeTopic) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + this.clientWillTopic = deviceType + File.separator + "disconnection"; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + this.initSubscriber(); + } + + /** + * Constructor for the MQTTTransportHandler which takes in the owner, type of the device + * and the MQTT Broker URL and the topic to subscribe. Additionally this constructor takes in + * the reconnection-time interval between successive attempts to connect to the broker. + * + * @param deviceOwner the owner of the device. + * @param deviceType the CDMF Device-Type of the device. + * @param mqttBrokerEndPoint the IP/URL of the MQTT broker endpoint. + * @param subscribeTopic the MQTT topic to which the client is to be subscribed + * @param intervalInMillis the time interval in MILLI-SECONDS between successive + * attempts to connect to the broker. + */ + protected MQTTTransportHandler(String deviceOwner, String deviceType, + String mqttBrokerEndPoint, String subscribeTopic, + int intervalInMillis) { + this.clientId = deviceOwner + ":" + deviceType; + this.subscribeTopic = subscribeTopic; + //TODO:: Use constant strings + this.clientWillTopic = deviceType + File.separator + "disconnection"; + this.mqttBrokerEndPoint = mqttBrokerEndPoint; + this.timeoutInterval = intervalInMillis; + this.initSubscriber(); + } + + public void setTimeoutInterval(int timeoutInterval) { + this.timeoutInterval = timeoutInterval; + } + + /** + * Initializes the MQTT-Client. Creates a client using the given MQTT-broker endpoint and the + * clientId (which is constructed by a concatenation of [deviceOwner]:[deviceType]). Also sets + * the client's options parameter with the clientWillTopic (in-case of connection failure) and + * other info. Also sets the call-back this current class. + */ + private void initSubscriber() { + try { + client = new MqttClient(this.mqttBrokerEndPoint, clientId, null); + //TODO:: Need to check for debug + log.info("MQTT subscriber was created with ClientID : " + clientId); + } catch (MqttException ex) { + //TODO:: Remove unnecessary formatting and print exception + String errorMsg = "MQTT Client Error\n" + "\tReason: " + ex.getReasonCode() + + "\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " + + ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() + + "\n\tException: " + ex; + log.error(errorMsg); + //TODO:: Throw the error out + } + + options = new MqttConnectOptions(); + options.setCleanSession(false); + //TODO:: Use constant strings + options.setWill(clientWillTopic, "Connection-Lost".getBytes(StandardCharsets.UTF_8), 2, + true); + client.setCallback(this); + } + + /** + * Checks whether the connection to the MQTT-Broker persists. + * + * @return true if the client is connected to the MQTT-Broker, else false. + */ + @Override + public boolean isConnected() { + return client.isConnected(); + } + + + protected void connectToQueue(String username, String password) throws TransportHandlerException { + options.setUserName(username); + options.setPassword(password.toCharArray()); + connectToQueue(); + } + + /** + * Connects to the MQTT-Broker and if successfully established connection. + * + * @throws TransportHandlerException in the event of 'Connecting to' the MQTT broker fails. + */ + protected void connectToQueue() throws TransportHandlerException { + try { + client.connect(options); + + if (log.isDebugEnabled()) { + log.debug("Subscriber connected to queue at: " + this.mqttBrokerEndPoint); + } + } catch (MqttSecurityException ex) { + String errorMsg = "MQTT Security Exception when connecting to queue\n" + "\tReason: " + + " " + + ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() + + "\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " + + ex.getCause() + "\n\tException: " + ex; + //TODO:: Compulsory log of errors and remove formatted error + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg, ex); + + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = "MQTT Exception when connecting to queue\n" + "\tReason: " + + ex.getReasonCode() + "\n\tMessage: " + ex.getMessage() + + "\n\tLocalMsg: " + ex.getLocalizedMessage() + "\n\tCause: " + + ex.getCause() + "\n\tException: " + ex; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + throw new TransportHandlerException(errorMsg, ex); + } + } + + /** + * Subscribes to the MQTT-Topic specific to this MQTT Client. (The MQTT-Topic specific to the + * device is taken in as a constructor parameter of this class) . + * + * @throws TransportHandlerException in the event of 'Subscribing to' the MQTT broker + * fails. + */ + protected void subscribeToQueue() throws TransportHandlerException { + try { + //TODO:: QoS Level take it from a variable + client.subscribe(subscribeTopic, 0); + log.info("Subscriber '" + clientId + "' subscribed to topic: " + subscribeTopic); + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = "MQTT Exception when trying to subscribe to topic: " + + subscribeTopic + "\n\tReason: " + ex.getReasonCode() + + "\n\tMessage: " + ex.getMessage() + "\n\tLocalMsg: " + + ex.getLocalizedMessage() + "\n\tCause: " + ex.getCause() + + "\n\tException: " + ex; + if (log.isDebugEnabled()) { + log.debug(errorMsg); + } + + throw new TransportHandlerException(errorMsg, ex); + } + } + + + /** + * This method is used to publish reply-messages for the control signals received. + * Invocation of this method calls its overloaded-method with a QoS equal to that of the + * default value. + * + * @param topic the topic to which the reply message is to be published. + * @param payLoad the reply-message (payload) of the MQTT publish action. + */ + protected void publishToQueue(String topic, String payLoad) + throws TransportHandlerException { + publishToQueue(topic, payLoad, DEFAULT_MQTT_QUALITY_OF_SERVICE, false); + } + + /** + * This is an overloaded method that publishes MQTT reply-messages for control signals + * received form the IoT-Server. + * + * @param topic the topic to which the reply message is to be published + * @param payLoad the reply-message (payload) of the MQTT publish action. + * @param qos the Quality-of-Service of the current publish action. + * Could be 0(At-most once), 1(At-least once) or 2(Exactly once) + */ + protected void publishToQueue(String topic, String payLoad, int qos, boolean retained) + throws TransportHandlerException { + try { + client.publish(topic, payLoad.getBytes(StandardCharsets.UTF_8), qos, retained); + if (log.isDebugEnabled()) { + log.debug("Message: " + payLoad + " to MQTT topic [" + topic + "] published successfully"); + } + } catch (MqttException ex) { + String errorMsg = + "MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " + + ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() + + "\n\tCause: " + ex.getCause() + "\n\tException: " + ex; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + + protected void publishToQueue(String topic, MqttMessage message) + throws TransportHandlerException { + try { + client.publish(topic, message); + if (log.isDebugEnabled()) { + log.debug("Message: " + message.toString() + " to MQTT topic [" + topic + "] published successfully"); + } + } catch (MqttException ex) { + //TODO:: Compulsory log of errors and remove formatted error + String errorMsg = + "MQTT Client Error" + "\n\tReason: " + ex.getReasonCode() + "\n\tMessage: " + + ex.getMessage() + "\n\tLocalMsg: " + ex.getLocalizedMessage() + + "\n\tCause: " + ex.getCause() + "\n\tException: " + ex; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, ex); + } + } + + + /** + * Callback method which is triggered once the MQTT client losers its connection to the broker. + * Spawns a new thread that executes necessary actions to try and reconnect to the endpoint. + * + * @param throwable a Throwable Object containing the details as to why the failure occurred. + */ + @Override + public void connectionLost(Throwable throwable) { + log.warn("Lost Connection for client: " + this.clientId + + " to " + this.mqttBrokerEndPoint + ".\nThis was due to - " + throwable.getMessage()); + + Thread reconnectThread = new Thread() { + public void run() { + connect(); + } + }; + reconnectThread.start(); + } + + /** + * Callback method which is triggered upon receiving a MQTT Message from the broker. Spawns a + * new thread that executes any actions to be taken with the received message. + * + * @param topic the MQTT-Topic to which the received message was published to and the + * client was subscribed to. + * @param mqttMessage the actual MQTT-Message that was received from the broker. + */ + @Override + public void messageArrived(final String topic, final MqttMessage mqttMessage) { + if (log.isDebugEnabled()) { + log.info("Got an MQTT message '" + mqttMessage.toString() + "' for topic '" + topic + "'."); + } + + Thread messageProcessorThread = new Thread() { + public void run() { + processIncomingMessage(mqttMessage, topic); + } + }; + messageProcessorThread.setDaemon(true); + messageProcessorThread.start(); + } + + /** + * Callback method which gets triggered upon successful completion of a message delivery to + * the broker. + * + * @param iMqttDeliveryToken the MQTT-DeliveryToken which includes the details about the + * specific message delivery. + */ + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + String message = ""; + try { + message = iMqttDeliveryToken.getMessage().toString(); + } catch (MqttException e) { + //TODO:: Throw errors + log.error( + "Error occurred whilst trying to read the message from the MQTT delivery token."); + } + String topic = iMqttDeliveryToken.getTopics()[0]; + String client = iMqttDeliveryToken.getClient().getClientId(); + + if (log.isDebugEnabled()) { + log.debug("Message - '" + message + "' of client [" + client + "] for the topic (" + + topic + ") was delivered successfully."); + } + } + + /** + * Closes the connection to the MQTT Broker. + */ + public void closeConnection() throws MqttException { + if (client != null && isConnected()) { + client.disconnect(); + } + } +} + diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/xmpp/XMPPTransportHandler.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/xmpp/XMPPTransportHandler.java new file mode 100644 index 000000000..4ac76f45b --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/transport/xmpp/XMPPTransportHandler.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.xmpp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.ToContainsFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandler; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.transport.TransportHandlerException; + +/** + * This is an abstract class that implements the "TransportHandler" interface. The interface is an abstraction for + * the core functionality with regards to device-server communication regardless of the Transport protocol. This + * specific class contains the HTTP-Transport specific implementations. The class implements utility methods for the + * case of a HTTP communication. However, this "abstract class", even-though it implements the "TransportHandler" + * interface, does not contain the logic relevant to the interface methods. The specific functionality of the + * interface methods are intended to be implemented by the concrete class that extends this abstract class and + * utilizes the HTTP specific functionality (ideally a device API writer who would like to communicate to the device + * via HTTP Protocol). + *

+ * This class contains the IoT-Server specific implementation for all the XMPP functionality. This includes + * connecting to a XMPP Server & Login-In using the device's/server's XMPP-Account, Setting listeners and filters on + * incoming XMPP messages and Sending XMPP replies for messages received. Makes use of the 'Smack-XMPP' library + * provided by jivesoftware/igniterealtime. + */ +public abstract class XMPPTransportHandler implements TransportHandler { + private static final Log log = LogFactory.getLog(XMPPTransportHandler.class); + + protected String server; + protected int timeoutInterval; // millis + + //TODO:: Shouldnt be hard-coded. Need to be read from configs + private static final int DEFAULT_XMPP_PORT = 5222; + private XMPPConnection connection; + private int port; + private ConnectionConfiguration config; + private PacketFilter filter; + private PacketListener listener; + + + /** + * Constructor for XMPPTransportHandler passing only the server-IP. + * + * @param server the IP of the XMPP server. + */ + protected XMPPTransportHandler(String server) { + this.server = server; + this.port = DEFAULT_XMPP_PORT; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP and the XMPP-port. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + */ + protected XMPPTransportHandler(String server, int port) { + this.server = server; + this.port = port; + this.timeoutInterval = DEFAULT_TIMEOUT_INTERVAL; + initXMPPClient(); + } + + /** + * Constructor for XMPPTransportHandler passing server-IP, the XMPP-port and the + * timeoutInterval used by listeners to the server and for reconnection schedules. + * + * @param server the IP of the XMPP server. + * @param port the XMPP server's port to connect to. (default - 5222) + * @param timeoutInterval the timeout interval to use for the connection and reconnection + */ + protected XMPPTransportHandler(String server, int port, int timeoutInterval) { + this.server = server; + this.port = port; + this.timeoutInterval = timeoutInterval; + initXMPPClient(); + } + + /** + * Sets the client's time-out-limit whilst waiting for XMPP-replies from server. + * + * @param millis the time in millis to be set as the time-out-limit whilst waiting for a + * XMPP-reply. + */ + public void setTimeoutInterval(int millis) { + this.timeoutInterval = millis; + } + + /** + * Checks whether the connection to the XMPP-Server persists. + * + * @return true if the client is connected to the XMPP-Server, else false. + */ + @Override + public boolean isConnected() { + return connection.isConnected(); + } + + /** + * Initializes the XMPP Client. Sets the time-out-limit whilst waiting for XMPP-replies from + * server. Sets the XMPP configurations to connect to the server and creates the + * XMPPConnection object used for connecting and Logging-In. + */ + private void initXMPPClient() { + log.info(String.format("Initializing connection to XMPP Server at %1$s via port " + + "%2$d.", server, port)); + SmackConfiguration.setPacketReplyTimeout(timeoutInterval); + config = new ConnectionConfiguration(server, port); +// TODO:: Need to enable SASL-Authentication appropriately + config.setSASLAuthenticationEnabled(false); + config.setSecurityMode(ConnectionConfiguration.SecurityMode.disabled); + connection = new XMPPConnection(config); + } + +//TODO:: Re-check all exception handling + + /** + * Connects to the XMPP-Server and if attempt unsuccessful, then throws exception. + * + * @throws TransportHandlerException in the event of 'Connecting to' the XMPP server fails. + */ + protected void connectToServer() throws TransportHandlerException { + try { + connection.connect(); + log.info(String.format("Connection to XMPP Server at %1$s established successfully......", server)); + + } catch (XMPPException xmppExcepion) { + String errorMsg = "Connection attempt to the XMPP Server at " + server + " via port " + port + " failed."; + log.info(errorMsg); + throw new TransportHandlerException(errorMsg, xmppExcepion); + } + } + + /** + * If successfully established connection, then tries to Log in using the device's XMPP + * Account credentials. + * + * @param username the username of the device's XMPP-Account. + * @param password the password of the device's XMPP-Account. + * @param resource the resource the resource, specific to the XMPP-Account to which the login + * is made to + * @throws TransportHandlerException in the event of 'Logging into' the XMPP server fails. + */ + protected void loginToServer(String username, String password, String resource) + throws TransportHandlerException { + if (isConnected()) { + try { + if (resource == null) { + connection.login(username, password); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s......", server, username)); + } else { + connection.login(username, password, resource); + log.info(String.format("Logged into XMPP Server at %1$s as user %2$s on resource %3$s......", + server, username, resource)); + } + } catch (XMPPException xmppException) { + String errorMsg = + "Login attempt to the XMPP Server at " + server + " with username - " + username + " failed."; + log.error(errorMsg); + throw new TransportHandlerException(errorMsg, xmppException); + } + } else { + String errorMsg = "Not connected to XMPP-Server to attempt Login. Please 'connectToServer' before Login"; + throw new TransportHandlerException(errorMsg); + } + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the Sender's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param senderJID the JID (XMPP-Account ID of the sender) to which the filter is to be set. + */ + protected void setFilterOnSender(String senderJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new FromContainsFilter( + senderJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the Receiver's JID (XMPP-Account ID). + * Also creates a listener for the incoming messages and connects the listener to the + * XMPPConnection alongside the set filter. + * + * @param receiverJID the JID (XMPP-Account ID of the receiver) to which the filter is to be + * set. + */ + protected void setFilterOnReceiver(String receiverJID) { + filter = new AndFilter(new PacketTypeFilter(Message.class), new ToContainsFilter( + receiverJID)); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sets a filter for all the incoming XMPP-Messages on the From-JID & To-JID (XMPP-Account IDs) + * passed in. Also creates a listener for the incoming messages and connects the listener to + * the XMPPConnection alongside the set filter. + * + * @param senderJID the From-JID (XMPP-Account ID) to which the filter is to be set. + * @param receiverJID the To-JID (XMPP-Account ID) to which the filter is to be set. + * @param andCondition if true: then filter is set with 'AND' operator (senderJID && + * receiverJID), + * if false: then the filter is set with 'OR' operator (senderJID | + * receiverJID) + */ + protected void setMessageFilterAndListener(String senderJID, String receiverJID, boolean + andCondition) { + PacketFilter jidFilter; + + if (andCondition) { + jidFilter = new AndFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } else { + jidFilter = new OrFilter(new FromContainsFilter(senderJID), new ToContainsFilter( + receiverJID)); + } + + filter = new AndFilter(new PacketTypeFilter(Message.class), jidFilter); + listener = new PacketListener() { + @Override + public void processPacket(Packet packet) { + if (packet instanceof Message) { + final Message xmppMessage = (Message) packet; + Thread msgProcessThread = new Thread() { + public void run() { + processIncomingMessage(xmppMessage); + } + }; + msgProcessThread.setDaemon(true); + msgProcessThread.start(); + } + } + }; + + connection.addPacketListener(listener, filter); + } + + + /** + * Sends an XMPP message. Calls the overloaded method with Subject set to "Reply-From-Device" + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, String message) { + sendXMPPMessage(JID, message, "XMPP-Message"); + } + + + /** + * Overloaded method to send an XMPP message. Includes the subject to be mentioned in the + * message that is sent. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param message the XMPP-Message that is to be sent. + * @param subject the subject that the XMPP-Message would carry. + */ + protected void sendXMPPMessage(String JID, String message, String subject) { + Message xmppMessage = new Message(); + xmppMessage.setTo(JID); + xmppMessage.setSubject(subject); + xmppMessage.setBody(message); + xmppMessage.setType(Message.Type.chat); + sendXMPPMessage(JID, xmppMessage); + } + + + /** + * Sends an XMPP message. + * + * @param JID the JID (XMPP Account ID) to which the message is to be sent to. + * @param xmppMessage the XMPP-Message that is to be sent. + */ + protected void sendXMPPMessage(String JID, Message xmppMessage) { + connection.sendPacket(xmppMessage); + if (log.isDebugEnabled()) { + log.debug("Message: '" + xmppMessage.getBody() + "' sent to XMPP JID [" + JID + + "] sent successfully."); + } + } + + + /** + * Disables default debugger provided by the XMPPConnection. + */ + protected void disableDebugger() { + connection.DEBUG_ENABLED = false; + } + + + /** + * Closes the connection to the XMPP Server. + */ + public void closeConnection() { + if (connection != null && isConnected()) { + connection.disconnect(); + } + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/VirtualHardwareManager.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/VirtualHardwareManager.java new file mode 100644 index 000000000..3b777cf75 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/VirtualHardwareManager.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentUtilOperations; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.ui.AgentUI; + +import javax.sound.midi.InvalidMidiDataException; +import javax.sound.midi.MidiSystem; +import javax.sound.midi.MidiUnavailableException; +import javax.sound.midi.Sequence; +import javax.sound.midi.Sequencer; +import javax.sound.sampled.Clip; +import javax.swing.*; +import java.io.IOException; +import java.io.InputStream; + +/** + * This class use to emulate virtual hardware functionality + */ +public class VirtualHardwareManager { + + private static final Log log = LogFactory.getLog(VirtualHardwareManager.class); + + private static VirtualHardwareManager virtualHardwareManager; + + private AgentUI agentUI; + private Sequencer sequencer = null; + + private int temperature = 30, humidity = 30; + private int temperatureMin = 20, temperatureMax = 50, humidityMin = 20, humidityMax = 50; + private int temperatureSVF = 50, humiditySVF = 50; + private boolean isTemperatureRandomized, isHumidityRandomized; + private boolean isTemperatureSmoothed, isHumiditySmoothed; + + private VirtualHardwareManager(){ + } + + public static VirtualHardwareManager getInstance(){ + if (virtualHardwareManager == null){ + virtualHardwareManager = new VirtualHardwareManager(); + } + return virtualHardwareManager; + } + + public void init(){ + try { + // Set System L&F for Device UI + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException e) { + log.error( + "'UnsupportedLookAndFeelException' error occurred whilst initializing the" + + " Agent UI."); + } catch (ClassNotFoundException e) { + log.error( + "'ClassNotFoundException' error occurred whilst initializing the Agent UI."); + } catch (InstantiationException e) { + log.error( + "'InstantiationException' error occurred whilst initializing the Agent UI."); + } catch (IllegalAccessException e) { + log.error( + "'IllegalAccessException' error occurred whilst initializing the Agent UI."); + } + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + agentUI = new AgentUI(); + agentUI.setVisible(true); + } + }); + setAudioSequencer(); + } + + /** + * Get temperature from emulated device + * @return Temperature + */ + public int getTemperature() { + if (isTemperatureRandomized) { + temperature = getRandom(temperatureMax, temperatureMin, temperature, + isTemperatureSmoothed, temperatureSVF); + agentUI.updateTemperature(temperature); + } + return temperature; + } + + public void setTemperature(int temperature) { + this.temperature = temperature; + } + + /** + * Get humidity from emulated device + * @return Humidity + */ + public int getHumidity() { + if (isHumidityRandomized) { + humidity = getRandom(humidityMax, humidityMin, humidity, isHumiditySmoothed, + humiditySVF); + agentUI.updateHumidity(humidity); + } + return humidity; + } + + public void setHumidity(int humidity) { + this.humidity = humidity; + } + + public void setTemperatureMin(int temperatureMin) { + this.temperatureMin = temperatureMin; + } + + public void setTemperatureMax(int temperatureMax) { + this.temperatureMax = temperatureMax; + } + + public void setHumidityMin(int humidityMin) { + this.humidityMin = humidityMin; + } + + public void setHumidityMax(int humidityMax) { + this.humidityMax = humidityMax; + } + + public void setIsHumidityRandomized(boolean isHumidityRandomized) { + this.isHumidityRandomized = isHumidityRandomized; + } + + public void setIsTemperatureRandomized(boolean isTemperatureRandomized) { + this.isTemperatureRandomized = isTemperatureRandomized; + } + + public void setTemperatureSVF(int temperatureSVF) { + this.temperatureSVF = temperatureSVF; + } + + public void setHumiditySVF(int humiditySVF) { + this.humiditySVF = humiditySVF; + } + + public void setIsTemperatureSmoothed(boolean isTemperatureSmoothed) { + this.isTemperatureSmoothed = isTemperatureSmoothed; + } + + public void setIsHumiditySmoothed(boolean isHumiditySmoothed) { + this.isHumiditySmoothed = isHumiditySmoothed; + } + + public void changeAlarmStatus(boolean isOn) { + agentUI.setAlarmStatus(isOn); + + if (isOn) { + sequencer.start(); + } else { + sequencer.stop(); + } + } + + private int getRandom(int max, int min, int current, boolean isSmoothed, int svf) { + + if (isSmoothed) { + int offset = (max - min) * svf / 100; + double mx = current + offset; + max = (mx > max) ? max : (int) Math.round(mx); + + double mn = current - offset; + min = (mn < min) ? min : (int) Math.round(mn); + } + + double rnd = Math.random() * (max - min) + min; + return (int) Math.round(rnd); + + } + + private void setAudioSequencer() { + InputStream audioSrc = AgentUtilOperations.class.getResourceAsStream( + "/" + AgentConstants.AUDIO_FILE_NAME); + Sequence sequence; + + try { + sequence = MidiSystem.getSequence(audioSrc); + sequencer = MidiSystem.getSequencer(); + sequencer.open(); + sequencer.setSequence(sequence); + } catch (InvalidMidiDataException e) { + log.error("AudioReader: Error whilst setting MIDI Audio reader sequence"); + } catch (IOException e) { + log.error("AudioReader: Error whilst getting audio sequence from stream"); + } catch (MidiUnavailableException e) { + log.error("AudioReader: Error whilst openning MIDI Audio reader sequencer"); + } + + sequencer.setLoopCount(Clip.LOOP_CONTINUOUSLY); + } + +} diff --git a/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/ui/AgentUI.java b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/ui/AgentUI.java new file mode 100644 index 000000000..4a9b76c86 --- /dev/null +++ b/src/main/java/org/wso2/carbon/device/mgt/iot/virtualfirealarm/agent/virtual/ui/AgentUI.java @@ -0,0 +1,954 @@ +/* + * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. 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 + * + * http://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.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.ui; + +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentConstants; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.core.AgentManager; +import org.wso2.carbon.device.mgt.iot.virtualfirealarm.agent.virtual.VirtualHardwareManager; + +import javax.swing.*; +import java.awt.*; +import java.net.URI; +import java.net.URL; + +public class AgentUI extends JFrame { + + private boolean isTemperatureRandomized, isHumidityRandomized; + private boolean isTemperatureSmoothed, isHumiditySmoothed; + + private volatile boolean isAlarmOn = false; + + private JLabel picLabelBulbOn, picLabelBulbOff; + + // Variables declaration - do not modify + private JButton btnControl; + private JButton btnView; + private JCheckBox chkbxEmulate; + private JCheckBox chkbxHumidityRandom; + private JCheckBox chkbxHumiditySmooth; + private JCheckBox chkbxTemperatureRandom; + private JCheckBox chkbxTemperatureSmooth; + private JComboBox cmbInterface; + private JComboBox cmbPeriod; + private JComboBox cmbProtocol; + private JLabel jLabel1; + private JLabel jLabel10; + private JLabel jLabel11; + private JLabel jLabel12; + private JLabel jLabel2; + private JLabel jLabel20; + private JLabel jLabel23; + private JLabel jLabel24; + private JLabel jLabel25; + private JLabel jLabel3; + private JLabel jLabel4; + private JLabel jLabel5; + private JLabel jLabel6; + private JLabel jLabel7; + private JLabel jLabel8; + private JLabel jLabel9; + private JPanel jPanel1; + private JPanel jPanel2; + private JPanel jPanel3; + private JPanel jPanel4; + private JPanel jPanel6; + private JPanel jPanel7; + private JPanel jPanel8; + private JPanel jPanel9; + private JSeparator jSeparator1; + private JSeparator jSeparator5; + private JLabel lblAgentName; + private JLabel lblStatus; + private JPanel pnlBulbStatus; + private JSpinner spinnerHumidity; + private JSpinner spinnerInterval; + private JSpinner spinnerTemperature; + private JTextField txtHumidityMax; + private JTextField txtHumidityMin; + private JTextField txtHumiditySVF; + private JTextField txtTemperatureMax; + private JTextField txtTemperatureMin; + private JTextField txtTemperatureSVF; + // End of variables declaration + + private Runnable uiUpdater = new Runnable() { + @Override + public void run() { + while (true) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + pnlBulbStatus.removeAll(); + pnlBulbStatus.add(isAlarmOn ? picLabelBulbOn : picLabelBulbOff); + pnlBulbStatus.updateUI(); + lblStatus.setText(AgentManager.getInstance().getAgentStatus()); + if (isTemperatureRandomized) { + txtTemperatureMinActionPerformed(null); + txtTemperatureMaxActionPerformed(null); + if (isTemperatureSmoothed) { + txtTemperatureSVFActionPerformed(null); + } + } + if (isHumidityRandomized) { + txtHumidityMinActionPerformed(null); + txtHumidityMaxActionPerformed(null); + if (isHumiditySmoothed) { + txtHumiditySVFActionPerformed(null); + } + } + } + }); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + break; + } + } + } + }; + + /** + * Creates new form AgentUI + */ + public AgentUI() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + private void initComponents() { + + lblAgentName = new JLabel(); + jLabel2 = new JLabel(); + jPanel1 = new JPanel(); + jLabel3 = new JLabel(); + pnlBulbStatus = new JPanel(); + jPanel2 = new JPanel(); + jLabel4 = new JLabel(); + chkbxTemperatureRandom = new JCheckBox(); + jSeparator1 = new JSeparator(); + jPanel7 = new JPanel(); + jLabel5 = new JLabel(); + txtTemperatureMin = new JTextField(); + jLabel6 = new JLabel(); + txtTemperatureMax = new JTextField(); + jLabel10 = new JLabel(); + txtTemperatureSVF = new JTextField(); + spinnerTemperature = new JSpinner(); + chkbxTemperatureSmooth = new JCheckBox(); + jPanel6 = new JPanel(); + jLabel20 = new JLabel(); + btnView = new JButton(); + btnControl = new JButton(); + lblStatus = new JLabel(); + jPanel8 = new JPanel(); + jLabel23 = new JLabel(); + chkbxHumidityRandom = new JCheckBox(); + jSeparator5 = new JSeparator(); + jPanel9 = new JPanel(); + jLabel24 = new JLabel(); + txtHumidityMin = new JTextField(); + jLabel25 = new JLabel(); + txtHumidityMax = new JTextField(); + txtHumiditySVF = new JTextField(); + jLabel11 = new JLabel(); + spinnerHumidity = new JSpinner(); + chkbxHumiditySmooth = new JCheckBox(); + jPanel3 = new JPanel(); + jLabel7 = new JLabel(); + spinnerInterval = new JSpinner(); + jLabel8 = new JLabel(); + jLabel9 = new JLabel(); + cmbProtocol = new JComboBox(); + jLabel12 = new JLabel(); + cmbInterface = new JComboBox(); + jPanel4 = new JPanel(); + chkbxEmulate = new JCheckBox(); + cmbPeriod = new JComboBox(); + jLabel1 = new JLabel(); + + setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + setTitle("Fire Alarm Emulator"); + setResizable(false); + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + setLocation(dim.width / 2 - 650 / 2, dim.height / 2 - 440 / 2); + + lblAgentName.setFont(new Font("Cantarell", 1, 24)); // NOI18N + lblAgentName.setHorizontalAlignment(SwingConstants.LEFT); + lblAgentName.setText("Device Name: " + AgentManager.getInstance().getDeviceName()); + + jLabel2.setHorizontalAlignment(SwingConstants.CENTER); + jLabel2.setText("Copyright (c) 2015, WSO2 Inc."); + + jPanel1.setBackground(new Color(220, 220, 220)); + + jLabel3.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel3.setHorizontalAlignment(SwingConstants.CENTER); + jLabel3.setText("Alarm Status"); + + pnlBulbStatus.setBackground(new Color(220, 220, 220)); + + GroupLayout pnlBulbStatusLayout = new GroupLayout(pnlBulbStatus); + pnlBulbStatus.setLayout(pnlBulbStatusLayout); + pnlBulbStatusLayout.setHorizontalGroup( + pnlBulbStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + pnlBulbStatusLayout.setVerticalGroup( + pnlBulbStatusLayout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGap(0, 167, Short.MAX_VALUE) + ); + + GroupLayout jPanel1Layout = new GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(GroupLayout.Alignment.TRAILING) + .addComponent(pnlBulbStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel3, GroupLayout.DEFAULT_SIZE, 190, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlBulbStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + jPanel2.setBackground(new Color(220, 220, 220)); + + jLabel4.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel4.setHorizontalAlignment(SwingConstants.CENTER); + jLabel4.setText("Temperature"); + + chkbxTemperatureRandom.setText("Randomize Data"); + chkbxTemperatureRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureRandomActionPerformed(evt); + } + }); + + jSeparator1.setOrientation(SwingConstants.VERTICAL); + + jPanel7.setBackground(new Color(220, 220, 220)); + + jLabel5.setHorizontalAlignment(SwingConstants.LEFT); + jLabel5.setText("Min"); + + txtTemperatureMin.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureMin.setText("20"); + txtTemperatureMin.setEnabled(false); + txtTemperatureMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMinActionPerformed(evt); + } + }); + + jLabel6.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel6.setText("Max"); + + txtTemperatureMax.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureMax.setText("50"); + txtTemperatureMax.setEnabled(false); + txtTemperatureMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMaxActionPerformed(evt); + } + }); + + jLabel10.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel10.setText("SV %"); + + txtTemperatureSVF.setHorizontalAlignment(JTextField.CENTER); + txtTemperatureSVF.setText("50"); + txtTemperatureSVF.setEnabled(false); + txtTemperatureSVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureSVFActionPerformed(evt); + } + }); + + GroupLayout jPanel7Layout = new GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMin, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMax, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel10) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureSVF, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel7Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel7Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(txtTemperatureMin, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(txtTemperatureMax, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(jLabel5) + .addComponent(jLabel10) + .addComponent(txtTemperatureSVF, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGap(35, 35, 35)) + ); + + spinnerTemperature.setFont(new Font("Cantarell", 1, 24)); // NOI18N + spinnerTemperature.setModel(new SpinnerNumberModel(30, 0, 100, 1)); + spinnerTemperature.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerTemperatureStateChanged(evt); + } + }); + + chkbxTemperatureSmooth.setText("Smooth Variation"); + chkbxTemperatureSmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureSmoothActionPerformed(evt); + } + }); + + GroupLayout jPanel2Layout = new GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerTemperature)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jPanel7, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(chkbxTemperatureRandom) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxTemperatureSmooth))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup( + + GroupLayout.Alignment.BASELINE) + .addComponent(chkbxTemperatureRandom) + .addComponent(chkbxTemperatureSmooth)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, GroupLayout.PREFERRED_SIZE, 51, GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerTemperature))) + .addContainerGap()) + ); + + jPanel6.setBackground(new Color(253, 254, 209)); + + jLabel20.setText("Connection Status:"); + jLabel20.setVerticalTextPosition(SwingConstants.TOP); + + btnView.setText("View Device Data"); + btnView.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnViewMouseClicked(evt); + } + }); + + btnControl.setText("Control Device"); + btnControl.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnControlMouseClicked(evt); + } + }); + + lblStatus.setFont(new Font("Cantarell", 1, 15)); // NOI18N + lblStatus.setText("Not Connected"); + + GroupLayout jPanel6Layout = new GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel20) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblStatus, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnControl) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnView) + .addContainerGap()) + ); + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel20, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(btnView, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnControl) + .addComponent(lblStatus))) + .addContainerGap()) + ); + + jPanel8.setBackground(new Color(220, 220, 220)); + + jLabel23.setFont(new Font("Cantarell", 0, 18)); // NOI18N + jLabel23.setHorizontalAlignment(SwingConstants.CENTER); + jLabel23.setText("Humidity"); + + chkbxHumidityRandom.setText("Randomize Data"); + chkbxHumidityRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumidityRandomActionPerformed(evt); + } + }); + + jSeparator5.setOrientation(SwingConstants.VERTICAL); + + jPanel9.setBackground(new Color(220, 220, 220)); + + jLabel24.setHorizontalAlignment(SwingConstants.LEFT); + jLabel24.setText("Min"); + + txtHumidityMin.setHorizontalAlignment(JTextField.CENTER); + txtHumidityMin.setText("20"); + txtHumidityMin.setEnabled(false); + txtHumidityMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMinActionPerformed(evt); + } + }); + + jLabel25.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel25.setText("Max"); + + txtHumidityMax.setHorizontalAlignment(JTextField.CENTER); + txtHumidityMax.setText("50"); + txtHumidityMax.setEnabled(false); + txtHumidityMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMaxActionPerformed(evt); + } + }); + + txtHumiditySVF.setHorizontalAlignment(JTextField.CENTER); + txtHumiditySVF.setText("50"); + txtHumiditySVF.setEnabled(false); + txtHumiditySVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumiditySVFActionPerformed(evt); + } + }); + + jLabel11.setHorizontalAlignment(SwingConstants.RIGHT); + jLabel11.setText("SV %"); + + GroupLayout jPanel9Layout = new GroupLayout(jPanel9); + jPanel9.setLayout(jPanel9Layout); + jPanel9Layout.setHorizontalGroup( + jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jLabel24) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMin, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel25) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMax, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel11) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumiditySVF, GroupLayout.PREFERRED_SIZE, 45, GroupLayout.PREFERRED_SIZE) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel9Layout.setVerticalGroup( + jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel9Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel11) + .addComponent(txtHumiditySVF, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel9Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(txtHumidityMin, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(txtHumidityMax, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel25) + .addComponent(jLabel24))) + .addGap(35, 35, 35)) + ); + + spinnerHumidity.setFont(new Font("Cantarell", 1, 24)); // NOI18N + spinnerHumidity.setModel(new SpinnerNumberModel(30, 0, 100, 1)); + spinnerHumidity.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerHumidityStateChanged(evt); + } + }); + + chkbxHumiditySmooth.setText("Smooth Variation"); + chkbxHumiditySmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumiditySmoothActionPerformed(evt); + } + }); + + GroupLayout jPanel8Layout = new GroupLayout(jPanel8); + jPanel8.setLayout(jPanel8Layout); + jPanel8Layout.setHorizontalGroup( + jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jLabel23, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerHumidity)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator5, GroupLayout.PREFERRED_SIZE, 6, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel9, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(chkbxHumidityRandom) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxHumiditySmooth))) + .addContainerGap()) + ); + jPanel8Layout.setVerticalGroup( + jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(jSeparator5) + .addGroup(jPanel8Layout.createSequentialGroup() + .addGroup(jPanel8Layout.createParallelGroup( + GroupLayout.Alignment.BASELINE) + .addComponent(chkbxHumidityRandom) + .addComponent(chkbxHumiditySmooth)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel9, GroupLayout.PREFERRED_SIZE, 51, GroupLayout.PREFERRED_SIZE) + .addGap(0, 1, Short.MAX_VALUE)) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(jLabel23, GroupLayout.PREFERRED_SIZE, 23, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerHumidity))) + .addContainerGap()) + ); + + jPanel3.setBackground(new Color(207, 233, 234)); + + jLabel7.setText("Data Push Interval:"); + + spinnerInterval.setModel(new SpinnerNumberModel(Integer.valueOf(AgentManager.getInstance().getPushInterval()), Integer.valueOf(1), null, Integer.valueOf(1))); + spinnerInterval.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerIntervalStateChanged(evt); + } + }); + + jLabel8.setText("Seconds"); + + jLabel9.setText("Protocol:"); + + cmbProtocol.setModel(new DefaultComboBoxModel(new String[] { "MQTT", "XMPP", "HTTP" })); + cmbProtocol.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbProtocolActionPerformed(evt); + } + }); + + jLabel12.setText("Interface:"); + + cmbInterface.setModel(new DefaultComboBoxModel(new String[] { "eth0" })); + cmbInterface.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbInterfaceActionPerformed(evt); + } + }); + + GroupLayout jPanel3Layout = new GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel7) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerInterval, GroupLayout.PREFERRED_SIZE, 55, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel8) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel12) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbInterface, GroupLayout.PREFERRED_SIZE, 100, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel9) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbProtocol, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(cmbInterface, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(spinnerInterval, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(jLabel9) + .addComponent(cmbProtocol, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + jPanel4.setBackground(new Color(169, 253, 173)); + + chkbxEmulate.setText("Emulate data"); + chkbxEmulate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxEmulateActionPerformed(evt); + } + }); + + cmbPeriod.setModel(new DefaultComboBoxModel(new String[] { "1 hour", "1 day", "1 week", "1 month " })); + cmbPeriod.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbPeriodActionPerformed(evt); + } + }); + + jLabel1.setText("Emulation Period"); + + GroupLayout jPanel4Layout = new GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(chkbxEmulate) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbPeriod, GroupLayout.PREFERRED_SIZE, 162, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel4Layout.createParallelGroup(GroupLayout.Alignment.BASELINE) + .addComponent(chkbxEmulate) + .addComponent(cmbPeriod, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addContainerGap()) + ); + + GroupLayout layout = new GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addComponent(lblAgentName, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup( + GroupLayout.Alignment.LEADING) + .addComponent(jPanel8, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(jLabel2, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAgentName, GroupLayout.PREFERRED_SIZE, 53, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel6, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel8, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addComponent(jPanel1, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, GroupLayout.PREFERRED_SIZE, 28, GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + + chkbxTemperatureSmooth.setEnabled(false); + chkbxTemperatureSmooth.setEnabled(false); + + cmbInterface.removeAllItems(); + for (String item : AgentManager.getInstance().getInterfaceList()){ + cmbInterface.addItem(item); + } + cmbInterface.setEnabled(false); + + cmbProtocol.removeAllItems(); + for (String item : AgentManager.getInstance().getProtocolList()){ + cmbProtocol.addItem(item); + } + cmbProtocol.setSelectedItem(AgentConstants.DEFAULT_PROTOCOL); + + URL urlAlarmOn = this.getClass().getResource("/alarm-on.gif"); + ImageIcon imageIconAlarmOn = new ImageIcon(urlAlarmOn); + + URL urlAlarmOff = this.getClass().getResource("/alarm-off.gif"); + ImageIcon imageIconAlarmOff = new ImageIcon(urlAlarmOff); + + picLabelBulbOn = new JLabel(imageIconAlarmOn); + picLabelBulbOn.setSize(pnlBulbStatus.getSize()); + + picLabelBulbOff = new JLabel(imageIconAlarmOff); + picLabelBulbOff.setSize(pnlBulbStatus.getSize()); + + new Thread(uiUpdater).start(); + + AgentManager.getInstance().setDeviceReady(true); + } + + private void btnControlMouseClicked(java.awt.event.MouseEvent evt) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(AgentManager.getInstance().getDeviceMgtControlUrl()); + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void btnViewMouseClicked(java.awt.event.MouseEvent evt) { + Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { + try { + URI uri = new URI(AgentManager.getInstance().getDeviceMgtAnalyticUrl()); + desktop.browse(uri); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void chkbxTemperatureRandomActionPerformed(java.awt.event.ActionEvent evt) { + isTemperatureRandomized = chkbxTemperatureRandom.isSelected(); + VirtualHardwareManager.getInstance().setIsTemperatureRandomized(isTemperatureRandomized); + spinnerTemperature.setEnabled(!isTemperatureRandomized); + txtTemperatureMax.setEnabled(isTemperatureRandomized); + txtTemperatureMin.setEnabled(isTemperatureRandomized); + chkbxTemperatureSmooth.setEnabled(isTemperatureRandomized); + txtTemperatureSVF.setEnabled(isTemperatureRandomized && isTemperatureSmoothed); + } + + private void chkbxHumidityRandomActionPerformed(java.awt.event.ActionEvent evt) { + isHumidityRandomized = chkbxHumidityRandom.isSelected(); + VirtualHardwareManager.getInstance().setIsHumidityRandomized(isHumidityRandomized); + spinnerHumidity.setEnabled(!isHumidityRandomized); + txtHumidityMax.setEnabled(isHumidityRandomized); + txtHumidityMin.setEnabled(isHumidityRandomized); + chkbxHumiditySmooth.setEnabled(isHumidityRandomized); + txtTemperatureSVF.setEnabled(isHumidityRandomized && isHumiditySmoothed); + } + + private void spinnerTemperatureStateChanged(javax.swing.event.ChangeEvent evt) { + if (!isTemperatureRandomized) { + try { + int temperature = Integer.parseInt(spinnerTemperature.getValue().toString()); + VirtualHardwareManager.getInstance().setTemperature(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerTemperature.setValue(VirtualHardwareManager.getInstance().getTemperature()); + } + } + } + + private void spinnerHumidityStateChanged(javax.swing.event.ChangeEvent evt) { + if (!isHumidityRandomized) { + try { + int humidity = Integer.parseInt(spinnerHumidity.getValue().toString()); + VirtualHardwareManager.getInstance().setHumidity(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerHumidity.setValue(VirtualHardwareManager.getInstance().getHumidity()); + } + } + } + + private void txtTemperatureMinActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperature = Integer.parseInt(txtTemperatureMin.getText()); + VirtualHardwareManager.getInstance().setTemperatureMin(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureMin.setText("20"); + } + } + + private void txtTemperatureMaxActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperature = Integer.parseInt(txtTemperatureMax.getText()); + VirtualHardwareManager.getInstance().setTemperatureMax(temperature); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid temperature value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureMax.setText("50"); + } + } + + private void txtHumidityMinActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humidity = Integer.parseInt(txtHumidityMin.getText()); + VirtualHardwareManager.getInstance().setHumidityMin(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumidityMin.setText("20"); + } + } + + private void txtHumidityMaxActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humidity = Integer.parseInt(txtHumidityMax.getText()); + VirtualHardwareManager.getInstance().setHumidityMax(humidity); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid humidity value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumidityMax.setText("50"); + } + } + + private void spinnerIntervalStateChanged(javax.swing.event.ChangeEvent evt) { + try { + int interval = Integer.parseInt(spinnerInterval.getValue().toString()); + AgentManager.getInstance().setPushInterval(interval); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid time interval value", "Error", JOptionPane.ERROR_MESSAGE); + spinnerInterval.setValue(5); + } + } + + private void cmbInterfaceActionPerformed(java.awt.event.ActionEvent evt) { + AgentManager.getInstance().setInterface(cmbInterface.getSelectedIndex()); + } + + private void cmbProtocolActionPerformed(java.awt.event.ActionEvent evt) { + if (cmbProtocol.getSelectedIndex() != -1 && cmbProtocol.getItemAt( + cmbProtocol.getSelectedIndex()).equals(AgentConstants.HTTP_PROTOCOL)) { + cmbInterface.setEnabled(true); + } else { + cmbInterface.setEnabled(false); + } + + AgentManager.getInstance().setProtocol(cmbProtocol.getSelectedIndex()); + + } + + private void txtTemperatureSVFActionPerformed(java.awt.event.ActionEvent evt) { + try { + int temperatureSVF = Integer.parseInt(txtTemperatureSVF.getText()); + VirtualHardwareManager.getInstance().setTemperatureSVF(temperatureSVF); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid value", "Error", JOptionPane.ERROR_MESSAGE); + txtTemperatureSVF.setText("50"); + } + } + + private void txtHumiditySVFActionPerformed(java.awt.event.ActionEvent evt) { + try { + int humiditySVF = Integer.parseInt(txtHumiditySVF.getText()); + VirtualHardwareManager.getInstance().setHumiditySVF(humiditySVF); + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "Invalid value", "Error", JOptionPane.ERROR_MESSAGE); + txtHumiditySVF.setText("50"); + } + } + + private void chkbxTemperatureSmoothActionPerformed(java.awt.event.ActionEvent evt) { + isTemperatureSmoothed = chkbxTemperatureSmooth.isSelected(); + txtTemperatureSVF.setEnabled(isTemperatureSmoothed); + VirtualHardwareManager.getInstance().setIsTemperatureSmoothed(isTemperatureSmoothed); + } + + private void chkbxHumiditySmoothActionPerformed(java.awt.event.ActionEvent evt) { + isHumiditySmoothed = chkbxHumiditySmooth.isSelected(); + txtHumiditySVF.setEnabled(isHumiditySmoothed); + VirtualHardwareManager.getInstance().setIsHumiditySmoothed(isHumiditySmoothed); + } + + private void cmbPeriodActionPerformed(java.awt.event.ActionEvent evt) { + // TODO add your handling code here: + } + + private void chkbxEmulateActionPerformed(java.awt.event.ActionEvent evt) { + // TODO add your handling code here: + } + + public void setAlarmStatus(boolean isAlarmOn) { + this.isAlarmOn = isAlarmOn; + } + + public void updateTemperature(int temperature) { + spinnerTemperature.setValue(temperature); + spinnerTemperature.updateUI(); + } + + public void updateHumidity(int humidity) { + spinnerHumidity.setValue(humidity); + spinnerHumidity.updateUI(); + } + +} diff --git a/src/main/resources/alarm-off.gif b/src/main/resources/alarm-off.gif new file mode 100644 index 000000000..c346605ad Binary files /dev/null and b/src/main/resources/alarm-off.gif differ diff --git a/src/main/resources/alarm-on.gif b/src/main/resources/alarm-on.gif new file mode 100644 index 000000000..d7c83f6aa Binary files /dev/null and b/src/main/resources/alarm-on.gif differ diff --git a/src/main/resources/deviceConfig.properties b/src/main/resources/deviceConfig.properties new file mode 100644 index 000000000..ba7e9922b --- /dev/null +++ b/src/main/resources/deviceConfig.properties @@ -0,0 +1,35 @@ +ad# +# Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +# +# 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 +# +# 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. +# +# + +#[Device-Configurations] +server-name=WSO2IoTServer +owner=shabirmean +deviceId=t4ctwq8qfl11 +device-name=SMEAN_t4ctwq8qfl11 +controller-context=/virtual_firealarm/device +scep-context=/virtual_firealarm_scep +https-ep=https://localhost:9443 +http-ep=http://localhost:9763 +apim-ep=http://192.168.67.21:8281 +mqtt-ep=tcp://192.168.67.21:1883 +xmpp-ep=http://204.232.188.215:5222 +auth-method=token +auth-token=79d68b50ae5f5a06e812889979b3453 +refresh-token=8bdda6359dddad218cff3354d5a8cb3b +network-interface=en0 +push-interval=14 +xmpp-server-name=localhost \ No newline at end of file diff --git a/src/main/resources/fireAlarmSound.mid b/src/main/resources/fireAlarmSound.mid new file mode 100644 index 000000000..d1a2241b2 Binary files /dev/null and b/src/main/resources/fireAlarmSound.mid differ diff --git a/src/main/ui/build.xml b/src/main/ui/build.xml new file mode 100644 index 000000000..bc3a40444 --- /dev/null +++ b/src/main/ui/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project VirtualAgentUI. + + + diff --git a/src/main/ui/manifest.mf b/src/main/ui/manifest.mf new file mode 100644 index 000000000..328e8e5bc --- /dev/null +++ b/src/main/ui/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/src/main/ui/nbproject/build-impl.xml b/src/main/ui/nbproject/build-impl.xml new file mode 100644 index 000000000..112454207 --- /dev/null +++ b/src/main/ui/nbproject/build-impl.xml @@ -0,0 +1,1413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/ui/nbproject/genfiles.properties b/src/main/ui/nbproject/genfiles.properties new file mode 100644 index 000000000..a6df38fd8 --- /dev/null +++ b/src/main/ui/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=e60df945 +build.xml.script.CRC32=7c331eea +build.xml.stylesheet.CRC32=8064a381@1.75.2.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=e60df945 +nbproject/build-impl.xml.script.CRC32=4fa004f7 +nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48 diff --git a/src/main/ui/nbproject/private/private.properties b/src/main/ui/nbproject/private/private.properties new file mode 100644 index 000000000..e59ac1df6 --- /dev/null +++ b/src/main/ui/nbproject/private/private.properties @@ -0,0 +1,2 @@ +compile.on.save=true +user.properties.file=/home/charitha/.netbeans/8.0.2/build.properties diff --git a/src/main/ui/nbproject/private/private.xml b/src/main/ui/nbproject/private/private.xml new file mode 100644 index 000000000..2f9a6910b --- /dev/null +++ b/src/main/ui/nbproject/private/private.xml @@ -0,0 +1,9 @@ + + + + + + file:/home/charitha/git/IoT/iot-server-agents/FireAlarmVirtualAgent/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java + + + diff --git a/src/main/ui/nbproject/project.properties b/src/main/ui/nbproject/project.properties new file mode 100644 index 000000000..fb798f614 --- /dev/null +++ b/src/main/ui/nbproject/project.properties @@ -0,0 +1,73 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processor.options= +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/VirtualAgentUI.jar +dist.javadoc.dir=${dist.dir}/javadoc +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +main.class=org.wso2.carbon.device.mgt.iot.agent.virtual.VirtualAgentUI +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/src/main/ui/nbproject/project.xml b/src/main/ui/nbproject/project.xml new file mode 100644 index 000000000..438f1e6a3 --- /dev/null +++ b/src/main/ui/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + VirtualAgentUI + + + + + + + + + diff --git a/src/main/ui/src/bulb-on.jpg b/src/main/ui/src/bulb-on.jpg new file mode 100644 index 000000000..51d40cd83 Binary files /dev/null and b/src/main/ui/src/bulb-on.jpg differ diff --git a/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java b/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java new file mode 100644 index 000000000..a2aa2116a --- /dev/null +++ b/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/VirtualAgentUI.java @@ -0,0 +1,37 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.wso2.carbon.device.mgt.iot.agent.virtual; + +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import org.wso2.carbon.device.mgt.iot.agent.virtual.ui.AgentUI; + +/** + * + * @author charitha + */ +public class VirtualAgentUI { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + try { + // Set System L&F + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + } catch (UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { + // handle exception + } + + java.awt.EventQueue.invokeLater(new Runnable() { + public void run() { + new AgentUI().setVisible(true); + } + }); + } + +} diff --git a/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form b/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form new file mode 100644 index 000000000..23b70e036 --- /dev/null +++ b/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.form @@ -0,0 +1,803 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java b/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java new file mode 100644 index 000000000..470a7d8fe --- /dev/null +++ b/src/main/ui/src/org/wso2/carbon/device/mgt/iot/agent/virtual/ui/AgentUI.java @@ -0,0 +1,744 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.wso2.carbon.device.mgt.iot.agent.virtual.ui; + +/** + * + * @author charitha + */ +public class AgentUI extends javax.swing.JFrame { + + /** + * Creates new form AgentUI + */ + public AgentUI() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lblAgentName = new javax.swing.JLabel(); + jLabel2 = new javax.swing.JLabel(); + jPanel1 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + pnlBulbStatus = new javax.swing.JPanel(); + jPanel2 = new javax.swing.JPanel(); + jLabel4 = new javax.swing.JLabel(); + chkbxTemperatureRandom = new javax.swing.JCheckBox(); + jSeparator1 = new javax.swing.JSeparator(); + jPanel7 = new javax.swing.JPanel(); + jLabel5 = new javax.swing.JLabel(); + txtTemperatureMin = new javax.swing.JTextField(); + jLabel6 = new javax.swing.JLabel(); + txtTemperatureMax = new javax.swing.JTextField(); + jLabel10 = new javax.swing.JLabel(); + txtTemperatureSVF = new javax.swing.JTextField(); + spinnerTemperature = new javax.swing.JSpinner(); + chkbxTemperatureSmooth = new javax.swing.JCheckBox(); + jPanel6 = new javax.swing.JPanel(); + jLabel20 = new javax.swing.JLabel(); + btnView = new javax.swing.JButton(); + btnControl = new javax.swing.JButton(); + lblStatus = new javax.swing.JLabel(); + jPanel8 = new javax.swing.JPanel(); + jLabel23 = new javax.swing.JLabel(); + chkbxHumidityRandom = new javax.swing.JCheckBox(); + jSeparator5 = new javax.swing.JSeparator(); + jPanel9 = new javax.swing.JPanel(); + jLabel24 = new javax.swing.JLabel(); + txtHumidityMin = new javax.swing.JTextField(); + jLabel25 = new javax.swing.JLabel(); + txtHumidityMax = new javax.swing.JTextField(); + txtHumiditySVF = new javax.swing.JTextField(); + jLabel11 = new javax.swing.JLabel(); + spinnerHumidity = new javax.swing.JSpinner(); + chkbxHumiditySmooth = new javax.swing.JCheckBox(); + jPanel3 = new javax.swing.JPanel(); + jLabel7 = new javax.swing.JLabel(); + spinnerInterval = new javax.swing.JSpinner(); + jLabel8 = new javax.swing.JLabel(); + jLabel9 = new javax.swing.JLabel(); + cmbProtocol = new javax.swing.JComboBox(); + jLabel12 = new javax.swing.JLabel(); + cmbInterface = new javax.swing.JComboBox(); + jPanel4 = new javax.swing.JPanel(); + chkbxEmulate = new javax.swing.JCheckBox(); + cmbPeriod = new javax.swing.JComboBox(); + jLabel1 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + setTitle("Fire Alarm Emulator"); + setResizable(false); + + lblAgentName.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + lblAgentName.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + lblAgentName.setText("Device Name: WSO2 IoT Virtual Agent"); + + jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel2.setText("Copyright (c) 2015, WSO2 Inc."); + + jPanel1.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel3.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel3.setText("Bulb Status"); + + pnlBulbStatus.setBackground(new java.awt.Color(220, 220, 220)); + + javax.swing.GroupLayout pnlBulbStatusLayout = new javax.swing.GroupLayout(pnlBulbStatus); + pnlBulbStatus.setLayout(pnlBulbStatusLayout); + pnlBulbStatusLayout.setHorizontalGroup( + pnlBulbStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 0, Short.MAX_VALUE) + ); + pnlBulbStatusLayout.setVerticalGroup( + pnlBulbStatusLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGap(0, 167, Short.MAX_VALUE) + ); + + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); + jPanel1.setLayout(jPanel1Layout); + jPanel1Layout.setHorizontalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(pnlBulbStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, 190, Short.MAX_VALUE)) + .addContainerGap()) + ); + jPanel1Layout.setVerticalGroup( + jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel1Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel3) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pnlBulbStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addContainerGap()) + ); + + jPanel2.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel4.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel4.setText("Temperature"); + + chkbxTemperatureRandom.setText("Randomize Data"); + chkbxTemperatureRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureRandomActionPerformed(evt); + } + }); + + jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL); + + jPanel7.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + jLabel5.setText("Min"); + + txtTemperatureMin.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureMin.setText("20"); + txtTemperatureMin.setEnabled(false); + txtTemperatureMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMinActionPerformed(evt); + } + }); + + jLabel6.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel6.setText("Max"); + + txtTemperatureMax.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureMax.setText("50"); + txtTemperatureMax.setEnabled(false); + txtTemperatureMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureMaxActionPerformed(evt); + } + }); + + jLabel10.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel10.setText("SV %"); + + txtTemperatureSVF.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtTemperatureSVF.setText("50"); + txtTemperatureSVF.setEnabled(false); + txtTemperatureSVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtTemperatureSVFActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel7Layout = new javax.swing.GroupLayout(jPanel7); + jPanel7.setLayout(jPanel7Layout); + jPanel7Layout.setHorizontalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel7Layout.createSequentialGroup() + .addComponent(jLabel5) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMin, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureMax, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel10) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtTemperatureSVF, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel7Layout.setVerticalGroup( + jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel7Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel7Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtTemperatureMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtTemperatureMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel6) + .addComponent(jLabel5) + .addComponent(jLabel10) + .addComponent(txtTemperatureSVF, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(35, 35, 35)) + ); + + spinnerTemperature.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + spinnerTemperature.setModel(new javax.swing.SpinnerNumberModel(30, 0, 100, 1)); + spinnerTemperature.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerTemperatureStateChanged(evt); + } + }); + + chkbxTemperatureSmooth.setText("Smooth Variation"); + chkbxTemperatureSmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxTemperatureSmoothActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2); + jPanel2.setLayout(jPanel2Layout); + jPanel2Layout.setHorizontalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(spinnerTemperature)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(chkbxTemperatureRandom) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxTemperatureSmooth))) + .addContainerGap()) + ); + jPanel2Layout.setVerticalGroup( + jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel2Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator1) + .addGroup(jPanel2Layout.createSequentialGroup() + .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxTemperatureRandom) + .addComponent(chkbxTemperatureSmooth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel7, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(jPanel2Layout.createSequentialGroup() + .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerTemperature))) + .addContainerGap()) + ); + + jPanel6.setBackground(new java.awt.Color(253, 254, 209)); + + jLabel20.setText("Connection Status:"); + jLabel20.setVerticalTextPosition(javax.swing.SwingConstants.TOP); + + btnView.setText("View Device Data"); + btnView.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnViewMouseClicked(evt); + } + }); + + btnControl.setText("Control Device"); + btnControl.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + btnControlMouseClicked(evt); + } + }); + + lblStatus.setFont(new java.awt.Font("Cantarell", 1, 15)); // NOI18N + lblStatus.setText("Not Connected"); + + javax.swing.GroupLayout jPanel6Layout = new javax.swing.GroupLayout(jPanel6); + jPanel6.setLayout(jPanel6Layout); + jPanel6Layout.setHorizontalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel6Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel20) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(lblStatus, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnControl) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(btnView) + .addContainerGap()) + ); + jPanel6Layout.setVerticalGroup( + jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel6Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel20, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel6Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(btnView, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(btnControl) + .addComponent(lblStatus))) + .addContainerGap()) + ); + + jPanel8.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel23.setFont(new java.awt.Font("Cantarell", 0, 18)); // NOI18N + jLabel23.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); + jLabel23.setText("Humidity"); + + chkbxHumidityRandom.setText("Randomize Data"); + chkbxHumidityRandom.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumidityRandomActionPerformed(evt); + } + }); + + jSeparator5.setOrientation(javax.swing.SwingConstants.VERTICAL); + + jPanel9.setBackground(new java.awt.Color(220, 220, 220)); + + jLabel24.setHorizontalAlignment(javax.swing.SwingConstants.LEFT); + jLabel24.setText("Min"); + + txtHumidityMin.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumidityMin.setText("20"); + txtHumidityMin.setEnabled(false); + txtHumidityMin.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMinActionPerformed(evt); + } + }); + + jLabel25.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel25.setText("Max"); + + txtHumidityMax.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumidityMax.setText("50"); + txtHumidityMax.setEnabled(false); + txtHumidityMax.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumidityMaxActionPerformed(evt); + } + }); + + txtHumiditySVF.setHorizontalAlignment(javax.swing.JTextField.CENTER); + txtHumiditySVF.setText("50"); + txtHumiditySVF.setEnabled(false); + txtHumiditySVF.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + txtHumiditySVFActionPerformed(evt); + } + }); + + jLabel11.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); + jLabel11.setText("SV %"); + + javax.swing.GroupLayout jPanel9Layout = new javax.swing.GroupLayout(jPanel9); + jPanel9.setLayout(jPanel9Layout); + jPanel9Layout.setHorizontalGroup( + jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createSequentialGroup() + .addComponent(jLabel24) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMin, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel25) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumidityMax, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel11) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtHumiditySVF, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + jPanel9Layout.setVerticalGroup( + jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel9Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel11) + .addComponent(txtHumiditySVF, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel9Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(txtHumidityMin, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(txtHumidityMax, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel25) + .addComponent(jLabel24))) + .addGap(35, 35, 35)) + ); + + spinnerHumidity.setFont(new java.awt.Font("Cantarell", 1, 24)); // NOI18N + spinnerHumidity.setModel(new javax.swing.SpinnerNumberModel(30, 0, 100, 1)); + spinnerHumidity.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerHumidityStateChanged(evt); + } + }); + + chkbxHumiditySmooth.setText("Smooth Variation"); + chkbxHumiditySmooth.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxHumiditySmoothActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel8Layout = new javax.swing.GroupLayout(jPanel8); + jPanel8.setLayout(jPanel8Layout); + jPanel8Layout.setHorizontalGroup( + jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jLabel23, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(spinnerHumidity)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator5, javax.swing.GroupLayout.PREFERRED_SIZE, 6, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jPanel9, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(chkbxHumidityRandom) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(chkbxHumiditySmooth))) + .addContainerGap()) + ); + jPanel8Layout.setVerticalGroup( + jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel8Layout.createSequentialGroup() + .addContainerGap() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSeparator5) + .addGroup(jPanel8Layout.createSequentialGroup() + .addGroup(jPanel8Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxHumidityRandom) + .addComponent(chkbxHumiditySmooth)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel9, javax.swing.GroupLayout.PREFERRED_SIZE, 51, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(0, 1, Short.MAX_VALUE)) + .addGroup(jPanel8Layout.createSequentialGroup() + .addComponent(jLabel23, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerHumidity))) + .addContainerGap()) + ); + + jPanel3.setBackground(new java.awt.Color(207, 233, 234)); + + jLabel7.setText("Data Push Interval:"); + + spinnerInterval.setModel(new javax.swing.SpinnerNumberModel(Integer.valueOf(5), Integer.valueOf(1), null, Integer.valueOf(1))); + spinnerInterval.addChangeListener(new javax.swing.event.ChangeListener() { + public void stateChanged(javax.swing.event.ChangeEvent evt) { + spinnerIntervalStateChanged(evt); + } + }); + + jLabel8.setText("Seconds"); + + jLabel9.setText("Protocol:"); + + cmbProtocol.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "MQTT", "XMPP", "HTTP" })); + cmbProtocol.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbProtocolActionPerformed(evt); + } + }); + + jLabel12.setText("Interface:"); + + cmbInterface.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "eth0" })); + cmbInterface.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbInterfaceActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3); + jPanel3.setLayout(jPanel3Layout); + jPanel3Layout.setHorizontalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel7) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(spinnerInterval, javax.swing.GroupLayout.PREFERRED_SIZE, 55, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel12) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbInterface, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel9) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbProtocol, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel3Layout.setVerticalGroup( + jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel12) + .addComponent(cmbInterface, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel7) + .addComponent(spinnerInterval, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel8) + .addComponent(jLabel9) + .addComponent(cmbProtocol, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap()) + ); + + jPanel4.setBackground(new java.awt.Color(169, 253, 173)); + + chkbxEmulate.setText("Emulate data"); + chkbxEmulate.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + chkbxEmulateActionPerformed(evt); + } + }); + + cmbPeriod.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "1 hour", "1 day", "1 week", "1 month " })); + cmbPeriod.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cmbPeriodActionPerformed(evt); + } + }); + + jLabel1.setText("Emulation Period"); + + javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4); + jPanel4.setLayout(jPanel4Layout); + jPanel4Layout.setHorizontalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jPanel4Layout.createSequentialGroup() + .addContainerGap() + .addComponent(chkbxEmulate) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cmbPeriod, javax.swing.GroupLayout.PREFERRED_SIZE, 162, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + jPanel4Layout.setVerticalGroup( + jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(chkbxEmulate) + .addComponent(cmbPeriod, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblAgentName, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel6, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jPanel8, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) + .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblAgentName, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel6, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel8, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + pack(); + }// //GEN-END:initComponents + + private void btnControlMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnControlMouseClicked + // TODO add your handling code here: + }//GEN-LAST:event_btnControlMouseClicked + + private void btnViewMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnViewMouseClicked + // TODO add your handling code here: + }//GEN-LAST:event_btnViewMouseClicked + + private void chkbxTemperatureRandomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxTemperatureRandomActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxTemperatureRandomActionPerformed + + private void chkbxHumidityRandomActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxHumidityRandomActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxHumidityRandomActionPerformed + + private void spinnerTemperatureStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerTemperatureStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerTemperatureStateChanged + + private void spinnerHumidityStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerHumidityStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerHumidityStateChanged + + private void txtTemperatureMinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureMinActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureMinActionPerformed + + private void txtTemperatureMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureMaxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureMaxActionPerformed + + private void txtHumidityMinActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumidityMinActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumidityMinActionPerformed + + private void txtHumidityMaxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumidityMaxActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumidityMaxActionPerformed + + private void spinnerIntervalStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerIntervalStateChanged + // TODO add your handling code here: + }//GEN-LAST:event_spinnerIntervalStateChanged + + private void cmbInterfaceActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbInterfaceActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbInterfaceActionPerformed + + private void cmbProtocolActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbProtocolActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbProtocolActionPerformed + + private void txtTemperatureSVFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtTemperatureSVFActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtTemperatureSVFActionPerformed + + private void txtHumiditySVFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtHumiditySVFActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_txtHumiditySVFActionPerformed + + private void chkbxTemperatureSmoothActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxTemperatureSmoothActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxTemperatureSmoothActionPerformed + + private void chkbxHumiditySmoothActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxHumiditySmoothActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxHumiditySmoothActionPerformed + + private void cmbPeriodActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cmbPeriodActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_cmbPeriodActionPerformed + + private void chkbxEmulateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chkbxEmulateActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_chkbxEmulateActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton btnControl; + private javax.swing.JButton btnView; + private javax.swing.JCheckBox chkbxEmulate; + private javax.swing.JCheckBox chkbxHumidityRandom; + private javax.swing.JCheckBox chkbxHumiditySmooth; + private javax.swing.JCheckBox chkbxTemperatureRandom; + private javax.swing.JCheckBox chkbxTemperatureSmooth; + private javax.swing.JComboBox cmbInterface; + private javax.swing.JComboBox cmbPeriod; + private javax.swing.JComboBox cmbProtocol; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; + private javax.swing.JLabel jLabel11; + private javax.swing.JLabel jLabel12; + private javax.swing.JLabel jLabel2; + private javax.swing.JLabel jLabel20; + private javax.swing.JLabel jLabel23; + private javax.swing.JLabel jLabel24; + private javax.swing.JLabel jLabel25; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel5; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JLabel jLabel9; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private javax.swing.JPanel jPanel6; + private javax.swing.JPanel jPanel7; + private javax.swing.JPanel jPanel8; + private javax.swing.JPanel jPanel9; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JSeparator jSeparator5; + private javax.swing.JLabel lblAgentName; + private javax.swing.JLabel lblStatus; + private javax.swing.JPanel pnlBulbStatus; + private javax.swing.JSpinner spinnerHumidity; + private javax.swing.JSpinner spinnerInterval; + private javax.swing.JSpinner spinnerTemperature; + private javax.swing.JTextField txtHumidityMax; + private javax.swing.JTextField txtHumidityMin; + private javax.swing.JTextField txtHumiditySVF; + private javax.swing.JTextField txtTemperatureMax; + private javax.swing.JTextField txtTemperatureMin; + private javax.swing.JTextField txtTemperatureSVF; + // End of variables declaration//GEN-END:variables +}