Fixed up the API and added API doc

This commit is contained in:
Ziver Koc 2023-01-05 22:24:47 +01:00
parent 253208b6f9
commit 0efa7320e3
12 changed files with 313 additions and 58 deletions

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Hal OpenAPI Documentation</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.15.3/swagger-ui.css">
<script src="https://unpkg.com/swagger-ui-dist@4.15.3/swagger-ui-bundle.js"></script>
<script>
function render() {
var ui = SwaggerUIBundle({
url: '/api/openapi.json',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
]
});
}
</script>
</head>
<body onload="render()">
<div id="swagger-ui"></div>
</body>
</html>

View file

@ -0,0 +1,189 @@
{
"components": {
"schemas": {
"sensorClass": {
"type": "object",
"properties": {
"data": {
"type": "object",
"$ref": "#/components/schemas/dataClass"
},
"name": {"type": "string"},
"id": {"type": "integer"},
"map_x": {"type": "number"},
"map_y": {"type": "number"},
"user": {"type": "string"},
"config": {
"type": "object",
"$ref": "#/components/schemas/configClass"
},
"aggregate": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "number"
}
},
"timestamps": {
"type": "array",
"items": {
"type": "integer"
}
}
}
}
}
},
"eventClass": {
"type": "object",
"properties": {
"data": {
"type": "object",
"$ref": "#/components/schemas/dataClass"
},
"name": {"type": "string"},
"id": {"type": "integer"},
"map_x": {"type": "number"},
"map_y": {"type": "number"},
"user": {"type": "string"},
"config": {
"type": "object",
"$ref": "#/components/schemas/configClass"
}
}
},
"configClass": {
"type": "object",
"properties": {
"typeConfig": {"type": "string"},
"typeData": {"type": "string"}
}
},
"dataClass": {
"type": "object",
"properties": {
"valueStr": {"type": "string"},
"value": {"type": "number"},
"timestamp": {"type": "integer"}
}
}
}
},
"servers": [
{
"description": "Hal Server",
"url": "/api"
}
],
"openapi": "3.0.1",
"paths": {
"/event": {
"get": {
"responses": {
"200": {
"description": "A successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"$ref": "#/components/schemas/eventClass"
}
}
}
}
},
"parameters": [
{
"schema": {
"type": "integer"
},
"in": "query",
"name": "id",
"required": false
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "typeConfig",
"required": false
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "typeData",
"required": false
}
]
}
},
"/sensor": {
"get": {
"responses": {
"200": {
"description": "A successful response.",
"content": {
"application/json": {
"schema": {
"type": "object",
"$ref": "#/components/schemas/sensorClass"
}
}
}
}
},
"parameters": [
{
"schema": {
"type": "integer"
},
"in": "query",
"name": "id",
"required": false
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "typeConfig",
"required": false
},
{
"schema": {
"type": "string"
},
"in": "query",
"name": "typeData",
"required": false
},
{
"schema": {
"type": "string",
"enum": [
"min",
"hour",
"day",
"week",
]
},
"in": "query",
"name": "aggregation",
"required": false
}
]
}
}
},
"info": {
"description": "This API allows developers and external tools to interface to Hal data and trigger different actions.",
"title": "Hal REST API",
"version": ""
}
}

View file

@ -93,8 +93,8 @@
<script>
$(function(){
createChart("#min-chart", "/api/sensor?aggr=minute&id={{sensor.getId()}}", 5*60*1000);
createChart("#hour-chart", "/api/sensor?aggr=hour&id={{sensor.getId()}}", 60*60*1000);
createChart("#week-chart", "/api/sensor?aggr=week&id={{sensor.getId()}}", 7*24*60*60*1000);
createChart("#min-chart", "/api/sensor?aggregation=minute&id={{sensor.getId()}}", 5*60*1000);
createChart("#hour-chart", "/api/sensor?aggregation=hour&id={{sensor.getId()}}", 60*60*1000);
createChart("#week-chart", "/api/sensor?aggregation=week&id={{sensor.getId()}}", 7*24*60*60*1000);
});
</script>

View file

@ -5,8 +5,8 @@
<title>HAL is initializing...</title>
<!-- Bootstrap core CSS -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/hal.css" rel="stylesheet">
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/hal.css" rel="stylesheet">
</head>
<body>

View file

@ -140,7 +140,7 @@ public class HalServer {
http.setPage("/", new HttpRedirectPage("/map"));
http.setPage(HalAlertManager.getInstance().getUrl(), HalAlertManager.getInstance());
for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalJsonPage.class); it.hasNext(); )
for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalApiEndpoint.class); it.hasNext(); )
registerPage(it.next());
for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalWebPage.class); it.hasNext(); )
registerPage(it.next());

View file

@ -224,12 +224,12 @@ public abstract class HalAbstractDevice<V extends HalAbstractDevice, C extends H
deviceNode.set("id", getId());
deviceNode.set("name", getName());
deviceNode.set("user", getUser().getUsername());
deviceNode.set("x", getX());
deviceNode.set("y", getY());
deviceNode.set("map_x", getX());
deviceNode.set("map_y", getY());
if (getDeviceConfig() != null) {
DataNode configNode = deviceNode.set("config", DataNode.DataType.Map);
configNode.set("type", getDeviceConfig().getClass().getSimpleName());
configNode.set("typeConfig", getDeviceConfig().getClass().getSimpleName());
configNode.set("typeData", getDeviceConfig().getDeviceDataClass().getSimpleName());
for (Configurator.ConfigurationParam param : getDeviceConfigurator().getConfiguration()) {

View file

@ -15,10 +15,10 @@ import java.util.logging.Logger;
/**
* A interface defining a Hal json endpoint
*/
public abstract class HalJsonPage extends HalWebPage {
public abstract class HalApiEndpoint extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
public HalJsonPage(String id) {
public HalApiEndpoint(String id) {
super(id);
}

View file

@ -1,7 +1,7 @@
package se.hal.page.api;
import se.hal.HalContext;
import se.hal.intf.HalJsonPage;
import se.hal.intf.HalApiEndpoint;
import se.hal.struct.Event;
import zutil.ArrayUtil;
import zutil.ObjectUtil;
@ -22,11 +22,11 @@ import java.util.logging.Logger;
* type: event data type name
* </pre>
*/
public class EventJsonPage extends HalJsonPage {
public class EventApiEndpoint extends HalApiEndpoint {
private static final Logger logger = LogUtil.getLogger();
public EventJsonPage() {
public EventApiEndpoint() {
super("api/event");
}
@ -39,31 +39,49 @@ public class EventJsonPage extends HalJsonPage {
DBConnection db = HalContext.getDB();
DataNode root = new DataNode(DataNode.DataType.List);
// Get Events
// --------------------------------------
// Get Action
// --------------------------------------
String[] req_ids = new String[0];
if (request.get("id") != null)
req_ids = request.get("id").split(",");
String req_type = request.get("type");
String req_typeConfig = request.get("typeConfig");
String req_typeData = request.get("typeData");
// Filter devices
List<Event> events = new ArrayList<>();
for (Event event : Event.getLocalEvents(db)) {
if (ArrayUtil.contains(req_ids, "" + event.getId())) { // id filtering
events.add(event);
boolean filter_match = true;
// id filtering
if (!ObjectUtil.isEmpty(req_ids) && !ArrayUtil.contains(req_ids, "" + event.getId())) {
filter_match = false;
}
if (!ObjectUtil.isEmpty(req_type) &&
event.getDeviceConfig().getDeviceDataClass().getSimpleName().contains(req_type)) { // device type filtering
events.add(event);
// device type filtering
if (!ObjectUtil.isEmpty(req_typeConfig) &&
!event.getDeviceConfig().getClass().getSimpleName().equals(req_typeConfig)) {
filter_match = false;
}
// no options defined, then add all events
if (ObjectUtil.isEmpty(req_ids, req_type)) {
// data type filtering
if (!ObjectUtil.isEmpty(req_typeData) &&
!event.getDeviceConfig().getDeviceDataClass().getSimpleName().equals(req_typeData)) {
filter_match = false;
}
// Check the filter
if (filter_match) {
events.add(event);
}
}
// --------------------------------------
// Generate DataNode
// --------------------------------------
for (Event event : events) {
DataNode deviceNode = event.getDataNode();

View file

@ -2,7 +2,7 @@ package se.hal.page.api;
import se.hal.HalContext;
import se.hal.intf.HalAbstractDevice;
import se.hal.intf.HalJsonPage;
import se.hal.intf.HalApiEndpoint;
import se.hal.struct.Event;
import se.hal.struct.Sensor;
import zutil.db.DBConnection;
@ -16,10 +16,10 @@ import java.util.logging.Logger;
/**
* TODO: This json endpoint might not be needed as we have SensorJsonPage?
*/
public class MapJsonPage extends HalJsonPage {
public class MapApiEndpoint extends HalApiEndpoint {
private static final Logger logger = LogUtil.getLogger();
public MapJsonPage() {
public MapApiEndpoint() {
super("api/map");
}

View file

@ -2,7 +2,7 @@ package se.hal.page.api;
import se.hal.HalContext;
import se.hal.daemon.SensorDataAggregatorDaemon;
import se.hal.intf.HalJsonPage;
import se.hal.intf.HalApiEndpoint;
import se.hal.struct.Sensor;
import se.hal.util.AggregateDataListSqlResult;
import se.hal.util.UTCTimeUtility;
@ -28,11 +28,11 @@ import java.util.logging.Logger;
* aggr: Aggregation periods, needs to be provided to retrieve data. Possible values: minute,hour,day,week
* </pre>
*/
public class SensorJsonPage extends HalJsonPage {
public class SensorApiEndpoint extends HalApiEndpoint {
private static final Logger logger = LogUtil.getLogger();
public SensorJsonPage() {
public SensorApiEndpoint() {
super("api/sensor");
}
@ -45,60 +45,83 @@ public class SensorJsonPage extends HalJsonPage {
DBConnection db = HalContext.getDB();
DataNode root = new DataNode(DataNode.DataType.List);
// Get sensors
String[] req_ids = new String[0];
// --------------------------------------
// Get Action
// --------------------------------------
String[] reqIds = new String[0];
if (request.get("id") != null)
req_ids = request.get("id").split(",");
String req_type = request.get("type");
reqIds = request.get("id").split(",");
String reqTypeConfig = request.get("typeConfig");
String reqTypeData = request.get("typeData");
List<Sensor> sensors = new ArrayList<>();
for (Sensor sensor : Sensor.getSensors(db)) {
if (ArrayUtil.contains(req_ids, "" + sensor.getId())) { // id filtering
sensors.add(sensor);
boolean filter_match = true;
// id filtering
if (!ObjectUtil.isEmpty(reqIds) && !ArrayUtil.contains(reqIds, "" + sensor.getId())) {
filter_match = false;
}
if (!ObjectUtil.isEmpty(req_type) &&
sensor.getDeviceConfig().getDeviceDataClass().getSimpleName().contains(req_type)) { // device type filtering
sensors.add(sensor);
// device type filtering
if (!ObjectUtil.isEmpty(reqTypeConfig) &&
!sensor.getDeviceConfig().getClass().getSimpleName().equals(reqTypeConfig)) {
filter_match = false;
}
// no options defined, then add all sensors
if (ObjectUtil.isEmpty(req_ids, req_type)) {
// data type filtering
if (!ObjectUtil.isEmpty(reqTypeData) &&
!sensor.getDeviceConfig().getDeviceDataClass().getSimpleName().equals(reqTypeData)) {
filter_match = false;
}
// Check the filter
if (filter_match) {
sensors.add(sensor);
}
}
// Figure out aggregation period
// --------------------------------------
// Was aggregated data requested
// --------------------------------------
SensorDataAggregatorDaemon.AggregationPeriodLength aggrType = null;
long aggrLength = -1;
if (request.get("aggr") != null) {
switch (request.get("aggr")) {
long aggregationLength = -1;
if (request.get("aggregation") != null) {
switch (request.get("aggregation")) {
case "minute":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.FIVE_MINUTES;
aggrLength = UTCTimeUtility.DAY_IN_MS;
aggregationLength = UTCTimeUtility.DAY_IN_MS;
break;
case "hour":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.HOUR;
aggrLength = UTCTimeUtility.WEEK_IN_MS;
aggregationLength = UTCTimeUtility.WEEK_IN_MS;
break;
case "day":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.DAY;
aggrLength = UTCTimeUtility.INFINITY;
aggregationLength = UTCTimeUtility.INFINITY;
break;
case "week":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.WEEK;
aggrLength = UTCTimeUtility.INFINITY;
aggregationLength = UTCTimeUtility.INFINITY;
break;
}
}
// --------------------------------------
// Generate DataNode
// --------------------------------------
for (Sensor sensor : sensors) {
DataNode deviceNode = sensor.getDataNode();
if (aggrLength > 0) {
DataNode aggregateNode = getAggregateDataNode(aggrLength,
AggregateDataListSqlResult.getAggregateDataForPeriod(db, sensor, aggrType, aggrLength));
if (aggregationLength > 0) {
DataNode aggregateNode = getAggregateDataNode(aggregationLength,
AggregateDataListSqlResult.getAggregateDataForPeriod(db, sensor, aggrType, aggregationLength));
deviceNode.set("aggregate", aggregateNode);
}

View file

@ -12,9 +12,9 @@
{"se.hal.intf.HalDaemon": "se.hal.daemon.SensorDataAggregatorDaemon"},
{"se.hal.intf.HalDaemon": "se.hal.daemon.SensorDataCleanupDaemon"},
{"se.hal.intf.HalJsonPage": "se.hal.page.api.MapJsonPage"},
{"se.hal.intf.HalJsonPage": "se.hal.page.api.EventJsonPage"},
{"se.hal.intf.HalJsonPage": "se.hal.page.api.SensorJsonPage"},
{"se.hal.intf.HalApiEndpoint": "se.hal.page.api.EventApiEndpoint"},
{"se.hal.intf.HalApiEndpoint": "se.hal.page.api.MapApiEndpoint"},
{"se.hal.intf.HalApiEndpoint": "se.hal.page.api.SensorApiEndpoint"},
{"se.hal.intf.HalWebPage": "se.hal.page.MapWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.SensorOverviewWebPage"},

View file

@ -20,10 +20,10 @@
<script>
$(function(){
createChart("#minute-power-chart", "/api/sensor?aggr=minute", 5*60*1000);
createChart("#hour-power-chart", "/api/sensor?aggr=hour", 60*60*1000);
createChart("#day-power-chart", "/api/sensor?aggr=day", 24*60*60*1000);
createChart("#week-power-chart", "/api/sensor?aggr=week", 7*24*60*60*1000);
createChart("#minute-power-chart", "/api/sensor?aggregation=minute", 5*60*1000);
createChart("#hour-power-chart", "/api/sensor?aggregation=hour", 60*60*1000);
createChart("#day-power-chart", "/api/sensor?aggregation=day", 24*60*60*1000);
createChart("#week-power-chart", "/api/sensor?aggregation=week", 7*24*60*60*1000);
});
</script>