diff --git a/.gitignore b/.gitignore index 020929a..05cde85 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ hs_err_pid* .idea/*.xml .gradle/ .iml +.db # eclipse things # .classpath diff --git a/src/main/java/device/DialogFlowWebHook.java b/src/main/java/device/DialogFlowWebHook.java index 2fc740e..5b7c2ec 100644 --- a/src/main/java/device/DialogFlowWebHook.java +++ b/src/main/java/device/DialogFlowWebHook.java @@ -7,10 +7,12 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import spark.Spark; import java.util.HashMap; import java.util.Map; +import static spark.Spark.get; import static spark.Spark.post; /** @@ -21,7 +23,12 @@ public class DialogFlowWebHook { /** * Un logger per vedere le cose piu' easy */ - private final Logger log = LoggerFactory.getLogger(this.getClass()); + private static final Logger LOG = LoggerFactory.getLogger(DialogFlowWebHook.class); + + /** + * Stringa che viene usata se l'azione esiste ma lancia un qualche tipo di errore + */ + public static final String ACTION_ERROR = "Purtroppo chi mi ha programmato e' un pirla, non posso fare cio' che hai chiesto"; /** * Errore che viene mostrato all'utente se l'azione inviata non corrisponde a nessuna di quelle inserite @@ -33,67 +40,89 @@ public class DialogFlowWebHook { */ public final String path; + /** + * La porta in cui il server ascoltera' + */ + public final int port; + /** * Mappa che contiene tutte le azioni e il loro ID */ private final Map actions; /** - * Crea una classe vuota per un server che risponde lle chiamate di Dialog-Flow. - * La path viene impostata di default a "/" + * Crea una classe vuota per un server che risponde alle chiamate di Dialog-Flow. + * La path viene impostata di default a "/" e la porta a 4567 */ - public DialogFlowWebHook() { - this("/"); - } + public DialogFlowWebHook() { this("/", 4567); } /** - * Crea una classe vuota per un server che risponde lle chiamate di Dialog-Flow. + * Crea una classe vuota per un server che risponde alle chiamate di Dialog-Flow. * @param path il percorso dopo l'url inidicato nel WebHook di Dialog-Flow + * @param port la porta da cui il Webhoook ascolta (se inserito numero negativo ascolta di default sulla porta 4567) */ - public DialogFlowWebHook(String path) { + public DialogFlowWebHook(String path, int port) { this.path = path; + this.port = port>0? port:4567; this.actions = new HashMap<>(); } /** * Aggiunge un'azione ad una specifica richiesta di Dialog-Flow * @param actionId il nome dell'azione che viene passata da Dialog-Flow - * @param action l'azione da fare (usare lambda) + * @param action l'azione da fare (e' consigliato usare le espressioni lambda) */ - public void addOnAction(String actionId, Action action) { - this.actions.put(actionId, action); - } + public void addOnAction(String actionId, Action action) { this.actions.put(actionId, action); } /** - * Fa partire il server per accettare richieste da Dialog-Flow. - * Ogni richiesta viene esaminata e fatta coincidere con una azione specificata precedentemente. - * Se nessuna azione viene riscontrata, viene inviato un errore, rimuovendo i messaggi + * Fa partire il server per accettare richieste da Dialog-Flow ascoltando connessioni in post.
+ * Ogni richiesta viene esaminata e fatta coincidere con un'azione specificata precedentemente.
+ * Se nessuna azione viene riscontrata, viene inviato un errore, rimuovendo i messaggi
+ * Inoltre aggiunge un'interfaccia in get che riguarda un iframe di dialogflow */ - public void startServer() { // todo add param port? Spark.port(num); + public void startServer() { + Spark.port(this.port); Gson gson = GsonFactory.getDefaultFactory().getGson(); post(this.path, (request, response) -> { Fulfillment output = new Fulfillment(); AIResponse input = gson.fromJson(request.body(), AIResponse.class); - String text = null; + String inputAction = input.getResult().getAction(); + Map inputParam = input.getResult().getParameters(); + String text; try { - log.info("AZIONE: "+input.getResult().getAction()); - Action action = actions.get(input.getResult().getAction()); - text = action.doAction(input.getResult().getParameters()); + LOG.debug("AZIONE: "+ inputAction + ", PARAMS: " + inputParam); + Action action = actions.get(inputAction); + try { + text = action.doAction(inputParam); + } catch (NullPointerException e) { + LOG.warn("AZIONE FALLITA: "+ inputAction); + text = ACTION_ERROR; + } } catch (NullPointerException e) { - log.info("NESSUNA AZIONE TROVATA"); + LOG.error("NESSUNA AZIONE TROVATA: "+ inputAction); text = ERROR; } - if(text != null) { - log.info(text); - output.setDisplayText(text); - output.setSpeech(text); - } + if(text == null) + text = input.getResult().getFulfillment().getSpeech(); + + LOG.debug("RISPOSTA: " + text); + output.setDisplayText(text); + output.setSpeech(text); response.type("application/json"); return output; }, gson::toJson); + + get(this.path, (request, response) -> { + return "\n" + + ""; + }); } /** @@ -104,7 +133,7 @@ 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 + * @param params una mappa contenente tutti i parametri impostati da dialogflow * @return Una stringa che verra' usata come messaggio o null se non si vuole */ String doAction(Map params); diff --git a/src/main/java/device/Fitbit.java b/src/main/java/device/Fitbit.java index 174996c..3437082 100644 --- a/src/main/java/device/Fitbit.java +++ b/src/main/java/device/Fitbit.java @@ -20,25 +20,29 @@ public class Fitbit { * Url da dove si possono prendere i dati dai vari dispositivi fitbit */ public static final String BASIC_URL = "https://api.fitbit.com/"; + /** * Utente del fitbit
* In questo caso e' universale e prende l'utente che e' attualmente loggato */ public static final String USER = "/user/-/"; + /** * Un minuto in millisecondi */ - private static final long MINUTE = 60000; /* 5 minutes in millisec */ + private static final long MINUTE = 60000; /** * L'oauth per l'account fitbit */ private final AuthFitbit auth; + /** * Una mappa contenente le ultime classi usate nelle richieste effettuate
* Una sorta di cache */ private final Map, Long> latestRequest = new HashMap<>(); + /** * Un calendario in modo da sapere la data per i dati */ @@ -48,26 +52,25 @@ public class Fitbit { * La classe per sapere i dati sul battito cardiaco */ private HeartRate heart = null; + /** * La classe per sapere i dati sul sonno */ private Sleep sleep = null; + /** * La classe per sapere i dati sui passi effettuati */ private Steps steps = null; /** - * Crea una istanza di fitbit
+ * Crea un'istanza di fitbit
* Se l'utente non ha ancora accettato i dati richiesti o l'utente non e' - * loggato,
- * verra' aperto il browser in modo che si possano inserire i dati + * loggato, verra' aperto il browser in modo che si possano inserire i dati * * @throws Exception Nel caso qualunque cosa andasse storta (vedi messaggio) */ - public Fitbit() throws Exception { - this.auth = new AuthFitbit(); - } + public Fitbit() throws Exception { this.auth = new AuthFitbit(); } /** * Ricevi i passi che l'utente ha effettuato nell'ultimo giorno @@ -87,15 +90,13 @@ 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 synchronized double getHeartRate() throws IOException { - return getHeartRate(15); - } + 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 * - * @param lastMinutes fino a quanti minuti bisogna tenere conto (positivi se no ritorno -1) + * @param lastMinutes fino a quanti minuti bisogna tenere conto (positivi e !=0 se no ritorno -1) * @return un intero rappresentante la media del battito cardiaco degli ultimi minuti specificati * @throws IOException nel caso la richiesta non vada a buon fine */ @@ -141,7 +142,7 @@ public class Fitbit { } /** - * Semplice funzione che controlla che si possa fare l'update o meno di una specifica classe
+ * 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 * @@ -150,7 +151,7 @@ public class Fitbit { * @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 { + private synchronized T update(Class varClass, T variable, String url) throws IOException { try { long current = System.currentTimeMillis(); long latest = latestRequest.get(varClass); diff --git a/src/main/java/device/Hue.java b/src/main/java/device/Hue.java index 560b0a3..7741c40 100644 --- a/src/main/java/device/Hue.java +++ b/src/main/java/device/Hue.java @@ -22,36 +22,36 @@ public class 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"; /** * L'url in cui si possono trovare le luci */ - private String lightsURL; // = baseURL+"/api/"+username+"/lights/"; + private final String lightsURL; /** * Tutte le luci che sono state registrate dall'url */ - private Map allLights; + private final Map> allLights; /** * L'ultima luminosita' impostata */ - private int brightness = 0; + private double brightness = 0; + + /** + * Riempimento della mappa con i vari colori + */ + static { // todo set right colors (in the simulation they are off, maybe in reality are ok) + COLORS.put("giall[oae]", new Double[]{0.475, 0.475}); + COLORS.put("ross[oae]", new Double[]{0.7, 0.25}); + COLORS.put("verd[ei]", new Double[]{0.1, 0.55}); + COLORS.put("blu", new Double[]{0.15, 0.175}); + COLORS.put("rosa", new Double[]{0.45, 0.275}); + COLORS.put("viola", new Double[]{0.25, 0.1}); + COLORS.put("azzurr[oae]", new Double[]{0.15, 0.25}); + COLORS.put("arancio(ne|ni)?", new Double[]{0.6, 0.35}); + //COLORS.put("nero", new Double[]{1.0, 1.0}); + COLORS.put("bianc(o|a|he)", new Double[]{0.3, 0.25}); + } /** * Cerca le luci Philips Hue a ll'indirizzo http://172.30.1.138/api/C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx/lights/ @@ -61,23 +61,31 @@ public class Hue { } /** - * Cerca le luci Philips Hue nell'indirizzo specificato e con l'utente specificato - * @param ip l'indirizzo IP + * Cerca le luci Philips Hue nell'indirizzo specificato e con l'utente specificato.
+ * Una volta trovate le luci le setta tutte alla stessa luminosita' e allo stesso colore
+ * (per ora fa una media e poi assegna il valore risultante a tutte) + * @param ip l'indirizzo IP (seguito dalla porta se e' diversa dalla solita 8000) * @param user l'utente */ public Hue(String ip, String user) { - this("http://" + ip + "/api/" + user + "/lights/"); - } + lightsURL = "http://" + ip + "/api/" + user + "/lights/"; + allLights = (Map>)Rest.get(lightsURL); - /** - * Inizializza la classe cercando tutte le luci all'indirizzo url specificato - * - * @param url l'indirizzo da inserire delle luci Hue - */ - public Hue (String url) { - lightsURL = url; - allLights = Rest.get(lightsURL); - // Todo brightness initial, maybe by default 50% or 75% of the total + if(allLights.size() != 0) { + double bri = 0; + double hue = 0; + for (String name: allLights.keySet()) { + Map state = (Map)allLights.get(name).get("state"); + bri += (Double) state.get("bri"); + hue += (Double) state.get("hue"); + } + bri = bri/allLights.size(); + hue = hue/allLights.size(); + setState("bri", String.valueOf(bri)); + setState("hue", String.valueOf(hue)); + + brightness = (bri*MAX_BRIGHTNESS)/100; + } } /** @@ -85,9 +93,7 @@ public class Hue { * * @return l'insieme dei nomi delle luci */ - public Set getNameLights() { - return allLights.keySet(); - } + public Set getNameLights() { return allLights.keySet(); } /** * Rimuove dal controllo tutte le luci che hanno il nome uguale ad uno contenuto nell'insieme passato @@ -102,95 +108,90 @@ public class Hue { /** * Accende tutte le luci controllate */ - public void turnOn() { - setState("on", "true"); - } + public void turnOn() { setState("on", "true"); } /** * Spegne tutte le luci controllate */ - public void turnOff() { - setState("on", "false"); - } + public void turnOff() { setState("on", "false"); } /** * Ritorna la liminosita' attuale delle luci controllate * @return */ - public int getCurrentBrightness() { - return brightness; - } + public double getCurrentBrightness() { return brightness; } /** * Modifica la luminosita' delle luci a seconda del valore inserito - * @param num la luminosita' che si vuole + * @param num la luminosita' che si vuole da (0 a 100) */ - public void setBrightness(int num) { + public void setBrightness(double num) { if (num<0) num=0; - setState("bri", String.valueOf(num)); + else if (num>100) + num=100; + + setState("bri", String.valueOf( (num*MAX_BRIGHTNESS)/100) ); brightness = num; } /** - * Dinuisce la luminosita' delle luci controllate della percentuale che viene passata - * @param percentage la percentuale di diminuzione + * Aumenta la luminosita' delle luci controllate della percentuale che viene passata + * @param percentage la percentuale di aumento della luminosita' */ - public void increaseBrightness(int percentage) { + public void increaseBrightness(double percentage) { if (percentage<0) percentage = 0; else if (percentage>100) percentage = 100; - setBrightness(brightness + (percentage*MAX_BRIGHTNESS)/100); + setBrightness(brightness + percentage); } /** - * Aumenta la luminosita' delle luci controllate del 10% + * Aumenta la luminosita' delle luci controllate del 15% */ - public void increaseBrightness() { - increaseBrightness(10); - } + public void increaseBrightness() { increaseBrightness(15); } /** * Dinuisce la luminosita' delle luci controllate della percentuale che viene passata - * @param percentage la percentuale di diminuzione + * @param percentage la percentuale di diminuzione della luminosita' */ public void decreaseBrightness(int percentage) { if (percentage<0) percentage = 0; else if (percentage>100) percentage = 100; - setBrightness(brightness - (percentage*MAX_BRIGHTNESS)/100); + setBrightness(brightness - percentage); } /** - * Dinuisce la luminosita' delle luci controllate del 10% + * Dinuisce la luminosita' delle luci controllate del 15% */ - public void decreaseBrightness() { - decreaseBrightness(10); - } + public void decreaseBrightness() { decreaseBrightness(15); } public void changeColor(String colorName) { - String hueColor = GsonFactory.getDefaultFactory().getGson().toJson(COLORS.get(colorName)); - setState("xy", hueColor); + for (String regex: COLORS.keySet()) + if(colorName.matches("(?i)" + regex)) + setState("xy", GsonFactory.getDefaultFactory().getGson().toJson(COLORS.get(regex))); } /** * Modifica il colore delle luci in modo da fare un bel effetto arcobaleno continuo */ - public void colorLoop() { - setState("effect", "colorloop"); - } + public void colorLoop() { setState("effect", "colorloop"); } /** * Funzione generale per poter utilizzare qualunque valore, ma non funziona
- * Da testare, visto che mi sembra strano che non funzi... + * Da testare, visto che mi sembra strano che non funzi...
* e invece funziona. */ public void setState(String attribute, String value){ - for (String light : allLights.keySet()) + for (String light : allLights.keySet()) { + Map state = (Map)allLights.get(light).get("state"); Rest.put(lightsURL + light + "/state", - "{ \"" + attribute + "\" : " + value + " }", - "application/json"); + "{ \"" + attribute + "\" : " + value + ", \"transitiontime\": 10 }", // todo check transition + "application/json"); + state.put(attribute, value); + } } } diff --git a/src/main/java/device/Sensor.java b/src/main/java/device/Sensor.java index ba084f4..2b77370 100644 --- a/src/main/java/device/Sensor.java +++ b/src/main/java/device/Sensor.java @@ -11,10 +11,6 @@ import support.ZWaySimpleCallback; */ public class Sensor { - /* todo ma serve il LOGGER? - private Logger logger = LoggerFactory.getLogger(Sensor.class); - */ - /** * IP del sensore a cui ci si vuole agganciare */ @@ -100,7 +96,8 @@ public class Sensor { device.update(); wait(timeout); } - /*public boolean IsLowLuminescence(int Luminescence) { + /* + public boolean IsLowLuminescence(int Luminescence) { if (dev.getProbeType().equalsIgnoreCase("Luminescence")) if (Integer.parseInt(dev.getMetrics().getLevel()) < Luminescence) return true; diff --git a/src/main/java/device/fitbitdata/HeartRate.java b/src/main/java/device/fitbitdata/HeartRate.java index 8d6db4e..79d4c2b 100644 --- a/src/main/java/device/fitbitdata/HeartRate.java +++ b/src/main/java/device/fitbitdata/HeartRate.java @@ -9,17 +9,9 @@ import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) public class HeartRate { - private String dateTime; private double average; - public double getAverage() { - return average; - } - - @JsonProperty("activities-heart") - public void quelloCheVoglio(Map[] activities){ - dateTime = (String) activities[0].get("dateTime"); - } + public double getAverage() { return average; } @JsonProperty("activities-heart-intraday") public void setAverage(Map map) { diff --git a/src/main/java/device/fitbitdata/Sleep.java b/src/main/java/device/fitbitdata/Sleep.java index 26fa0be..945a6ee 100644 --- a/src/main/java/device/fitbitdata/Sleep.java +++ b/src/main/java/device/fitbitdata/Sleep.java @@ -3,6 +3,7 @@ package device.fitbitdata; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -48,14 +49,14 @@ public class Sleep { } public class SleepData { - public final Date start_date; + public final Timestamp start_date; public final long duration; - public final Date end_date; + public final Timestamp end_date; public SleepData(Date start_date, long duration) { - this.start_date = start_date; + this.start_date = new Timestamp(start_date.getTime()); this.duration = duration; - this.end_date = start_date!=null? new Date(start_date.getTime() + duration):null; + this.end_date = start_date!=null? new Timestamp(start_date.getTime() + duration):null; } } } diff --git a/src/main/java/device/fitbitdata/Steps.java b/src/main/java/device/fitbitdata/Steps.java index 7340206..5ceab80 100644 --- a/src/main/java/device/fitbitdata/Steps.java +++ b/src/main/java/device/fitbitdata/Steps.java @@ -23,8 +23,6 @@ public class Steps { steps = Integer.parseInt(map.get("value")); } - public int getSteps() { - return steps; - } + public int getSteps() { return steps; } } diff --git a/src/main/java/main/Main.java b/src/main/java/main/Main.java index 220758b..700db7c 100644 --- a/src/main/java/main/Main.java +++ b/src/main/java/main/Main.java @@ -1,14 +1,10 @@ package main; -import ai.api.GsonFactory; import device.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import support.Database; -import java.io.IOException; - -// todo docs /** * Created by 20015159 on 28/08/2018. */ @@ -23,23 +19,22 @@ public class Main { 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) { + + // todo setting log level -> useful if you dont need to see a spam of things but not working + // System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY , SimpleLogger.S); + LOG.info("Connecting to hue lights"); - lights = new Hue("localhost:8090", "newdeveloper"); + lights = new Hue("192.168.1.7:8090", "newdeveloper"); LOG.info("Connecting to the sensors"); sensor = new Sensor(); try { - LOG.info("Connecting to Database"); - database = Database.getInstance(); - LOG.info("Connecting to Fitbit"); fitbit = new Fitbit(); @@ -56,13 +51,27 @@ public class Main { * Fa partire il server Webhook per DialogFlow e continua l'esecuzione */ private static void startWebhook() { - LOG.info("Adding actions to Webhook"); DialogFlowWebHook df = new DialogFlowWebHook(); 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;}); + df.addOnAction("SetLights", (params) -> {lights.setBrightness(params.get("intensity").getAsInt()); return null;}); + df.addOnAction("LightsDOWN", (params) -> { + if(params.get("intensity").getAsString().equals("")) + lights.decreaseBrightness(); + else + lights.decreaseBrightness(params.get("intensity").getAsInt()); + return null; + }); + df.addOnAction("LightsUP", (params) -> { + if(params.get("intensity").getAsString().equals("")) + lights.increaseBrightness(); + else + lights.increaseBrightness(params.get("intensity").getAsInt()); + return null; + }); LOG.info("Starting Webhook"); df.startServer(); @@ -72,47 +81,36 @@ public class Main { * Gestione DB in modo che si aggiorni ogni ora */ private static void startInsertData() { - LOG.info("Connecting to DB to write fitbit data periodically"); try { - fitbit.getHoursSleep(); - Database database = Database.getInstance(); Thread hourlyData = new Thread(database.insertHourlyData(fitbit), "updating-hour-data"); Thread dailyData = new Thread(database.insertDailyData(fitbit), "updating-day-data"); + LOG.info("Starting threads for automatic update"); hourlyData.start(); dailyData.start(); - LOG.info("Threads started for updating database"); - } catch (IOException e) { + + } catch (Exception e) { e.printStackTrace(); } } /* - public static void main(String[] args) throws Exception { - Fitbit fitbit = new Fitbit(); - Sensor sensor = new Sensor(); - Hue hue = new Hue(); + TODO AUTOMATIC: {B}, {C}, {D}, {E} - while(true) { - double heart = fitbit.getHeartRate(); - int brightness = sensor.getBrightnessLevel(); + XXX Gestione DB in modo che si aggiorni ogni ora + /B/ Gestione luci in modo che la luminosità sia sempre la stessa + /C/ Gestione luci a seconda del battito cardiaco + /D/ Ad una certa ora guarda i passi e se sono pochi dillo + /E/ Se i battiti sono troppo bassi/alti avvisare il tizio - // AUTOMATIC - /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 - // Se i battiti sono troppo bassi/alti avvisare il tizio + TODO USER-INTERACTION {A}, {C} - // USER-INTERACTION - // Dati del sonno/battito/passi che l'utente puo' richiedere - /X/ Gestione luci secondo le esigenze dell'utente ( settare Dialogflow e server + risolvere bug ) - // EXTRA Gestione musica tramite comando vocale + /A/ Dati del sonno/battito/passi che l'utente puo' richiedere + XXX Gestione luci secondo le esigenze dell'utente ( settare Dialogflow e server + risolvere bug ) + /C/ EXTRA Gestione musica tramite comando vocale - // Randomly at night heavy metal start - } - } + // Randomly at night heavy metal start */ } diff --git a/src/main/java/oauth/AuthFitbit.java b/src/main/java/oauth/AuthFitbit.java index 829c7b4..b652f46 100644 --- a/src/main/java/oauth/AuthFitbit.java +++ b/src/main/java/oauth/AuthFitbit.java @@ -3,7 +3,6 @@ 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; @@ -16,7 +15,6 @@ import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.GenericJson; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.jackson2.JacksonFactory; @@ -25,14 +23,74 @@ import com.google.api.client.util.store.FileDataStoreFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -//todo add docs +/** + * Classe piu' importante per la connessione al fitbit + */ public class AuthFitbit { + /** + * Un logger per rendere le cose semplici in caso di casini + */ private static final Logger LOG = LoggerFactory.getLogger("Fitbit Response"); + /** + * Un mapper per trasformare i json in mappe. + */ + private static final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Directory dove vengono messi i dati utente (Token)
+ *
+ * Throw a Warning when change permission: they said it's a google bug 'cause is meant to run in linux/unix
+ * https://stackoverflow.com/questions/30634827/warning-unable-to-change-permissions-for-everybody
+ * https://github.com/google/google-http-java-client/issues/315
+ */ + private static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"), ".store/seniorAssistant"); + + /** + * OAuth 2 scope.
+ * Nel nostro caso sono le varie categorie dove si trovano le informazioni di cui abbiamo bisogno + */ + private static final String SCOPE[] = new String[]{"activity","heartrate","sleep","settings"}; + + /** + * Instanza globale di HttpTranspot necessaria per l'autorizzazione e per le richieste + */ + private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); + + /** + * Istanza globale di una Json Factory + */ + private static final JsonFactory JSON_FACTORY = new JacksonFactory(); + + /** + * Url dove e' possiblie richiedere il token + */ + private static final String TOKEN_SERVER_URL = " https://api.fitbit.com/oauth2/token"; + + /** + * Pagina dove si richiede l'autorizzazione a tutti i campi richiesti + */ + private static final String AUTHORIZATION_SERVER_URL = "https://www.fitbit.com/oauth2/authorize"; + + /** + * Istanza globale del {@link DataStoreFactory}. Il miglior metodo e' creare una singola + * istanza globale condivisa attraverso tutta l'applicazione. + */ + private static FileDataStoreFactory DATA_STORE_FACTORY; + + /** + * Un HttpRequestFactory che serve per creare una richiesta http + */ private final HttpRequestFactory requestFactory; + /** + * Prova a connettersi al sito di Fitbit per controllare l'autorizzazione
+ * Se la richiesta inviata e' sbagliata lancia una eccezione.
+ * Se l'utente non ha ancora autorizzato l'applicazioe, allora una pagina sul browser (o un link in console) appare
+ * ci si logga e si lascia l'autorizzazione a questa applicazione. + * @throws Exception per qualunque cosa apparentemente (e noi ci siamo riusciti) + */ public AuthFitbit() throws Exception { DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR); final Credential credential = authorize(); @@ -43,34 +101,9 @@ public class AuthFitbit { }); } - private static ObjectMapper mapper = new ObjectMapper(); - /** Directory to store user credentials. */ - /* Throw a Warning when change permission: they said it's a google bug 'cause is meant to run in linux/unix - * - * https://stackoverflow.com/questions/30634827/warning-unable-to-change-permissions-for-everybody - * https://github.com/google/google-http-java-client/issues/315 - */ - private static final java.io.File DATA_STORE_DIR = - new java.io.File(System.getProperty("user.home"), ".store/seniorAssistant"); - /** - * Global instance of the {@link DataStoreFactory}. The best practice is to make it a single - * globally shared instance across your application. + * Autorizza l'applicazione ad accedere ai dati utente richiesti */ - private static FileDataStoreFactory DATA_STORE_FACTORY; - - /** OAuth 2 scope. */ - private static final String SCOPE[] = new String[]{"activity","heartrate","sleep","settings"}; - /** Global instance of the HTTP transport. */ - private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); - - /** Global instance of the JSON factory. */ - private static final JsonFactory JSON_FACTORY = new JacksonFactory(); - - private static final String TOKEN_SERVER_URL = " https://api.fitbit.com/oauth2/token"; - private static final String AUTHORIZATION_SERVER_URL = "https://www.fitbit.com/oauth2/authorize"; - - /** Authorizes the installed application to access user's protected data. */ private Credential authorize() throws Exception { // set up authorization code flow AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken @@ -90,38 +123,39 @@ public class AuthFitbit { return new AuthorizationCodeInstalledApp(flow, receiver).authorize( "user" ); } - public O run(String url, Class returnClass) throws IOException { - return run(url, returnClass, false); - } - - public O run(String url, Class returnClass, boolean useAsParse) throws IOException { - FITBITUrl fitbitUrl = new FITBITUrl(url); - fitbitUrl.setFields(""); - - HttpRequest request = requestFactory.buildGetRequest(fitbitUrl); + /** + * Effettua una chiamata al server fitbit richiedendo i dati indicati dall'url + * + * @param url l'url in cui effettuare la richiesta + * @return una stringa in formato Json, che e' il risultato + * @throws IOException nel caso ci sia un errore con la richiesta + */ + public String run(String url) throws IOException { + GenericUrl genericUrl = new GenericUrl(url); + HttpRequest request = requestFactory.buildGetRequest(genericUrl); HttpResponse response = request.execute(); - /* - GenericJson json = response.parseAs(GenericJson.class); + String content = response.parseAsString(); response.disconnect(); + LOG.debug("Recived: " + content); - System.out.println(returnClass.getSimpleName()); - System.out.println(url); - System.out.println(json.toPrettyString()); + return content; + } - return mapper.readValue(json.toString(), returnClass); - */ - - /**/ - O ret = ( useAsParse ? - response.parseAs(returnClass) : - mapper.readValue(response.parseAs(GenericJson.class).toString(), returnClass) - ); - - response.disconnect(); - - // todo remove this, it's only useful if you need to see the request - LOG.info(GsonFactory.getDefaultFactory().getGson().toJson(ret)); + /** + * Fa una chiamata al server fitbit richiedendo i dati indicati dall'url
+ * La classe e' richiesta se si vuole fare il parsing diretto e ricevere la classe parsificata con un mapper
+ * in questo modo non tutti i campi devono esistere + * + * @param url l'url in cui effettuare la richiesta + * @param returnClass la classe da ritornare + * @param la classe che ritorna + * @return un oggetto rappresentante la richiesta fatta all'url + * @throws IOException nel caso ci sia un errore con la richiesta o con il parsing di quest'ultima + */ + public O run(String url, Class returnClass) throws IOException { + O ret = MAPPER.readValue(this.run(url), returnClass); + LOG.debug("Saved in class: " + JSON_FACTORY.toString(ret)); return ret; /**/ diff --git a/src/main/java/oauth/FITBITUrl.java b/src/main/java/oauth/FITBITUrl.java deleted file mode 100644 index b4fc9a5..0000000 --- a/src/main/java/oauth/FITBITUrl.java +++ /dev/null @@ -1,29 +0,0 @@ -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 - private String fields; - - public FITBITUrl(String encodedUrl) { - super(encodedUrl); - } - - /** - * @return the fields - */ - public String getFields() { - return fields; - } - - /** - * @param fields the fields to set - */ - public void setFields(String fields) { - this.fields = fields; - } -} diff --git a/src/main/java/oauth/OAuth2ClientCredentials.java b/src/main/java/oauth/OAuth2ClientCredentials.java index c4b1444..54c4dfa 100644 --- a/src/main/java/oauth/OAuth2ClientCredentials.java +++ b/src/main/java/oauth/OAuth2ClientCredentials.java @@ -1,18 +1,28 @@ package oauth; -// todo add docs +/** + * Classe supporto per l'oauth contenente tutti i dati dell'applicazione + */ public class OAuth2ClientCredentials { - /** Value of the "API Key". */ + /** + * Il valore dell' "API Key". + */ public static final String API_KEY = "22CSTL"; //maybe togliere le virgolette - /** Value of the "API Secret". */ + /** + * Il valore dell' "API Secret" + */ public static final String API_SECRET = "ea2452013abd35609940ce5601960a08"; //maybe togliere le virgolette - /** Port in the "Callback URL". */ + /** + * La porta per il "Callback URL". + */ public static final int PORT = 8080; - /** Domain name in the "Callback URL". */ + /** + * Il dominio del "Callback URL" + */ public static final String DOMAIN = "127.0.0.1"; } diff --git a/src/main/java/support/Database.java b/src/main/java/support/Database.java index feef523..3aff0f7 100644 --- a/src/main/java/support/Database.java +++ b/src/main/java/support/Database.java @@ -5,30 +5,48 @@ 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.sql.*; 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) + * Classe che si connette al database e permette di aggiornare i dati in modo automatico tramite dei runnable */ 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(); + /** + * Un logger per scrivere a console eventuali errori o informazioni + */ private static final Logger LOG = LoggerFactory.getLogger(Database.class); + /** + * Il percorso di dove trovare il database, strutturato in: <interfaccia>:<implementazione>:<percorso vero e proprio> + */ + public static final String DB_LOCATION = "jdbc:sqlite:"; + + /** + * Il nome del database (aka il nome del file) + */ + public static final String DB_NAME = "user_data.db"; + + /** + * Un calendario, visto che ci serve sapere quando inseriamo i dati + */ + private static final Calendar CALENDAR = Calendar.getInstance(); + + /** + * Una costante che indica quanti millisecondi ci sono in un minuto (utile per le conversioni) + */ + private static final int MINUTES_TO_MILLISEC = 60000; + + /** + * L'unica istanza del database (dato che e' una classe Singleton) + */ private static Database instance; + /** + * La connessione al database + */ private final Connection conn; /** @@ -46,7 +64,7 @@ public class Database { 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 heart (day_hour DATE PRIMARY KEY, rate DOUBLE)"); statement.execute("CREATE TABLE IF NOT EXISTS sleep (sleep_start DATE PRIMARY KEY, sleep_end DATE, duration INTEGER)"); } @@ -58,8 +76,10 @@ public class Database { *
  • 'heart' battito + orario
  • *
  • 'sleep' inizio + fine + durata
  • * + *
    + * L'implementazione e' synchronized (si spera thread safe) */ - public static Database getInstance() { + public synchronized static Database getInstance() { try { instance = instance==null? new Database():instance; } catch (SQLException e) { @@ -69,82 +89,96 @@ public class Database { } /** - * Prendi il Runnable che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco) + * Prendi il Runnable che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco)
    + *
    + * Il runnable e' synchronized (si spera thread safe) * @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; + return new Runnable() { + @Override + public synchronized void run() { + boolean notInterrupted = true; + boolean retry = false; + double heartRate = 0; + Timestamp now = null; - while(notInterrupted) { - try { - wait((retry? 1:59-CALENDAR.get(Calendar.MINUTE)) * 60000); - if (retry == false) { - heartRate = fitbit.getHeartRate(60); - now = CALENDAR.getTime(); + while (notInterrupted) { + CALENDAR.setTimeInMillis(System.currentTimeMillis()); + try { + wait((retry ? 1:58-CALENDAR.get(Calendar.MINUTE)) * MINUTES_TO_MILLISEC); + CALENDAR.setTimeInMillis(System.currentTimeMillis()); + if (retry == false) { + now = new Timestamp(CALENDAR.getTimeInMillis()); + heartRate = fitbit.getHeartRate(60); + } + + conn.createStatement().execute("INSERT INTO heart (day_hour, rate) VALUES ( ' " + now + " ', '" + heartRate + "')"); + LOG.info(CALENDAR.getTime() + " > Ho inserito i dati orari "+now); + retry = false; + wait(MINUTES_TO_MILLISEC); + + } catch (InterruptedException e) { + e.printStackTrace(); + notInterrupted = false; + } catch (Exception e) { + LOG.error(CALENDAR.getTime() + " " + e.getLocalizedMessage() + " > Non e' stato possibile aggingere i dati orari al database, riprovo fra un minuto "+now); + retry = true; } - - 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 + * Prendi il Runnable che automaticamente gestisce l'inserimento dei dati giornalieri
    + *
    + * Il runnable e' synchronized (si spera thread safe) * @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; + return new Runnable() { + @Override + public synchronized void run() { + boolean notInterrupted = true; + boolean retry = false; + double heartRate = 0; + long sleepTime = 0; + long steps = 0; + List sleepDatas = null; + Timestamp now = null; - while (notInterrupted) { - int hourToWait = 23 - CALENDAR.get(Calendar.HOUR); + while (notInterrupted) { + CALENDAR.setTimeInMillis(System.currentTimeMillis()); + try { + wait((retry? 1:(22-CALENDAR.get(Calendar.HOUR))*60) * MINUTES_TO_MILLISEC); + CALENDAR.setTimeInMillis(System.currentTimeMillis()); + if (retry == false) { + heartRate = fitbit.getHeartRate(60 * 24); + sleepTime = fitbit.getHoursSleep(); + sleepDatas = fitbit.getDetailedSleep(); + steps = fitbit.getSteps(); + now = new Timestamp(CALENDAR.getTimeInMillis()); + } - 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) VALUES ( '" + now + "', '" + sleepTime + "', '" + heartRate + "', '" + steps + "' )"); + for (Sleep.SleepData data : sleepDatas) + conn.createStatement().execute("INSERT INTO total (sleep_start, sleep_end, duration) VALUES ( '" + data.start_date + "', '" + data.end_date + "', '" + data.duration + "' )"); + LOG.info(CALENDAR.getTime() + " > Ho inserito i dati giornalieri"); + retry = false; + wait(MINUTES_TO_MILLISEC); + + } catch (InterruptedException e) { + e.printStackTrace(); + notInterrupted = false; + } catch (Exception e) { + LOG.error(CALENDAR.getTime() + " " + e.getLocalizedMessage() + " > Non e' stato possibile aggingere i dati della giornata al database, riprovo fra un minuto"); + retry = true; } - - 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; } }