Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ce6c18476 | ||
| d7cef1f623 | |||
| abd68e0eb1 | |||
|
|
70dadc2111 | ||
|
|
75ebc025a3 | ||
|
|
898e25fcf3 | ||
| eea9dd50e0 | |||
| 2ed9eef8d1 | |||
| e5237b5da4 | |||
| afe644c9e7 | |||
|
|
dd708201c1 | ||
| e1efbafe45 | |||
|
|
40fb445262 | ||
| a4c6c095c6 | |||
|
|
662fb40df3 | ||
|
|
e7dd9162c2 | ||
| 4ab578226c | |||
|
|
db1c6e0801 | ||
| f7942abae2 | |||
| d891e53c8e | |||
|
|
f2e8991049 | ||
| 1f4a4b80c0 | |||
| 7b21345f89 | |||
|
|
2c6789c33a | ||
| 8feb04990f | |||
| 09d8b7781f | |||
|
|
3798d18844 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,6 +29,7 @@ hs_err_pid*
|
||||
.idea/*.xml
|
||||
.gradle/
|
||||
.iml
|
||||
.db
|
||||
|
||||
# eclipse things #
|
||||
.classpath
|
||||
|
||||
201
LICENSE.md
Normal file
201
LICENSE.md
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
3
META-INF/MANIFEST.MF
Normal file
3
META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: main.SeniorAssistant
|
||||
|
||||
@@ -11,7 +11,13 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.settings" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/META-INF" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/lib" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/out" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
57
build.gradle
57
build.gradle
@@ -8,32 +8,51 @@ version '1.0-SNAPSHOT'
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url("https://plugins.gradle.org/m2/")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Tests
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
// compile "com.sparkjava:spark-core:2.5.5"
|
||||
compile "com.google.code.gson:gson:2.8.0"
|
||||
// compile "org.xerial:sqlite-jdbc:3.15.1"
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.3'
|
||||
compile 'com.google.api-client:google-api-client:1.23.0'
|
||||
compile group: 'com.google.oauth-client', name: 'google-oauth-client-jetty', version: '1.11.0-beta'
|
||||
|
||||
// Database
|
||||
compile "org.xerial:sqlite-jdbc:3.21.0.1"
|
||||
|
||||
// z-way-lib and all its dependencies (from https://github.com/pathec/ZWay-library-for-Java)
|
||||
// Rest request
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.6'
|
||||
|
||||
// Z-way
|
||||
compile files('lib/zway-lib-0.2.9-SNAPSHOT.jar')
|
||||
compile 'org.apache.commons:commons-lang3:3.8'
|
||||
|
||||
compile 'org.apache.commons:commons-lang3:3.4'
|
||||
compile 'org.eclipse.jetty:jetty-client:9.3.11.v20160721'
|
||||
compile 'org.eclipse.jetty:jetty-http:9.3.11.v20160721'
|
||||
compile 'org.eclipse.jetty:jetty-io:9.3.11.v20160721'
|
||||
compile 'org.eclipse.jetty:jetty-util:9.3.11.v20160721'
|
||||
compile 'org.eclipse.jetty.websocket:websocket-api:9.3.12.v20160915'
|
||||
compile 'org.eclipse.jetty.websocket:websocket-client:9.3.12.v20160915'
|
||||
compile 'org.eclipse.jetty.websocket:websocket-common:9.3.12.v20160915'
|
||||
compile 'org.slf4j:slf4j-simple:1.7.21'
|
||||
// Logger
|
||||
compile 'org.slf4j:slf4j-simple:1.7.25'
|
||||
|
||||
//for objectMapper
|
||||
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
|
||||
// Server Spark
|
||||
compile "com.sparkjava:spark-core:2.7.2"
|
||||
|
||||
// Oauth
|
||||
compile ( group: 'com.google.oauth-client', name: 'google-oauth-client-jetty', version: '1.23.0') {
|
||||
// don't pull in an old ancient jetty version
|
||||
exclude group: 'org.mortbay.jetty', module: 'jetty'
|
||||
exclude group: 'org.mortbay.jetty', module: 'jetty-util'
|
||||
exclude group: 'org.mortbay.jetty', module: 'servlet-api'
|
||||
}
|
||||
compile 'com.google.api-client:google-api-client:1.23.0'
|
||||
|
||||
// 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
|
||||
|
||||
// YOUTUBE VIDEO
|
||||
|
||||
//compile files('lib/SWT_linux32.jar')
|
||||
//compile files('lib/SWT_linux64.jar')
|
||||
compile files('lib/SWT_win64.jar')
|
||||
compile group: 'com.hynnet', name: 'DJNativeSwing', version: '1.0.0'
|
||||
compile group: 'com.hynnet', name: 'DJNativeSwing-SWT', version: '1.0.0'
|
||||
}
|
||||
|
||||
BIN
lib/SWT_linux32.jar
Normal file
BIN
lib/SWT_linux32.jar
Normal file
Binary file not shown.
BIN
lib/SWT_linux64.jar
Normal file
BIN
lib/SWT_linux64.jar
Normal file
Binary file not shown.
BIN
lib/SWT_win64.jar
Normal file
BIN
lib/SWT_win64.jar
Normal file
Binary file not shown.
@@ -1,13 +0,0 @@
|
||||
import manage.FITBITData.FitBit;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) throws Exception {
|
||||
FitBit fitBit = new FitBit();
|
||||
fitBit.getHoursSleep();
|
||||
|
||||
System.out.println("Today's average heart-rate: "+fitBit.getHeartRate());
|
||||
System.out.println("Today's hours of sleep: "+fitBit.getHoursSleep());
|
||||
System.out.println("Today's steps: "+fitBit.getSteps());
|
||||
System.out.println("Fine.");
|
||||
}
|
||||
}
|
||||
148
src/main/java/device/DialogFlowWebHook.java
Normal file
148
src/main/java/device/DialogFlowWebHook.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package device;
|
||||
|
||||
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;
|
||||
import spark.Spark;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static spark.Spark.get;
|
||||
import static spark.Spark.post;
|
||||
|
||||
/**
|
||||
* Classe per creare un Webhook che Dialog-Flow possa utilizzare per le sue azioni
|
||||
*/
|
||||
public class DialogFlowWebHook {
|
||||
|
||||
/**
|
||||
* Un logger per ottenere messaggi sull'esecuzione del programma
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger("WebHook");
|
||||
|
||||
/**
|
||||
* Stringa che viene usata se l'azione esiste ma lancia un qualche tipo di errore
|
||||
*/
|
||||
public static final String ACTION_ERROR = "L'azione non è eseguibile";
|
||||
|
||||
/**
|
||||
* Errore che viene mostrato all'utente se l'azione inviata non corrisponde a nessuna di quelle inserite
|
||||
*/
|
||||
public static final String ERROR = "Non posso eseguire nessuna azione di questo tipo";
|
||||
|
||||
/**
|
||||
* L'eventuale path successiva all'url dichiarato nel Webhook di Dialog-Flow
|
||||
*/
|
||||
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<String, Action> actions;
|
||||
|
||||
/**
|
||||
* 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("/", 4567); }
|
||||
|
||||
/**
|
||||
* 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, 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
|
||||
*/
|
||||
public void addOnAction(String actionId, Action action) { this.actions.put(actionId, action); }
|
||||
|
||||
/**
|
||||
* Fa partire il server per accettare richieste da Dialog-Flow ascoltando connessioni in post.<br>
|
||||
* Ogni richiesta viene esaminata e fatta coincidere con un'azione specificata precedentemente.<br>
|
||||
* Se nessuna azione viene riscontrata, viene inviato un errore, rimuovendo i messaggi<br>
|
||||
* Inoltre aggiunge un'interfaccia in get che riguarda un iframe di dialogflow
|
||||
*/
|
||||
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 inputAction = input.getResult().getAction();
|
||||
Map<String, JsonElement> inputParam = input.getResult().getParameters();
|
||||
String text;
|
||||
try {
|
||||
LOG.info("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.error("NESSUNA AZIONE TROVATA: "+ inputAction);
|
||||
text = ERROR;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Qualcosa e' andato storto: " + e.getMessage());
|
||||
text = ERROR;
|
||||
}
|
||||
|
||||
if(text == null)
|
||||
text = input.getResult().getFulfillment().getSpeech();
|
||||
|
||||
for(String param: inputParam.keySet())
|
||||
text = text.replace("@"+param, inputParam.get(param).getAsString());
|
||||
|
||||
LOG.info("RISPOSTA: " + text);
|
||||
output.setDisplayText(text);
|
||||
output.setSpeech(text);
|
||||
|
||||
response.type("application/json");
|
||||
return output;
|
||||
}, gson::toJson);
|
||||
|
||||
get(this.path, (request, response) -> {
|
||||
return "<iframe\n" +
|
||||
" allow=\"microphone;\"\n" +
|
||||
" width=\"350\"\n" +
|
||||
" height=\"430\"\n" +
|
||||
" src=\"https://console.dialogflow.com/api-client/demo/embedded/SeniorAssistant\">\n" +
|
||||
"</iframe>";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Interfaccia usata per fare un'azione per ogni Id di Dialog-Flow
|
||||
*/
|
||||
public interface Action {
|
||||
/**
|
||||
* Fa l'azione desiderata.
|
||||
* Se ritorna una stringa allora il testo viene cambiato. Se ritorna null non cambia il testo
|
||||
*
|
||||
* @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
|
||||
* @throws Exception una qualunque eccezione che si vuole lanciare
|
||||
*/
|
||||
String doAction(Map<String, JsonElement> params) throws Exception;
|
||||
}
|
||||
}
|
||||
215
src/main/java/device/Fitbit.java
Normal file
215
src/main/java/device/Fitbit.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package device;
|
||||
|
||||
import device.fitbitdata.HeartRate;
|
||||
import device.fitbitdata.Sleep;
|
||||
import device.fitbitdata.Steps;
|
||||
import oauth.AuthFitbit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Classe che permette di ricevere i dati di un particolare account FitBit
|
||||
*/
|
||||
public class Fitbit {
|
||||
|
||||
/**
|
||||
* Logger per vedere cosa invia e riceve questa classe
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger("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<br>
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* L'oauth per l'account fitbit
|
||||
*/
|
||||
private final AuthFitbit auth;
|
||||
|
||||
/**
|
||||
* Una mappa contenente le ultime classi usate nelle richieste effettuate<br>
|
||||
* Una sorta di cache
|
||||
*/
|
||||
private final Map<Class<?>, Long> latestRequest = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Un calendario in modo da sapere la data per prendere i dati
|
||||
*/
|
||||
private final Calendar calendar = Calendar.getInstance();
|
||||
|
||||
/**
|
||||
* 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 un'istanza di fitbit<br>
|
||||
* 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
|
||||
*
|
||||
* @throws Exception Nel caso qualunque cosa andasse storta (vedi messaggio)
|
||||
*/
|
||||
public Fitbit() throws Exception { this.auth = new AuthFitbit(); }
|
||||
|
||||
/**
|
||||
* Ricevi i passi che l'utente ha effettuato nell'ultimo giorno
|
||||
*
|
||||
* @return un intero rappresentante i passi effettuati
|
||||
*/
|
||||
public synchronized int getSteps() {
|
||||
steps = update(Steps.class, steps, "1" + USER + "activities/steps/date/today/1d/1min.json");
|
||||
return steps.getSteps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi i passi che l'utente ha effettuato negli ultimi minuti richiesti
|
||||
*
|
||||
* @param lastMinutes gli ultimi minuti che si vogliono vedere (positivi e !=0 se no ritorno -1)
|
||||
* @return un intero rappresentante i passi effettuati
|
||||
*/
|
||||
public synchronized int getSteps(int lastMinutes) {
|
||||
if(lastMinutes<=0)
|
||||
return -1;
|
||||
|
||||
steps = update(Steps.class, steps, "1" + USER + "activities/steps/date/today/1d/1min.json");
|
||||
|
||||
List<Map<String, Object>> list = steps.getStepsData();
|
||||
final int now = list.size()-1;
|
||||
|
||||
int totalSteps = 0;
|
||||
for(int i=0; i<lastMinutes; i++)
|
||||
totalSteps += (int)list.get(now-i).get("value");
|
||||
|
||||
return totalSteps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi il battito cardiaco dell'utente<br>
|
||||
* Il risultato e' una media del battito che l'utente ha avuto negli ultimi 15 minuti
|
||||
*
|
||||
* @return un intero rappresentante la media del battito cardiaco degli ultimi 15 minuti
|
||||
*/
|
||||
public synchronized double getHeartRate() { return getHeartRate(15); }
|
||||
|
||||
/**
|
||||
* Ricevi il battito cardiaco dell'utente<br>
|
||||
* 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 e !=0 se no ritorno -1)
|
||||
* @return un intero rappresentante la media del battito cardiaco degli ultimi minuti specificati
|
||||
*/
|
||||
public synchronized double getHeartRate(int lastMinutes) {
|
||||
if(lastMinutes<=0)
|
||||
return -1;
|
||||
|
||||
long currentMillisec = System.currentTimeMillis();
|
||||
|
||||
String now = getHourMinutes(currentMillisec);
|
||||
String ago = getHourMinutes(currentMillisec - (MINUTE * lastMinutes));
|
||||
|
||||
if (now.compareTo(ago) < 0)
|
||||
ago = "00:00";
|
||||
|
||||
heart = update(HeartRate.class, null,"1" + USER + "activities/heart/date/today/1d/1sec/time/" + ago + "/" + now + ".json");
|
||||
return heart.getAverage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi le ore di sonno che l'utente ha fatto nell'ultimo giorno
|
||||
*
|
||||
* @return un intero rappresentante le ore passate a dormire
|
||||
*/
|
||||
public synchronized double getHoursSleep() {
|
||||
sleep = update(Sleep.class, sleep,"1.2" + USER + "sleep/date/today.json");
|
||||
return (double)sleep.getMinutesAsleep()/60;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi tutti i dati presenti per il sonno di questo giorno.
|
||||
* La lista contiene per ogni volta che l'utente ha dormito:<br>
|
||||
* - la data di quando si e' addormentato<br>
|
||||
* - la durata del sonno
|
||||
* @return una lista contenente ogni volta che l'utente ha dormito
|
||||
*/
|
||||
public synchronized List<Sleep.SleepData> getDetailedSleep() {
|
||||
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.<br>
|
||||
* Se e' possibile fare l'update viene mandata una run all'url selezionato e viene ritornata la variabile aggiornata<br>
|
||||
* Altrimenti viene ritornata la variabile passata<br>
|
||||
* 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> T update(Class<T> varClass, T variable, String url) {
|
||||
try {
|
||||
long current = System.currentTimeMillis();
|
||||
long latest = latestRequest.get(varClass);
|
||||
|
||||
// don't update
|
||||
if( (variable!=null) && (current - latest < MINUTE * 5) )
|
||||
return variable;
|
||||
} catch (NullPointerException e) {
|
||||
// do nothing and update
|
||||
}
|
||||
|
||||
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());
|
||||
e.printStackTrace();
|
||||
}
|
||||
return variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Funzione che transforma i millisecondi nel formato "hh:mm"
|
||||
*
|
||||
* @param milliseconds millisecondi da trasformare
|
||||
* @return una stringa nel formato "hh:mm"
|
||||
*/
|
||||
private String getHourMinutes(long milliseconds) {
|
||||
calendar.setTimeInMillis(milliseconds);
|
||||
|
||||
int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
int minu = calendar.get(Calendar.MINUTE);
|
||||
return String.format("%02d:%02d", hour, minu);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +1,223 @@
|
||||
package device;
|
||||
|
||||
import ai.api.GsonFactory;
|
||||
import com.google.common.util.concurrent.AtomicDouble;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import support.Rest;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import manage.Rest;
|
||||
|
||||
/**
|
||||
* Classe che permette di controllare le luci Philips Hue
|
||||
*/
|
||||
public class Hue {
|
||||
//private String baseURL = "192.168.0.2";
|
||||
//private String username = "C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx";
|
||||
private String lightsURL;// = baseURL+"/api/"+username+"/lights/";
|
||||
|
||||
private Map<String, ?> allLights;
|
||||
/**
|
||||
* Log che serve per debug
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger("Hue");
|
||||
|
||||
public Hue () {
|
||||
this("http://172.30.1.138/api/C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx/lights/");
|
||||
}
|
||||
public Hue (String url) {
|
||||
lightsURL = url;
|
||||
allLights = Rest.get(lightsURL);
|
||||
/**
|
||||
* La luminosita' massima a cui si puo' arrivare
|
||||
*/
|
||||
private static final int MAX_BRIGHTNESS = 254;
|
||||
|
||||
/**
|
||||
* Una mappa che ad ogni colore (in italiano) assegna il proprio valore in hue
|
||||
*/
|
||||
private static final Map<String, Double[]> COLORS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* L'url in cui si possono trovare le luci
|
||||
*/
|
||||
private final String lightsURL;
|
||||
|
||||
/**
|
||||
* Tutte le luci che sono state registrate dall'url
|
||||
*/
|
||||
private final Map<String, Map<String, Object>> allLights;
|
||||
|
||||
/**
|
||||
* L'ultima luminosita' impostata
|
||||
*/
|
||||
private final AtomicDouble brightness = new AtomicDouble(0);
|
||||
|
||||
// Riempimento della mappa con i colori
|
||||
static {
|
||||
COLORS.put("giall[oae]", new Double[]{0.45, 0.45});
|
||||
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.12, 0.125});
|
||||
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.55, 0.4});
|
||||
//COLORS.put("nero", new Double[]{1.0, 1.0});
|
||||
COLORS.put("bianc(o|a|he)", new Double[]{0.275, 0.3});
|
||||
}
|
||||
|
||||
public Set<String> getNameLights() {
|
||||
return allLights.keySet();
|
||||
/**
|
||||
* Cerca le luci Philips Hue all'indirizzo <a href="http://172.30.1.138/api/C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx/lights/">http://172.30.1.138/api/C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx/lights/</a>
|
||||
* @throws NullPointerException se non trova nessun bridge
|
||||
*/
|
||||
public Hue () throws NullPointerException { this("172.30.1.138", "C0vPwqjJZo5Jt9Oe5HgO6sBFFMxgoR532IxFoGmx"); }
|
||||
|
||||
/**
|
||||
* Cerca le luci Philips Hue nell'indirizzo specificato e con l'utente specificato.<br>
|
||||
* Una volta trovate le luci le setta tutte alla stessa luminosita' e allo stesso colore<br>
|
||||
* (luminosità massima e colore bianco)
|
||||
* @param ip l'indirizzo IP (seguito dalla porta se e' diversa dalla solita 8000)
|
||||
* @param user l'utente
|
||||
* @throws NullPointerException se non trova nessun bridge
|
||||
*/
|
||||
public Hue(String ip, String user) throws NullPointerException {
|
||||
lightsURL = "http://" + ip + "/api/" + user + "/lights/";
|
||||
allLights = (Map<String, Map<String, Object>>)Rest.get(lightsURL);
|
||||
|
||||
if(allLights.isEmpty())
|
||||
throw new NullPointerException("Non e' stato possibile connettersi alle luci");
|
||||
|
||||
setBrightness(100);
|
||||
changeColor("bianco");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ritorna un insieme contenente tutti i nomi delle luci che si sono trovate
|
||||
*
|
||||
* @return l'insieme dei nomi delle luci
|
||||
*/
|
||||
public Set<String> getNameLights() { return allLights.keySet(); }
|
||||
|
||||
/**
|
||||
* Rimuove dal controllo tutte le luci che hanno il nome uguale ad uno contenuto nell'insieme passato
|
||||
*
|
||||
* @param toRemove le luci da rimuovere
|
||||
*/
|
||||
public void removeLights(Set<String> toRemove) {
|
||||
for(String string : toRemove)
|
||||
allLights.remove(string);
|
||||
}
|
||||
|
||||
public void turnOn() {
|
||||
/**
|
||||
* Accende o spegne le luci controllate
|
||||
* @param on vero se si vuole le luci accese, false per spegnerle
|
||||
*/
|
||||
public void on(boolean on) { setState("on", on, false); }
|
||||
|
||||
/**
|
||||
* Accende tutte le luci controllate
|
||||
*/
|
||||
public void turnOn() { on(true); }
|
||||
|
||||
/**
|
||||
* Spegne tutte le luci controllate
|
||||
*/
|
||||
public void turnOff() { on(false); }
|
||||
|
||||
/**
|
||||
* Ritorna la luminosita' attuale delle luci controllate
|
||||
* @return il valore e' compreso tra 0 e 100
|
||||
*/
|
||||
public double getCurrentBrightness() { return brightness.doubleValue(); }
|
||||
|
||||
/**
|
||||
* Modifica la luminosita' delle luci a seconda del valore inserito
|
||||
* @param num la luminosita' che si vuole da (0 a 100)
|
||||
*/
|
||||
public void setBrightness(double num) {
|
||||
if (num<0)
|
||||
num=0;
|
||||
else if (num>100)
|
||||
num=100;
|
||||
|
||||
setState("bri", (int) (num*MAX_BRIGHTNESS)/100, true);
|
||||
brightness.set(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggiunge il valore delta alla luminosita' corrente delle luci.
|
||||
* @param delta un qualsiasi numero che va da -100 a 100
|
||||
*/
|
||||
public void addBrightness(double delta) {
|
||||
setBrightness(brightness.doubleValue() + delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aumenta la luminosita' delle luci controllate della percentuale che viene passata
|
||||
* @param percentage la percentuale di aumento della luminosita'
|
||||
*/
|
||||
public void increaseBrightness(double percentage) {
|
||||
if (percentage<0)
|
||||
percentage = 0;
|
||||
else if (percentage>100)
|
||||
percentage = 100;
|
||||
setBrightness(brightness.doubleValue() + percentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aumenta la luminosita' delle luci controllate del 25%
|
||||
*/
|
||||
public void increaseBrightness() { increaseBrightness(25); }
|
||||
|
||||
/**
|
||||
* Diminuisce la luminosita' delle luci controllate della percentuale che viene passata
|
||||
* @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.doubleValue() - percentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Diminuisce la luminosita' delle luci controllate del 25%
|
||||
*/
|
||||
public void decreaseBrightness() { decreaseBrightness(25); }
|
||||
|
||||
public void changeColor(String colorName) {
|
||||
for (String regex: COLORS.keySet())
|
||||
if(colorName.matches("(?i)" + regex))
|
||||
setState("xy", COLORS.get(regex), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifica il colore delle luci in modo da fare un bel effetto arcobaleno continuo
|
||||
*/
|
||||
public void colorLoop() { setState("effect", "colorloop", false); }
|
||||
|
||||
/**
|
||||
* Invia una richiesta a tutte le luci hue con l'attributo selezionato ed il suo valore<br>
|
||||
* Con esso invia anche un valore di transizione in modo che sia piu fluido il cambiamento se si mette true al terzo parametro
|
||||
* @param attribute l'attributo da modificare
|
||||
* @param value il valore da inserire
|
||||
* @param transition se includere la transizione o meno
|
||||
*/
|
||||
private void setState(String attribute, Object value, boolean transition){
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(attribute, value);
|
||||
if(transition)
|
||||
map.put("transition", 200);
|
||||
setState(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invia una richiesta a tutte le luci hue con gli attributi selezionati ed il loro valore
|
||||
* @param attributes una mappa di attributi -> valori
|
||||
*/
|
||||
private synchronized void setState(Map<String, Object> attributes) {
|
||||
String body = GsonFactory.getDefaultFactory().getGson().toJson(attributes);
|
||||
LOG.info("Setting: " + body);
|
||||
for (String light : allLights.keySet()) {
|
||||
String callURL = lightsURL + light + "/state";
|
||||
String body = "{ \"on\" : true }";
|
||||
Rest.put(callURL, body, "application/json");
|
||||
}
|
||||
}
|
||||
Rest.put(lightsURL + light + "/state", body,"application/json");
|
||||
|
||||
public void turnOff() {
|
||||
for (String light : allLights.keySet()) {
|
||||
String callURL = lightsURL + light + "/state";
|
||||
String body = "{ \"on\" : false }";
|
||||
Rest.put(callURL, body, "application/json");
|
||||
Map<String, Object> state = (Map<String, Object>)allLights.get(light).get("state");
|
||||
for (String attr : attributes.keySet())
|
||||
state.put(attr, attributes.get(attr));
|
||||
}
|
||||
}
|
||||
|
||||
public void setBrightness(int num) {
|
||||
for (String light : allLights.keySet()) {
|
||||
String callURL = lightsURL + light + "/state";
|
||||
String body = "{ \"bri\" : "+num+" }";
|
||||
Rest.put(callURL, body, "application/json");
|
||||
}
|
||||
}
|
||||
|
||||
/*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 colorLoop() {
|
||||
for (String light : allLights.keySet()) {
|
||||
String callURL = lightsURL + light + "/state";
|
||||
String body = "{ \"on\" : true, \"effect\" : \"colorloop\" }";
|
||||
Rest.put(callURL, body, "application/json");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -4,39 +4,99 @@ import de.fh_zwickau.informatik.sensor.IZWayApi;
|
||||
import de.fh_zwickau.informatik.sensor.ZWayApiHttp;
|
||||
import de.fh_zwickau.informatik.sensor.model.devices.Device;
|
||||
import de.fh_zwickau.informatik.sensor.model.devices.DeviceList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import support.ZWaySimpleCallback;
|
||||
|
||||
/**
|
||||
* Sensore che permette di registrare vari dati dell'ambiente (noi utilizziamo solo la luminosità)
|
||||
*/
|
||||
public class Sensor {
|
||||
// init logger
|
||||
Logger logger = LoggerFactory.getLogger(Sensor.class);
|
||||
|
||||
// sample RaZberry IP address
|
||||
String ipAddress = "http://172.30.1.137:8083";
|
||||
/**
|
||||
* IP del sensore a cui ci si vuole agganciare
|
||||
*/
|
||||
private static final String IP_ADDRESS = "172.30.1.137";
|
||||
|
||||
// sample username and password
|
||||
String username = "admin";
|
||||
String password = "raz4reti2";
|
||||
/**
|
||||
* Porta in cui si ascolta per i sensori
|
||||
*/
|
||||
private static final int PORT = 8083;
|
||||
|
||||
IZWayApi zwayApi;
|
||||
/**
|
||||
* Username con cui si entra nel dispositivo
|
||||
*/
|
||||
private static final String USERNAME = "admin";
|
||||
/**
|
||||
* Password del dispositivo
|
||||
*/
|
||||
private final String PASSWORD = "raz4reti2";
|
||||
|
||||
public Sensor() {
|
||||
/**
|
||||
* Tutti i devices che esistono nella rete
|
||||
*/
|
||||
private DeviceList allZWaveDevices;
|
||||
/**
|
||||
* I device che vengono selezionati e filtrati dall'utente
|
||||
* (ovvero quelli che verranno usati per prendere i dati)
|
||||
*/
|
||||
private DeviceList devices;
|
||||
|
||||
/**
|
||||
* Crea un sensore contenente tutti i nodi
|
||||
* @throws NullPointerException se non trova nessun sensore
|
||||
*/
|
||||
public Sensor() throws NullPointerException { this(null); }
|
||||
|
||||
/**
|
||||
* Si connette ad un sensore che ha il nodeId selezioniato
|
||||
* @param nodeId nodo che viene selezionato
|
||||
* @throws NullPointerException se non trova nessun sensore
|
||||
*/
|
||||
public Sensor (Integer nodeId) throws NullPointerException {
|
||||
// create an instance of the Z-Way library; all the params are mandatory (we are not going to use the remote service/id)
|
||||
zwayApi = new ZWayApiHttp(ipAddress, 8083, "http", username, password, 0, false, new ZWaySimpleCallback());
|
||||
}
|
||||
IZWayApi zwayApi = new ZWayApiHttp(IP_ADDRESS, PORT, "http", USERNAME, PASSWORD, 0, false, new ZWaySimpleCallback());
|
||||
|
||||
// get all the Z-Wave devices
|
||||
DeviceList allDevices = zwayApi.getDevices();
|
||||
allZWaveDevices = zwayApi.getDevices();
|
||||
|
||||
public boolean IsLowLuminescence(int Luminescence) {
|
||||
for (Device dev : allDevices.getAllDevices()) {
|
||||
if (dev.getDeviceType().equalsIgnoreCase("SensorMultilevel"))
|
||||
if (dev.getProbeType().equalsIgnoreCase("luminescence"))
|
||||
if (Integer.parseInt(dev.getMetrics().getLevel()) < Luminescence)
|
||||
return true;
|
||||
if(allZWaveDevices == null)
|
||||
throw new NullPointerException("I sensori non sono stati trovati");
|
||||
|
||||
if(nodeId != null)
|
||||
useNode(nodeId);
|
||||
else
|
||||
return false;
|
||||
devices = allZWaveDevices;
|
||||
}
|
||||
return false;
|
||||
|
||||
/**
|
||||
* Cambia i dispositivi selezionati in base al nodeId che viene scelto
|
||||
* @param nodeId il nodo che viene selezionato
|
||||
*/
|
||||
public void useNode(int nodeId) {
|
||||
devices = new DeviceList();
|
||||
for (Device devi : allZWaveDevices.getAllDevices())
|
||||
if(devi.getNodeId() == nodeId)
|
||||
devices.addDevice(devi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Legge i valori della luminosita' segnata dai dispositivi e ne ritorna il valore
|
||||
* @return la luminosita' segnata dai dispositivi (da 0 a 100)
|
||||
*/
|
||||
public double getBrightnessLevel() {
|
||||
update();
|
||||
for (Device device : devices.getAllDevices())
|
||||
if (device.getMetrics().getProbeTitle().equalsIgnoreCase("luminiscence"))
|
||||
return Double.parseDouble(device.getMetrics().getLevel())/10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fa in modo di forzare l'aggiornamento dei dispositivi
|
||||
*/
|
||||
synchronized private void update() {
|
||||
for (Device device : devices.getAllDevices())
|
||||
try {
|
||||
device.update();
|
||||
} catch (Exception e) { }
|
||||
}
|
||||
}
|
||||
|
||||
20
src/main/java/device/fitbitdata/FitbitData.java
Normal file
20
src/main/java/device/fitbitdata/FitbitData.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package device.fitbitdata;
|
||||
|
||||
/**
|
||||
* Classe che serve ad avere una data collegata al dato preso dal fitbit
|
||||
*/
|
||||
public abstract class FitbitData {
|
||||
private long millisec = 0;
|
||||
|
||||
/**
|
||||
* Setta la data collegata al dato preso dal fitbit
|
||||
* @param millisec la data in millisecondi
|
||||
*/
|
||||
public void setDate(long millisec) { this.millisec = millisec; }
|
||||
|
||||
/**
|
||||
* Ricevi la data collegata al dato richiesto dal fitbit
|
||||
* @return la data in millisecondi
|
||||
*/
|
||||
public long getDate() { return this.millisec; }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package manage.FITBITData;
|
||||
package device.fitbitdata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
@@ -6,23 +6,16 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Classe per vedere le informazioni sul battito cardiaco
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class HeartRate {
|
||||
public class HeartRate extends FitbitData {
|
||||
|
||||
private String dateTime;
|
||||
private double average;
|
||||
|
||||
public double getAverage() {
|
||||
return average;
|
||||
}
|
||||
|
||||
@JsonProperty("activities-heart")
|
||||
public void quelloCheVoglio(Map<String, Object>[] activities){
|
||||
dateTime = (String) activities[0].get("dateTime");
|
||||
}
|
||||
|
||||
@JsonProperty("activities-heart-intraday")
|
||||
public void setAverage(Map<String, Object> map) {
|
||||
private void setAverage(Map<String, Object> map) {
|
||||
List<Map> data = (List) map.get("dataset");
|
||||
|
||||
int sum = 0;
|
||||
@@ -33,4 +26,16 @@ public class HeartRate {
|
||||
if(data.size() == 0)
|
||||
average = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setta il vaore medio del battito cardiaco
|
||||
* @param average il valore medio del battito
|
||||
*/
|
||||
public void setAverage(double average) { this.average = average; }
|
||||
|
||||
/**
|
||||
* Ricevi il valore medio del battito cardiaco
|
||||
* @return il valore medio
|
||||
*/
|
||||
public double getAverage() { return average; }
|
||||
}
|
||||
79
src/main/java/device/fitbitdata/Sleep.java
Normal file
79
src/main/java/device/fitbitdata/Sleep.java
Normal file
@@ -0,0 +1,79 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Classe per recuperare i dati del sonno dell'utente
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Sleep {
|
||||
|
||||
private int minutesAsleep;
|
||||
|
||||
private List<SleepData> datas;
|
||||
|
||||
@JsonProperty("summary")
|
||||
private void setMinutesAsleep(Map<String, Object> map) {
|
||||
minutesAsleep = (int) map.get("totalMinutesAsleep");
|
||||
}
|
||||
|
||||
@JsonProperty("sleep")
|
||||
private void setSleepsList(Map<String, Object>[] array) {
|
||||
datas = new ArrayList<>();
|
||||
SimpleDateFormat sdf = new SimpleDateFormat();
|
||||
for(Map<String, Object> 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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I minuti totali che l'utente ha avuto di sonno durante il giorno
|
||||
* @return i minuti totali
|
||||
*/
|
||||
public long getMinutesAsleep() {
|
||||
return minutesAsleep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi i dati più specifici riguardo al sonno
|
||||
* @return una lista con i dati specifici del sonno
|
||||
*/
|
||||
public List<SleepData> getDatas() {
|
||||
return datas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe utilizzata per avere i dati più specifici del sonno
|
||||
*/
|
||||
public class SleepData {
|
||||
/**
|
||||
* La data d'inizio del sonno in millisec
|
||||
*/
|
||||
public final long start_date;
|
||||
/**
|
||||
* La durata del sonno in millisec
|
||||
*/
|
||||
public final long duration;
|
||||
|
||||
SleepData(Date start_date, long duration) {
|
||||
this.start_date = start_date.getTime();
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/main/java/device/fitbitdata/Steps.java
Normal file
54
src/main/java/device/fitbitdata/Steps.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package device.fitbitdata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Classe che serve a vedere i passi fatti secondo il fitbit
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Steps extends FitbitData {
|
||||
|
||||
private int steps;
|
||||
private List<Map<String, Object>> stepsData;
|
||||
|
||||
@JsonProperty("activities-steps")
|
||||
private void setSteps(Map<String, String>[] array) {
|
||||
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Date now = new Date();
|
||||
String strDate = sdfDate.format(now);
|
||||
|
||||
for(Map<String, String> map : array)
|
||||
if(map.get("dateTime").equals(strDate))
|
||||
steps = Integer.parseInt(map.get("value"));
|
||||
}
|
||||
|
||||
@JsonProperty("activities-steps-intraday")
|
||||
private void setDetailedSteps(Map<String, Object> map) {
|
||||
stepsData = new ArrayList<>((List<Map<String, Object>>) map.get("dataset"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assegna il valore dei passi
|
||||
* @param steps i passi
|
||||
*/
|
||||
public void setSteps(int steps) { this.steps = steps; }
|
||||
|
||||
/**
|
||||
* I passi totali fatti durante il giorno
|
||||
* @return i passi totali
|
||||
*/
|
||||
public int getSteps() { return steps; }
|
||||
|
||||
/**
|
||||
* Prendi i dati specifici dei passi
|
||||
* @return una lista contenente una mappa con delle ore e minuti ad un valore dei passi
|
||||
*/
|
||||
public List<Map<String, Object>> getStepsData() { return stepsData; }
|
||||
}
|
||||
124
src/main/java/main/SeniorAssistant.java
Normal file
124
src/main/java/main/SeniorAssistant.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package main;
|
||||
|
||||
import device.Fitbit;
|
||||
import device.Hue;
|
||||
import device.Sensor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import support.database.Database;
|
||||
import support.database.LocalDB;
|
||||
import support.database.RemoteDB;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Ci si puo' interfacciare con l'assistente tramite Telegram o dal sito di ngrok.
|
||||
*/
|
||||
public class SeniorAssistant {
|
||||
|
||||
/**
|
||||
* Un Logger per seguire l'esecuzione del programma.
|
||||
*/
|
||||
public static final Logger LOG = LoggerFactory.getLogger("SeniorAssistant");
|
||||
|
||||
/**
|
||||
* Funzione principale, qui si creano tutte le classi che verranno utilizzate.<br>
|
||||
* Si possono passare dei parametri usando -(nome parametro)::(valore parametro)<br>
|
||||
* Ogni parametro deve esser separato da uno o piu spazi<br>
|
||||
* Parametri possibili:<br>
|
||||
* <ul>
|
||||
* <li>hueAddress</li>
|
||||
* <li>hueUser</li>
|
||||
* <li>autoBrightness</li>
|
||||
* <li>sensorNode</li>
|
||||
* <li>remoteDbUser</li>
|
||||
* </ul>
|
||||
* @param args i possibili argomenti da passare al programma
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
VariousThreads threads = new VariousThreads(); // this should be the first action of the main
|
||||
Map<String, String> arguments = getArgsMap(args);
|
||||
|
||||
// list of arguments to use in the classes
|
||||
String hueAddress = arguments.get("hueaddress");
|
||||
String hueUser = arguments.get("hueuser");
|
||||
Integer sensorNode = getInt(arguments.get("sensornode"));
|
||||
String remoteDbUser = arguments.get("remotedbuser");
|
||||
boolean autoBrightness = arguments.containsKey("autobrightness");
|
||||
|
||||
try {
|
||||
LOG.info("Connessione alle Philips Hue...");
|
||||
Hue lights = (hueAddress!=null && hueUser!=null? new Hue(hueAddress, hueUser):new Hue());
|
||||
|
||||
if(autoBrightness) try {
|
||||
LOG.info("Connessione ai sensori...");
|
||||
Sensor sensor = new Sensor(sensorNode);
|
||||
|
||||
threads.startHueAutoBrightness(lights, sensor);
|
||||
} catch (Exception e) {
|
||||
LOG.warn(e.getMessage());
|
||||
}
|
||||
|
||||
// Lo dichiaro qui, cosi' anche se non ci si puo' collegare al fitbit si puo comunque comandare le luci
|
||||
Fitbit fitbit=null;
|
||||
try {
|
||||
LOG.info("Connessione al Fitbit, ignorare eventuale errore per setPermissionsToOwnerOnly...");
|
||||
fitbit = new Fitbit();
|
||||
|
||||
LOG.info("Connessione al database...");
|
||||
Database database = remoteDbUser == null ? new LocalDB() : new RemoteDB(remoteDbUser);
|
||||
if(remoteDbUser != null && !database.isReachable())
|
||||
database = new LocalDB();
|
||||
|
||||
threads.startInsertData(database, fitbit);
|
||||
threads.startHueControlledByHeartBeat(lights, fitbit, database);
|
||||
threads.startCheckSteps(database);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Non e' stato possibile collegarsi al fitbit");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
threads.startWebhook(lights, fitbit);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage());
|
||||
}
|
||||
LOG.info("FINE MAIN");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------
|
||||
Le funzioni qui sotto servono solamente per gli argomenti passati al main
|
||||
------------------------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Prende gli argomenti nel formato "^-(?<name>[a-zA-Z]+)(::)?(?<argument>.*)$" e li inserisce in una mappa.
|
||||
* Se l'argomento non e' nel formato giusto lo ignora.
|
||||
* @param args un array di stringhe contenente i vari argomenti
|
||||
* @return una mappa con key il nome dell'argomento (la parte prima del :: e dopo il meno) e come valore il valore di esso (la parte dopo ::)
|
||||
*/
|
||||
private static Map<String, String> getArgsMap(String[] args) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
Pattern pattern = Pattern.compile("^-(?<name>[a-zA-Z]+)(::)?(?<argument>.*)$");
|
||||
|
||||
for (String arg: args) {
|
||||
Matcher matcher = pattern.matcher(arg);
|
||||
if (matcher.find())
|
||||
map.put(matcher.group("name").toLowerCase(), matcher.group("argument"));
|
||||
}
|
||||
LOG.info(map.toString());
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Funzione creata per gli argomenti che vengono passati in modo da evitare troppi try and catch
|
||||
* @param num la stringa da trasformare in numero
|
||||
* @return il numero trasformato, null se fallisce
|
||||
*/
|
||||
private static Integer getInt(String num) {
|
||||
Integer returnNum = null;
|
||||
try { returnNum = Integer.parseInt(num); } catch (Exception e) {}
|
||||
return returnNum;
|
||||
}
|
||||
}
|
||||
337
src/main/java/main/VariousThreads.java
Normal file
337
src/main/java/main/VariousThreads.java
Normal file
@@ -0,0 +1,337 @@
|
||||
package main;
|
||||
|
||||
import device.DialogFlowWebHook;
|
||||
import device.Fitbit;
|
||||
import device.Hue;
|
||||
import device.Sensor;
|
||||
import device.fitbitdata.HeartRate;
|
||||
import device.fitbitdata.Steps;
|
||||
import support.audio.Audio;
|
||||
import support.audio.AudioFile;
|
||||
import support.database.Database;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Classe che contiene tutti i thread che servono al programma per funzionare
|
||||
*/
|
||||
public class VariousThreads {
|
||||
|
||||
/**
|
||||
* Una costante che indica quanti millisecondi ci sono in un minuto (utile per le conversioni)
|
||||
*/
|
||||
public static final int MILLISEC_IN_MINUTE = 60000;
|
||||
|
||||
/**
|
||||
* Quanti minuti di cooldown da impostare dopo che l'utente ha chiesto di modificare le luci
|
||||
*/
|
||||
private static final int COOLDOWN_IN_MINUTES = 20;
|
||||
|
||||
/**
|
||||
* Variabile che serve ad impostare un cooldown per la luminosita' automatica
|
||||
*/
|
||||
private final AtomicInteger cooldown = new AtomicInteger(0);
|
||||
|
||||
/**
|
||||
* La variabile per far partire della musica
|
||||
*/
|
||||
private final Audio audio;
|
||||
|
||||
/**
|
||||
* Costruttore
|
||||
*/
|
||||
public VariousThreads() {
|
||||
audio = new AudioFile(); // se si vuole solamente far partire una traccia tra quelle fornite e non Youtube
|
||||
// audio = System.getProperty("os.name").startsWith("Windows")? new Musich():new AudioFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fa partire il server Webhook per DialogFlow e continua l'esecuzione
|
||||
* @param lights le luci che deve controllare
|
||||
*/
|
||||
public void startWebhook(final Hue lights, final Fitbit fitbit) {
|
||||
DialogFlowWebHook df = new DialogFlowWebHook();
|
||||
|
||||
df.addOnAction("LightsON", (params) -> { lights.turnOn(); cooldown.set(COOLDOWN_IN_MINUTES); return null; });
|
||||
df.addOnAction("LightsOFF", (params) -> { lights.turnOff(); cooldown.set(COOLDOWN_IN_MINUTES); 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());
|
||||
cooldown.set(COOLDOWN_IN_MINUTES);
|
||||
return null;
|
||||
});
|
||||
df.addOnAction("LightsDOWN", (params) -> {
|
||||
if(params.get("intensity").getAsString().equals(""))
|
||||
lights.decreaseBrightness();
|
||||
else
|
||||
lights.decreaseBrightness(params.get("intensity").getAsInt());
|
||||
cooldown.set(COOLDOWN_IN_MINUTES);
|
||||
return null;
|
||||
});
|
||||
df.addOnAction("LightsUP", (params) -> {
|
||||
if(params.get("intensity").getAsString().equals(""))
|
||||
lights.increaseBrightness();
|
||||
else
|
||||
lights.increaseBrightness(params.get("intensity").getAsInt());
|
||||
cooldown.set(COOLDOWN_IN_MINUTES);
|
||||
return null;
|
||||
});
|
||||
df.addOnAction("SetMusic", (param) -> {
|
||||
audio.playRandom(param.get("musicType").getAsString());
|
||||
return null;
|
||||
});
|
||||
df.addOnAction("StopMusic", (params) -> { audio.stop(); return null; });
|
||||
df.addOnAction("ReqHearthbeat", (params) -> {
|
||||
double rate = fitbit.getHeartRate(60);
|
||||
return "Il battito medio dell'ultima ora e' di "+rate;
|
||||
});
|
||||
df.addOnAction("ReqSteps", (params) -> {
|
||||
int steps = fitbit.getSteps();
|
||||
return "I passi fatti oggi sono "+steps;
|
||||
});
|
||||
df.addOnAction("ReqDistance", (params) -> {
|
||||
double steps = fitbit.getSteps();
|
||||
return String.format("Oggi hai percorso circa %.2f kilometri", steps/2000);
|
||||
});
|
||||
df.addOnAction("ReqSleep", (params) -> {
|
||||
double sleep = fitbit.getHoursSleep();
|
||||
return String.format("Oggi hai dormito per %.2f ore", sleep);
|
||||
});
|
||||
|
||||
df.startServer();
|
||||
SeniorAssistant.LOG.info("Webhook partito");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestione DB in modo che si aggiorni ogni ora e ogni giorno.<br>
|
||||
* Da quando viene fatto partire aspetta un giorno e poi aggiorna i dati sul DB; ripete.<br>
|
||||
* Da quando viene fatto partire aspetta un'ora e poi aggiorna i dati sul DB; ripete.
|
||||
* @param database il database a cui inviare i dati
|
||||
* @param fitbit la sorgente di dati
|
||||
*/
|
||||
public void startInsertData(final Database database, final Fitbit fitbit) {
|
||||
try {
|
||||
Thread hourlyData = Database.insertHourlyDataIn(database, fitbit, 5);
|
||||
Thread dailyData = Database.insertDailyData(database, fitbit, 5);
|
||||
|
||||
hourlyData.start();
|
||||
dailyData.start();
|
||||
SeniorAssistant.LOG.info("Thread per gli aggiornamenti automatici partiti");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestione delle luci in modo che cambino la luminosita' in base ai dati ricevuti dal sensore<br>
|
||||
* Se l'utente pero' cambia il valore delle luci di sua volonta', il processo non modifichera' le luci per almeno un'ora.
|
||||
* @param lights le luci da controllare
|
||||
* @param sensor i sensori da cui prendere i dati
|
||||
*/
|
||||
public void startHueAutoBrightness(final Hue lights, final Sensor sensor) {
|
||||
final int minute = 1;
|
||||
final int minBrightness = 20; // valore che va da 0 a 100
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
|
||||
Thread thread = getThreadStartingEach(() -> {
|
||||
if(cooldown.addAndGet(-minute) <= 0) {
|
||||
calendar.setTimeInMillis(System.currentTimeMillis());
|
||||
|
||||
// puo' avere un valore compreso tra -1 e 1
|
||||
final double brightFactor =
|
||||
calculateBrightFactor(
|
||||
calendar.get(Calendar.HOUR_OF_DAY),
|
||||
calendar.get(Calendar.MINUTE),
|
||||
sensor.getBrightnessLevel(),
|
||||
minBrightness
|
||||
);
|
||||
|
||||
lights.addBrightness(brightFactor*100);
|
||||
}
|
||||
}, minute, "auto-brightness");
|
||||
|
||||
thread.start();
|
||||
SeniorAssistant.LOG.info("Thread per l'impostazione automatica della luminosita' partito");
|
||||
}
|
||||
|
||||
/**
|
||||
* Permette di far partire un thread che controlla le Hue in base al battito cardiaco.<br>
|
||||
* Il battito e' preso sia in tempo reale dal fitbit, che dal databse, che permette di fare una analisi statistica.
|
||||
* @param lights le luci da controllare
|
||||
* @param fitbit da dove ricevo i dati in tempo reale
|
||||
* @param database da dove posso analizzare i vecchi dati
|
||||
*/
|
||||
public void startHueControlledByHeartBeat(final Hue lights, final Fitbit fitbit, final Database database) {
|
||||
final int minutes = 30;
|
||||
final int delta = 15;
|
||||
Thread thread = getThreadStartingEach(new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
double sum=0;
|
||||
int count=0;
|
||||
double average;
|
||||
|
||||
List<HeartRate> heartRate = database.getHeartDataOfLast(15);
|
||||
Calendar now = Calendar.getInstance();
|
||||
Calendar past = Calendar.getInstance();
|
||||
now.setTimeInMillis(System.currentTimeMillis());
|
||||
|
||||
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;
|
||||
|
||||
double rateNow = fitbit.getHeartRate(minutes);
|
||||
if (Math.abs(rateNow-average) > delta ) { // ALTO
|
||||
lights.decreaseBrightness();
|
||||
audio.playRandom("relax");
|
||||
new AudioFile("molti battiti.wav");
|
||||
}
|
||||
else if (Math.abs(rateNow-average) < delta) { // BASSO avvisa ma niente musica
|
||||
lights.increaseBrightness();
|
||||
new AudioFile("pochi battiti.wav");
|
||||
}
|
||||
}
|
||||
}, minutes, "lights-with-heartbeat");
|
||||
|
||||
thread.start();
|
||||
SeniorAssistant.LOG.info("Thread per il controllo delle luci tramite il battito cardiaco partito");
|
||||
}
|
||||
|
||||
/**
|
||||
* Controlla che ad una certa ora si siano fatti abbastanza passi, ed in caso contrario avvisa tramite un messaggio vocale.<br>
|
||||
*
|
||||
* @param database da dove vediamo se si sono fatti abbastanza passi
|
||||
*/
|
||||
public void startCheckSteps(final Database database) {
|
||||
final int norm = 4500;
|
||||
final int delta = 1500; // average steps for 60 year old -> 3.500-5.500 or so they say
|
||||
|
||||
Thread thread = getThreadStartingAt(new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
List<Steps> list = database.getStepDataOfLast(1);
|
||||
|
||||
double sum=0;
|
||||
int size = list.size();
|
||||
for(Steps steps: list)
|
||||
sum += steps.getSteps();
|
||||
final long average = size!=0? (long)(sum/size):0;
|
||||
|
||||
if (Math.abs(norm-average) > delta )
|
||||
new AudioFile("molti passi.wav");
|
||||
else if (Math.abs(norm-average) < delta)
|
||||
new AudioFile("pochi passi.wav");
|
||||
|
||||
}
|
||||
}, 20, "checking-steps");
|
||||
|
||||
thread.start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Calcola un numero compreso fra -1 e 1 che indica se c'e' bisogno o meno di luminosita'<br>
|
||||
* Se i valori inseriti sono maggiori o minori di quelli consentiti, allora verranno limitati<br>
|
||||
* ovvero se sono minori del minimo esso diventera' il minimo, stessa cosa con il massimo.
|
||||
* @param hour l'ora corrente (valore da 0 a 23)
|
||||
* @param minutes i minuti correnti (valore da 0 a 59)
|
||||
* @param sensorBright la luminosita' segnata dal sensore (valore da 0 a 100)
|
||||
* @param minBrightness la luminosita' minima che si vuole avere (valore da 0 a 100)
|
||||
* @return un valore indicante quanta luminosita' si ha bisogno nell'ora indicata e con la luminosita' indicata
|
||||
*/
|
||||
public static double calculateBrightFactor(int hour, int minutes, double sensorBright, double minBrightness) {
|
||||
hour = hour<0? 0:hour>23? 23:hour;
|
||||
minutes = minutes<0? 0:minutes>59? 59:minutes;
|
||||
minBrightness = minBrightness<0? 0:minBrightness>100? 100:minBrightness;
|
||||
sensorBright = sensorBright<0? 0:sensorBright>100? 100:sensorBright;
|
||||
|
||||
// Valore compreso tra -1(poca luminosita') e 1(molta luminosita')
|
||||
sensorBright = sensorBright/100;
|
||||
minBrightness = 0.5*Math.abs(1-minBrightness/100);
|
||||
|
||||
// Puo' avere un valore compreso tra 1(mezzanotte) e 0(mezzogiorno) => il valore minimo(0) puo' aumentare grazie a minBrightness)
|
||||
final double maxIntensity = minBrightness*Math.cos((2*Math.PI*(hour + (minutes/60.0)) /24)) + (1-minBrightness);
|
||||
|
||||
return maxIntensity-sensorBright;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restuisce un thread che se fatto partire, esegue il runnable in un sub-thread all'ora indicata ogni giorno<br>
|
||||
* Il sotto thread lanciato avra' lo stesso nome, ma con un trattino e la data di lancio a seguito<br>
|
||||
* Se il thread viene interrotto non fa piu' partire il runnable e si ferma
|
||||
* @param runnable il runnable da lanciare
|
||||
* @param hour l'ora a cui far partire il thread, se negativa o maggiore di 23 ritorna null
|
||||
* @param threadName il nome da dare al thread
|
||||
*/
|
||||
public static Thread getThreadStartingAt(final Runnable runnable, final int hour, String threadName) {
|
||||
if(hour<0 || hour>23)
|
||||
return null;
|
||||
|
||||
return new Thread(new Runnable() {
|
||||
@Override
|
||||
public synchronized void run() {
|
||||
boolean notInterrupted = true;
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
do {
|
||||
try {
|
||||
double hourCalculated = calendar.get(Calendar.HOUR_OF_DAY)+((double)calendar.get(Calendar.MINUTE)/60);
|
||||
double hourToWait = hour-hourCalculated;
|
||||
if(hourToWait<0)
|
||||
hourToWait += 24;
|
||||
|
||||
wait((long)(hourToWait * 60 * MILLISEC_IN_MINUTE));
|
||||
Thread thread = new Thread(runnable, threadName + "-" + new Timestamp(System.currentTimeMillis()));
|
||||
thread.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
notInterrupted = false;
|
||||
}
|
||||
} while (notInterrupted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restuisce un thread che se fatto partire, esegue il runnable in un sub-thread ogni X minuti<br>
|
||||
* Il sotto thread lanciato avra' lo stesso nome, ma con un trattino e la data di lancio a seguito<br>
|
||||
* Se il thread viene interrotto non fa piu' partire il runnable e si ferma
|
||||
* @param runnable il runnable da lanciare
|
||||
* @param minutes i minuti da aspettare, se negativi o 0 ritorna null
|
||||
* @param threadName il nome da dare al thread
|
||||
*/
|
||||
public static Thread getThreadStartingEach(final Runnable runnable, final int minutes, String threadName) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package manage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
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;
|
||||
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;
|
||||
import com.google.api.client.util.store.DataStoreFactory;
|
||||
import com.google.api.client.util.store.FileDataStoreFactory;
|
||||
|
||||
|
||||
public class AuthFITBIT {
|
||||
|
||||
private final HttpRequestFactory requestFactory;
|
||||
|
||||
public AuthFITBIT() throws Exception {
|
||||
DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
|
||||
final Credential credential = authorize();
|
||||
|
||||
this.requestFactory = HTTP_TRANSPORT.createRequestFactory( request -> {
|
||||
credential.initialize(request);
|
||||
request.setParser(new JsonObjectParser(JSON_FACTORY));
|
||||
});
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
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
|
||||
.authorizationHeaderAccessMethod(),
|
||||
HTTP_TRANSPORT,
|
||||
JSON_FACTORY,
|
||||
new GenericUrl(TOKEN_SERVER_URL),
|
||||
new BasicAuthentication (
|
||||
OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
|
||||
OAuth2ClientCredentials.API_KEY,
|
||||
AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
|
||||
.setDataStoreFactory(DATA_STORE_FACTORY).build();
|
||||
// authorize
|
||||
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
|
||||
OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
|
||||
|
||||
return new AuthorizationCodeInstalledApp(flow, receiver).authorize( "user" );
|
||||
}
|
||||
|
||||
public <O> O run(String url, Class<O> returnClass) throws IOException {
|
||||
return run(url, returnClass, false);
|
||||
}
|
||||
|
||||
public <O> O run(String url, Class<O> returnClass, boolean useAsParse) throws IOException {
|
||||
FITBITUrl fitbitUrl = new FITBITUrl(url);
|
||||
fitbitUrl.setFields("");
|
||||
|
||||
HttpRequest request = requestFactory.buildGetRequest(fitbitUrl);
|
||||
HttpResponse response = request.execute();
|
||||
|
||||
/*
|
||||
GenericJson json = response.parseAs(GenericJson.class);
|
||||
response.disconnect();
|
||||
|
||||
System.out.println(returnClass.getSimpleName());
|
||||
System.out.println(url);
|
||||
System.out.println(json.toPrettyString());
|
||||
|
||||
return mapper.readValue(json.toString(), returnClass);
|
||||
*/
|
||||
|
||||
/**/
|
||||
O ret = ( useAsParse ?
|
||||
response.parseAs(returnClass) :
|
||||
mapper.readValue(response.parseAs(GenericJson.class).toString(), returnClass)
|
||||
);
|
||||
|
||||
response.disconnect();
|
||||
return ret;
|
||||
/**/
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package manage.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<Map<String,String>> sync){
|
||||
lastSyncTime = null;
|
||||
for(Map<String, String > d: sync) {
|
||||
String temp = d.get("lastSyncTime");
|
||||
if ((lastSyncTime == null) || (lastSyncTime.compareTo(temp) < 0))
|
||||
lastSyncTime = temp;
|
||||
}
|
||||
return lastSyncTime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package manage.FITBITData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import manage.AuthFITBIT;
|
||||
|
||||
public class FitBit {
|
||||
|
||||
public static final String BASIC_URL = "https://api.fitbit.com/";
|
||||
public static final String USER = "/user/-/";
|
||||
private static final long MINUTE = 60000; /* 5 minutes in millisec */
|
||||
|
||||
private final AuthFITBIT auth;
|
||||
private final Map<Class<?>, Long> latestRequest = new HashMap<>();
|
||||
private final Calendar calendar = Calendar.getInstance();
|
||||
|
||||
private HeartRate heart = null;
|
||||
private Sleep sleep = null;
|
||||
private Steps steps = null;
|
||||
|
||||
public FitBit() throws Exception {
|
||||
this(new AuthFITBIT());
|
||||
}
|
||||
|
||||
public FitBit(AuthFITBIT auth) {
|
||||
if(auth == null)
|
||||
throw new NullPointerException("I must have an Auth for the FitBit");
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
/* passi */
|
||||
public int getSteps() throws IOException {
|
||||
if(shouldUpdateFor(Steps.class)) {
|
||||
long currentMillisec = System.currentTimeMillis();
|
||||
|
||||
steps = auth.run(BASIC_URL + "1" + USER + "activities/steps/date/today/1w.json", Steps.class);
|
||||
latestRequest.put(steps.getClass(), currentMillisec);
|
||||
}
|
||||
return steps.getSteps();
|
||||
}
|
||||
|
||||
/* battito */
|
||||
public double getHeartRate() throws IOException {
|
||||
if(shouldUpdateFor(HeartRate.class)) {
|
||||
long currentMillisec = System.currentTimeMillis();
|
||||
|
||||
String now = getHourMinutes(currentMillisec);
|
||||
String ago = getHourMinutes(currentMillisec-(MINUTE*15));
|
||||
|
||||
if(now.compareTo(ago) < 0)
|
||||
ago = "00:00";
|
||||
|
||||
heart = auth.run(BASIC_URL + "1" + USER + "activities/heart/date/today/1d/1sec/time/"+ago+"/"+now+".json", HeartRate.class);
|
||||
latestRequest.put(heart.getClass(), currentMillisec);
|
||||
}
|
||||
return heart.getAverage();
|
||||
}
|
||||
|
||||
/* sonno */
|
||||
public Object getHoursSleep() throws IOException {
|
||||
if(shouldUpdateFor(Sleep.class)) {
|
||||
long currentMillisec = System.currentTimeMillis();
|
||||
|
||||
sleep = auth.run(BASIC_URL + "1.2" + USER + "sleep/date/today.json", Sleep.class);
|
||||
latestRequest.put(sleep.getClass(), currentMillisec);
|
||||
}
|
||||
return sleep.getMinutesAsleep();
|
||||
}
|
||||
|
||||
private boolean shouldUpdateFor(Class<?> type) {
|
||||
try {
|
||||
long current = System.currentTimeMillis();
|
||||
long latest = latestRequest.get(type);
|
||||
|
||||
if (current - latest > MINUTE * 5)
|
||||
return true;
|
||||
return false;
|
||||
} catch (NullPointerException e) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getHourMinutes(long milliseconds) {
|
||||
calendar.setTimeInMillis(milliseconds);
|
||||
|
||||
int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
int minu = calendar.get(Calendar.MINUTE);
|
||||
return String.format("%02d:%02d", hour, minu);
|
||||
}
|
||||
|
||||
// Device dev = auth.run(BASIC_URL + "devices.json", Device.class);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package manage.FITBITData;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Sleep {
|
||||
|
||||
private int minutesAsleep;
|
||||
|
||||
public int getMinutesAsleep() {
|
||||
return minutesAsleep;
|
||||
}
|
||||
|
||||
@JsonProperty("summary")
|
||||
public void setMinutesAsleep(Map<String, Object> map) {
|
||||
minutesAsleep = (int) map.get("totalMinutesAsleep");
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package manage.FITBITData;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Steps {
|
||||
|
||||
private int steps=0;
|
||||
|
||||
@JsonProperty("activities-steps")
|
||||
public void setSteps(Map<String, String>[] array) {
|
||||
SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||
Date now = new Date();
|
||||
String strDate = sdfDate.format(now);
|
||||
|
||||
for(Map<String, String> map : array)
|
||||
if(map.get("dateTime").equals(strDate))
|
||||
steps = Integer.parseInt(map.get("value"))+1;
|
||||
}
|
||||
|
||||
public int getSteps() {
|
||||
return steps;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package manage;
|
||||
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.util.Key;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package manage;
|
||||
|
||||
public class OAuth2ClientCredentials {
|
||||
|
||||
/** Value of the "API Key". */
|
||||
public static final String API_KEY = "22CSTL"; //maybe togliere le virgolette
|
||||
|
||||
/** Value of the "API Secret". */
|
||||
public static final String API_SECRET = "ea2452013abd35609940ce5601960a08"; //maybe togliere le virgolette
|
||||
|
||||
/** Port in the "Callback URL". */
|
||||
public static final int PORT = 8080;
|
||||
|
||||
/** Domain name in the "Callback URL". */
|
||||
public static final String DOMAIN = "127.0.0.1";
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package manage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
/**
|
||||
* A generic class to perform HTTP calls and parse the resulting JSON.
|
||||
*
|
||||
* @author <a href="mailto:luigi.derussis@uniupo.it">Luigi De Russis</a>
|
||||
* @version 1.0 (18/05/2017)
|
||||
*/
|
||||
public class Rest {
|
||||
|
||||
// init gson
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Perform a GET request towards a server
|
||||
*
|
||||
* @param URL the @link{URL} to call
|
||||
* @return the response, parsed from JSON
|
||||
*/
|
||||
public static Map<String, ?> get(String URL) {
|
||||
// init
|
||||
Map<String, ?> response = new HashMap<>();
|
||||
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet request = new HttpGet(URL);
|
||||
|
||||
CloseableHttpResponse result = null;
|
||||
|
||||
try {
|
||||
result = httpclient.execute(request);
|
||||
String json = EntityUtils.toString(result.getEntity());
|
||||
// do something useful with the response body
|
||||
response = gson.fromJson(json, Map.class);
|
||||
// should be inside a finally...
|
||||
result.close();
|
||||
httpclient.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a PUT request towards a server
|
||||
*
|
||||
* @param URL the @link{URL} to call
|
||||
* @param contentBody the content body of the request
|
||||
* @param contentType the content type of the request
|
||||
*/
|
||||
public static void put(String URL, String contentBody, String contentType) {
|
||||
// init
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpPut request = new HttpPut(URL);
|
||||
StringEntity params = null;
|
||||
|
||||
try {
|
||||
params = new StringEntity(contentBody);
|
||||
request.addHeader("content-type", contentType);
|
||||
request.setEntity(params);
|
||||
// I don't really care about the response
|
||||
HttpResponse result = httpclient.execute(request);
|
||||
// should be in finally...
|
||||
httpclient.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
158
src/main/java/oauth/AuthFitbit.java
Normal file
158
src/main/java/oauth/AuthFitbit.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package oauth;
|
||||
|
||||
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.http.*;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Classe piu' importante per la connessione al fitbit
|
||||
*/
|
||||
public class AuthFitbit {
|
||||
|
||||
/**
|
||||
* Un logger per rendere più semplice il debug
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger("Fitbit Auth");
|
||||
|
||||
/**
|
||||
* Un mapper per trasformare i json in mappe.
|
||||
*/
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* Directory dove vengono messi i dati utente (Token)<br>
|
||||
* <br>
|
||||
* Throw a Warning when change permission: they said it's a google bug 'cause is meant to run in linux/unix<br>
|
||||
* https://stackoverflow.com/questions/30634827/warning-unable-to-change-permissions-for-everybody<br>
|
||||
* https://github.com/google/google-http-java-client/issues/315<br>
|
||||
*/
|
||||
private static final java.io.File DATA_STORE_DIR = new java.io.File(System.getProperty("user.home"), ".store/seniorAssistant");
|
||||
|
||||
/**
|
||||
* OAuth2 scope.<br>
|
||||
* 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<br>
|
||||
* Se la richiesta inviata e' sbagliata lancia una eccezione.<br>
|
||||
* Se l'utente non ha ancora autorizzato l'applicazioe, allora una pagina sul browser (o un link in console) appare<br>
|
||||
* 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();
|
||||
|
||||
this.requestFactory = HTTP_TRANSPORT.createRequestFactory( request -> {
|
||||
credential.initialize(request);
|
||||
request.setParser(new JsonObjectParser(JSON_FACTORY));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Autorizza l'applicazione ad accedere ai dati utente richiesti
|
||||
*/
|
||||
private Credential authorize() throws Exception {
|
||||
// set up authorization code flow
|
||||
AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
|
||||
.authorizationHeaderAccessMethod(),
|
||||
HTTP_TRANSPORT,
|
||||
JSON_FACTORY,
|
||||
new GenericUrl(TOKEN_SERVER_URL),
|
||||
new BasicAuthentication (
|
||||
OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
|
||||
OAuth2ClientCredentials.API_KEY,
|
||||
AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
|
||||
.setDataStoreFactory(DATA_STORE_FACTORY).build();
|
||||
// authorize
|
||||
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
|
||||
OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
|
||||
|
||||
return new AuthorizationCodeInstalledApp(flow, receiver).authorize( "user" );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
String content = response.parseAsString();
|
||||
response.disconnect();
|
||||
LOG.debug("Recived: " + content);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fa una chiamata al server fitbit richiedendo i dati indicati dall'url<br>
|
||||
* La classe e' richiesta se si vuole fare il parsing diretto e ricevere la classe parsificata con un mapper<br>
|
||||
* 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 <O> 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> O run(String url, Class<O> returnClass) throws IOException {
|
||||
O ret = MAPPER.readValue(this.run(url), returnClass);
|
||||
LOG.debug("Saved in class: " + JSON_FACTORY.toString(ret));
|
||||
|
||||
return ret;
|
||||
/**/
|
||||
}
|
||||
}
|
||||
296
src/main/java/oauth/LocalServerReceiver.java
Normal file
296
src/main/java/oauth/LocalServerReceiver.java
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* Implementation is thread-safe.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* Use {@link Builder} if you need to specify any of the optional parameters.
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* Implementation is not thread-safe.
|
||||
* </p>
|
||||
*/
|
||||
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("<html>");
|
||||
doc.println("<head><title>OAuth 2.0 Authentication Token Received</title></head>");
|
||||
doc.println("<body>");
|
||||
doc.println("Received verification code. You may now close this window.");
|
||||
doc.println("</body>");
|
||||
doc.println("</html>");
|
||||
doc.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/main/java/oauth/OAuth2ClientCredentials.java
Normal file
28
src/main/java/oauth/OAuth2ClientCredentials.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package oauth;
|
||||
|
||||
/**
|
||||
* Classe supporto per l'oauth contenente tutti i dati dell'applicazione
|
||||
*/
|
||||
public class OAuth2ClientCredentials {
|
||||
|
||||
/**
|
||||
* Il valore dell' "API Key".
|
||||
*/
|
||||
public static final String API_KEY = "22CSTL";
|
||||
|
||||
/**
|
||||
* Il valore dell' "API Secret"
|
||||
*/
|
||||
public static final String API_SECRET = "ea2452013abd35609940ce5601960a08";
|
||||
|
||||
/**
|
||||
* La porta per il "Callback URL".
|
||||
*/
|
||||
public static final int PORT = 8080;
|
||||
|
||||
/**
|
||||
* Il dominio del "Callback URL"
|
||||
*/
|
||||
public static final String DOMAIN = "127.0.0.1";
|
||||
}
|
||||
|
||||
94
src/main/java/support/Rest.java
Normal file
94
src/main/java/support/Rest.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package support;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Una classe generica che invia delle richieste Rest e le parsifica nel JSON corrispondente
|
||||
*/
|
||||
public class Rest {
|
||||
|
||||
/**
|
||||
* Un logger per vedere quando le richieste falliscono
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger("Rest");
|
||||
|
||||
/**
|
||||
* Un GSON utile per il parsing della risposta
|
||||
*/
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Perform a GET request towards a server
|
||||
*
|
||||
* @param URL the @link{URL} to call
|
||||
* @return the response, parsed from JSON
|
||||
*/
|
||||
public static Map<String, ?> get(String URL) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
String json = "";
|
||||
|
||||
try {
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpGet request = new HttpGet(URL);
|
||||
CloseableHttpResponse result = httpclient.execute(request);
|
||||
json = EntityUtils.toString(result.getEntity());
|
||||
|
||||
try {
|
||||
response = gson.fromJson(json, Map.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
response.put("list", gson.fromJson(json, List.class));
|
||||
}
|
||||
|
||||
result.close();
|
||||
httpclient.close();
|
||||
LOG.debug("GET response: " + json);
|
||||
} catch (Exception e) {
|
||||
LOG.error("GET: " + URL + " " + e.getMessage() + " " + json);
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a PUT request towards a server
|
||||
*
|
||||
* @param URL the @link{URL} to call
|
||||
* @param contentBody the content body of the request
|
||||
* @param contentType the content type of the request
|
||||
*/
|
||||
public static void put(String URL, String contentBody, String contentType) {
|
||||
try {
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
HttpPut request = new HttpPut(URL);
|
||||
StringEntity params = new StringEntity(contentBody);
|
||||
|
||||
request.addHeader("content-type", contentType);
|
||||
request.setEntity(params);
|
||||
|
||||
HttpResponse result = httpclient.execute(request);
|
||||
String json = EntityUtils.toString(result.getEntity());
|
||||
httpclient.close();
|
||||
|
||||
LOG.debug("PUT response: " + json);
|
||||
} catch (Exception e) {
|
||||
LOG.error("PUT: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package device;
|
||||
package support;
|
||||
|
||||
import de.fh_zwickau.informatik.sensor.IZWayApiCallbacks;
|
||||
import de.fh_zwickau.informatik.sensor.model.devicehistory.DeviceHistory;
|
||||
@@ -17,6 +17,7 @@ import de.fh_zwickau.informatik.sensor.model.profiles.Profile;
|
||||
import de.fh_zwickau.informatik.sensor.model.profiles.ProfileList;
|
||||
import de.fh_zwickau.informatik.sensor.model.zwaveapi.controller.ZWaveController;
|
||||
import de.fh_zwickau.informatik.sensor.model.zwaveapi.devices.ZWaveDevice;
|
||||
import device.Sensor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
28
src/main/java/support/audio/Audio.java
Normal file
28
src/main/java/support/audio/Audio.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package support.audio;
|
||||
|
||||
/**
|
||||
* Classe che serve ad aiutare a far partire la musica
|
||||
*/
|
||||
public interface Audio {
|
||||
|
||||
/**
|
||||
* Fa partire una traccia audio in base al nome di essa.<br>
|
||||
* Se un audio era gia' stato fatto partire esso viene fermato<br>
|
||||
* Il nome puo' variare in base all'implementazione: nome di file o nome da cercare su internet...
|
||||
* @param name la stringa per far partire la canzone
|
||||
*/
|
||||
void play(String name);
|
||||
|
||||
/**
|
||||
* Fa' partire un audio a caso fra quelli selezionati dalla stringa<br>
|
||||
* Se un audio era gia' stato fatto partire esso viene fermato<br>
|
||||
* In base all'implementazione puo' essere un nome che appartiene a vari file,<br>
|
||||
* il nome di una cartella contenente i file, o una stringa di ricerca...
|
||||
*/
|
||||
void playRandom(String name);
|
||||
|
||||
/**
|
||||
* Ferma l'ultimo audio che e' stato fatto partire
|
||||
*/
|
||||
void stop();
|
||||
}
|
||||
138
src/main/java/support/audio/AudioFile.java
Normal file
138
src/main/java/support/audio/AudioFile.java
Normal file
@@ -0,0 +1,138 @@
|
||||
package support.audio;
|
||||
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Usa i file nella cartella resources/audio/ per riprodurre i suoni
|
||||
*/
|
||||
public class AudioFile implements Audio {
|
||||
|
||||
/**
|
||||
* La path dove si trovano gli audio
|
||||
*/
|
||||
public static final String PATH_AUDIO = "src/main/resources/audio/";
|
||||
|
||||
/**
|
||||
* L'ultimo audio fatto partire
|
||||
*/
|
||||
private final Clip clip;
|
||||
|
||||
/**
|
||||
* Serve per crearsi una mappa di tutte le canzoni
|
||||
*/
|
||||
private final static Map<String, File> files = getAllFiles(PATH_AUDIO);
|
||||
|
||||
/**
|
||||
* Mappa che serve ad avere per ogni sotto-dir di audio una lista di ogni file audio che c'e'
|
||||
*/
|
||||
private final static Map<String, List<File>> dirs = getAllDirs(PATH_AUDIO);
|
||||
|
||||
/**
|
||||
* Crea un oggetto audio che si puo' poi far riprodurre e stoppare
|
||||
*/
|
||||
public AudioFile() {
|
||||
Clip clip = null;
|
||||
try {
|
||||
clip = AudioSystem.getClip();
|
||||
} catch (LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.clip = clip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilizzando questo costruttore si fa partire in automatico il file scelto
|
||||
* @param file il nome del file scelto da far partire subito
|
||||
*/
|
||||
public AudioFile(String file) {
|
||||
this();
|
||||
this.play(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fa partire una canzone che si trova nella cartella audio o in una delle sue sottocartelle
|
||||
* @param name la stringa per far partire la canzone
|
||||
*/
|
||||
@Override
|
||||
public void play(String name) {
|
||||
stop();
|
||||
try {
|
||||
File file = files.get(name);
|
||||
AudioInputStream audioIn = AudioSystem.getAudioInputStream(file);
|
||||
clip.open(audioIn);
|
||||
clip.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fa' partire una canzone a caso nella cartella selezionata
|
||||
* @param name il nome della cartella
|
||||
*/
|
||||
@Override
|
||||
public void playRandom(String name) {
|
||||
List<File> songs = dirs.get(name);
|
||||
play(songs.get((int)(Math.random()*songs.size())).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
try {
|
||||
clip.stop();
|
||||
clip.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fa in modo di mappare tutti i file in una directory e in tutte le sue sub-dir
|
||||
* @param path la path iniziale
|
||||
* @return una mappa di NomeFile -> File
|
||||
*/
|
||||
private static Map<String, File> getAllFiles(String path) {
|
||||
File folder = new File(path);
|
||||
File[] listOfFiles = folder.listFiles();
|
||||
Map<String, File> map = new HashMap<>();
|
||||
|
||||
for (File file : listOfFiles) {
|
||||
if(file.isFile())
|
||||
map.put(file.getName(), file);
|
||||
else
|
||||
map.putAll(getAllFiles(file.getPath()));
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea una mappa contenente tutti i file della cartella scelta associati con ogni dir corrente<br>
|
||||
* @param path la path iniziale
|
||||
* @return una mappa di directory con i loro file
|
||||
*/
|
||||
private static Map<String, List<File>> getAllDirs(String path) {
|
||||
File folder = new File(path);
|
||||
File[] listOfFiles = folder.listFiles();
|
||||
List<File> list = new LinkedList<>();
|
||||
Map<String, List<File>> map = new HashMap<>();
|
||||
|
||||
for (File file : listOfFiles) {
|
||||
if(file.isFile())
|
||||
list.add(file);
|
||||
else
|
||||
map.putAll(getAllDirs(file.getPath()));
|
||||
}
|
||||
map.put(folder.getName(), list);
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
179
src/main/java/support/audio/Musich.java
Normal file
179
src/main/java/support/audio/Musich.java
Normal file
@@ -0,0 +1,179 @@
|
||||
package support.audio;
|
||||
|
||||
import chrriis.dj.nativeswing.swtimpl.NativeInterface;
|
||||
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;
|
||||
import org.apache.http.annotation.Obsolete;
|
||||
import support.Rest;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Classe che serve a far partire un video di youtube in una frame
|
||||
*/
|
||||
@Deprecated
|
||||
public class Musich implements Audio {
|
||||
|
||||
/**
|
||||
* L'url da dove possiamo pescare i dati di youtube dei video
|
||||
*/
|
||||
private static final String API_URL = "https://www.googleapis.com/youtube/v3/search?";
|
||||
/**
|
||||
* La key necessaria per prendere i dati da youtube<br>
|
||||
*/
|
||||
private static final String KEY = "AIzaSyAYcQcX9P5btBTfgdwWwETNh_7jV20cQp0";
|
||||
|
||||
/**
|
||||
* IL thread che ha fatto partire il frame corrente
|
||||
*/
|
||||
private Thread currentThread;
|
||||
/**
|
||||
* Il frame (ovvero la windows) che e' attualmente attiva
|
||||
*/
|
||||
private JFrame currentFrame;
|
||||
|
||||
/**
|
||||
* Serve ad inizializzare la libreria SWT di eclipse e chrriis.dj
|
||||
*/
|
||||
public Musich() {
|
||||
/*
|
||||
* Viene mandato questo errore in console:
|
||||
* Exception "java.lang.ClassNotFoundException: com/intellij/codeInsight/editorActions/FoldingData"while constructing DataFlavor for: application/x-java-jvm-local-objectref; class=com.intellij.codeInsight.editorActions.FoldingData
|
||||
* Sta' di fatto che per quanto ne so non compromette il funzionamento.
|
||||
*/
|
||||
NativeInterface.initialize();
|
||||
NativeInterface.open();
|
||||
NativeInterface.runEventPump(); // this should be called at the end of main (it said this but who cares)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve ad avere una lista di id dei video che corrispondono alle keywords indicate nella ricerca
|
||||
* @param search la ricerca da fare su youtube
|
||||
* @param maxResult quanti risultati si vuole avere
|
||||
* @return una lista di id dei video che corrispondono alla ricerca
|
||||
*/
|
||||
public static List<String> getVideosId(String search, final int maxResult) {
|
||||
try {
|
||||
search = URLEncoder.encode(search, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Map<String, ?> response = Rest.get(API_URL +
|
||||
"maxResults=" + maxResult +
|
||||
"&part=snippet" +
|
||||
"&type=video" +
|
||||
"&q=" + search +
|
||||
"&key=" + KEY);
|
||||
|
||||
List<Map<String, ?>> items = (List<Map<String, ?>>)response.get("items");
|
||||
List<String> videosId = new ArrayList<>(maxResult);
|
||||
|
||||
for(Map<String, ?> obj: items) {
|
||||
Map<String, String> id = (Map<String, String>)obj.get("id");
|
||||
videosId.add(id.get("videoId"));
|
||||
}
|
||||
|
||||
return videosId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve ad avere una lista di id dei video che corrispondono alle keywords indicate nella ricerca<br>
|
||||
* La lista conterra' i primi 5 id dei video trovati.
|
||||
* @param search la ricerca da fare su youtube
|
||||
* @return una lista di id dei video che corrispondono alla ricerca
|
||||
*/
|
||||
public List<String> getVideosId(String search) {
|
||||
return Musich.getVideosId(search, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dato l'id di un video viene fatto partire un frame contenente il video richiesto<br>
|
||||
* Come al solito, youtube potrebbe far partire una pubblicita'.
|
||||
* @param videoId l'id del video da far vedere
|
||||
* @param seconds da quanti secondi bisogna far partire il video
|
||||
*/
|
||||
public void play(final String videoId, int seconds) {
|
||||
this.stop();
|
||||
currentThread = new Thread( () -> {
|
||||
currentFrame = new JFrame("YouTube Viewer");
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
JWebBrowser browser = new JWebBrowser();
|
||||
browser.setBarsVisible(false);
|
||||
browser.navigate(getYoutubeLink(videoId, seconds));
|
||||
|
||||
currentFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
currentFrame.getContentPane().add(browser, BorderLayout.CENTER);
|
||||
currentFrame.setSize(800, 600);
|
||||
currentFrame.setLocationByPlatform(true);
|
||||
currentFrame.setVisible(true);
|
||||
});
|
||||
|
||||
// don't forget to properly close native components
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> NativeInterface.close()));
|
||||
}, "NativeInterface");
|
||||
|
||||
currentThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dato l'id di un video viene fatto partire un frame contenente il video richiesto<br>
|
||||
* Come al solito, youtube potrebbe far partire una pubblicita'.
|
||||
* @param videoId l'id del video da far vedere
|
||||
*/
|
||||
public void play(final String videoId) {
|
||||
play(videoId, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve a far partire un video a caso tra quelli che corrispondono alle keywords indicate nella ricerca<br>
|
||||
* Come al solito, youtube potrebbe far partire una pubblicita'.
|
||||
* @param search la ricerca da fare su youtube
|
||||
* @param maxResult fra quanti risultati deve scegliere
|
||||
*/
|
||||
public void playRandom(String search, int maxResult) {
|
||||
this.play(getVideosId(search, maxResult).get((int)(Math.random()*maxResult)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve a far partire un video a caso tra quelli che corrispondono alle keywords indicate nella ricerca<br>
|
||||
* Esso scegliera' fra i primi 5 risultati<br>
|
||||
* Come al solito, youtube potrebbe far partire una pubblicita'.
|
||||
* @param search la ricerca da fare su youtube
|
||||
*/
|
||||
public void playRandom(String search) {
|
||||
this.playRandom(search, 5);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ferma il video che e' in riprduzione in questo momento.<br>
|
||||
* Se non ce ne sono non fa nulla.
|
||||
*/
|
||||
public void stop() {
|
||||
if(currentThread != null) {
|
||||
currentThread.interrupt();
|
||||
currentFrame.dispatchEvent(new WindowEvent(currentFrame, WindowEvent.WINDOW_CLOSING));
|
||||
|
||||
currentThread = null;
|
||||
currentFrame = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ricevi il link di youtube del video a partire dal suo ID
|
||||
* @param videoId l'id del video
|
||||
* @param seconds i secondi dall'inizio del video (0 o negativi e viene ignorato)
|
||||
* @return una stringa
|
||||
*/
|
||||
public static String getYoutubeLink(String videoId, int seconds) {
|
||||
return videoId==null? "":"https://www.youtube.com/watch?v=" + videoId + (seconds>0? "&t="+seconds:"");
|
||||
}
|
||||
}
|
||||
130
src/main/java/support/database/Database.java
Normal file
130
src/main/java/support/database/Database.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package support.database;
|
||||
|
||||
import device.Fitbit;
|
||||
import device.fitbitdata.HeartRate;
|
||||
import device.fitbitdata.Sleep;
|
||||
import device.fitbitdata.Steps;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static main.VariousThreads.*;
|
||||
|
||||
/**
|
||||
* Interfaccia per collegarsi al database
|
||||
*/
|
||||
public interface Database {
|
||||
|
||||
/**
|
||||
* Un logger per scrivere a console eventuali errori o informazioni
|
||||
*/
|
||||
Logger LOG = LoggerFactory.getLogger("DB");
|
||||
|
||||
/**
|
||||
* 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<HeartRate> getHeartDataOfLast(int days);
|
||||
|
||||
/**
|
||||
* Riceve i dati dei passi dal giorno selezionato fino ad oggi
|
||||
* @param days quanti giorni devono esser considerati
|
||||
* @return una lista dei passifatti negli ultimi X giorni (ordinati da oggi al giorno X)
|
||||
*/
|
||||
List<Steps> getStepDataOfLast(int days);
|
||||
|
||||
/**
|
||||
* Prende il Thread che automaticamente gestisce l'inserimento dei dati orari (per ora solo il battito cardiaco)<br>
|
||||
* Se per caso c'e' un fallimento riprova ad inserire i dati ogni x minuti, indicati dal terzo parametro<br>
|
||||
* @param database il database in cui inserire i dati
|
||||
* @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() {
|
||||
try {
|
||||
boolean retry;
|
||||
long now = System.currentTimeMillis();
|
||||
double heartRate = fitbit.getHeartRate(60);
|
||||
int steps = fitbit.getSteps(1);
|
||||
do {
|
||||
retry = !database.updateHeart(now, heartRate);
|
||||
retry = retry && !database.updateSteps(now, steps);
|
||||
LOG.info("Aggiornamento orario " + (!retry ? "riuscito" : "fallito, riprovo fra " + retryMinutes + " minuti"));
|
||||
if (retry)
|
||||
wait(retryMinutes * MILLISEC_IN_MINUTE);
|
||||
} while(retry);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Aggiornamento orario interrotto");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return getThreadStartingEach(runnable, 60, "update-hourly-data");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prendi il Thread che automaticamente gestisce l'inserimento dei dati giornalieri, esso fara' i tentativi alle 23<br>
|
||||
* Se per caso c'e' un fallimento riprova ad inserire i dati ogni x minuti, indicati dal terzo parametro<br>
|
||||
* @param database il database in cui inserire i dati
|
||||
* @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() {
|
||||
try {
|
||||
List<Sleep.SleepData> sleepDatas = fitbit.getDetailedSleep();
|
||||
boolean retry = !sleepDatas.isEmpty();
|
||||
do {
|
||||
for (Sleep.SleepData data : sleepDatas)
|
||||
retry = retry && !database.updateSleep(data.start_date, data.duration);
|
||||
|
||||
LOG.info("Aggiornamento giornaliero" + (!retry ? "riuscito" : "fallito, riprovo fra " + retryMinutes + " minuti"));
|
||||
if (retry)
|
||||
wait(retryMinutes * MILLISEC_IN_MINUTE);
|
||||
} while (retry);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Aggiornamento giornaliero interrotto");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return getThreadStartingAt(runnable, 23, "update-daily-data");
|
||||
}
|
||||
}
|
||||
144
src/main/java/support/database/LocalDB.java
Normal file
144
src/main/java/support/database/LocalDB.java
Normal file
@@ -0,0 +1,144 @@
|
||||
package support.database;
|
||||
|
||||
import device.fitbitdata.HeartRate;
|
||||
import device.fitbitdata.Steps;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Classe che utilizza un database sqlite che contiene le seguenti tabelle:<br>
|
||||
* <ul>
|
||||
* <li>'heart' battito + orario</li>
|
||||
* <li>'sleep' inizio + durata</li>
|
||||
* <li>'steps' data + passi.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class LocalDB implements Database {
|
||||
|
||||
/**
|
||||
* Il percorso dove trovare il database, strutturato in: <interfaccia>:<implementazione>:<percorso vero e proprio>
|
||||
*/
|
||||
public static final String DB_LOCATION = "jdbc:sqlite:src/main/resources/";
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* <ul>
|
||||
* <li>'heart' battito + orario</li>
|
||||
* <li>'sleep' inizio + durata</li>
|
||||
* <li>'step' data + passi.</li>
|
||||
* </ul>
|
||||
* @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) {
|
||||
Timestamp time = new Timestamp(dateMilliSec);
|
||||
return query("IF NOT EXISTS (" +
|
||||
"SELECT * " +
|
||||
"FROM heart " +
|
||||
"WHERE day = '" + time + "') " +
|
||||
"BEGIN INSERT INTO heart (day, rate) VALUES ( ' " + time + " ', '" + heartRate + "') " +
|
||||
"END;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSleep(long dateStartSleep, long duration) {
|
||||
Timestamp time = new Timestamp(dateStartSleep);
|
||||
return query("IF NOT EXISTS (" +
|
||||
"SELECT * " +
|
||||
"FROM sleep " +
|
||||
"WHERE sleep_start = '" + time + "') " +
|
||||
"BEGIN INSERT INTO sleep (sleep_start, duration) VALUES ( ' " + time + " ', '" + duration + "') " +
|
||||
"END;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSteps(long dateMilliSec, long steps) {
|
||||
Timestamp time = new Timestamp(dateMilliSec);
|
||||
return query("IF NOT EXISTS (" +
|
||||
"SELECT * " +
|
||||
"FROM steps " +
|
||||
"WHERE day = '" + time + "') " +
|
||||
"INSERT INTO steps (day, steps) VALUES ( ' " + time + " ', '" + steps + "') " +
|
||||
"END;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeartRate> getHeartDataOfLast(int days) {
|
||||
try {
|
||||
int dayToSubtract = 15;
|
||||
long time = System.currentTimeMillis() - (dayToSubtract * 24 * 60 * 1000); // meno 24 ore per 60 secondi per 100 millisec
|
||||
|
||||
ResultSet result = conn.createStatement().executeQuery("SELECT * FROM heart WHERE day>='" + new Timestamp(time) + "'");
|
||||
List<HeartRate> 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Steps> getStepDataOfLast(int days) {
|
||||
try {
|
||||
int dayToSubtract = 15;
|
||||
long time = System.currentTimeMillis() - (dayToSubtract * 24 * 60 * 1000);
|
||||
|
||||
ResultSet result = conn.createStatement().executeQuery("SELECT * FROM steps WHERE day>='" + new Timestamp(time) + "'");
|
||||
List<Steps> list = new LinkedList<>();
|
||||
|
||||
while(result.next()) {
|
||||
Steps steps = new Steps();
|
||||
steps.setSteps(result.getInt("rate"));
|
||||
steps.setDate(result.getDate("day").getTime());
|
||||
|
||||
list.add(steps);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
src/main/java/support/database/RemoteDB.java
Normal file
143
src/main/java/support/database/RemoteDB.java
Normal file
@@ -0,0 +1,143 @@
|
||||
package support.database;
|
||||
|
||||
import ai.api.GsonFactory;
|
||||
import com.google.gson.Gson;
|
||||
import device.fitbitdata.HeartRate;
|
||||
import device.fitbitdata.Steps;
|
||||
import support.Rest;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Classe che si connette al server del progetto di C#
|
||||
*/
|
||||
public class RemoteDB implements Database {
|
||||
|
||||
/**
|
||||
* Serve per mandare i messaggi, convertendo le classi in Json
|
||||
*/
|
||||
private static final Gson GSON = GsonFactory.getDefaultFactory().getGson();
|
||||
|
||||
private static final DateFormat STD_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH");
|
||||
|
||||
/**
|
||||
* L'url base che verra' usato per inviare/ricevere i dati
|
||||
*/
|
||||
public final String base_url;
|
||||
/**
|
||||
* L'username dell'utente
|
||||
*/
|
||||
public final String username;
|
||||
|
||||
/**
|
||||
* Inseredo lo username e basta l'indirizzo a cui tentera' la connesione e' <a href="http://127.0.0.1:5000/api/">http://127.0.0.1:5000/api/</a>
|
||||
* @param username il nome utente assiociato per aggiornare i dati
|
||||
*/
|
||||
public RemoteDB(String username) {
|
||||
this(username, "http://127.0.0.1:5000/api/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Costruttore che ha bisogno sia dello username che dell'url per la connesione
|
||||
* @param username il nome utente assiociato per aggiornare i dati
|
||||
* @param base_url l'url a cui si punta per aggiornare/leggere i dati
|
||||
*/
|
||||
public RemoteDB(String username, String base_url) {
|
||||
this.username = username;
|
||||
this.base_url = base_url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReachable() {
|
||||
return !Rest.get(base_url+"user/"+username).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateHeart(long dateMilliSec, double heartRate) {
|
||||
return sendData("heartbeat", dateMilliSec, heartRate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSleep(long dateStartSleep, long duration) {
|
||||
return sendData("sleep", dateStartSleep, duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateSteps(long dateMilliSec, long steps) {
|
||||
return sendData("step", dateMilliSec, steps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HeartRate> getHeartDataOfLast(int days) {
|
||||
try {
|
||||
String url = base_url+"heartbeat/"+username+"/last/"+(days*24);
|
||||
Map<String, List<Map<String, Object>>> map = (Map<String, List<Map<String, Object>>>) Rest.get(url);
|
||||
|
||||
List<HeartRate> list = new ArrayList<>(map.get("list").size());
|
||||
for(Map<String, Object> data: map.get("list")) {
|
||||
HeartRate heart = new HeartRate();
|
||||
heart.setAverage((double)data.get("value"));
|
||||
heart.setDate(STD_FORMAT.parse((String)data.get("time")).getTime());
|
||||
|
||||
list.add(heart);
|
||||
}
|
||||
return list;
|
||||
} catch (ParseException e) {
|
||||
LOG.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Steps> getStepDataOfLast(int days) {
|
||||
try {
|
||||
String url = base_url+"step/"+username+"/last/"+(days*24);
|
||||
Map<String, List<Map<String, Object>>> map = (Map<String, List<Map<String, Object>>>) Rest.get(url);
|
||||
|
||||
List<Steps> list = new ArrayList<>(map.get("list").size());
|
||||
for(Map<String, Object> data: map.get("list")) {
|
||||
Steps steps = new Steps();
|
||||
steps.setSteps((int)data.get("value"));
|
||||
steps.setDate(STD_FORMAT.parse((String)data.get("time")).getTime());
|
||||
|
||||
list.add(steps);
|
||||
}
|
||||
return list;
|
||||
} catch (ParseException e) {
|
||||
LOG.error(e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve ad inviare una richiesta PUT per aggiornare i dati
|
||||
* @param type il tipo di dato che deve esser aggiornato
|
||||
* @param date la data da inserire in millisecondi
|
||||
* @param value l'oggetto da inviare
|
||||
* @return vero se dopo la PUT si e' riusciti ad inserire il valore
|
||||
*/
|
||||
private boolean sendData(String type, long date, Object value) {
|
||||
String url = base_url+type+"/";
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("username", username);
|
||||
map.put("time", new Timestamp(date));
|
||||
map.put("value", value);
|
||||
|
||||
Rest.put(url, GSON.toJson(map), "application/json");
|
||||
DateFormat format = new SimpleDateFormat("yyyy-MM-dd/HH");
|
||||
|
||||
url = url+username+"/"+format.format(new Date(date));
|
||||
List<Map<String, Object>> list = (List<Map<String, Object>>) Rest.get(url).get("list");
|
||||
for(Map<String, Object> obj: list)
|
||||
try {
|
||||
if (STD_FORMAT.parse((String) obj.get("time")).getTime() == date)
|
||||
return true;
|
||||
} catch (Exception e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
BIN
src/main/resources/Relazione progetto SENIORASSISTANT.pdf
Normal file
BIN
src/main/resources/Relazione progetto SENIORASSISTANT.pdf
Normal file
Binary file not shown.
BIN
src/main/resources/audio/random/Godzilla.wav
Normal file
BIN
src/main/resources/audio/random/Godzilla.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/random/LeeroyJenkins.wav
Normal file
BIN
src/main/resources/audio/random/LeeroyJenkins.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/random/Tullio.wav
Normal file
BIN
src/main/resources/audio/random/Tullio.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/relax/Marconi Union - Weightless.wav
Normal file
BIN
src/main/resources/audio/relax/Marconi Union - Weightless.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/main/resources/audio/relax/Slow down - Paul Collier.wav
Normal file
BIN
src/main/resources/audio/relax/Slow down - Paul Collier.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/relax/the xx - Intro.wav
Normal file
BIN
src/main/resources/audio/relax/the xx - Intro.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/warnings/molti battiti.wav
Normal file
BIN
src/main/resources/audio/warnings/molti battiti.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/warnings/molti passi.wav
Normal file
BIN
src/main/resources/audio/warnings/molti passi.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/warnings/pochi battiti.wav
Normal file
BIN
src/main/resources/audio/warnings/pochi battiti.wav
Normal file
Binary file not shown.
BIN
src/main/resources/audio/warnings/pochi passi.wav
Normal file
BIN
src/main/resources/audio/warnings/pochi passi.wav
Normal file
Binary file not shown.
2
src/main/resources/simplelogger.properties
Normal file
2
src/main/resources/simplelogger.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
org.slf4j.simpleLogger.defaultLogLevel=info
|
||||
org.slf4j.simpleLogger.logFile=System.out
|
||||
BIN
src/main/resources/user_data.db
Normal file
BIN
src/main/resources/user_data.db
Normal file
Binary file not shown.
@@ -1,42 +0,0 @@
|
||||
import device.Hue;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class TestLights {
|
||||
|
||||
@Test
|
||||
synchronized public void firstTestLights() throws InterruptedException {
|
||||
Hue lights = new Hue();
|
||||
|
||||
Set<String> toRemove = new HashSet<>();
|
||||
for(String str: lights.getNameLights())
|
||||
if(!str.equals("4"))
|
||||
toRemove.add(str);
|
||||
lights.removeLights(toRemove);
|
||||
|
||||
for(int i=0; i<10; i++) {
|
||||
lights.turnOn();
|
||||
this.wait(0b11001000); // 200
|
||||
lights.turnOff();
|
||||
this.wait(0b11001000); // 200
|
||||
}
|
||||
|
||||
lights.turnOn();
|
||||
for(int i=256; i>=0; i--) {
|
||||
lights.setBrightness(i);
|
||||
this.wait(50);
|
||||
}
|
||||
|
||||
for(int i=0; i<256; i++) {
|
||||
lights.setBrightness(i);
|
||||
this.wait(50);
|
||||
}
|
||||
|
||||
lights.colorLoop();
|
||||
this.wait(10000); // 10 sec
|
||||
lights.turnOff();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import device.Hue;
|
||||
import device.Sensor;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestSensor {
|
||||
Sensor sensor = new Sensor();
|
||||
Hue hue = new Hue();
|
||||
|
||||
@Test
|
||||
public void firstTestSensor() {
|
||||
assertTrue(sensor.IsLowLuminescence(50));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void secondTestSensor() {
|
||||
if(sensor.IsLowLuminescence(50)) {
|
||||
hue.turnOn();
|
||||
hue.setBrightness(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/test/java/test/TestDialogFlow.java
Normal file
18
src/test/java/test/TestDialogFlow.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package test;
|
||||
|
||||
import device.DialogFlowWebHook;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class TestDialogFlow {
|
||||
|
||||
@Test
|
||||
public void test01() {
|
||||
DialogFlowWebHook webHook = new DialogFlowWebHook();
|
||||
|
||||
webHook.addOnAction("LightsON", (param) -> {return "Luci accese";});
|
||||
webHook.addOnAction("LightsOFF", (param) -> {return "Luci spente";});
|
||||
|
||||
webHook.startServer();
|
||||
}
|
||||
}
|
||||
27
src/test/java/test/TestFitbit.java
Normal file
27
src/test/java/test/TestFitbit.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package test;
|
||||
|
||||
import device.Fitbit;
|
||||
import org.junit.Test;
|
||||
import support.Rest;
|
||||
|
||||
public class TestFitbit {
|
||||
|
||||
@Test
|
||||
public void test01() throws Exception {
|
||||
Fitbit fitBit = new Fitbit();
|
||||
fitBit.getHoursSleep();
|
||||
|
||||
System.out.println("Today's average heart-rate: "+fitBit.getHeartRate());
|
||||
System.out.println("Today's hours of sleep: "+fitBit.getHoursSleep());
|
||||
System.out.println("Today's steps: "+fitBit.getSteps());
|
||||
System.out.println("Fine.");
|
||||
|
||||
Rest.get("https://api.fitbit.com/1/user/-/activities/steps/date/today/1d.json");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test02() throws Exception {
|
||||
Fitbit fitbit = new Fitbit();
|
||||
System.out.println(fitbit.getSteps(60));
|
||||
}
|
||||
}
|
||||
77
src/test/java/test/TestLights.java
Normal file
77
src/test/java/test/TestLights.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package test;
|
||||
|
||||
import device.Hue;
|
||||
import main.VariousThreads;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestLights {
|
||||
|
||||
private static final int TIMEOUT = 200;
|
||||
private static final int MAX = 100;
|
||||
private Hue lights;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
lights = new Hue();
|
||||
lights.turnOn();
|
||||
lights.setBrightness(100);
|
||||
lights.changeColor("bianco");
|
||||
}
|
||||
|
||||
@Test
|
||||
synchronized public void firstTestLights() throws InterruptedException {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
lights.turnOn();
|
||||
this.wait(TIMEOUT);
|
||||
lights.turnOff();
|
||||
this.wait(TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
synchronized public void testBrightness() throws InterruptedException {
|
||||
for (int i = MAX; i > 0; i -= 10) {
|
||||
lights.setBrightness(i);
|
||||
this.wait(TIMEOUT);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX; i += 10) {
|
||||
lights.setBrightness(i);
|
||||
this.wait(TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
synchronized public void testColorLoop() throws InterruptedException {
|
||||
lights.colorLoop();
|
||||
this.wait(TIMEOUT * 10);
|
||||
}
|
||||
|
||||
@Test
|
||||
synchronized public void testColor() throws InterruptedException {
|
||||
String[] colors = {"rosso", "giallo", "verde", "blu", "bianco", "azzurro", "arancio"};
|
||||
|
||||
for (String color : colors) {
|
||||
lights.changeColor(color);
|
||||
this.wait(TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
synchronized public void testAutoBright() throws InterruptedException {
|
||||
lights.setBrightness(MAX);
|
||||
|
||||
for(int hour=0; hour<24; hour++)
|
||||
for(int minutes=0; minutes<60; minutes++) {
|
||||
final double hueBrightnes = lights.getCurrentBrightness();
|
||||
final double brightFactor = VariousThreads.calculateBrightFactor(hour, minutes, 100, 20);
|
||||
final double bright = brightFactor*100;
|
||||
|
||||
System.out.printf("%2d:%02d: %+.3f {bri:%3.0f -> add:%+4.0f}\n", hour, minutes, brightFactor, hueBrightnes, bright);
|
||||
lights.addBrightness(bright);
|
||||
wait(TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
75
src/test/java/test/TestMusich.java
Normal file
75
src/test/java/test/TestMusich.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package test;
|
||||
|
||||
import org.junit.Test;
|
||||
import support.audio.AudioFile;
|
||||
import support.audio.Musich;
|
||||
|
||||
public class TestMusich {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Musich musich = new Musich();
|
||||
musich.playRandom("fairy tail motivational soundtrack", 10);
|
||||
waitAndPrint(20);
|
||||
musich.play("X9di06iCmuw", 114);
|
||||
waitAndPrint(60);
|
||||
musich.stop();
|
||||
waitAndPrint(10);
|
||||
musich.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
AudioFile audio = new AudioFile();
|
||||
audio.play("Godzilla.wav");
|
||||
waitAndPrint(3);
|
||||
audio.play("Tullio.wav");
|
||||
waitAndPrint(10);
|
||||
audio.stop();
|
||||
waitAndPrint(2);
|
||||
audio.play("LeeroyJenkins.wav");
|
||||
waitAndPrint(5);
|
||||
audio.playRandom("random");
|
||||
waitAndPrint(10);
|
||||
audio.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test3() {
|
||||
AudioFile audio = new AudioFile();
|
||||
AudioFile audio2 = new AudioFile();
|
||||
audio.play("Tullio.wav");
|
||||
waitAndPrint(3);
|
||||
audio2.play("Tullio.wav");
|
||||
waitAndPrint(10);
|
||||
audio.stop();
|
||||
audio2.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test4() {
|
||||
AudioFile audio = new AudioFile();
|
||||
|
||||
new AudioFile("pochi passi.wav");
|
||||
waitAndPrint(3);
|
||||
audio.play("molti battiti.wav");
|
||||
waitAndPrint(3);
|
||||
audio.stop();
|
||||
}
|
||||
|
||||
|
||||
public void waitAndPrint(Integer seconds) {
|
||||
if(seconds != null) synchronized (seconds) {
|
||||
try {
|
||||
for(int i=seconds; i>0; i--) {
|
||||
System.out.println("Tempo rimanente: " + i);
|
||||
seconds.wait(1000); // 1 sec
|
||||
}
|
||||
System.out.println("Finito");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("INTERRUPTED " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/test/java/test/TestRemoteDB.java
Normal file
24
src/test/java/test/TestRemoteDB.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package test;
|
||||
|
||||
import org.junit.Test;
|
||||
import support.database.Database;
|
||||
import support.database.RemoteDB;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestRemoteDB {
|
||||
|
||||
private static final String REMOTE_URL = "http://127.0.0.1:5000/api/";
|
||||
private static final String USERNAME = "vecchio1";
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Database database = new RemoteDB(USERNAME, REMOTE_URL);
|
||||
assertTrue(database.isReachable());
|
||||
|
||||
//assertTrue(database.updateHeart(System.currentTimeMillis(), Math.random()*70 + 50));
|
||||
//assertTrue(database.updateSleep(System.currentTimeMillis(), (long) (Math.random()*7200000) + 0));
|
||||
//assertTrue(database.updateSteps(System.currentTimeMillis(), (long) (Math.random()*100) + 100));
|
||||
//database.getHeartDataOfLast(10);
|
||||
}
|
||||
}
|
||||
44
src/test/java/test/TestSensor.java
Normal file
44
src/test/java/test/TestSensor.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package test;
|
||||
|
||||
import device.Hue;
|
||||
import device.Sensor;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class TestSensor {
|
||||
private Sensor sensor = new Sensor(2);
|
||||
private Hue lights;
|
||||
|
||||
@Test
|
||||
synchronized public void firstTestSensor() {
|
||||
System.out.println(sensor.getBrightnessLevel());
|
||||
}
|
||||
|
||||
@Test
|
||||
synchronized public void secondTestSensor() {
|
||||
int i=0;
|
||||
lights = new Hue();
|
||||
Set<String> toRemove = new HashSet<>();
|
||||
|
||||
for(String str: lights.getNameLights())
|
||||
if(!str.equals("4"))
|
||||
toRemove.add(str);
|
||||
lights.removeLights(toRemove);
|
||||
lights.turnOn();
|
||||
|
||||
while(i<999999) {
|
||||
if (sensor.getBrightnessLevel() < 200) {
|
||||
lights.getCurrentBrightness();
|
||||
lights.setBrightness(200);
|
||||
}
|
||||
|
||||
if (sensor.getBrightnessLevel() > 600) {
|
||||
lights.setBrightness(0);
|
||||
}
|
||||
System.out.println(i+"-"+sensor.getBrightnessLevel());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user