mirror of
https://github.com/ConnectedHumber/Air-Quality-Web
synced 2024-11-26 07:02:59 +00:00
[server] Add device-data API action
This commit is contained in:
parent
0cefd047f4
commit
39869afea0
6 changed files with 145 additions and 6 deletions
|
@ -110,6 +110,7 @@ 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.
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
Contributions are welcome - feel free to [open an issue](https://github.com/ConnectedHumber/Air-Quality-Web/issues/new) or (even better) a [pull request](https://github.com/ConnectedHumber/Air-Quality-Web/compare).
|
Contributions are welcome - feel free to [open an issue](https://github.com/ConnectedHumber/Air-Quality-Web/issues/new) or (even better) a [pull request](https://github.com/ConnectedHumber/Air-Quality-Web/compare).
|
||||||
|
|
||||||
|
|
103
logic/Actions/DeviceData.php
Normal file
103
logic/Actions/DeviceData.php
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AirQuality\Actions;
|
||||||
|
|
||||||
|
use \SBRL\TomlConfig;
|
||||||
|
use \AirQuality\Repositories\IMeasurementDataRepository;
|
||||||
|
use \AirQuality\Repositories\IMeasurementTypeRepository;
|
||||||
|
use \AirQuality\ApiResponseSender;
|
||||||
|
|
||||||
|
use \AirQuality\Validator;
|
||||||
|
use \AirQuality\PerfFormatter;
|
||||||
|
|
||||||
|
class DeviceData implements IAction {
|
||||||
|
/** @var TomlConfig */
|
||||||
|
private $settings;
|
||||||
|
|
||||||
|
/** @var IMeasurementDataRepository */
|
||||||
|
private $measurement_repo;
|
||||||
|
/** @var IMeasurementTypeRepository */
|
||||||
|
private $type_repo;
|
||||||
|
|
||||||
|
/** @var ApiResponseSender */
|
||||||
|
private $sender;
|
||||||
|
|
||||||
|
/** @var Validator */
|
||||||
|
private $validator;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
TomlConfig $in_settings,
|
||||||
|
IMeasurementDataRepository $in_measurement_repo,
|
||||||
|
IMeasurementTypeRepository $in_type_repo,
|
||||||
|
ApiResponseSender $in_sender) {
|
||||||
|
$this->settings = $in_settings;
|
||||||
|
$this->measurement_repo = $in_measurement_repo;
|
||||||
|
$this->type_repo = $in_type_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->exists("reading-type");
|
||||||
|
$this->validator->is_max_length("reading-type", 256);
|
||||||
|
$this->validator->is_datetime("start");
|
||||||
|
$this->validator->is_datetime("end");
|
||||||
|
$this->validator->run();
|
||||||
|
|
||||||
|
if(!empty($_GET["average-seconds"]) && intval($_GET["average-seconds"]) == 0) {
|
||||||
|
$this->sender->send_error_plain(
|
||||||
|
400, "Error: That average-seconds value is invalid (an integer greater than 0 required).", [
|
||||||
|
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->type_repo->is_valid_type($_GET["reading-type"])) {
|
||||||
|
$this->sender->send_error_plain(
|
||||||
|
400, "Error: That reading type is invalid.", [
|
||||||
|
[ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2: Pull data from database
|
||||||
|
$data = $this->measurement_repo->get_readings_by_device(
|
||||||
|
intval($_GET["device-id"]),
|
||||||
|
$_GET["reading-type"],
|
||||||
|
new \DateTime($_GET["start"]),
|
||||||
|
new \DateTime($_GET["end"]),
|
||||||
|
!empty($_GET["average-seconds"]) ? intval($_GET["average-seconds"]) : 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,9 +13,9 @@ use \AirQuality\PerfFormatter;
|
||||||
class FetchData implements IAction {
|
class FetchData implements IAction {
|
||||||
/** @var TomlConfig */
|
/** @var TomlConfig */
|
||||||
private $settings;
|
private $settings;
|
||||||
|
|
||||||
/** @var IMeasurementDataRepository */
|
/** @var IMeasurementDataRepository */
|
||||||
private $measurement_repo;
|
private $measurement_repo;
|
||||||
|
|
||||||
/** @var IMeasurementTypeRepository */
|
/** @var IMeasurementTypeRepository */
|
||||||
private $type_repo;
|
private $type_repo;
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class Database
|
||||||
}
|
}
|
||||||
|
|
||||||
public function query($sql, $variables = []) {
|
public function query($sql, $variables = []) {
|
||||||
error_log("[Database/SQL] $sql");
|
//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);
|
||||||
|
|
|
@ -24,8 +24,9 @@ interface IMeasurementDataRepository {
|
||||||
* @param string $reading_type The reading type to fetch.
|
* @param string $reading_type The reading type to fetch.
|
||||||
* @param DateTime $start The starting DateTime.
|
* @param DateTime $start The starting DateTime.
|
||||||
* @param DateTime $end The ending DateTime.
|
* @param DateTime $end The ending DateTime.
|
||||||
|
* @param int $average_seconds The number of seconds to averageg the data over. For example a value of 3600 (1 hour) will return 1 data point per hour, with the value of each point an average of all the readings for that hour.
|
||||||
* @return array The requested data.
|
* @return array The requested data.
|
||||||
*/
|
*/
|
||||||
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end);
|
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end, int $average_seconds = 1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,12 +108,46 @@ class MariaDBMeasurementDataRepository implements IMeasurementDataRepository {
|
||||||
)) AS end
|
)) AS end
|
||||||
FROM {$s("table_name_metadata")}
|
FROM {$s("table_name_metadata")}
|
||||||
WHERE {$s("table_name_metadata")}.{$s("column_metadata_device_id")} = :device_id;", [
|
WHERE {$s("table_name_metadata")}.{$s("column_metadata_device_id")} = :device_id;", [
|
||||||
$device_id
|
"device_id" => $device_id
|
||||||
]
|
]
|
||||||
)->fetch();
|
)->fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end) {
|
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end, int $average_seconds = 1) {
|
||||||
|
if($average_seconds < 1)
|
||||||
|
throw new Exception("Error: average_seconds must be greater than 1, but '$average_seconds' was specified.");
|
||||||
|
$s = $this->get_static;
|
||||||
|
return $this->database->query(
|
||||||
|
"SELECT
|
||||||
|
AVG({$s("table_name_values")}.{$s("column_values_value")}) AS {$s("column_values_value")},
|
||||||
|
MIN({$s("table_name_values")}.{$s("column_values_reading_id")}) AS {$s("column_values_reading_id")},
|
||||||
|
|
||||||
|
MIN(COALESCE(
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
|
||||||
|
)) AS datetime
|
||||||
|
FROM {$s("table_name_values")}
|
||||||
|
JOIN {$s("table_name_metadata")} ON
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_id")} = {$s("table_name_values")}.{$s("column_values_reading_id")}
|
||||||
|
WHERE
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_device_id")} = :device_id AND
|
||||||
|
COALESCE(
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
|
||||||
|
) >= :start_datetime AND
|
||||||
|
COALESCE(
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
|
||||||
|
) <= :end_datetime
|
||||||
|
GROUP BY CEIL(UNIX_TIMESTAMP(COALESCE(
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
|
||||||
|
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
|
||||||
|
)) / :average_seconds);", [
|
||||||
|
"device_id" => $device_id,
|
||||||
|
"start_datetime" => $start->format(\DateTime::ISO8601),
|
||||||
|
"end_datetime" => $end->format(\DateTime::ISO8601),
|
||||||
|
"average_seconds" => $average_seconds
|
||||||
|
]
|
||||||
|
)->fetchAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue