mirror of
https://github.com/ConnectedHumber/Air-Quality-Web
synced 2024-11-25 06:53:00 +00:00
[server] Add device-data-bounds API call
This commit is contained in:
parent
556585e848
commit
0cefd047f4
8 changed files with 224 additions and 3 deletions
14
README.md
14
README.md
|
@ -93,6 +93,20 @@ _No parameters are currently supported by this action._
|
||||||
https://example.com/path/to/api.php?action=list-reading-types
|
https://example.com/path/to/api.php?action=list-reading-types
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### device-data-bounds
|
||||||
|
> Gets the start and end DateTime bounds for the data recorded for a specific device.
|
||||||
|
|
||||||
|
Parameter | Type | Meaning
|
||||||
|
--------------------|-----------|---------------------
|
||||||
|
`device-id` | int | Required. The id of the device to get the data DateTime bounds for.
|
||||||
|
|
||||||
|
```
|
||||||
|
https://example.com/path/to/api.php?action=device-data-bounds&device-id=18
|
||||||
|
https://example.com/path/to/api.php?action=device-data-bounds&device-id=11
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
- Readings are taken every 6 minutes as standard.
|
- Readings are taken every 6 minutes as standard.
|
||||||
|
|
||||||
|
|
42
client_src/js/DeviceReadingDisplay.mjs
Normal file
42
client_src/js/DeviceReadingDisplay.mjs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// import CreateElement from 'dom-create-element-query-selector';
|
||||||
|
// We're using the git repo for now until an update is released, and rollup doesn't like that apparently
|
||||||
|
import CreateElement from '../../node_modules/dom-create-element-query-selector/src/index.js';
|
||||||
|
|
||||||
|
import Chart from 'chart.js';
|
||||||
|
|
||||||
|
import GetFromUrl from './Helpers/GetFromUrl.mjs';
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceReadingDisplay {
|
||||||
|
get device_id() { return this._device_id; }
|
||||||
|
set device_id(value) {
|
||||||
|
this._device_id = value;
|
||||||
|
this.update_display();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(in_config, in_device_id) {
|
||||||
|
this.setup_display();
|
||||||
|
|
||||||
|
this.config = in_config;
|
||||||
|
this._device_id = in_device_id; // We don't want to update until we have everything we need
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_display() {
|
||||||
|
this.display = CreateElement("div.chart-device-data",
|
||||||
|
CreateElement("canvas.canvas-chart"),
|
||||||
|
CreateElement("ul.reading-types")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
set_reading_types(new_reading_types, starting_index) {
|
||||||
|
this.reading_types = new_reading_types;
|
||||||
|
this.selected_reading_type = this.reading_types[starting_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
async update_display() {
|
||||||
|
let new_data = JSON.parse(await GetFromUrl(`${this.config.api_root}?action=`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeviceReadingDisplay;
|
72
logic/Actions/DeviceDataBounds.php
Normal file
72
logic/Actions/DeviceDataBounds.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AirQuality\Actions;
|
||||||
|
|
||||||
|
use \SBRL\TomlConfig;
|
||||||
|
use \AirQuality\Repositories\IMeasurementDataRepository;
|
||||||
|
use \AirQuality\ApiResponseSender;
|
||||||
|
|
||||||
|
use \AirQuality\Validator;
|
||||||
|
use \AirQuality\PerfFormatter;
|
||||||
|
|
||||||
|
class DeviceDataBounds implements IAction {
|
||||||
|
/** @var TomlConfig */
|
||||||
|
private $settings;
|
||||||
|
/** @var IMeasurementDataRepository */
|
||||||
|
private $measurement_repo;
|
||||||
|
|
||||||
|
/** @var ApiResponseSender */
|
||||||
|
private $sender;
|
||||||
|
|
||||||
|
/** @var Validator */
|
||||||
|
private $validator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TomlConfig $in_settings,
|
||||||
|
IMeasurementDataRepository $in_measurement_repo,
|
||||||
|
ApiResponseSender $in_sender) {
|
||||||
|
$this->settings = $in_settings;
|
||||||
|
$this->measurement_repo = $in_measurement_repo;
|
||||||
|
$this->sender = $in_sender;
|
||||||
|
|
||||||
|
$this->validator = new Validator($_GET);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle() : bool {
|
||||||
|
global $start_time;
|
||||||
|
|
||||||
|
$start_handle = microtime(true);
|
||||||
|
|
||||||
|
// 1: Validate params
|
||||||
|
$this->validator->is_numberish("device-id");
|
||||||
|
$this->validator->run();
|
||||||
|
|
||||||
|
|
||||||
|
// 2: Pull data from database
|
||||||
|
$data = $this->measurement_repo->get_device_reading_bounds(
|
||||||
|
intval($_GET["device-id"])
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2.5: Validate data from database
|
||||||
|
if(empty($data)) {
|
||||||
|
http_response_code(404);
|
||||||
|
header("content-type: text/plain");
|
||||||
|
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null));
|
||||||
|
echo("Error: No data has been recorded from the device id or it doesn't exist.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 3: Serialise data
|
||||||
|
$start_encode = microtime(true);
|
||||||
|
$response = json_encode($data);
|
||||||
|
|
||||||
|
|
||||||
|
// 4: Send response
|
||||||
|
header("content-length: " . strlen($response));
|
||||||
|
header("content-type: application/json");
|
||||||
|
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
|
||||||
|
echo($response);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ class Database
|
||||||
}
|
}
|
||||||
|
|
||||||
public function query($sql, $variables = []) {
|
public function query($sql, $variables = []) {
|
||||||
|
error_log("[Database/SQL] $sql");
|
||||||
// FUTURE: Optionally cache prepared statements?
|
// FUTURE: Optionally cache prepared statements?
|
||||||
$statement = $this->connection->prepare($sql);
|
$statement = $this->connection->prepare($sql);
|
||||||
$statement->execute($variables);
|
$statement->execute($variables);
|
||||||
|
|
|
@ -3,5 +3,29 @@
|
||||||
namespace AirQuality\Repositories;
|
namespace AirQuality\Repositories;
|
||||||
|
|
||||||
interface IMeasurementDataRepository {
|
interface IMeasurementDataRepository {
|
||||||
public function get_readings_by_date(\DateTime $datetime, string $reading_type);
|
/**
|
||||||
|
* Returns the specified reading type for all devices at the specified date
|
||||||
|
* and time.
|
||||||
|
* @param DateTime $datetime The date and time to get the readings for.
|
||||||
|
* @param string $reading_type The reading type to fetch.
|
||||||
|
* @return array The requested readings.
|
||||||
|
*/
|
||||||
|
public function get_readings_by_date(\DateTime $datetime, string $reading_type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first and last DateTimes of the readings for a specified device.
|
||||||
|
* @param int $device_id The device id to fetch for.
|
||||||
|
* @return DateTime[] The first and last DateTimes for which readings are stored.
|
||||||
|
*/
|
||||||
|
public function get_device_reading_bounds(int $device_id);
|
||||||
|
/**
|
||||||
|
* Gets the readings of a specified type for a specific device between the 2 given dates and times.
|
||||||
|
* @param int $device_id The id of the device to fetch data for.
|
||||||
|
* @param string $reading_type The reading type to fetch.
|
||||||
|
* @param DateTime $start The starting DateTime.
|
||||||
|
* @param DateTime $end The ending DateTime.
|
||||||
|
* @return array The requested data.
|
||||||
|
*/
|
||||||
|
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,4 +93,27 @@ class MariaDBMeasurementDataRepository implements IMeasurementDataRepository {
|
||||||
]
|
]
|
||||||
)->fetchAll();
|
)->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_device_reading_bounds(int $device_id) {
|
||||||
|
$s = $this->get_static;
|
||||||
|
return $this->database->query(
|
||||||
|
"SELECT
|
||||||
|
MIN(COALESCE(
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
|
||||||
|
)) AS start,
|
||||||
|
MAX(COALESCE(
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
|
||||||
|
)) AS end
|
||||||
|
FROM {$s("table_name_metadata")}
|
||||||
|
WHERE {$s("table_name_metadata")}.{$s("column_metadata_device_id")} = :device_id;", [
|
||||||
|
$device_id
|
||||||
|
]
|
||||||
|
)->fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
47
package-lock.json
generated
47
package-lock.json
generated
|
@ -4,6 +4,12 @@
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/chart.js": {
|
||||||
|
"version": "2.7.42",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.42.tgz",
|
||||||
|
"integrity": "sha512-+0v+PV2J9wsV7u36Vqt5Oke7ugtiZqNeYJgNk5mgNMkEkqtjxo2Q9N5Q9znHlgmXeOTfQL6e1sfcAbTnPHAzlA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/estree": {
|
"@types/estree": {
|
||||||
"version": "0.0.39",
|
"version": "0.0.39",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||||
|
@ -247,6 +253,39 @@
|
||||||
"supports-color": "^5.3.0"
|
"supports-color": "^5.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chart.js": {
|
||||||
|
"version": "2.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz",
|
||||||
|
"integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==",
|
||||||
|
"requires": {
|
||||||
|
"chartjs-color": "^2.1.0",
|
||||||
|
"moment": "^2.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chartjs-color": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
|
||||||
|
"integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
|
||||||
|
"requires": {
|
||||||
|
"chartjs-color-string": "^0.5.0",
|
||||||
|
"color-convert": "^0.5.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
|
||||||
|
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chartjs-color-string": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"clap": {
|
"clap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",
|
||||||
|
@ -321,8 +360,7 @@
|
||||||
"color-name": {
|
"color-name": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"color-string": {
|
"color-string": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
|
@ -1193,6 +1231,11 @@
|
||||||
"minimist": "0.0.8"
|
"minimist": "0.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"moment": {
|
||||||
|
"version": "2.23.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz",
|
||||||
|
"integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA=="
|
||||||
|
},
|
||||||
"normalize-path": {
|
"normalize-path": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/sbrl/ConnectedHumber-Air-Quality-Interface#readme",
|
"homepage": "https://github.com/sbrl/ConnectedHumber-Air-Quality-Interface#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"chart.js": "^2.7.3",
|
||||||
"dom-create-element-query-selector": "github:hekigan/dom-create-element-query-selector",
|
"dom-create-element-query-selector": "github:hekigan/dom-create-element-query-selector",
|
||||||
"leaflet": "^1.4.0",
|
"leaflet": "^1.4.0",
|
||||||
"leaflet-fullscreen": "^1.0.2",
|
"leaflet-fullscreen": "^1.0.2",
|
||||||
|
@ -25,6 +26,7 @@
|
||||||
"smartsettings": "^1.2.3"
|
"smartsettings": "^1.2.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/chart.js": "^2.7.42",
|
||||||
"@types/leaflet": "^1.2.14",
|
"@types/leaflet": "^1.2.14",
|
||||||
"@types/leaflet-fullscreen": "^1.0.4",
|
"@types/leaflet-fullscreen": "^1.0.4",
|
||||||
"leaflet-heatmap": "^1.0.0",
|
"leaflet-heatmap": "^1.0.0",
|
||||||
|
|
Loading…
Reference in a new issue