From a4c6c095c61ccf4f053413ffd8fe648fbf4b975a Mon Sep 17 00:00:00 2001 From: Giacomo Date: Mon, 10 Sep 2018 21:32:09 +0200 Subject: [PATCH] Database revolution - added in fitbitData an interface for keeping track of date - removed annoying IOException, now will write a LOG - same thing in sensor - fixed main and adapted to new Database system - Database now has Local or Remote (to implement) --- src/main/java/device/Fitbit.java | 27 ++- src/main/java/device/Sensor.java | 17 +- src/main/java/device/fitbitdata/Device.java | 24 --- .../java/device/fitbitdata/FitbitData.java | 8 + .../java/device/fitbitdata/HeartRate.java | 7 +- src/main/java/device/fitbitdata/Sleep.java | 6 +- src/main/java/device/fitbitdata/Steps.java | 8 +- src/main/java/main/Main.java | 122 +++++------ src/main/java/support/Database.java | 190 ------------------ src/main/java/support/Rest.java | 1 - src/main/java/support/database/Database.java | 156 ++++++++++++++ src/main/java/support/database/LocalDB.java | 106 ++++++++++ src/main/java/support/database/RemoteDB.java | 39 ++++ 13 files changed, 399 insertions(+), 312 deletions(-) delete mode 100644 src/main/java/device/fitbitdata/Device.java create mode 100644 src/main/java/device/fitbitdata/FitbitData.java delete mode 100644 src/main/java/support/Database.java create mode 100644 src/main/java/support/database/Database.java create mode 100644 src/main/java/support/database/LocalDB.java create mode 100644 src/main/java/support/database/RemoteDB.java diff --git a/src/main/java/device/Fitbit.java b/src/main/java/device/Fitbit.java index 8a67048..d157f5e 100644 --- a/src/main/java/device/Fitbit.java +++ b/src/main/java/device/Fitbit.java @@ -85,7 +85,7 @@ public class Fitbit { * @return un intero rappresentante i passi effettuati * @throws IOException nel caso la richiesta non vada a buon fine */ - public synchronized int getSteps() throws IOException { + public synchronized int getSteps() { steps = update(Steps.class, steps, "1" + USER + "activities/steps/date/today/1w.json"); return steps.getSteps(); } @@ -97,7 +97,7 @@ 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() { return getHeartRate(15); } /** * Ricevi il battito cardiaco dell'utente
@@ -107,7 +107,7 @@ 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 synchronized double getHeartRate(int lastMinutes) throws IOException { + public synchronized double getHeartRate(int lastMinutes) { if(lastMinutes<=0) return -1; @@ -129,7 +129,7 @@ public class Fitbit { * @return un intero rappresentante le ore passate a dormire * @throws IOException nel caso la richiesta non vada a buon fine */ - public synchronized long getHoursSleep() throws IOException { + public synchronized long getHoursSleep() { sleep = update(Sleep.class, sleep,"1.2" + USER + "sleep/date/today.json"); return sleep.getMinutesAsleep()/60; } @@ -143,7 +143,7 @@ public class Fitbit { * @return una lista contenente ogni volta che l'utente ha dormito * @throws IOException */ - public synchronized List getDetailedSleep() throws IOException { + public synchronized List getDetailedSleep() { sleep = update(Sleep.class, sleep,"1.2" + USER + "sleep/date/today.json"); return sleep.getDatas(); } @@ -151,14 +151,15 @@ public class Fitbit { /** * 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 + * Altrimenti viene ritornata la variabile passata
+ * Nel caso di fallimento della richiesta varra' restituito la variabile passata in input * * @param varClass la classe della variabile passata * @param variable la variabile che deve fare l'update (passando null si forza la richiesta) * @param url l'url da cui prende i dati aggiornati * @return la variabile aggiornata */ - private synchronized T update(Class varClass, T variable, String url) throws IOException { + private synchronized T update(Class varClass, T variable, String url) { try { long current = System.currentTimeMillis(); long latest = latestRequest.get(varClass); @@ -168,10 +169,16 @@ public class Fitbit { return variable; } catch (NullPointerException e) { // do nothing and update + } finally { + LOG.info("Updating " + varClass.getSimpleName() + " form " + BASIC_URL + url); + try { + variable = auth.run(BASIC_URL + url, varClass); + latestRequest.put(varClass, System.currentTimeMillis()); + } catch (IOException e) { + LOG.error("Non sono riuscito a prender i dati aggiornati: " + e.getMessage()); + } + return variable; } - latestRequest.put(varClass, System.currentTimeMillis()); - LOG.info("Updating " + varClass.getSimpleName() + " form " + BASIC_URL + url); - return auth.run(BASIC_URL + url, varClass); } /** diff --git a/src/main/java/device/Sensor.java b/src/main/java/device/Sensor.java index c672b26..6ac2e9a 100644 --- a/src/main/java/device/Sensor.java +++ b/src/main/java/device/Sensor.java @@ -92,16 +92,17 @@ public class Sensor { /** * Fa in modo di forzare l'aggiornamento dei dispositivi * @param timeout fa aspettare un tot di tempo prima di provare a forzare e dopo l'aggiornameto - * @throws InterruptedException nel caso che succeda qualcosa */ - synchronized public void update(int timeout) throws InterruptedException { - wait(timeout); - for (Device device : devices.getAllDevices()) - try { - device.update(); - } catch (Exception e) {} + synchronized public void update(int timeout) { + try { + wait(timeout / 2); + for (Device device : devices.getAllDevices()) + try { + device.update(); + } catch (Exception e) { } - wait(timeout); + wait(timeout / 2); + } catch (InterruptedException e) { } } /* public boolean IsLowLuminescence(int Luminescence) { diff --git a/src/main/java/device/fitbitdata/Device.java b/src/main/java/device/fitbitdata/Device.java deleted file mode 100644 index 3008632..0000000 --- a/src/main/java/device/fitbitdata/Device.java +++ /dev/null @@ -1,24 +0,0 @@ -package device.fitbitdata; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -import java.util.List; -import java.util.Map; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class Device { - - public String lastSyncTime; - - // @JsonProperty ("lastSyncTime") - public String getLastSyncTime(List> sync){ - lastSyncTime = null; - for(Map d: sync) { - String temp = d.get("lastSyncTime"); - if ((lastSyncTime == null) || (lastSyncTime.compareTo(temp) < 0)) - lastSyncTime = temp; - } - return lastSyncTime; - } - -} diff --git a/src/main/java/device/fitbitdata/FitbitData.java b/src/main/java/device/fitbitdata/FitbitData.java new file mode 100644 index 0000000..cef5864 --- /dev/null +++ b/src/main/java/device/fitbitdata/FitbitData.java @@ -0,0 +1,8 @@ +package device.fitbitdata; + +public abstract class FitbitData { + private long millisec = 0; + + public void setDate(long millisec) { this.millisec = millisec; } + public long getDate() { return this.millisec; } +} diff --git a/src/main/java/device/fitbitdata/HeartRate.java b/src/main/java/device/fitbitdata/HeartRate.java index 79d4c2b..36e3b46 100644 --- a/src/main/java/device/fitbitdata/HeartRate.java +++ b/src/main/java/device/fitbitdata/HeartRate.java @@ -7,12 +7,10 @@ import java.util.List; import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) -public class HeartRate { +public class HeartRate extends FitbitData { private double average; - public double getAverage() { return average; } - @JsonProperty("activities-heart-intraday") public void setAverage(Map map) { List data = (List) map.get("dataset"); @@ -25,4 +23,7 @@ public class HeartRate { if(data.size() == 0) average = 0; } + + public void setAverage(double average) { this.average = average; } + public double getAverage() { return average; } } diff --git a/src/main/java/device/fitbitdata/Sleep.java b/src/main/java/device/fitbitdata/Sleep.java index 945a6ee..b368f51 100644 --- a/src/main/java/device/fitbitdata/Sleep.java +++ b/src/main/java/device/fitbitdata/Sleep.java @@ -49,14 +49,12 @@ public class Sleep { } public class SleepData { - public final Timestamp start_date; + public final long start_date; public final long duration; - public final Timestamp end_date; public SleepData(Date start_date, long duration) { - this.start_date = new Timestamp(start_date.getTime()); + this.start_date = start_date.getTime(); this.duration = duration; - 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 5ceab80..f092152 100644 --- a/src/main/java/device/fitbitdata/Steps.java +++ b/src/main/java/device/fitbitdata/Steps.java @@ -8,9 +8,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @JsonIgnoreProperties(ignoreUnknown = true) -public class Steps { +public class Steps extends FitbitData { - private int steps=0; + private int steps = 0; @JsonProperty("activities-steps") public void setSteps(Map[] array) { @@ -22,7 +22,7 @@ public class Steps { if(map.get("dateTime").equals(strDate)) steps = Integer.parseInt(map.get("value")); } - - public int getSteps() { return steps; } + public void setSteps(int steps) { this.steps = steps; } + public int getSteps() { return steps; } } diff --git a/src/main/java/main/Main.java b/src/main/java/main/Main.java index 5827eac..6b94d1d 100644 --- a/src/main/java/main/Main.java +++ b/src/main/java/main/Main.java @@ -1,16 +1,16 @@ package main; import device.*; +import device.fitbitdata.HeartRate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import support.Database; +import support.database.Database; +import support.database.LocalDB; +import support.database.RemoteDB; -import java.io.IOException; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; import java.util.Calendar; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,6 +28,7 @@ public class Main { private static Hue lights = null; private static Fitbit fitbit = null; private static Sensor sensor = null; + private static Database database = null; /** * Funzione principale, qui si creano tutte le classi che verranno utilizzate.
@@ -39,8 +40,9 @@ public class Main { *
  • hueUser
  • *
  • sensorNode
  • *
  • sensorLog
  • + *
  • remoteDb
  • * - * @param args per ora nulla, ma forse in futuro si potrebbe mettere roba + * @param args */ public static void main(String[] args) { Map arguments = getArgsMap(args); @@ -50,6 +52,7 @@ public class Main { String hueUser = arguments.get("hueUser"); Integer sensorLog = getInt(arguments.get("sensorLog")); Integer sensorNode = getInt(arguments.get("sensorNode")); + String remoteDbUrl = arguments.get("remoteDb"); try { LOG.info("Connessione alle Philips Hue..."); @@ -70,6 +73,9 @@ public class Main { LOG.info("Connessione al Fitbit, ignorare eventuale errore per setPermissionsToOwnerOnly..."); fitbit = new Fitbit(); + LOG.info("Connessione al database..."); + database = remoteDbUrl==null? new LocalDB():new RemoteDB(remoteDbUrl); + startInsertData(); startCheckSteps(); startHueControlledByHeartBeat(); @@ -121,10 +127,8 @@ public class Main { */ private static void startInsertData() { try { - Database database = Database.getInstance(); - - Thread hourlyData = new Thread(database.insertHourlyData(fitbit), "updating-hour-data"); - Thread dailyData = new Thread(database.insertDailyData(fitbit), "updating-day-data"); + Thread hourlyData = Database.insertHourlyDataIn(database, fitbit, 5); + Thread dailyData = Database.insertDailyData(database, fitbit, 5); LOG.info("Starting threads for automatic update"); hourlyData.start(); @@ -138,24 +142,18 @@ public class Main { /** * funzione che logga periodicalmente i valori ricevuti dal sensore */ - private static void startSensorLog(int seconds) { - Thread thread = new Thread(new Runnable() { + private static void startSensorLog(int minutes) { + LOG.info("Starting thread for logging sensor data"); + + Runnable runnable = new Runnable() { @Override public synchronized void run() { - boolean notInterrupted = true; - while(notInterrupted) { - try { - sensor.update((seconds/2) * 1000); - LOG.info("Luminosita' rilevata: " + sensor.getBrightnessLevel()); - } catch (InterruptedException e) { - e.printStackTrace(); - notInterrupted = false; - } - } + sensor.update(0); + LOG.info("Luminosita' rilevata: " + sensor.getBrightnessLevel()); } - }, "sensor"); + }; - thread.start(); + Database.getThreadStartingEach(runnable, "sensor", minutes).start(); } // TODO AUTO:{B} Gestione luci in modo che la luminosità sia sempre la stessa @@ -172,7 +170,6 @@ public class Main { boolean notInterrupted = true; Calendar calendar = Calendar.getInstance(); while(notInterrupted) { - calendar.setTimeInMillis(System.currentTimeMillis()); int bright = sensor.getBrightnessLevel(); int hour = calendar.get(Calendar.HOUR_OF_DAY); @@ -220,53 +217,42 @@ public class Main { // controllare che non differiscano di un valore Delta // se differiscono di almeno Delta modificare le luci abbassandole o alzandole secondo le esigenze // (nel caso modificare anche il colore e renderlo meno intenso o di piu) + LOG.info("Starting thread lights for heartbeat"); + final int minutes = 30; + final int delta = 15; + Runnable runnable = new Runnable() { + @Override + public synchronized void run() { + int sum=0; + int count=0; + double average; - Calendar past = Calendar.getInstance(); - Database instance = Database.getInstance(); - int sum =0; - int count =0; - double avg =0; - Timestamp twoWeeksAgo ; + List heartRate = database.getHeartDataOfLast(15); //TODO da discriminare l'ora (mi sa che c'è da mettere mano al db + Calendar now = Calendar.getInstance(); + Calendar past = Calendar.getInstance(); + now.setTimeInMillis(System.currentTimeMillis()); - - while(true){ - - past.setTimeInMillis(System.currentTimeMillis()); - try { - - past.add(Calendar.DAY_OF_YEAR, -15); - twoWeeksAgo = new Timestamp (past.getTimeInMillis()); - - ResultSet rs = instance.getDataFromDatabase("SELECT rate FROM heart WHERE day_hour > "+twoWeeksAgo); //TODO da discriminare l'ora (mi sa che c'è da mettere mano al db - - while (rs.next()) { - sum += rs.getDouble("rate"); - count ++; + for(HeartRate rate: heartRate) { + past.setTimeInMillis(rate.getDate()); + if(past.get(Calendar.HOUR_OF_DAY) == now.get(Calendar.HOUR_OF_DAY)) { + sum += rate.getAverage(); + count++; + } } + average = count!=0? sum/count:0; - avg = sum/count; - - Fitbit fitBit = new Fitbit(); - double rateNow = fitBit.getHeartRate(30); - if ((rateNow-avg) >= 15 ) - lights.decreaseBrightness(); - else if ((rateNow-avg) <=-15) - //alzare le luci? - ; - - - Thread.sleep(1800000); - - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (SQLException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); + double rateNow = fitbit.getHeartRate(minutes); + if ((rateNow-average) >= delta ) + lights.decreaseBrightness(); + //avvisare con una voce registrata? + else if ((rateNow-average) <= -delta) + //alzare le luci? + //avvisare con una voce registrata? + ; } - } + }; + + Database.getThreadStartingEach(runnable, "lights-with-heartbeat", minutes).start(); } // TODO AUTO:{D} Ad una certa ora guarda i passi e se sono pochi dillo @@ -299,7 +285,7 @@ public class Main { /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 + /C/ Gestione musica tramite comando vocale // Randomly at night heavy metal start */ diff --git a/src/main/java/support/Database.java b/src/main/java/support/Database.java deleted file mode 100644 index 12d973c..0000000 --- a/src/main/java/support/Database.java +++ /dev/null @@ -1,190 +0,0 @@ -package support; - -import device.Fitbit; -import device.fitbitdata.Sleep; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.*; -import java.util.Calendar; -import java.util.List; - -/** - * Classe che si connette al database e permette di aggiornare i dati in modo automatico tramite dei runnable - */ -public class Database { - - /** - * 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; - - /** - * 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, 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
    • - *
    - *
    - * L'implementazione e' synchronized (si spera thread safe) - */ - public synchronized 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)
    - *
    - * 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) { - return new Runnable() { - @Override - public synchronized void run() { - boolean notInterrupted = true; - boolean retry = false; - double heartRate = 0; - Timestamp now = null; - - 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; - } - } - } - }; - } - - /** - * 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) { - 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) { - 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()); - } - - 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; - } - } - } - }; - } - public ResultSet getDataFromDatabase(String sql) throws SQLException { - Statement statement = conn.createStatement(); - ResultSet rs = statement.executeQuery(sql); - return rs; - - } -} diff --git a/src/main/java/support/Rest.java b/src/main/java/support/Rest.java index 80f8966..b234695 100644 --- a/src/main/java/support/Rest.java +++ b/src/main/java/support/Rest.java @@ -83,7 +83,6 @@ public class Rest { } catch (Exception e) { LOG.error("PUT: " + e.getMessage()); } - } } \ No newline at end of file diff --git a/src/main/java/support/database/Database.java b/src/main/java/support/database/Database.java new file mode 100644 index 0000000..a16474a --- /dev/null +++ b/src/main/java/support/database/Database.java @@ -0,0 +1,156 @@ +package support.database; + +import device.Fitbit; +import device.fitbitdata.HeartRate; +import device.fitbitdata.Sleep; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Timestamp; +import java.util.List; + +public interface Database { + + /** + * Un logger per scrivere a console eventuali errori o informazioni + */ + Logger LOG = LoggerFactory.getLogger("DB"); + + /** + * Una costante che indica quanti millisecondi ci sono in un minuto (utile per le conversioni) + */ + int MILLISEC_IN_MINUTE = 60000; + + /** + * Dice solamente se e' possibile collegarsi al database e se si possono fare delle query + * @return vero se si pu' falso in altri casi. + */ + boolean isReachable(); + + /** + * Inserisce nuovi dati del paziente nel database. + * @param dateMilliSec la data che si vuole inserire in millisec + * @param heartRate il battito cardiaco + * @return vero se ha inserito o i dati esistevano gia' falso se non ce l'ha fatta + */ + boolean updateHeart(long dateMilliSec, double heartRate); + /** + * Inserisce nuovi dati del paziente nel database. + * @param dateStartSleep la data che si vuole inserire in millisec + * @param duration per quanto e' durato il sonno + * @return vero se ha inserito o i dati esistevano gia' falso se non ce l'ha fatta + */ + boolean updateSleep(long dateStartSleep, long duration); + /** + * Inserisce nuovi dati del paziente nel database. + * @param dateMilliSec la data che si vuole inserire in millisec + * @param steps i passi fatti + * @return vero se ha inserito o i dati esistevano gia' falso se non ce l'ha fatta + */ + boolean updateSteps(long dateMilliSec, long steps); + + /** + * Riceve i dati del cuore dal giorno selezionato fino ad oggi + * @param days quanti giorni devono esser considerati + * @return una lista dei battiti cardiaci degli ultimi X giorni (ordinati da oggi al giorno X) + */ + List getHeartDataOfLast(int days); + + /** + * Prendi il Thread che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco)
    + * Se per caso c'e' un fallimento riprova ad inserire i dati ogni x minuti, indicati dal terzo parametro
    + * @param database il database in cui inserirlo + * @param fitbit la classe che contiene i dati aggiornati + * @param retryMinutes ogni quanti minuti deve riprovare ad inviare la richiesta + * @return un Thread + */ + static Thread insertHourlyDataIn(Database database, Fitbit fitbit, int retryMinutes) { + Runnable runnable = new Runnable() { + @Override + public synchronized void run() { + LOG.info("Aggiornamento orario iniziato"); + try { + boolean retry; + long now = System.currentTimeMillis(); + double heartRate = 30;//fitbit.getHeartRate(60); + do { + retry = !database.updateHeart(now, heartRate); + LOG.info("Aggiornamento " + (!retry ? "riuscito" : "fallito, riprovo fra " + retryMinutes + " minuti")); + if (retry) + wait(retryMinutes * MILLISEC_IN_MINUTE); + } while(retry); + } catch (Exception e) { + LOG.warn("Aggiornamento interrotto"); + } + } + }; + + return getThreadStartingEach(runnable, "update-hourly-data", 1); + } + + /** + * Prendi il Thread che automaticamente gestisce l'inserimento dei dati giornalieri
    + * Se per caso c'e' un fallimento riprova ad inserire i dati ogni x minuti, indicati dal terzo parametro
    + * @param database il database in cui inserirlo + * @param fitbit la classe che contiene i dati aggiornati + * @param retryMinutes ogni quanti minuti deve riprovare ad inviare la richiesta + * @return un Thread da far partire + */ + static Thread insertDailyData(Database database, Fitbit fitbit, int retryMinutes) { + Runnable runnable = new Runnable() { + @Override + public synchronized void run() { + LOG.info("Aggiornamento giornaliero iniziato"); + try { + boolean retry; + long steps = fitbit.getSteps(); + List sleepDatas = fitbit.getDetailedSleep(); + long now = System.currentTimeMillis(); + do { + retry = !database.updateSteps(now, steps); + for (Sleep.SleepData data : sleepDatas) + retry = retry && !database.updateSleep(data.start_date, data.duration); + + LOG.info("Aggiornamento " + (!retry ? "riuscito" : "fallito, riprovo fra " + retryMinutes + " minuti")); + if (retry) + wait(retryMinutes * MILLISEC_IN_MINUTE); + } while (retry); + } catch (Exception e) { + LOG.warn("Aggiornamento interrotto"); + } + } + }; + + return getThreadStartingEach(runnable, "update-daily-data", 24*60); + } + + /** + * Restuisce un thread che se fatto partire, esegue il runnable in un sub-thread ogni X minuti
    + * Il sotto thread lanciato avra' lo stesso nome, ma con un trattino e la data di lancio a seguito
    + * Se il thread viene interrotto non fa piu' partire il runnable e si ferma + * @param runnable il runnable da lanciare + * @param threadName il nome da dare al thread + * @param minutes i minuti da aspettare, se negativi o 0 ritorna null + */ + static Thread getThreadStartingEach(final Runnable runnable, String threadName, int minutes) { + if(minutes<1) + return null; + + return new Thread(new Runnable() { + @Override + public synchronized void run() { + boolean notInterrupted = true; + do { + try { + wait(minutes * MILLISEC_IN_MINUTE); + Thread thread = new Thread(runnable, threadName + "-" + new Timestamp(System.currentTimeMillis())); + thread.start(); + } catch (Exception e) { + e.printStackTrace(); + notInterrupted = false; + } + } while (notInterrupted); + } + }, threadName); + } +} diff --git a/src/main/java/support/database/LocalDB.java b/src/main/java/support/database/LocalDB.java new file mode 100644 index 0000000..522573f --- /dev/null +++ b/src/main/java/support/database/LocalDB.java @@ -0,0 +1,106 @@ +package support.database; + +import device.fitbitdata.HeartRate; + +import java.sql.*; +import java.util.LinkedList; +import java.util.List; + +/** + * Classe che utilizza un database sqlite che contiene le seguenti tabelle:
    + *
      + *
    • 'heart' battito + orario
    • + *
    • 'sleep' inizio + durata
    • + *
    • 'steps' data + passi.
    • + *
    + */ +public class LocalDB implements Database { + + /** + * 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"; + + /** + * La connessione al database + */ + 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: + *
      + *
    • 'heart' battito + orario
    • + *
    • 'sleep' inizio + durata
    • + *
    • 'step' data + passi.
    • + *
    + * @throws SQLException se qualcosa e' andato storto + */ + public LocalDB() throws SQLException { + conn = DriverManager.getConnection(DB_LOCATION + DB_NAME); + Statement statement = conn.createStatement(); + statement.execute("CREATE TABLE IF NOT EXISTS heart (day DATE PRIMARY KEY, rate DOUBLE)"); + statement.execute("CREATE TABLE IF NOT EXISTS sleep (sleep_start DATE PRIMARY KEY, duration INTEGER)"); + statement.execute("CREATE TABLE IF NOT EXISTS steps (day DATE PRIMARY KEY, steps INTEGER)"); + } + + @Override + public boolean isReachable() { return conn!=null; } + + @Override + public boolean updateHeart(long dateMilliSec, double heartRate) { + return query("INSERT INTO heart (day, rate) VALUES ( ' " + new Timestamp(dateMilliSec) + " ', '" + heartRate + "')"); + } + + @Override + public boolean updateSleep(long dateStartSleep, long duration) { + return query("INSERT INTO sleep (sleep_start, duration) VALUES ( ' " + new Timestamp(dateStartSleep) + " ', '" + duration + "')"); + } + + @Override + public boolean updateSteps(long dateMilliSec, long steps) { + return query("INSERT INTO steps (day, steps) VALUES ( ' " + new Timestamp(dateMilliSec) + " ', '" + steps + "')"); + } + + @Override + public List getHeartDataOfLast(int days) { + try { + int dayToSubtract = 15; +// Calendar calendar = Calendar.getInstance(); +// calendar.setTimeInMillis(System.currentTimeMillis()); +// calendar.add(Calendar.DATE, -dayToSubtract); +// long time = calendar.getTimeInMillis(); + long time = System.currentTimeMillis() - (dayToSubtract * 24 * 60 * 1000); // meno 24 giorni per 60 secondi per 100 millisec + + ResultSet result = conn.createStatement().executeQuery("SELECT * FROM heart WHERE day>='" + new Timestamp(time) + "'"); + List list = new LinkedList<>(); + + while(result.next()) { + HeartRate rate = new HeartRate(); + rate.setAverage(result.getDouble("rate")); + rate.setDate(result.getDate("day").getTime()); + + list.add(rate); + } + return list; + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + private boolean query(String sql) { + try { + conn.createStatement().execute(sql); + return true; + } catch (SQLException e) { + LOG.error(e.getMessage()); + return false; + } + } +} diff --git a/src/main/java/support/database/RemoteDB.java b/src/main/java/support/database/RemoteDB.java new file mode 100644 index 0000000..a89c2f5 --- /dev/null +++ b/src/main/java/support/database/RemoteDB.java @@ -0,0 +1,39 @@ +package support.database; + +import device.fitbitdata.HeartRate; + +import java.util.List; + +// TODO implement +public class RemoteDB implements Database { + + public RemoteDB(String url) { + + } + + @Override + public boolean isReachable() { + return false; + } + + @Override + public boolean updateHeart(long dateMilliSec, double heartRate) { + return false; + } + + @Override + public boolean updateSleep(long dateStartSleep, long duration) { + return false; + } + + @Override + public boolean updateSteps(long dateMilliSec, long steps) { + return false; + } + + @Override + public List getHeartDataOfLast(int days) { + return null; + } + +}