/* * Copyright (c) 2014, 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. */ package org.wso2.carbon.device.mgt.iot.firealarm.api; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.concurrent.FutureCallback; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.apache.http.impl.nio.client.HttpAsyncClients; import org.wso2.carbon.device.mgt.common.DeviceIdentifier; import org.wso2.carbon.device.mgt.common.DeviceManagementException; import org.wso2.carbon.device.mgt.iot.common.DeviceController; import org.wso2.carbon.device.mgt.iot.common.DeviceValidator; import org.wso2.carbon.device.mgt.iot.common.datastore.impl.DataStreamDefinitions; import org.wso2.carbon.device.mgt.iot.common.exception.DeviceControllerException; import org.wso2.carbon.device.mgt.iot.common.exception.UnauthorizedException; import org.wso2.carbon.device.mgt.iot.firealarm.api.util.DeviceJSON; import org.wso2.carbon.device.mgt.iot.firealarm.constants.FireAlarmConstants; import org.wso2.carbon.utils.CarbonUtils; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.HttpMethod; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.ProtocolException; import java.net.URL; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; public class FireAlarmControllerService { private static Log log = LogFactory.getLog(FireAlarmControllerService.class); private static final String URL_PREFIX = "http://"; private static final String BULB_CONTEXT = "/BULB/"; private static final String FAN_CONTEXT = "/FAN/"; private static final String TEMPERATURE_CONTEXT = "/TEMP/"; private static final String SONAR_CONTEXT = "/SONAR/"; public static final String XMPP_PROTOCOL = "XMPP"; public static final String HTTP_PROTOCOL = "HTTP"; public static final String MQTT_PROTOCOL = "MQTT"; private static ConcurrentHashMap<String, String> deviceToIpMap = new ConcurrentHashMap<String, String>(); @Path("/register/{owner}/{deviceId}/{ip}") @POST public String registerDeviceIP(@PathParam("owner") String owner, @PathParam("deviceId") String deviceId, @PathParam("ip") String deviceIP, @Context HttpServletResponse response) { String result; log.info("Got register call from IP: " + deviceIP + " for Device ID: " + deviceId + " of owner: " + owner); deviceToIpMap.put(deviceId, deviceIP); result = "Device-IP Registered"; response.setStatus(HttpStatus.SC_OK); if (log.isDebugEnabled()) { log.debug(result); } return result; } /* Service to switch "ON" and "OFF" the FireAlarm bulb Called by an external client intended to control the FireAlarm bulb */ @Path("/bulb/{state}") @POST public void switchBulb(@HeaderParam("owner") String owner, @HeaderParam("deviceId") String deviceId, @HeaderParam("protocol") String protocol, @PathParam("state") String state, @Context HttpServletResponse response) { try { DeviceValidator deviceValidator = new DeviceValidator(); if (!deviceValidator.isExist(owner, new DeviceIdentifier(deviceId, FireAlarmConstants .DEVICE_TYPE))) { response.setStatus(HttpStatus.SC_UNAUTHORIZED); return; } } catch (DeviceManagementException e) { log.error("DeviceValidation Failed for deviceId: " + deviceId + " of user: " + owner); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return; } String switchToState = state.toUpperCase(); if (!switchToState.equals(FireAlarmConstants.STATE_ON) && !switchToState.equals( FireAlarmConstants.STATE_OFF)) { log.error("The requested state change shoud be either - 'ON' or 'OFF'"); response.setStatus(HttpStatus.SC_BAD_REQUEST); return; } String deviceIP = deviceToIpMap.get(deviceId); if (deviceIP == null) { response.setStatus(HttpStatus.SC_PRECONDITION_FAILED); return; } String protocolString = protocol.toUpperCase(); String callUrlPattern = BULB_CONTEXT + switchToState; log.info("Sending command: '" + callUrlPattern + "' to firealarm at: " + deviceIP + " " + "via" + " " + protocol); try { switch (protocolString) { case HTTP_PROTOCOL: sendCommandViaHTTP(deviceIP, 80, callUrlPattern, true); break; case MQTT_PROTOCOL: callUrlPattern = BULB_CONTEXT.replace("/", ""); sendCommandViaMQTT(owner, deviceId, callUrlPattern, switchToState); break; default: if (protocolString == null) { sendCommandViaHTTP(deviceIP, 80, callUrlPattern, true); } else { response.setStatus(HttpStatus.SC_NOT_IMPLEMENTED); return; } break; } } catch (DeviceManagementException e) { log.error("Failed to send command '" + callUrlPattern + "' to: " + deviceIP + " via" + " " + protocol); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return; } response.setStatus(HttpStatus.SC_OK); } @Path("/fan/{state}") @POST public void switchFan(@HeaderParam("owner") String owner, @HeaderParam("deviceId") String deviceId, @HeaderParam("protocol") String protocol, @PathParam("state") String state, @Context HttpServletResponse response) { try { DeviceValidator deviceValidator = new DeviceValidator(); if (!deviceValidator.isExist(owner, new DeviceIdentifier(deviceId, FireAlarmConstants .DEVICE_TYPE))) { response.setStatus(HttpStatus.SC_UNAUTHORIZED); return; } } catch (DeviceManagementException e) { log.error("DeviceValidation Failed for deviceId: " + deviceId + " of user: " + owner); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return; } String switchToState = state.toUpperCase(); if (!switchToState.equals(FireAlarmConstants.STATE_ON) && !switchToState.equals( FireAlarmConstants.STATE_OFF)) { log.error("The requested state change shoud be either - 'ON' or 'OFF'"); response.setStatus(HttpStatus.SC_BAD_REQUEST); return; } String deviceIP = deviceToIpMap.get(deviceId); if (deviceIP == null) { response.setStatus(HttpStatus.SC_PRECONDITION_FAILED); return; } String protocolString = protocol.toUpperCase(); String callUrlPattern = FAN_CONTEXT + switchToState; log.info("Sending command: '" + callUrlPattern + "' to firealarm at: " + deviceIP + " " + "via" + " " + protocol); try { switch (protocolString) { case HTTP_PROTOCOL: sendCommandViaHTTP(deviceIP, 80, callUrlPattern, true); break; case MQTT_PROTOCOL: callUrlPattern = FAN_CONTEXT.replace("/", ""); sendCommandViaMQTT(owner, deviceId, callUrlPattern, switchToState); break; default: if (protocolString == null) { sendCommandViaHTTP(deviceIP, 80, callUrlPattern, true); } else { response.setStatus(HttpStatus.SC_NOT_IMPLEMENTED); return; } break; } } catch (DeviceManagementException e) { log.error("Failed to send command '" + callUrlPattern + "' to: " + deviceIP + " via" + " " + protocol); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return; } response.setStatus(HttpStatus.SC_OK); } @Path("/readtemperature") @GET public String requestTemperature(@HeaderParam("owner") String owner, @HeaderParam("deviceId") String deviceId, @HeaderParam("protocol") String protocol, @Context HttpServletResponse response) { String replyMsg = ""; DeviceValidator deviceValidator = new DeviceValidator(); try { if (!deviceValidator.isExist(owner, new DeviceIdentifier(deviceId, FireAlarmConstants .DEVICE_TYPE))) { response.setStatus(HttpStatus.SC_UNAUTHORIZED); return "Unauthorized Access"; } } catch (DeviceManagementException e) { replyMsg = e.getErrorMessage(); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return replyMsg; } String deviceIp = deviceToIpMap.get(deviceId); if (deviceIp == null) { replyMsg = "IP not registered for device: " + deviceId + " of owner: " + owner; response.setStatus(HttpStatus.SC_PRECONDITION_FAILED); return replyMsg; } try { switch (protocol) { case HTTP_PROTOCOL: log.info("Sending request to read firealarm-temperature at : " + deviceIp + " via " + HTTP_PROTOCOL); replyMsg = sendCommandViaHTTP(deviceIp, 80, TEMPERATURE_CONTEXT, false); break; case XMPP_PROTOCOL: log.info("Sending request to read firealarm-temperature at : " + deviceIp + " via " + XMPP_PROTOCOL); replyMsg = requestTemperatureViaXMPP(deviceIp, response); break; default: if (protocol == null) { log.info("Sending request to read firealarm-temperature at : " + deviceIp + " via " + HTTP_PROTOCOL); replyMsg = sendCommandViaHTTP(deviceIp, 80, TEMPERATURE_CONTEXT, false); } else { replyMsg = "Requested protocol '" + protocol + "' is not supported"; response.setStatus(HttpStatus.SC_NOT_IMPLEMENTED); return replyMsg; } break; } } catch (DeviceManagementException e) { replyMsg = e.getErrorMessage(); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return replyMsg; } response.setStatus(HttpStatus.SC_OK); replyMsg = "The current temperature of the device is " + replyMsg; return replyMsg; } public String requestTemperatureViaXMPP(String deviceIp, @Context HttpServletResponse response) { String replyMsg = ""; String sep = File.separator; String scriptsFolder = "repository" + sep + "resources" + sep + "scripts"; String scriptPath = CarbonUtils.getCarbonHome() + sep + scriptsFolder + sep + "xmpp_client.py"; String command = "python " + scriptPath; replyMsg = executeCommand(command); response.setStatus(HttpStatus.SC_OK); return replyMsg; } private String executeCommand(String command) { StringBuffer output = new StringBuffer(); Process p; try { p = Runtime.getRuntime().exec(command); p.waitFor(); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { output.append(line + "\n"); } } catch (Exception e) { log.info(e.getMessage(), e); } return output.toString(); } @Path("/readsonar") @GET public String requestSonar(@HeaderParam("owner") String owner, @HeaderParam("deviceId") String deviceId, @HeaderParam("protocol") String protocol, @Context HttpServletResponse response) { String replyMsg = ""; DeviceValidator deviceValidator = new DeviceValidator(); try { if (!deviceValidator.isExist(owner, new DeviceIdentifier(deviceId, FireAlarmConstants .DEVICE_TYPE))) { response.setStatus(HttpStatus.SC_UNAUTHORIZED); return "Unauthorized Access"; } } catch (DeviceManagementException e) { replyMsg = e.getErrorMessage(); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return replyMsg; } String deviceIp = deviceToIpMap.get(deviceId); if (deviceIp == null) { replyMsg = "IP not registered for device: " + deviceId + " of owner: " + owner; response.setStatus(HttpStatus.SC_PRECONDITION_FAILED); return replyMsg; } try { switch (protocol) { case HTTP_PROTOCOL: log.info("Sending request to read firealarm-sonar at : " + deviceIp + " via " + HTTP_PROTOCOL); replyMsg = sendCommandViaHTTP(deviceIp, 80, SONAR_CONTEXT, false); break; // case XMPP_PROTOCOL: // log.info("Sending request to read firealarm-sonar at : " + deviceIp + // " via " + // XMPP_PROTOCOL); // replyMsg = requestTemperatureViaXMPP(deviceIp, response); // break; default: if (protocol == null) { log.info("Sending request to read firealarm-sonar at : " + deviceIp + " via " + HTTP_PROTOCOL); replyMsg = sendCommandViaHTTP(deviceIp, 80, SONAR_CONTEXT, false); } else { replyMsg = "Requested protocol '" + protocol + "' is not supported"; response.setStatus(HttpStatus.SC_NOT_IMPLEMENTED); return replyMsg; } break; } } catch (DeviceManagementException e) { replyMsg = e.getErrorMessage(); response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); return replyMsg; } response.setStatus(HttpStatus.SC_OK); replyMsg = "The current sonar reading of the device is " + replyMsg; return replyMsg; } @Path("/push_temperature") @POST @Consumes(MediaType.APPLICATION_JSON) public void pushTemperatureData( final DeviceJSON dataMsg, @Context HttpServletResponse response) { boolean result; String deviceId = dataMsg.deviceId; String deviceIp = dataMsg.reply; String temperature = dataMsg.value; String registeredIp = deviceToIpMap.get(deviceId); if (registeredIp == null) { log.warn( "Unregistered IP: Temperature Data Received from an un-registered IP " + deviceIp + " for device ID - " + deviceId); response.setStatus(HttpStatus.SC_PRECONDITION_FAILED); return; } else if (!registeredIp.equals(deviceIp)) { log.warn("Conflicting IP: Received IP is " + deviceIp + ". Device with ID " + deviceId + " is already registered under some other IP. Re-registration " + "required"); response.setStatus(HttpStatus.SC_CONFLICT); return; } try { DeviceController deviceController = new DeviceController(); result = deviceController.pushBamData(dataMsg.owner, FireAlarmConstants .DEVICE_TYPE, dataMsg.deviceId, System.currentTimeMillis(), "DeviceData", temperature, DataStreamDefinitions.StreamTypeLabel .TEMPERATURE); if (!result) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); } } catch (UnauthorizedException e) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Data Push Attempt Failed for BAM Publisher: " + e.getMessage()); } try { DeviceController deviceController = new DeviceController(); result = deviceController.pushCepData(dataMsg.owner, FireAlarmConstants .DEVICE_TYPE, dataMsg.deviceId, System.currentTimeMillis(), "DeviceData", temperature, DataStreamDefinitions.StreamTypeLabel .TEMPERATURE); if (!result) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); } } catch (UnauthorizedException e) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Data Push Attempt Failed for CEP Publisher: " + e.getMessage()); } } /* Service to push all the sensor data collected by the FireAlarm Called by the FireAlarm device */ @Path("/pushalarmdata") @POST @Consumes(MediaType.APPLICATION_JSON) public void pushAlarmData(final DeviceJSON dataMsg, @Context HttpServletResponse response) { boolean result; String sensorValues = dataMsg.value; log.info("Recieved Sensor Data Values: " + sensorValues); String sensors[] = sensorValues.split(":"); try { if (sensors.length == 3) { String temperature = sensors[0]; String bulb = sensors[1]; String fan = sensors[2]; sensorValues = "Temperature:" + temperature + "C\tBulb Status:" + bulb + "\t\tFan Status:" + fan; log.info(sensorValues); DeviceController deviceController = new DeviceController(); result = deviceController.pushBamData(dataMsg.owner, FireAlarmConstants .DEVICE_TYPE, dataMsg.deviceId, System.currentTimeMillis(), "DeviceData", temperature, "TEMPERATURE"); if (!result) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Error whilst pushing temperature: " + sensorValues); return; } result = deviceController.pushBamData(dataMsg.owner, FireAlarmConstants .DEVICE_TYPE, dataMsg.deviceId, System.currentTimeMillis(), "DeviceData", bulb, "BULB"); if (!result) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Error whilst pushing Bulb data: " + sensorValues); return; } result = deviceController.pushBamData(dataMsg.owner, FireAlarmConstants .DEVICE_TYPE, dataMsg.deviceId, System.currentTimeMillis(), "DeviceData", fan, "FAN"); if (!result) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Error whilst pushing Fan data: " + sensorValues); } } else { DeviceController deviceController = new DeviceController(); result = deviceController.pushBamData(dataMsg.owner, FireAlarmConstants .DEVICE_TYPE, dataMsg.deviceId, System.currentTimeMillis(), "DeviceData", dataMsg.value, dataMsg.reply); if (!result) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Error whilst pushing sensor data: " + sensorValues); } } } catch (UnauthorizedException e) { response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR); log.error("Data Push Attempt Failed at Publisher: " + e.getMessage()); } } private boolean sendCommandViaMQTT(String deviceOwner, String deviceId, String resource, String state) throws DeviceManagementException { boolean result = false; DeviceController deviceController = new DeviceController(); try { result = deviceController.publishMqttControl(deviceOwner, FireAlarmConstants.DEVICE_TYPE, deviceId, resource, state); } catch (DeviceControllerException e) { String errorMsg = "Error whilst trying to publish to MQTT Queue"; log.error(errorMsg); throw new DeviceManagementException(errorMsg, e); } return result; } private String sendCommandViaHTTP(final String deviceIp, int deviceServerPort, String callUrlPattern, boolean fireAndForgot) throws DeviceManagementException { if (deviceServerPort == 0) { deviceServerPort = 80; } String responseMsg = ""; String urlString = URL_PREFIX + deviceIp + ":" + deviceServerPort + callUrlPattern; if (log.isDebugEnabled()) { log.debug(urlString); } if (!fireAndForgot) { HttpURLConnection httpConnection = getHttpConnection(urlString); try { httpConnection.setRequestMethod(HttpMethod.GET); } catch (ProtocolException e) { String errorMsg = "Protocol specific error occurred when trying to set method to GET" + " for:" + urlString; log.error(errorMsg); throw new DeviceManagementException(errorMsg, e); } responseMsg = readResponseFromGetRequest(httpConnection); } else { CloseableHttpAsyncClient httpclient = null; try { httpclient = HttpAsyncClients.createDefault(); httpclient.start(); HttpGet request = new HttpGet(urlString); final CountDownLatch latch = new CountDownLatch(1); Future<HttpResponse> future = httpclient.execute( request, new FutureCallback<HttpResponse>() { @Override public void completed(HttpResponse httpResponse) { latch.countDown(); } @Override public void failed(Exception e) { latch.countDown(); } @Override public void cancelled() { latch.countDown(); } }); latch.await(); } catch (InterruptedException e) { if (log.isDebugEnabled()) { log.debug("Sync Interrupted"); } } finally { try { if (httpclient != null) { httpclient.close(); } } catch (IOException e) { if (log.isDebugEnabled()) { log.debug("Failed on close"); } } } } return responseMsg; } /* Utility methods relevant to creating and sending http requests */ /* This methods creates and returns a http connection object */ private HttpURLConnection getHttpConnection(String urlString) throws DeviceManagementException { URL connectionUrl = null; HttpURLConnection httpConnection = null; 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 DeviceManagementException(errorMsg, e); } catch (IOException e) { String errorMsg = "Error occured whilst trying to open a connection to: " + connectionUrl.toString(); log.error(errorMsg); throw new DeviceManagementException(errorMsg, e); } return httpConnection; } /* This methods reads and returns the response from the connection */ private String readResponseFromGetRequest(HttpURLConnection httpConnection) throws DeviceManagementException { BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new InputStreamReader( httpConnection.getInputStream())); } catch (IOException e) { String errorMsg = "There is an issue with connecting the reader to the input stream at: " + httpConnection.getURL(); log.error(errorMsg); throw new DeviceManagementException(errorMsg, e); } String responseLine; StringBuffer completeResponse = new StringBuffer(); try { while ((responseLine = bufferedReader.readLine()) != null) { completeResponse.append(responseLine); } } catch (IOException e) { String errorMsg = "Error occured whilst trying read from the connection stream at: " + httpConnection.getURL(); log.error(errorMsg); throw new DeviceManagementException(errorMsg, e); } try { bufferedReader.close(); } catch (IOException e) { log.error( "Could not succesfully close the bufferedReader to the connection at: " + httpConnection.getURL()); } return completeResponse.toString(); } }