Commit cb6b9f53 authored by Nicolas Richard Walter Boeckh's avatar Nicolas Richard Walter Boeckh 💬

Merge branch 'ui-data-pane' into 'main'

Ui data pane

See merge request !4
parents 069108fc b80210db
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.0\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"android":[{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.0\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"macos":[{"name":"shared_preferences_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_macos-0.0.1+8\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"shared_preferences_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_web-0.1.2+5\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_blue","dependencies":[]},{"name":"geolocator","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","dependencies":[]},{"name":"location_permissions","dependencies":[]},{"name":"path_provider","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2020-07-17 13:50:49.319377","version":"1.20.0-3.0.pre.126"}
\ No newline at end of file
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.0\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"android":[{"name":"flutter_blue","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_blue-0.7.2\\\\","dependencies":[]},{"name":"geolocator","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\geolocator-5.2.1\\\\","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_api_availability-2.0.2\\\\","dependencies":[]},{"name":"location_permissions","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location_permissions-2.0.4+1\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-1.6.0\\\\","dependencies":[]},{"name":"permission_handler","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\permission_handler-4.2.0+hotfix.3\\\\","dependencies":[]},{"name":"shared_preferences","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences-0.5.7+2\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"macos":[{"name":"shared_preferences_macos","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_macos-0.0.1+8\\\\","dependencies":[]},{"name":"sqflite","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\sqflite-1.2.0\\\\","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"shared_preferences_web","path":"C:\\\\src\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\shared_preferences_web-0.1.2+5\\\\","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_blue","dependencies":[]},{"name":"geolocator","dependencies":["google_api_availability","location_permissions"]},{"name":"google_api_availability","dependencies":[]},{"name":"location_permissions","dependencies":[]},{"name":"path_provider","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]},{"name":"sqflite","dependencies":[]}],"date_created":"2020-08-21 09:20:35.704809","version":"1.21.0-10.0.pre.212"}
\ No newline at end of file
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:latlong/latlong.dart';
import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/logic/handlers/data_handler.dart';
import 'package:logair_application/logic/handlers/database_handler.dart';
import 'package:logair_application/logic/handlers/network_handler.dart';
import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/logic/handlers/position_handler.dart';
import 'package:logair_application/utils/pm_symbol.dart';
/// The [MapDisplayController] is a [Singleton] that handles the mapping of information.
class MapDisplayController {
......@@ -33,37 +33,60 @@ class MapDisplayController {
/// [Marker] representing the user's current [Position].
Marker _marker;
HashMap<String, List<CircleMarker>> _pmMarkers = HashMap.from({'PM1': <CircleMarker>[], 'PM2_5': <CircleMarker>[], 'PM4': <CircleMarker>[], 'PM10': <CircleMarker>[]});
/// [List] of [Color]s to be smudged as a gradient and cherry picked based on a value (lerped).
/// @see [_colorizeValue]
List<Color> _colors = [ Color.fromARGB(0xff, 0, 0xcc, 0), Color.fromARGB(0xff, 0, 0xcc, 0), Color.fromARGB(0xff, 0xff, 0xff, 0), Color.fromARGB(0xff, 0xeb, 0x8a, 0x14), Color.fromARGB(0xff, 0xff, 0, 0), Color.fromARGB(0xff, 0xa1, 0x06, 0x49), Color.fromARGB(0xff, 0x7e, 0, 0x23) ];
/// [List] of stops for the lerping to occur.
/// @see [_colorizeValue]
List<double> _stops = [0, 12, 35.4, 55.4, 150.4, 250.4, 500.4];
Future<void> _rebuildMarkers() async {
List<DataPacket> packets = await DatabaseHandler().getLastPackets(200);
packets.forEach((DataPacket packet) {
switch (_pmSymbol) {
case PMSymbol.PM1:
this.addToList(this._currentPos, packet.pm1(), 200);
break;
case PMSymbol.PM1:
this.addToList(this._currentPos, packet.pm2_5(), 200);
break;
case PMSymbol.PM4:
this.addToList(this._currentPos, packet.pm4(), 200);
break;
case PMSymbol.PM10:
this.addToList(this._currentPos, packet.pm10(), 200);
break;
default:
break;
}
});
}
List<CircleMarker> _pmMarkers = <CircleMarker>[];
/// [Singleton]() instantiation factory.
factory MapDisplayController() => _singleton;
/// @Getter for [_polylines].
List<Polyline> polylines() => this._polylines;
List<Polyline> get polylines => this._polylines;
/// @Getter for [_marker].
Marker marker() => this._marker != null ? this._marker : _buildLocationMarker(LatLng(0, 0));
Marker get marker => this._marker != null ? this._marker : _buildLocationMarker(LatLng(0, 0));
/// Object used to calculate the real-life distance of two [Position]s
final Distance _distance = new Distance();
// TODO Faster to rebuild than to store ?
List<CircleMarker> pmMarkers() => this._pmMarkers[_pmKey[_pmKeyIndex]];
List<String> _pmKey = ["PM1", "PM2_5", "PM4", "PM10"];
List<CircleMarker> get pmMarkers => this._pmMarkers;
int _pmKeyIndex = 1;
PMSymbol _pmSymbol = PMSymbol.PM2_5;
void setPMKeyIndex(PMSymbol value) {
print('PM Index $value gives ${value.key}');
this._pmSymbol = value;
this._rebuildMarkers();
}
void setPMKeyIndex(int value) { print('PM Index $value gives ${_pmKey[value]}'); this._pmKeyIndex = value; }
void addToList(LatLng position, double value, int maxPoints) {
if (this._pmMarkers.length > maxPoints)
this._pmMarkers.removeRange(0, this._pmMarkers.length - 200);
this._pmMarkers.add(_buildPMMarker(position, value));
}
/// This function is executed on first initialization of the [MapDisplayController]
/// TODO: Throttle to 1 in 5 updates for efficiency ?
......@@ -73,6 +96,7 @@ class MapDisplayController {
this._controller = new MapController();
/// This function is hooked to the [PositionHandler]s progress, ie. when a new [Position] is acquired (once per second), it is broadcasted across the app and can be listened to.
// TODO Fix this to allow external data (bounded map data)
PositionHandler().getCurrentOrLastPosition().listen((posChanged) {
if (posChanged == null)
return;
......@@ -109,10 +133,26 @@ class MapDisplayController {
this._polylines.last.points.add(this._currentPos);
if (latest != null) {
if (latest.pm1() != null) this._pmMarkers['PM1'].add(_buildPMMarker(this._currentPos, latest.pm1()));
if (latest.pm2_5() != null) this._pmMarkers['PM2_5'].add(_buildPMMarker(this._currentPos, latest.pm2_5()));
if (latest.pm4() != null) this._pmMarkers['PM4'].add(_buildPMMarker(this._currentPos, latest.pm4()));
if (latest.pm10() != null) this._pmMarkers['PM10'].add(_buildPMMarker(this._currentPos, latest.pm10()));
switch (_pmSymbol) {
case PMSymbol.PM1:
if (latest.pm1() != null)
this.addToList(this._currentPos, latest.pm1(), 200);
break;
case PMSymbol.PM2_5:
if (latest.pm2_5() != null)
this.addToList(this._currentPos, latest.pm2_5(), 200);
break;
case PMSymbol.PM4:
if (latest.pm4() != null)
this.addToList(this._currentPos, latest.pm4(), 200);
break;
case PMSymbol.PM10:
if (latest.pm10() != null)
this.addToList(this._currentPos, latest.pm10(), 200);
break;
default:
break;
}
}
}
});
......@@ -121,8 +161,10 @@ class MapDisplayController {
// TODO Set user defined duration
// TODO Set user defined radius
new Timer.periodic(Duration(minutes: 1), (Timer t) {
if (this._controller != null && this._controller.bounds != null)
if (this._controller != null && this._controller.bounds != null) {
print('${this._controller.bounds.southWest}, ${this._controller.bounds.northEast}');
NetworkHandler().getProximityPoints(DataHeader().deviceID, this._controller.bounds);
}
});
}
......@@ -144,14 +186,15 @@ class MapDisplayController {
}
/// Make the map respond to automatic events once more.
/// TODO Possibly remove the one tick delay.
void setControllerAutomatic() => this._manualMotion = false;
/// Enables the user to have a controlled zoom in, zoom out experience.
void setControllerZoom(bool zoomIn) {
/// Bound the zoomability.
if (zoomIn && this._zoom <= 19) this._zoom += 0.5;
else if (!zoomIn && this._zoom >= 1) this._zoom -= 0.5;
if (zoomIn && this._zoom <= 19)
this._zoom += 0.5;
else if (!zoomIn && this._zoom >= 1)
this._zoom -= 0.5;
this._controller.move(this.controller().center, this._zoom);
}
......@@ -164,21 +207,6 @@ class MapDisplayController {
}
}
/// Gets the lerped [Color] value at a for a given PM value.
Color _colorizeValue(double pmValue) {
for (int stop = 0; stop < _stops.length - 1; stop++) {
final double left = _stops[stop], right = _stops[stop + 1];
final Color colorLeft = _colors[stop], colorRight = _colors[stop + 1];
if (pmValue <= left)
return colorLeft;
else if (pmValue < right) {
final section = (pmValue - left) / (right - left);
return Color.lerp(colorLeft, colorRight, section);
}
}
return _colors.last;
}
/// Wrapper function to build a [Marker], with default values.
Marker _buildLocationMarker(LatLng position) => Marker(
width: 20.0,
......@@ -189,12 +217,12 @@ class MapDisplayController {
),
);
/// TODO MAKE PM Value be switchable.
CircleMarker _buildPMMarker(LatLng position, double pmValue) {
return CircleMarker(
radius: 8,
point: position,
color: _colorizeValue(pmValue).withAlpha(0xff),
color: this._pmSymbol.colorizeValue(pmValue).withAlpha(0xff),
borderStrokeWidth: 1,
borderColor: Colors.blue[300]
);
......
......@@ -25,6 +25,7 @@ class DataHeader {
/// Retrieve the deviceID from the header
String get deviceID => (this._headerData != null && this._headerData.length > 0) ? this._headerData[0] : '';
String get url => (this._headerData != null && this._headerData.length > 0) ? this._headerData[1] : '';
/// @Getter for _headerSet;
bool get headerSet => this._headerSet;
......
......@@ -56,8 +56,8 @@ class DataPacket {
this._speed = (acquiredData[4] != '') ? parseDoubleWrapper(acquiredData[4]) : null;
this._temperature = (acquiredData[5] != '') ? parseDoubleWrapper(acquiredData[5]) : null;
this._pressure = (acquiredData[6] != '') ? parseDoubleWrapper(acquiredData[6]) : null;
this._relativeHumidity = (acquiredData[7] != '') ? parseDoubleWrapper(acquiredData[7]) : null;
this._relativeHumidity = (acquiredData[6] != '') ? parseDoubleWrapper(acquiredData[6]) : null;
this._pressure = (acquiredData[7] != '') ? parseDoubleWrapper(acquiredData[7]) : null;
this._pm1 = (acquiredData[8] != '') ? parseDoubleWrapper(acquiredData[8]) : null;
this._pm2_5 = (acquiredData[9] != '') ? parseDoubleWrapper(acquiredData[9]) : null;
......
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:kotlin_flavor/scope_functions.dart';
import 'package:logair_application/logic/handlers/preference_handler.dart';
......@@ -144,7 +145,12 @@ class BTLEHandler {
/// Wrapper function that aggregates all of the steps to connect to a [BluetoothDevice] and stream its data.
Future<void> _asyncConnectionProcess() async {
await this._device.connect();
try {
await this._device.connect();
} on PlatformException {
await this._device.disconnect();
await this._device.connect();
}
if (Platform.isAndroid) {
/// Android can negotiate greater Message Transmission Units.
/// 517 is the maximum defined in the BLE specification, the request will assign the largest available MTU instead.
......
......@@ -2,11 +2,15 @@ import 'package:logair_application/logic/handlers/bluetooth_le_handler.dart';
import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:logair_application/logic/handlers/database_handler.dart';
import 'package:logair_application/logic/handlers/network_handler.dart';
class DataHandler {
factory DataHandler() => _singleton;
DataHandler._internal();
DataHandler._internal() {
NetworkHandler();
}
static const HEADER_SIZE = 11;
static final DataHandler _singleton = new DataHandler._internal();
......@@ -54,14 +58,14 @@ class DataHandler {
DataHeader().setHeader(packet);
/// Retroactively add all previous packets
if (!wasSet && DataHeader().headerSet)
_sortedData.forEach((packet) => DatabaseHandler().insertData(packet, DataHeader().deviceID));
_sortedData.forEach((packet) => DatabaseHandler().insertData(packet, DataHeader().deviceID, DataHeader().url));
/// Or if it is a [DataPacket] instance.
} else if (packet[0] == 91) {
_sortedData.add(DataPacket(packet));
print('PACKET ${String.fromCharCodes(packet)}');
if (DataHeader().headerSet)
DatabaseHandler().insertData(_sortedData.last, DataHeader().deviceID);
DatabaseHandler().insertData(_sortedData.last, DataHeader().deviceID, DataHeader().url);
}
}
/// TODO Used for debugging, later
......@@ -70,12 +74,8 @@ class DataHandler {
}
/// Get the entirety of the [DataPacket]s.
/// TODO Caching
List<DataPacket> getData() => _sortedData;
/// Get a subrange of [DataPacket]s.
List<DataPacket> getDataRange(int range) => (range > _sortedData.length) ? _sortedData : _sortedData.sublist(0, range);
/// Remove the entirety of the acquired data.
void clearData() {
_sortedData.removeWhere((element) => true);
......@@ -83,7 +83,8 @@ class DataHandler {
/// Remove the first element of the [DataPacket]s. Used for surgical removal.
void pop() {
_sortedData.removeAt(0);
if (_sortedData.isNotEmpty)
_sortedData.removeAt(0);
}
/// Get the latest [DataPacket]
......@@ -93,13 +94,15 @@ class DataHandler {
Stream<DataPacket> getDataStream() async* {
while(true) {
yield (_sortedData.length > 0) ? _sortedData.last : null;
await Future.delayed(Duration(milliseconds: 500));
await Future.delayed(Duration(seconds: 1));
}
}
@deprecated
/// Used to monitor the amount of unsent [DataPacket]s.
Stream<int> getUnsentPacketsLength() async* {
Stream<DataPacket> getMockDataStream() async* {
while(true) {
yield new DataPacket.fromData(1597957282000, 5, 42, 420, 3.0, 32, 24, 10123, 32, null, 5, null, 9, "");
await Future.delayed(Duration(seconds: 1));
}
}
void dispose() {
......
import 'package:logair_application/logic/data_header.dart';
import 'package:logair_application/logic/data_packet.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
......@@ -12,68 +11,53 @@ class DatabaseHandler {
bool shouldCache;
final String createString = '''CREATE TABLE data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
url TEXT,
timestamp_nix INTEGER,
latitude REAL,
longitude REAL,
altitude INTEGER,
speed REAL,
heading INTEGER,
temperature FLOAT,
relative_humidity FLOAT,
pressure FLOAT,
pm_1 FLOAT,
pm_2_5 FLOAT,
pm_4 FLOAT,
pm_10 FLOAT,
extra TEXT,
exported TINYINT DEFAULT 0
)''';
Future<Database> get _database async {
if (_db == null)
this._db = openDatabase(
join((await getDatabasesPath()), 'logair_data.db'),
version: 1,
onCreate: (db, version) => db.execute('''CREATE TABLE data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
timestamp_nix INTEGER,
latitude REAL,
longitude REAL,
altitude INTEGER,
speed REAL,
heading INTEGER,
temperature FLOAT,
relative_humidity FLOAT,
pressure FLOAT,
pm_1 FLOAT,
pm_2_5 FLOAT,
pm_4 FLOAT,
pm_10 FLOAT,
extra TEXT,
exported TINYINT DEFAULT 0
)''')
version: 2,
onCreate: (db, version) => db.execute(createString)
);
return this._db;
}
// TODO REMOVE !
Future<void> recreateDB() async {
final Database database = await _database;
await database.rawQuery('DROP TABLE IF EXISTS data');
await database.rawQuery('''CREATE TABLE data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
device_id TEXT,
timestamp_nix INTEGER,
latitude REAL,
longitude REAL,
altitude INTEGER,
speed REAL,
heading INTEGER,
temperature FLOAT,
relative_humidity FLOAT,
pressure FLOAT,
pm_1 FLOAT,
pm_2_5 FLOAT,
pm_4 FLOAT,
pm_10 FLOAT,
extra TEXT,
exported TINYINT DEFAULT 0
)''');
await database.rawQuery(createString);
}
DatabaseHandler._internal();
static final DatabaseHandler _singleton = new DatabaseHandler._internal();
Future<void> insertData(DataPacket packet, String deviceId) async {
Future<void> insertData(DataPacket packet, String deviceId, String url) async {
final Database database = await _database;
Map<String, dynamic> insert = packet.toMap();
insert['device_id'] = deviceId;
insert['url'] = url;
await database.insert(
'data',
insert,
......@@ -89,14 +73,12 @@ class DatabaseHandler {
return List.generate(data.length, (i) => DataPacket.fromMappedData(data[i]));
}
Future<int> deleteAll() async => await (await this._database).delete('data');
Future<void> close() async => (await this._database).close();
Stream<int> getDBSize() async* {
final Database database = await _database;
while (true) {
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT COUNT(*) AS count FROM data');
final List<Map<String, dynamic>> data = await database.query('data', columns: ['COUNT(*) AS count']);
yield data[0]['count'] ?? 0;
await Future.delayed(Duration(seconds: 5));
}
......@@ -116,10 +98,12 @@ class DatabaseHandler {
return List.generate(data.length, (i) => Tuple2(data[i]['id'], DataPacket.fromMappedData(data[i])));
}
/// This flags all of the internal packets that are given as [ids] as exported to the external servers.
Future<void> setExported(List<int> ids) async {
final Database database = await _database;
print('UPDATE data SET exported = 1 WHERE id IN (${ids.join(', ')})');
int count = await database.rawUpdate('UPDATE data SET exported = 1 WHERE id IN (${ids.join(', ')})');
print('Exported $count');
}
......@@ -127,7 +111,11 @@ class DatabaseHandler {
Stream<int> getUnsentPacketsLength() async* {
final Database database = await _database;
while (true) {
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT COUNT(*) AS count FROM data WHERE exported = 0');
final List<Map<String, dynamic>> data = await database.query('data',
columns: ['COUNT(*) AS count'],
where: 'exported = ?',
whereArgs: [0]
);
yield data[0]['count'] ?? 0;
await Future.delayed(Duration(seconds: 1));
}
......@@ -136,9 +124,19 @@ class DatabaseHandler {
Stream<Map<String, dynamic>> getLatest() async* {
final Database database = await _database;
while (true) {
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT MIN(timestamp_nix) AS start, AVG(pm_1) AS pm_1, AVG(pm_2_5) AS pm_2_5, AVG(pm_4) AS pm_4, AVG(pm_10) AS pm_10 FROM (SELECT * FROM data ORDER BY id DESC LIMIT 5)');
// TODO Change query to timestamp - x time
final timestampLimit = DateTime.now().millisecondsSinceEpoch - 10000;
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT MIN(timestamp_nix) AS start, AVG(pm_1) AS pm_1, AVG(pm_2_5) AS pm_2_5, AVG(pm_4) AS pm_4, AVG(pm_10) AS pm_10 FROM (SELECT * FROM data WHERE timestamp_nix >= $timestampLimit ORDER BY id DESC LIMIT 5)');
yield data[0] ?? Map();
await Future.delayed(Duration(seconds: 5));
}
}
Future<List<DataPacket>> getLastPackets(int maxResults) async {
final Database database = await _database;
final List<Map<String, dynamic>> data = await database.rawQuery('SELECT * FROM data ORDER BY id DESC LIMIT $maxResults;');
return List.generate(data.length, (i) => DataPacket.fromMappedData(data[i]));
}
}
\ No newline at end of file
......@@ -12,7 +12,6 @@ import 'package:logair_application/logic/handlers/database_handler.dart';
import 'package:tuple/tuple.dart';
/// Singleton data structure that contains services enabling the application to send data to a webserver.
/// TODO Make URL user configurable.
class NetworkHandler {
factory NetworkHandler() => _singleton;
......@@ -29,11 +28,11 @@ class NetworkHandler {
/// Method to send acquired data packets by using a POST requests.
void _sendDataToServer() async {
// TODO Preferences
/// The default endpoint.
String url = 'https://api.logair.unige.ch/v1/service';
/// Break conditions ([DataHeader] unset or no [DataPacket] available).
// TODO add url to db,
if (!DataHeader().headerSet || DataHandler().getData().length == 0)
return;
......@@ -62,7 +61,7 @@ class NetworkHandler {
body: json.encode(postData)
)
/// On error, the error should be ignored and not break the thread.
.catchError((e) => print('COULDN\'T CONNECT'));
.catchError((e) => print("${e.toString()} COULDN'T CONNECT"));
/// If the response is defined as an instance of [http.Response], check it's status code and remove all of the packets from the log.
if (response != null && response.statusCode == 200) {
......@@ -78,7 +77,7 @@ class NetworkHandler {
/// Get all of the points in proximity to the device.
void getProximityPoints(String deviceId, LatLngBounds bounds) async {
/// The default endpoint.
String url = 'https://api.logair.unige.ch/v1/service/map/bounds?localId=$deviceId&lat1=${bounds.northEast.latitude}&lng1=${bounds.northEast.longitude}&lat2=${bounds.southWest.latitude}&lng2=${bounds.southWest.longitude}&age=${5000}&limit=${200}&skip=0';
String url = 'https://api.logair.unige.ch/v1/geo/bounds?device_id=$deviceId&lat1=${bounds.northEast.latitude}&lng1=${bounds.northEast.longitude}&lat2=${bounds.southWest.latitude}&lng2=${bounds.southWest.longitude}&age=${300000}&limit=${200}';
/// Attempt to send the data to the endpoint via HTTP POST request.
http.Response response = await http.get(
url,
......@@ -99,4 +98,6 @@ class NetworkHandler {
await Future.delayed(Duration(seconds: 2));
}
}
//TODO Generate a stream from a place for Place display
}
\ No newline at end of file
......@@ -3,15 +3,13 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logair_application/services/battery_service.dart';
import 'package:logair_application/ui/base/custom_paint.dart';
import 'dart:math';
class BatteryVisualizerWidget extends StatelessWidget {
BatteryVisualizerWidget(this.isLeft, {Key key}) : super(key: key);
Stream<int> getBatteryLevel2() async* {
Random r = new Random();
while (true) {
yield 100; //r.nextInt(100);
yield 100;
await Future.delayed(Duration(seconds: 10));
}
}
......
......@@ -12,7 +12,7 @@ class BluetoothButton extends StatefulWidget {
BluetoothButton({Key key}) : super(key: key);
@override
_BluetoothButtonState createState() => _BluetoothButtonState();
_BluetoothButtonState createState() => _BluetoothButtonState(key: key);
}
class _BluetoothButtonState extends State<BluetoothButton> {
......
......@@ -11,7 +11,7 @@ class BluetoothDialog extends StatefulWidget {
BluetoothDialog({Key key}) : super(key: key);
@override
_BluetoothDialogState createState() => _BluetoothDialogState();
_BluetoothDialogState createState() => _BluetoothDialogState(key: key);
}
class _BluetoothDialogState extends State<BluetoothDialog> {
......
......@@ -47,7 +47,10 @@ class _BluetoothSelectionDialogState extends State<BluetoothSelectionDialog> {
builder: (context, sizingInfo) => Container(
height: sizingInfo.screenSize.height * 0.9,
child: RefreshIndicator(
onRefresh: () => BTLEHandler().fblueInstance.startScan(),
onRefresh: () async {
await BTLEHandler().disconnect();
BTLEHandler().fblueInstance.startScan();
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.all(0),
......@@ -77,10 +80,9 @@ class _BluetoothSelectionDialogState extends State<BluetoothSelectionDialog> {
if (await BTLEHandler().fblueInstance.isScanning.first)
BTLEHandler().fblueInstance.stopScan();
try {
await BTLEHandler().disconnect();
BTLEHandler().fblueInstance.startScan(timeout: Duration(seconds: 10));
} catch (e) {
}
} catch (e) { }