diff --git a/build.gradle b/build.gradle index f6d7a58..d17e623 100644 --- a/build.gradle +++ b/build.gradle @@ -16,14 +16,9 @@ repositories { dependencies { // Tests testCompile group: 'junit', name: 'junit', version: '4.12' -// implementation 'junit:junit:4.12' - - // GSON but works even without it anyway -// compile "com.google.code.gson:gson:2.8.4" // Database compile "org.xerial:sqlite-jdbc:3.21.0.1" -// compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.12' // Rest request compile 'org.apache.httpcomponents:httpclient:4.5.6' @@ -36,26 +31,19 @@ dependencies { // Server Spark compile "com.sparkjava:spark-core:2.7.2" -// compile 'org.apache.commons:commons-lang3:3.4' -// compile group: 'org.eclipse.jetty', name: 'jetty-webapp', version: '9.4.11.v20180605' -// compile 'org.eclipse.jetty:jetty-client:9.4.11.v20180605' -// compile 'org.eclipse.jetty.websocket:websocket-server:9.4.11.v20180605' // Oauth +// compile group: 'org.pac4j', name: 'spark-pac4j', version: '2.3.0' //todo use this insted? compile ( group: 'com.google.oauth-client', name: 'google-oauth-client-jetty', version: '1.23.0') { // don't pull in an old ancient jetty version - // todo it doesn't seems to work exclude group: 'org.mortbay.jetty', module: 'jetty' exclude group: 'org.mortbay.jetty', module: 'jetty-util' exclude group: 'org.mortbay.jetty', module: 'servlet-api' } -// compile group: 'org.mortbay.jetty', name: 'jetty', version: '7.0.0.pre5' -// compile group: 'org.mortbay.jetty', name: 'servlet-api', version: '3.0.20100224' compile 'com.google.api-client:google-api-client:1.23.0' // DialogFlow - compile "ai.api:libai:1.6.12" -// compile 'com.google.cloud:google-cloud-dialogflow:0.59.0-alpha' // for the v2 of dialogflow + compile group: "ai.api", name:"libai", version:"1.6.12" // objectMapper for fitbitdata compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5' // maybe duplicate in google-api diff --git a/src/main/java/device/DialogFlowWebHook.java b/src/main/java/device/DialogFlowWebHook.java index 8bdc3f9..2fc740e 100644 --- a/src/main/java/device/DialogFlowWebHook.java +++ b/src/main/java/device/DialogFlowWebHook.java @@ -4,6 +4,7 @@ import ai.api.GsonFactory; import ai.api.model.AIResponse; import ai.api.model.Fulfillment; import com.google.gson.Gson; +import com.google.gson.JsonElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,7 +79,7 @@ public class DialogFlowWebHook { try { log.info("AZIONE: "+input.getResult().getAction()); Action action = actions.get(input.getResult().getAction()); - text = action.doAction(); + text = action.doAction(input.getResult().getParameters()); } catch (NullPointerException e) { log.info("NESSUNA AZIONE TROVATA"); text = ERROR; @@ -102,8 +103,10 @@ public class DialogFlowWebHook { /** * Fai l'azione desiderata. * Se ritorna una stringa allora il testo viene cambiato. Se ritorna null non cambia il testo + * + * @param params a map containing all the parameters passed form the request * @return Una stringa che verra' usata come messaggio o null se non si vuole */ - String doAction(); + String doAction(Map params); } } diff --git a/src/main/java/device/Fitbit.java b/src/main/java/device/Fitbit.java index 539d49e..174996c 100644 --- a/src/main/java/device/Fitbit.java +++ b/src/main/java/device/Fitbit.java @@ -3,6 +3,7 @@ package device; import java.io.IOException; import java.util.Calendar; import java.util.HashMap; +import java.util.List; import java.util.Map; import device.fitbitdata.HeartRate; @@ -74,9 +75,8 @@ public class Fitbit { * @return un intero rappresentante i passi effettuati * @throws IOException nel caso la richiesta non vada a buon fine */ - public int getSteps() throws IOException { - if (shouldUpdateFor(Steps.class)) - steps = auth.run(BASIC_URL + "1" + USER + "activities/steps/date/today/1w.json", Steps.class); + public synchronized int getSteps() throws IOException { + steps = update(Steps.class, steps, "1" + USER + "activities/steps/date/today/1w.json"); return steps.getSteps(); } @@ -87,9 +87,10 @@ public class Fitbit { * @return un intero rappresentante la media del battito cardiaco degli ultimi 15 minuti * @throws IOException nel caso la richiesta non vada a buon fine */ - public double getHeartRate() throws IOException { + public synchronized double getHeartRate() throws IOException { return getHeartRate(15); } + /** * Ricevi il battito cardiaco dell'utente
* Il risultato e' una media del battito che l'utente ha avuto negli ultimi minuti @@ -98,22 +99,19 @@ public class Fitbit { * @return un intero rappresentante la media del battito cardiaco degli ultimi minuti specificati * @throws IOException nel caso la richiesta non vada a buon fine */ - public double getHeartRate(int lastMinutes) throws IOException { + public synchronized double getHeartRate(int lastMinutes) throws IOException { if(lastMinutes<=0) return -1; - if (shouldUpdateFor(HeartRate.class)) { - long currentMillisec = System.currentTimeMillis(); - String now = getHourMinutes(currentMillisec); - String ago = getHourMinutes(currentMillisec - (MINUTE * lastMinutes)); + long currentMillisec = System.currentTimeMillis(); - if (now.compareTo(ago) < 0) - ago = "00:00"; + String now = getHourMinutes(currentMillisec); + String ago = getHourMinutes(currentMillisec - (MINUTE * lastMinutes)); - heart = auth.run( - BASIC_URL + "1" + USER + "activities/heart/date/today/1d/1sec/time/" + ago + "/" + now + ".json", - HeartRate.class); - } + if (now.compareTo(ago) < 0) + ago = "00:00"; + + heart = update(HeartRate.class, heart,"1" + USER + "activities/heart/date/today/1d/1sec/time/" + ago + "/" + now + ".json"); return heart.getAverage(); } @@ -123,33 +121,48 @@ public class Fitbit { * @return un intero rappresentante le ore passate a dormire * @throws IOException nel caso la richiesta non vada a buon fine */ - public Object getHoursSleep() throws IOException { - if (shouldUpdateFor(Sleep.class)) - sleep = auth.run(BASIC_URL + "1.2" + USER + "sleep/date/today.json", Sleep.class); - return sleep.getMinutesAsleep(); + public synchronized long getHoursSleep() throws IOException { + sleep = update(Sleep.class, sleep,"1.2" + USER + "sleep/date/today.json"); + return sleep.getMinutesAsleep()/60; } /** - * Semplice classe che controlla che si possa fare l'update o meno di una specifica classe
- * Se e' possibile fare l'update inserisce la classe nella mappa
- * In questo modo se questa funzione viene chiamata una seconda volta con lo stesso parametro restituira' falso
- * a meno che non si aspetti 5 minuti - * - * @param type la classe da fare l'update - * @return vero se si puo' fare l'update + * Ricevi tutti i dati presenti per il sonno di questo giorno. + * La lista contiene per ogni volta che l'utente ha dormito:
+ * - la data di quando si e' addormentato
+ * - la durata del sonno
+ * - la data di fine
+ * @return una lista contenente ogni volta che l'utente ha dormito + * @throws IOException */ - private boolean shouldUpdateFor(Class type) { + public synchronized List getDetailedSleep() throws IOException { + sleep = update(Sleep.class, sleep,"1.2" + USER + "sleep/date/today.json"); + return sleep.getDatas(); + } + + /** + * Semplice funzione che controlla che si possa fare l'update o meno di una specifica classe
+ * Se e' possibile fare l'update viene mandata una run all'url selezionato e viene ritornata la variabile aggiornata
+ * Altrimenti viene ritornata la variabile passata + * + * @param varClass la classe della variabile passata + * @param variable la variabile che vede fare l'update + * @param url l'url da cui prende i dati aggiornati + * @return la variabile aggiornata + */ + private T update(Class varClass, T variable, String url) throws IOException { try { long current = System.currentTimeMillis(); - long latest = latestRequest.get(type); + long latest = latestRequest.get(varClass); + // don't update if(current - latest < MINUTE * 5) - return false; + return variable; } catch (NullPointerException e) { + // do nothing and update } - - latestRequest.put(heart.getClass(), System.currentTimeMillis()); - return true; + latestRequest.put(varClass, System.currentTimeMillis()); + return auth.run(BASIC_URL + url, varClass); } /** diff --git a/src/main/java/device/Hue.java b/src/main/java/device/Hue.java index 8e83c16..560b0a3 100644 --- a/src/main/java/device/Hue.java +++ b/src/main/java/device/Hue.java @@ -1,8 +1,10 @@ package device; +import java.util.HashMap; import java.util.Map; import java.util.Set; +import ai.api.GsonFactory; import support.Rest; /** @@ -13,7 +15,27 @@ public class Hue { /** * La luminopsita' massima a cui si puo' arrivare */ - public static final int MAX_BRIGHTNESS = 255; + public static final int MAX_BRIGHTNESS = 254; + + /** + * Una mappa che ad ogni colore (in lingua ita) assegna il proprio valore in hue + */ + public static final Map COLORS = new HashMap<>(); + + // todo set right colors + static { + COLORS.put("giallo", new Double[]{0.55, 0.45}); + COLORS.put("rosso", new Double[]{0.7, 0.25}); + COLORS.put("verde", new Double[]{0.15, 0.65}); + COLORS.put("blu", new Double[]{0.0, 0.0}); + COLORS.put("rosa", new Double[]{0.45, 0.15}); + COLORS.put("viola", new Double[]{0.25, .1}); + COLORS.put("azzurro", new Double[]{0.15, 0.25}); + COLORS.put("arancione", new Double[]{0.63, 0.35}); + //COLORS.put("nero", new Double[]{1.0, 1.0}); + COLORS.put("bianco", new Double[]{0.35, 0.3}); + } + //private String baseURL = "192.168.0.2"; //private String username = "C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx"; /** @@ -38,14 +60,13 @@ public class Hue { this("172.30.1.138", "C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx"); } - //todo maybe the key is the user, but who knows? /** - * Cerca le luci Philips Hue nell'indirizzo specificato e con la chiave specificata + * Cerca le luci Philips Hue nell'indirizzo specificato e con l'utente specificato * @param ip l'indirizzo IP - * @param key la chiuave utililzzata + * @param user l'utente */ - public Hue(String ip, String key) { - this("http://" + ip + "/api/" + key + "/lights/"); + public Hue(String ip, String user) { + this("http://" + ip + "/api/" + user + "/lights/"); } /** @@ -61,7 +82,7 @@ public class Hue { /** * Ritorna un insieme contenente tutti i nomi delle luci che si sono trovate - * + * * @return l'insieme dei nomi delle luci */ public Set getNameLights() { @@ -82,22 +103,14 @@ public class Hue { * Accende tutte le luci controllate */ public void turnOn() { - for (String light : allLights.keySet()) { - String callURL = lightsURL + light + "/state"; - String body = "{ \"on\" : true }"; - Rest.put(callURL, body, "application/json"); - } + setState("on", "true"); } /** * Spegne tutte le luci controllate */ public void turnOff() { - for (String light : allLights.keySet()) { - String callURL = lightsURL + light + "/state"; - String body = "{ \"on\" : false }"; - Rest.put(callURL, body, "application/json"); - } + setState("on", "false"); } /** @@ -115,11 +128,7 @@ public class Hue { public void setBrightness(int num) { if (num<0) num=0; - for (String light : allLights.keySet()) { - String callURL = lightsURL + light + "/state"; - String body = "{ \"bri\" : "+num+" }"; - Rest.put(callURL, body, "application/json"); - } + setState("bri", String.valueOf(num)); brightness = num; } @@ -161,25 +170,27 @@ public class Hue { decreaseBrightness(10); } + public void changeColor(String colorName) { + String hueColor = GsonFactory.getDefaultFactory().getGson().toJson(COLORS.get(colorName)); + setState("xy", hueColor); + } + /** * Modifica il colore delle luci in modo da fare un bel effetto arcobaleno continuo */ public void colorLoop() { - for (String light : allLights.keySet()) { - String callURL = lightsURL + light + "/state"; - String body = "{ \"on\" : true, \"effect\" : \"colorloop\" }"; - Rest.put(callURL, body, "application/json"); - } + setState("effect", "colorloop"); } /** - * Funzione generale per poter utilizzare qualunque valore, ma non funziona + * Funzione generale per poter utilizzare qualunque valore, ma non funziona
+ * Da testare, visto che mi sembra strano che non funzi... + * e invece funziona. */ - /*public void setAttribute(String attribute, String value){ - for (String light : allLights.keySet()) { - String callURL = lightsURL + light + "/state"; - String body = "{ \""+attribute+"\" : "+value+" }"; - Rest.put(callURL, body, "application/json"); - } - }*/ + public void setState(String attribute, String value){ + for (String light : allLights.keySet()) + Rest.put(lightsURL + light + "/state", + "{ \"" + attribute + "\" : " + value + " }", + "application/json"); + } } diff --git a/src/main/java/device/fitbitdata/Sleep.java b/src/main/java/device/fitbitdata/Sleep.java index 5c2eb03..26fa0be 100644 --- a/src/main/java/device/fitbitdata/Sleep.java +++ b/src/main/java/device/fitbitdata/Sleep.java @@ -3,6 +3,11 @@ package device.fitbitdata; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) @@ -10,12 +15,47 @@ public class Sleep { private int minutesAsleep; + private List datas; + public int getMinutesAsleep() { return minutesAsleep; } + public List getDatas() { + return datas; + } + @JsonProperty("summary") public void setMinutesAsleep(Map map) { minutesAsleep = (int) map.get("totalMinutesAsleep"); } + + @JsonProperty("sleep") + public void setSleepsList(Map[] array) { + datas = new ArrayList<>(); + SimpleDateFormat sdf = new SimpleDateFormat(); + for(Map map : array) { + Date date_start = null; + try { + date_start = sdf.parse((String) map.get("startTime")); + } catch (ParseException e) { + e.printStackTrace(); + } + int duration = (int) map.get("duration"); + + datas.add(new SleepData(date_start, duration)); + } + } + + public class SleepData { + public final Date start_date; + public final long duration; + public final Date end_date; + + public SleepData(Date start_date, long duration) { + this.start_date = start_date; + this.duration = duration; + this.end_date = start_date!=null? new Date(start_date.getTime() + duration):null; + } + } } diff --git a/src/main/java/main/Main.java b/src/main/java/main/Main.java index ac0e471..220758b 100644 --- a/src/main/java/main/Main.java +++ b/src/main/java/main/Main.java @@ -1,10 +1,14 @@ package main; +import ai.api.GsonFactory; import device.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import support.DBConnect; +import support.Database; +import java.io.IOException; + +// todo docs /** * Created by 20015159 on 28/08/2018. */ @@ -13,66 +17,76 @@ public class Main { /** * Un Logger per capire meglio quali pezzi vengono eseguiti e quali no. */ - private static Logger log = LoggerFactory.getLogger("SeniorAssistant"); + private static final Logger LOG = LoggerFactory.getLogger("SeniorAssistant"); + + private static Hue lights; + private static Fitbit fitbit; + private static Sensor sensor; + + private static Database database; /** * Funzione principale, qui si creano tutte le classi che verranno utilizzate. * @param args per ora nulla, ma forse in futuro si potrebbe mettere roba */ public static void main(String[] args) { - log.info("Connecting to hue lights"); - Hue lights = new Hue(); - log.info("Connecting to the sensors"); - Sensor sensor = new Sensor(); + LOG.info("Connecting to hue lights"); + lights = new Hue("localhost:8090", "newdeveloper"); + + LOG.info("Connecting to the sensors"); + sensor = new Sensor(); try { - log.info("Connecting to Fitbit"); - Fitbit fitbit = new Fitbit(); - startInsertData(fitbit); // add here functions associated with fitbit - } catch (Exception e) { // in this way the program will continue without fitbit + LOG.info("Connecting to Database"); + database = Database.getInstance(); + + LOG.info("Connecting to Fitbit"); + fitbit = new Fitbit(); + + startInsertData(); + // add here functions associated with fitbit + } catch (Exception e) { // in this way the program will continue even without fitbit e.printStackTrace(); } - startWebhook(lights); + startWebhook(); } /** * Fa partire il server Webhook per DialogFlow e continua l'esecuzione - * @param hue Le luci che vengono modificate a seconda delle richieste dell'utente */ - private static void startWebhook(Hue hue) { - log.info("Adding actions to Webhook"); + private static void startWebhook() { + LOG.info("Adding actions to Webhook"); DialogFlowWebHook df = new DialogFlowWebHook(); - df.addOnAction("LightsON", () -> {hue.turnOn(); return "Luci accese";}); - df.addOnAction("LightsOFF", () -> {hue.turnOff(); return "Luci spente";}); + df.addOnAction("LightsON", (params) -> {lights.turnOn(); return null;}); + df.addOnAction("LightsOFF", (params) -> {lights.turnOff(); return null;}); + df.addOnAction("ColorLoop", (params) -> {lights.colorLoop(); return null;}); + df.addOnAction("ChangeColor", (params) -> {lights.changeColor(params.get("color").getAsString()); return null;}); - log.info("Starting Webhook"); + LOG.info("Starting Webhook"); df.startServer(); } /** - * Cose da fare in questa funzione: - * - far partire il database - * - ogni ora aggiornare i dati del cuore. (Runnable che gira da se') - * - alla fine della giornata fare un riepilogo del paziente (Runnable che gira da se') - * (magari ci si calcola quando bisogna risvegliarsi e si mette un wait) - * @param fibit da dove prende i dati + * Gestione DB in modo che si aggiorni ogni ora */ - private static void startInsertData(Fitbit fibit) { - log.info("Connecting to DB to write fitbit data periodically"); - /* + private static void startInsertData() { + LOG.info("Connecting to DB to write fitbit data periodically"); try { - Connection conn = DBConnect.getInstance().getConnection(); - PreparedStatement st = conn.prepareStatement(""); + fitbit.getHoursSleep(); - ResultSet rs = st.executeQuery(); - conn.close(); + Database database = Database.getInstance(); - } catch (SQLException e) { + Thread hourlyData = new Thread(database.insertHourlyData(fitbit), "updating-hour-data"); + Thread dailyData = new Thread(database.insertDailyData(fitbit), "updating-day-data"); + + hourlyData.start(); + dailyData.start(); + LOG.info("Threads started for updating database"); + } catch (IOException e) { e.printStackTrace(); } - */ } /* @@ -86,7 +100,7 @@ public class Main { int brightness = sensor.getBrightnessLevel(); // AUTOMATIC - // Gestione DB in modo che si aggiorni ogni ora + /X/ Gestione DB in modo che si aggiorni ogni ora // Gestione luci in modo che la luminosità sia sempre la stessa // Gestione luci a seconda del battito cardiaco // Ad una certa ora guarda i passi e se sono pochi dillo @@ -94,7 +108,7 @@ public class Main { // USER-INTERACTION // Dati del sonno/battito/passi che l'utente puo' richiedere - // Gestione luci secondo le esigenze dell'utente + /X/ Gestione luci secondo le esigenze dell'utente ( settare Dialogflow e server + risolvere bug ) // EXTRA Gestione musica tramite comando vocale // Randomly at night heavy metal start diff --git a/src/main/java/oauth/AuthFitbit.java b/src/main/java/oauth/AuthFitbit.java index c7e385a..829c7b4 100644 --- a/src/main/java/oauth/AuthFitbit.java +++ b/src/main/java/oauth/AuthFitbit.java @@ -3,12 +3,12 @@ package oauth; import java.io.IOException; import java.util.Arrays; +import ai.api.GsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; -import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; import com.google.api.client.http.BasicAuthentication; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; @@ -22,10 +22,15 @@ import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.store.DataStoreFactory; import com.google.api.client.util.store.FileDataStoreFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +//todo add docs public class AuthFitbit { + private static final Logger LOG = LoggerFactory.getLogger("Fitbit Response"); + private final HttpRequestFactory requestFactory; public AuthFitbit() throws Exception { @@ -114,6 +119,10 @@ public class AuthFitbit { ); response.disconnect(); + + // todo remove this, it's only useful if you need to see the request + LOG.info(GsonFactory.getDefaultFactory().getGson().toJson(ret)); + return ret; /**/ } diff --git a/src/main/java/oauth/FITBITUrl.java b/src/main/java/oauth/FITBITUrl.java index b29bacd..b4fc9a5 100644 --- a/src/main/java/oauth/FITBITUrl.java +++ b/src/main/java/oauth/FITBITUrl.java @@ -3,6 +3,7 @@ package oauth; import com.google.api.client.http.GenericUrl; import com.google.api.client.util.Key; +// todo add docs public class FITBITUrl extends GenericUrl { @Key diff --git a/src/main/java/oauth/LocalServerReceiver.java b/src/main/java/oauth/LocalServerReceiver.java new file mode 100644 index 0000000..73bdfb2 --- /dev/null +++ b/src/main/java/oauth/LocalServerReceiver.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2012 Google Inc. + * + * 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. + */ + +// todo test if fitbit request still works with this class + +package oauth; + +import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver; +import com.google.api.client.util.Throwables; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.concurrent.Semaphore; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * OAuth 2.0 verification code receiver that runs a Jetty server on a free port, waiting for a + * redirect with the verification code. + * + *

+ * Implementation is thread-safe. + *

+ * + * @since 1.11 + * @author Yaniv Inbar + */ +public final class LocalServerReceiver implements VerificationCodeReceiver { + + private static final String LOCALHOST = "localhost"; + + private static final String CALLBACK_PATH = "/Callback"; + + /** Server or {@code null} before {@link #getRedirectUri()}. */ + private Server server; + + /** Verification code or {@code null} for none. */ + String code; + + /** Error code or {@code null} for none. */ + String error; + + /** To block until receiving an authorization response or stop() is called. */ + final Semaphore waitUnlessSignaled = new Semaphore(0 /* initially zero permit */); + + /** Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. */ + private int port; + + /** Host name to use. */ + private final String host; + + /** Callback path of redirect_uri */ + private final String callbackPath; + + /** + * URL to an HTML page to be shown (via redirect) after successful login. If null, a canned + * default landing page will be shown (via direct response). + */ + private String successLandingPageUrl; + + /** + * URL to an HTML page to be shown (via redirect) after failed login. If null, a canned + * default landing page will be shown (via direct response). + */ + private String failureLandingPageUrl; + + /** + * Constructor that starts the server on {@link #LOCALHOST} and an unused port. + * + *

+ * Use {@link Builder} if you need to specify any of the optional parameters. + *

+ */ + public LocalServerReceiver() { + this(LOCALHOST, -1, CALLBACK_PATH, null, null); + } + + /** + * Constructor. + * + * @param host Host name to use + * @param port Port to use or {@code -1} to select an unused port + */ + LocalServerReceiver(String host, int port, + String successLandingPageUrl, String failureLandingPageUrl) { + this(host, port, CALLBACK_PATH, successLandingPageUrl, failureLandingPageUrl); + } + + /** + * Constructor. + * + * @param host Host name to use + * @param port Port to use or {@code -1} to select an unused port + */ + LocalServerReceiver(String host, int port, String callbackPath, + String successLandingPageUrl, String failureLandingPageUrl) { + this.host = host; + this.port = port; + this.callbackPath = callbackPath; + this.successLandingPageUrl = successLandingPageUrl; + this.failureLandingPageUrl = failureLandingPageUrl; + } + + @Override + public String getRedirectUri() throws IOException { + server = new Server(port != -1 ? port : 0); + //Connector connector = server.getConnectors()[0]; + //connector.setHost(host); + server.setHandler(new CallbackHandler()); + try { + server.start(); + //port = connector.getLocalPort(); + } catch (Exception e) { + Throwables.propagateIfPossible(e); + throw new IOException(e); + } + return "http://" + host + ":" + port + callbackPath; + } + + /** + * Blocks until the server receives a login result, or the server is stopped + * by {@link #stop()}, to return an authorization code. + * + * @return authorization code if login succeeds; may return {@code null} if the server + * is stopped by {@link #stop()} + * @throws IOException if the server receives an error code (through an HTTP request + * parameter {@code error}) + */ + @Override + public String waitForCode() throws IOException { + waitUnlessSignaled.acquireUninterruptibly(); + if (error != null) { + throw new IOException("User authorization failed (" + error + ")"); + } + return code; + } + + @Override + public void stop() throws IOException { + waitUnlessSignaled.release(); + if (server != null) { + try { + server.stop(); + } catch (Exception e) { + Throwables.propagateIfPossible(e); + throw new IOException(e); + } + server = null; + } + } + + /** Returns the host name to use. */ + public String getHost() { + return host; + } + + /** + * Returns the port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. + */ + public int getPort() { + return port; + } + + /** + * Returns callback path used in redirect_uri. + */ + public String getCallbackPath() { + return callbackPath; + } + + /** + * Builder. + * + *

+ * Implementation is not thread-safe. + *

+ */ + public static final class Builder { + + /** Host name to use. */ + private String host = LOCALHOST; + + /** Port to use or {@code -1} to select an unused port. */ + private int port = -1; + + private String successLandingPageUrl; + private String failureLandingPageUrl; + + private String callbackPath = CALLBACK_PATH; + + /** Builds the {@link LocalServerReceiver}. */ + public LocalServerReceiver build() { + return new LocalServerReceiver(host, port, callbackPath, + successLandingPageUrl, failureLandingPageUrl); + } + + /** Returns the host name to use. */ + public String getHost() { + return host; + } + + /** Sets the host name to use. */ + public Builder setHost(String host) { + this.host = host; + return this; + } + + /** Returns the port to use or {@code -1} to select an unused port. */ + public int getPort() { + return port; + } + + /** Sets the port to use or {@code -1} to select an unused port. */ + public Builder setPort(int port) { + this.port = port; + return this; + } + + /** Returns the callback path of redirect_uri */ + public String getCallbackPath() { + return callbackPath; + } + + /** Set the callback path of redirect_uri */ + public Builder setCallbackPath(String callbackPath) { + this.callbackPath = callbackPath; + return this; + } + + public Builder setLandingPages(String successLandingPageUrl, String failureLandingPageUrl) { + this.successLandingPageUrl = successLandingPageUrl; + this.failureLandingPageUrl = failureLandingPageUrl; + return this; + } + } + + /** + * Jetty handler that takes the verifier token passed over from the OAuth provider and stashes it + * where {@link #waitForCode} will find it. + */ + class CallbackHandler extends AbstractHandler { + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + if (!CALLBACK_PATH.equals(target)) { + return; + } + + try { + ((Request) request).setHandled(true); + error = request.getParameter("error"); + code = request.getParameter("code"); + + if (error == null && successLandingPageUrl != null) { + response.sendRedirect(successLandingPageUrl); + } else if (error != null && failureLandingPageUrl != null) { + response.sendRedirect(failureLandingPageUrl); + } else { + writeLandingHtml(response); + } + response.flushBuffer(); + } + finally { + waitUnlessSignaled.release(); + } + } + + private void writeLandingHtml(HttpServletResponse response) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("text/html"); + + PrintWriter doc = response.getWriter(); + doc.println(""); + doc.println("OAuth 2.0 Authentication Token Received"); + doc.println(""); + doc.println("Received verification code. You may now close this window."); + doc.println(""); + doc.println(""); + doc.flush(); + } + } +} diff --git a/src/main/java/oauth/OAuth2ClientCredentials.java b/src/main/java/oauth/OAuth2ClientCredentials.java index 12db501..c4b1444 100644 --- a/src/main/java/oauth/OAuth2ClientCredentials.java +++ b/src/main/java/oauth/OAuth2ClientCredentials.java @@ -1,5 +1,6 @@ package oauth; +// todo add docs public class OAuth2ClientCredentials { /** Value of the "API Key". */ diff --git a/src/main/java/support/DBConnect.java b/src/main/java/support/DBConnect.java deleted file mode 100644 index 9d67509..0000000 --- a/src/main/java/support/DBConnect.java +++ /dev/null @@ -1,40 +0,0 @@ -package support; - -import java.sql.*; - -/** - * Handle the connection to the SQLite database that stores our tasks. - * @author Luigi De Russis - * @version 1.1 (06/05/2018) - */ -public class DBConnect { - - // todo add a db where we put daily (or hourly, but only for heart) updates - public static final String DB_LOCATION = "jdbc:sqlite:src/main/resources/"; - public static final String DB_NAME = "user_data.db"; - - private static DBConnect instance; - - private final Connection conn; - - private DBConnect() throws SQLException { - conn = DriverManager.getConnection(DB_LOCATION + DB_NAME); - buildTablesIfNotExisting(); - } - - public static DBConnect getInstance() { - try { - instance = instance==null? new DBConnect():instance; - } catch (SQLException e) { - e.printStackTrace(); - } - return instance; - } - - private void buildTablesIfNotExisting() throws SQLException { - Statement statement = conn.createStatement(); - // todo working, but not quite well - statement.execute("CREATE TABLE IF NOT EXISTS user (user VARCHAR(16) PRIMARY KEY, name VARCHAR(16), birthday DATE);"); - statement.execute("CREATE TABLE IF NOT EXISTS heart_rate (date DATE, rate DOUBLE, user VARCHAR(16), PRIMARY KEY(date, user));"); - } -} diff --git a/src/main/java/support/Database.java b/src/main/java/support/Database.java new file mode 100644 index 0000000..feef523 --- /dev/null +++ b/src/main/java/support/Database.java @@ -0,0 +1,150 @@ +package support; + +import device.Fitbit; +import device.fitbitdata.Sleep; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +// todo add docs +/** + * Handle the connection to the SQLite database that stores our tasks. + * @author Luigi De Russis + * @version 1.1 (06/05/2018) + */ +public class Database { + + public static final String DB_LOCATION = "jdbc:sqlite:src/main/resources/"; + public static final String DB_NAME = "user_data.db"; + + private static final Calendar CALENDAR = Calendar.getInstance(); + private static final Logger LOG = LoggerFactory.getLogger(Database.class); + + private static Database instance; + + private final Connection conn; + + /** + * Crea una connessione al Database specificato in DB_LOCATION e con il nome DB_NAME. + * Se il Database non esiste lo crea e inizializza anche delle tabelle: + *
    + *
  • 'total' contiene i dati utente giorno per giorno.
  • + *
  • 'heart' battito + orario
  • + *
  • 'sleep' inizio + fine + durata
  • + *
+ * @throws SQLException se qualcosa e' andato storto + */ + private Database() throws SQLException { + CALENDAR.setTimeInMillis(System.currentTimeMillis()); + conn = DriverManager.getConnection(DB_LOCATION + DB_NAME); + Statement statement = conn.createStatement(); + statement.execute("CREATE TABLE IF NOT EXISTS total (day DATE PRIMARY KEY, sleep_time INTEGER, heart_rate DOUBLE, steps INTEGER)"); + statement.execute("CREATE TABLE IF NOT EXISTS heart (day_hour DATE PRIMARY KEY, heart_rate DOUBLE)"); + statement.execute("CREATE TABLE IF NOT EXISTS sleep (sleep_start DATE PRIMARY KEY, sleep_end DATE, duration INTEGER)"); + } + + /** + * Crea una connessione al Database specificato in DB_LOCATION e con il nome DB_NAME. + * Se il Database non esiste lo crea e inizializza anche delle tabelle: + *
    + *
  • 'total' contiene i dati utente giorno per giorno.
  • + *
  • 'heart' battito + orario
  • + *
  • 'sleep' inizio + fine + durata
  • + *
+ */ + public static Database getInstance() { + try { + instance = instance==null? new Database():instance; + } catch (SQLException e) { + e.printStackTrace(); + } + return instance; + } + + /** + * Prendi il Runnable che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco) + * @param fitbit la classe che contiene i dati aggiornati + * @return un Runnable + */ + public Runnable insertHourlyData(Fitbit fitbit) { + Runnable runnable = () -> { + boolean notInterrupted = true; + boolean retry = false; + double heartRate = 0; + Date now = null; + + while(notInterrupted) { + try { + wait((retry? 1:59-CALENDAR.get(Calendar.MINUTE)) * 60000); + if (retry == false) { + heartRate = fitbit.getHeartRate(60); + now = CALENDAR.getTime(); + } + + conn.createStatement().execute("INSERT INTO heart (day_hour, rate) VALUE ( ' " + now + " ', '" + heartRate + "')"); + retry = false; + + } catch (InterruptedException e) { + e.printStackTrace(); + notInterrupted = false; + } catch (Exception e) { + LOG.error("Non e' stato possibile aggingere i dati orari al database, riprovo fra un minuto"); + retry = true; + } + } + }; + return runnable; + } + + /** + * Prendi il Runnable che automaticamente gestisce l'inserimento dei dati giornalieri + * @param fitbit la classe che contiene i dati aggiornati + * @return un Runnable + */ + public Runnable insertDailyData(Fitbit fitbit) { + Runnable runnable = () -> { + boolean notInterrupted = true; + boolean retry = false; + double heartRate = 0; + long sleepTime = 0; + long steps = 0; + List sleepDatas = null; + Date now = null; + + while (notInterrupted) { + int hourToWait = 23 - CALENDAR.get(Calendar.HOUR); + + try { + wait(hourToWait * 3600000); + if (retry == false) { + heartRate = fitbit.getHeartRate(60 * 24); + sleepTime = fitbit.getHoursSleep(); + sleepDatas = fitbit.getDetailedSleep(); + steps = fitbit.getSteps(); + now = CALENDAR.getTime(); + } + + conn.createStatement().execute("INSERT INTO total (day, sleep_time, heart_rate, steps) VALUE ( '" + now + "', '" + sleepTime + "', '" + heartRate + "', '" + steps + "' )"); + for (Sleep.SleepData data : sleepDatas) + conn.createStatement().execute("INSERT INTO total (sleep_start, sleep_end, duration) VALUE ( '" + data.start_date + "', '" + data.end_date + "', '" + data.duration + "' )"); + retry = false; + + } catch (InterruptedException e) { + e.printStackTrace(); + notInterrupted = false; + } catch (Exception e) { + LOG.error("Non e' stato possibile aggingere i dati orari al database, riprovo fra un minuto"); + retry = true; + } + } + }; + return runnable; + } +} diff --git a/src/main/resources/user_data.db b/src/main/resources/user_data.db index 0e61cba..94bb9af 100644 Binary files a/src/main/resources/user_data.db and b/src/main/resources/user_data.db differ diff --git a/src/test/java/test/TestDialogFlow.java b/src/test/java/test/TestDialogFlow.java index b435b21..72c4d79 100644 --- a/src/test/java/test/TestDialogFlow.java +++ b/src/test/java/test/TestDialogFlow.java @@ -10,8 +10,8 @@ public class TestDialogFlow { public void test01() { DialogFlowWebHook webHook = new DialogFlowWebHook(); - webHook.addOnAction("LightsON", () -> {return "Luci accese";}); - webHook.addOnAction("LightsOFF", () -> {return "Luci spente";}); + webHook.addOnAction("LightsON", (param) -> {return "Luci accese";}); + webHook.addOnAction("LightsOFF", (param) -> {return "Luci spente";}); webHook.startServer(); } diff --git a/src/test/java/test/TestLights.java b/src/test/java/test/TestLights.java index d21d0a5..ebc5809 100644 --- a/src/test/java/test/TestLights.java +++ b/src/test/java/test/TestLights.java @@ -1,5 +1,6 @@ package test; +import ai.api.GsonFactory; import device.Hue; import org.junit.Test; @@ -8,38 +9,49 @@ import java.util.Set; public class TestLights { + public static final int TIMEOUT = 200; + @Test synchronized public void firstTestLights() throws InterruptedException { - Hue lights = new Hue(); + Hue lights = new Hue("localhost:8090", "newdeveloper"); Set toRemove = new HashSet<>(); for(String str: lights.getNameLights()) - if(!str.equals("4")) + if(!(Integer.parseInt(str)%2 == 0)) toRemove.add(str); lights.removeLights(toRemove); for(int i=0; i<10; i++) { lights.turnOn(); - this.wait(0b11001000); // 200 + this.wait(TIMEOUT); lights.turnOff(); - this.wait(0b11001000); // 200 + this.wait(TIMEOUT); } lights.turnOn(); - for(int i=256; i>=0; i--) { + for(int i=Hue.MAX_BRIGHTNESS; i>0; i-=10) { lights.setBrightness(i); - this.wait(25); + this.wait(TIMEOUT); } - for(int i=0; i<256; i++) { + for(int i=0; i