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

View file

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

View file

@ -140,7 +140,7 @@ public class HalServer {
http.setPage("/", new HttpRedirectPage("/map")); http.setPage("/", new HttpRedirectPage("/map"));
http.setPage(HalAlertManager.getInstance().getUrl(), HalAlertManager.getInstance()); 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()); registerPage(it.next());
for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalWebPage.class); it.hasNext(); ) for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalWebPage.class); it.hasNext(); )
registerPage(it.next()); 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("id", getId());
deviceNode.set("name", getName()); deviceNode.set("name", getName());
deviceNode.set("user", getUser().getUsername()); deviceNode.set("user", getUser().getUsername());
deviceNode.set("x", getX()); deviceNode.set("map_x", getX());
deviceNode.set("y", getY()); deviceNode.set("map_y", getY());
if (getDeviceConfig() != null) { if (getDeviceConfig() != null) {
DataNode configNode = deviceNode.set("config", DataNode.DataType.Map); 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()); configNode.set("typeData", getDeviceConfig().getDeviceDataClass().getSimpleName());
for (Configurator.ConfigurationParam param : getDeviceConfigurator().getConfiguration()) { for (Configurator.ConfigurationParam param : getDeviceConfigurator().getConfiguration()) {

View file

@ -15,10 +15,10 @@ import java.util.logging.Logger;
/** /**
* A interface defining a Hal json endpoint * 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(); private static final Logger logger = LogUtil.getLogger();
public HalJsonPage(String id) { public HalApiEndpoint(String id) {
super(id); super(id);
} }

View file

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

View file

@ -2,7 +2,7 @@ package se.hal.page.api;
import se.hal.HalContext; import se.hal.HalContext;
import se.hal.intf.HalAbstractDevice; import se.hal.intf.HalAbstractDevice;
import se.hal.intf.HalJsonPage; import se.hal.intf.HalApiEndpoint;
import se.hal.struct.Event; import se.hal.struct.Event;
import se.hal.struct.Sensor; import se.hal.struct.Sensor;
import zutil.db.DBConnection; 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? * 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(); private static final Logger logger = LogUtil.getLogger();
public MapJsonPage() { public MapApiEndpoint() {
super("api/map"); super("api/map");
} }

View file

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

View file

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