blob: 00de11f190cade0b234d7feafcfcd6423dc61347 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2016 Orange.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.om2m.sdt.home.netatmo.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.HttpsURLConnection;
import org.eclipse.om2m.sdt.home.netatmo.model.Event;
import org.eclipse.om2m.sdt.home.netatmo.model.Home;
import org.eclipse.om2m.sdt.home.netatmo.model.Person;
import org.eclipse.om2m.sdt.home.netatmo.model.Token;
import org.eclipse.om2m.sdt.home.netatmo.model.WeatherStation;
import org.eclipse.om2m.sdt.home.netatmo.model.WeatherStationModule;
import org.eclipse.om2m.sdt.home.netatmo.model.WelcomeCamera;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class Server {
static class Response {
int code;
JSONObject json;
String message;
}
private static final String CLIENT_ID_NAME = "client_id";
private static final String CLIENT_SECRET_NAME = "client_secret";
private static final String GRANT_TYPE_NAME = "grant_type";
private static final String USERNAME_NAME = "username";
private static final String PASSWORD_NAME = "password";
private static final String REFRESH_TOKEN = "refresh_token";
private static final String SCOPE_NAME = "scope";
private static final String HOME_ID = "home_id";
private static final String ACCESS_TOKEN = "access_token";
private static final String EXPIRE_IN = "expire_in";
private static final String SIZE = "size";
private static final String BODY = "body";
private static final String DEVICES = "devices";
private static final String HOMES = "homes";
private static final String ON = "on";
private static final String GRANT_TYPE_VALUE = "password";
private static final String GRANT_TYPE_REFRESH = "refresh_token";
private static final String NETATMO_CLOUD_BASE_URL = "https://api.netatmo.com";
private static final String OAUTH_TOKEN_URL = "/oauth2/token";
private static final String GET_HOME_DATA = "/api/gethomedata";
private static final String GET_STATIONS_DATA = "/api/getstationsdata";
private final String clientId;
private final String clientSecret;
private final String grantType;
private final String username;
private final String password;
private Token token;
public Server(String pClientId, String pClientSecret, String pUsername, String pPassword) {
clientId = pClientId;
clientSecret = pClientSecret;
username = pUsername;
password = pPassword;
grantType = GRANT_TYPE_VALUE;
}
protected Token getToken() {
synchronized (this) {
if (token == null) {
// authenticate
token = authenticate();
} else if (! token.isValid()) {
token = refreshToken(token);
}
}
return token;
}
public List<WeatherStation> getStationsData() {
List<WeatherStation> wss = new ArrayList<>();
Token currentToken = getToken();
if (currentToken == null) {
Activator.logger.info("current token is null", Server.class);
return wss;
}
try {
// retrieve weather stations data
Response resp = post(ACCESS_TOKEN + "=" + currentToken.getAccessToken(),
NETATMO_CLOUD_BASE_URL + GET_STATIONS_DATA);
if (resp.code == HttpURLConnection.HTTP_OK) {
JSONObject body = (JSONObject) resp.json.get(BODY);
for (Object deviceObject : (JSONArray) body.get(DEVICES)) {
JSONObject device = (JSONObject) deviceObject;
WeatherStation ws = (WeatherStation) retrieveWeatherStationData(device);
// for each module
JSONArray modules = (JSONArray) device.get(WeatherStation.MODULES);
for (Object module : modules) {
ws.addOrUpdateModule(retrieveWeatherStationData((JSONObject) module));
}
wss.add(ws);
}
} else { // ko
Activator.logger.info("bad getstationsdata (code=" + resp.code
+ ", message=" + resp.message + ")", Server.class);
if (resp.code == 403) {
// invalid token
currentToken.invalidateToken();
}
}
} catch (IOException e) {
Activator.logger.warning("unable to open connection", Server.class, e);
}
return wss;
}
/**
* @param deviceObject
* @return
*/
private WeatherStationModule retrieveWeatherStationData(JSONObject deviceJsonObject) {
// id + name
String id = (String) deviceJsonObject.get(WeatherStation.ID);
String name = (String) deviceJsonObject.get(WeatherStation.NAME);
WeatherStation ws = new WeatherStation(id, name);
// dashboard_data
JSONObject dashboardData = (JSONObject) deviceJsonObject.get(WeatherStation.DASHBOARD_DATA);
// datatypes
JSONArray dataTypesJsonArray = (JSONArray) deviceJsonObject.get(WeatherStation.DATA_TYPE);
for (Object dataTypeObject : dataTypesJsonArray) {
ws.addDataType((String) dataTypeObject);
}
List<String> dataTypes = ws.getDataTypes();
if (dataTypes.contains(WeatherStation.PRESSURE)) {
ws.setAbsolutePressure(getDoubleValue(dashboardData.get(WeatherStation.ABSOLUTE_PRESSURE)));
ws.setPressure(getDoubleValue(dashboardData.get(WeatherStation.PRESSURE)));
}
if (dataTypes.contains(WeatherStation.TEMPERATURE_DATA_TYPE)) {
ws.setCurrentTemperature(getDoubleValue(dashboardData.get(WeatherStation.TEMPERATURE)));
ws.setTemperatureTrend((String) dashboardData.get(WeatherStation.TEMPERATURE_TREND));
ws.setMinTemperature(getDoubleValue(dashboardData.get(WeatherStation.MIN_TEMPERATURE)));
ws.setMaxTemperature(getDoubleValue(dashboardData.get(WeatherStation.MAX_TEMPERATURE)));
}
if (dataTypes.contains(WeatherStation.HUMIDITY_DATA_TYPE)) {
ws.setHumidity((long) dashboardData.get(WeatherStation.HUMIDITY));
}
if (dataTypes.contains(WeatherStation.CO2_DATA_TYPE)) {
ws.setCo2((long) dashboardData.get(WeatherStation.CO2));
}
if (dataTypes.contains(WeatherStation.NOISE_DATA_TYPE)) {
ws.setNoise((long) dashboardData.get(WeatherStation.NOISE));
}
// date
ws.setDate((long) dashboardData.get(WeatherStation.DATE));
return ws;
}
public List<Home> getHomeData(String homeId, Long eventSize) {
Token currentToken = getToken();
List<Home> homes = new ArrayList<>();
if (currentToken == null) {
Activator.logger.info("current token is null");
return homes;
}
try {
StringBuffer sb = new StringBuffer();
// access token - mandatory
sb.append(ACCESS_TOKEN).append("=").append(currentToken.getAccessToken());
// homeId - optional
if (homeId != null) {
sb.append("&").append(HOME_ID).append("=").append(homeId);
}
// size (nb of events) - optional
if (eventSize != null) {
sb.append("&").append(SIZE).append("=").append(eventSize);
}
Response resp = post(sb.toString(), NETATMO_CLOUD_BASE_URL + GET_HOME_DATA);
if (resp.code != HttpURLConnection.HTTP_OK) {
Activator.logger.info("bad gethomedata (code=" + resp.code
+ ", message=" + resp.message + ")", Server.class);
if (resp.code == 403) {
// invalid token
currentToken.invalidateToken();
}
return homes;
}
// retrieve body
JSONObject body = (JSONObject) resp.json.get(BODY);
// retrieve homes
JSONArray homesJson = (JSONArray) body.get(HOMES);
if ((homesJson == null) || homesJson.isEmpty()) {
Activator.logger.info("no homes !!!!", Server.class);
return homes;
}
for (Object homeObject : homesJson) {
// each object is a JSONObject
JSONObject jsHome = (JSONObject) homeObject;
// create home
Home home = new Home((String) jsHome.get(Home.ID), (String) jsHome.get(Home.NAME));
homes.add(home);
// parse persons
JSONArray personsJsonArray = (JSONArray) jsHome.get(Home.PERSONS);
if ((personsJsonArray == null) || personsJsonArray.isEmpty()) {
Activator.logger.info("no persons into " + home, Server.class);
} else {
for (Object personObject : personsJsonArray) {
JSONObject jsPerson = (JSONObject) personObject;
Person person = new Person((String) jsPerson.get(Person.ID),
(String) jsPerson.get(Person.PSEUDO));
person.setLastSeen((Long) jsPerson.get(Person.LAST_SEEN));
person.setOutOfSight((Boolean) jsPerson.get(Person.OUT_OF_SIGHT));
home.addOrUpdatePerson(person);
}
}
// parse cameras
JSONArray camerasJsonArray = (JSONArray) jsHome.get(Home.CAMERAS);
if ((camerasJsonArray == null) || camerasJsonArray.isEmpty()) {
Activator.logger.info("no cameras into " + home, Server.class);
} else {
for (Object cameraObject : camerasJsonArray) {
JSONObject jsCam = (JSONObject) cameraObject;
WelcomeCamera camera = new WelcomeCamera(
(String) jsCam.get(WelcomeCamera.ID),
(String) jsCam.get(WelcomeCamera.TYPE),
(String) jsCam.get(WelcomeCamera.NAME));
camera.setAlimOk(ON.equals((String) jsCam.get(WelcomeCamera.ALIM_STATUS)));
camera.setIsOn(ON.equals((String) jsCam.get(WelcomeCamera.STATUS)));
camera.setSdOk(ON.equals((String) jsCam.get(WelcomeCamera.SD_STATUS)));
camera.setVpnUrl((String) jsCam.get(WelcomeCamera.VPN_URL));
home.addOrUpdateCamera(camera);
}
}
// parse events
List<Event> events = new ArrayList<>();
JSONArray eventsJsonArray = (JSONArray) jsHome.get(Home.EVENTS);
if ((eventsJsonArray == null) || eventsJsonArray.isEmpty()) {
Activator.logger.info("no events into " + home, Server.class);
} else {
for (Object eventObject : eventsJsonArray) {
JSONObject jsEvent = (JSONObject) eventObject;
Event event = new Event((String) jsEvent.get(Event.ID),
(String) jsEvent.get(Event.TYPE),
(Long) jsEvent.get(Event.TIME),
(String) jsEvent.get(Event.CAMERA_ID),
(String) jsEvent.get(Event.PERSON_ID));
events.add(event);
}
}
home.setEvents(events);
}
} catch (IOException e) {
Activator.logger.warning("unable to open connection", Server.class, e);
}
return homes;
}
/**
* Authenticate request
*
* @return a valid Token
*/
private Token authenticate() {
try {
StringBuffer sb = new StringBuffer();
// client id
sb.append(CLIENT_ID_NAME).append("=").append(clientId);
// client secret
sb.append("&").append(CLIENT_SECRET_NAME).append("=").append(clientSecret);
// grant type
sb.append("&").append(GRANT_TYPE_NAME).append("=").append(grantType);
// username
sb.append("&").append(USERNAME_NAME).append("=").append(username);
// password
sb.append("&").append(PASSWORD_NAME).append("=").append(password);
// scope
sb.append("&").append(SCOPE_NAME).append("=").append("read_camera+access_camera+read_station");
Response resp = post(sb.toString(), NETATMO_CLOUD_BASE_URL + OAUTH_TOKEN_URL);
if (resp.code == HttpURLConnection.HTTP_OK) {
Token token = new Token((String) resp.json.get(ACCESS_TOKEN),
(String) resp.json.get(REFRESH_TOKEN), (Long) resp.json.get(EXPIRE_IN));
Activator.logger.info("received token=" + token.toString());
return token;
} else {
Activator.logger.info("bad auth (code=" + resp.code + ", message=" + resp.message + ")",
Server.class);
}
} catch (IOException e) {
Activator.logger.warning("unable to open connection: " + e.getMessage(), Server.class);
}
return null;
}
private Token refreshToken(Token toBeRefreshed) {
try {
StringBuffer sb = new StringBuffer();
// client id
sb.append(CLIENT_ID_NAME).append("=").append(clientId);
// client secret
sb.append("&").append(CLIENT_SECRET_NAME).append("=").append(clientSecret);
// grant type
sb.append("&").append(GRANT_TYPE_NAME).append("=").append(GRANT_TYPE_REFRESH);
// refreshToken
sb.append("&").append(REFRESH_TOKEN).append("=").append(token.getRefreshToken());
Response resp = post(sb.toString(), NETATMO_CLOUD_BASE_URL + OAUTH_TOKEN_URL);
if (resp.code == HttpURLConnection.HTTP_OK) {
Token token = new Token((String) resp.json.get(ACCESS_TOKEN),
(String) resp.json.get(REFRESH_TOKEN), (Long) resp.json.get(EXPIRE_IN));
Activator.logger.info("received token=" + token.toString());
return token;
} else {
Activator.logger.info("bad auth (code=" + resp.code + ", message=" + resp.message + ")",
Server.class);
}
} catch (IOException e) {
Activator.logger.warning("unable to open connection", Server.class, e);
}
return null;
}
private static final Double getDoubleValue(final Object object) {
if (object == null) {
return null;
}
if (object instanceof Long) {
return ((Long) object).doubleValue();
}
// double
return (object instanceof Double) ? (Double) object : null;
}
private Response post(String data, String url) throws IOException {
HttpURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.getOutputStream().write(data.getBytes());
connection.connect();
Response resp = new Response();
resp.code = connection.getResponseCode();
if (resp.code == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
try {
resp.json = (JSONObject) new JSONParser().parse(br);
} catch (ParseException e) {
throw new IOException(e);
}
} else {
resp.message = connection.getResponseMessage();
}
connection.disconnect();
return resp;
}
}