F I N
This commit is contained in:
@@ -10,8 +10,8 @@ public class Constants {
|
|||||||
public static final String CONTAMINUS_EB = "contaminus.eventbus";
|
public static final String CONTAMINUS_EB = "contaminus.eventbus";
|
||||||
public static Logger LOGGER = LoggerFactory.getLogger(Constants.APP_NAME);
|
public static Logger LOGGER = LoggerFactory.getLogger(Constants.APP_NAME);
|
||||||
|
|
||||||
public static final int SENSOR_ROLE = 0;
|
public static final Integer SENSOR_ROLE = 0;
|
||||||
public static final int ACTUATOR_ROLE = 1;
|
public static final Integer ACTUATOR_ROLE = 1;
|
||||||
|
|
||||||
/* API Endpoints */
|
/* API Endpoints */
|
||||||
public static final String GROUPS = RAW_API_PREFIX + "/groups"; // GET, POST
|
public static final String GROUPS = RAW_API_PREFIX + "/groups"; // GET, POST
|
||||||
@@ -33,8 +33,8 @@ public class Constants {
|
|||||||
public static final String ADD_CO_VALUE = RAW_API_PREFIX + "/groups/:groupId/devices/:deviceId/sensors/:sensorId/co_values"; // POST
|
public static final String ADD_CO_VALUE = RAW_API_PREFIX + "/groups/:groupId/devices/:deviceId/sensors/:sensorId/co_values"; // POST
|
||||||
|
|
||||||
public static final String ACTUATORS = RAW_API_PREFIX + "/groups/:groupId/devices/:deviceId/actuators"; // GET, POST
|
public static final String ACTUATORS = RAW_API_PREFIX + "/groups/:groupId/devices/:deviceId/actuators"; // GET, POST
|
||||||
public static final String ACTUATOR = RAW_API_PREFIX + "/groups/:groupId/devices/:deviceId/actuators/:actuator_id"; // GET, PUT
|
public static final String ACTUATOR = RAW_API_PREFIX + "/groups/:groupId/devices/:deviceId/actuators/:actuatorId"; // GET, PUT
|
||||||
public static final String ACTUATOR_STATUS = API_PREFIX + "/groups/:groupId/devices/:deviceId/actuators/:actuator_id/status"; // GET
|
public static final String ACTUATOR_STATUS = API_PREFIX + "/groups/:groupId/devices/:deviceId/actuators/:actuatorId/status"; // GET, PUT
|
||||||
|
|
||||||
public static final String VIEW_LATEST_VALUES = RAW_API_PREFIX + "/v_latest_values"; // GET
|
public static final String VIEW_LATEST_VALUES = RAW_API_PREFIX + "/v_latest_values"; // GET
|
||||||
public static final String VIEW_POLLUTION_MAP = RAW_API_PREFIX + "/v_pollution_map"; // GET
|
public static final String VIEW_POLLUTION_MAP = RAW_API_PREFIX + "/v_pollution_map"; // GET
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.locationtech.jts.geom.Geometry;
|
|||||||
import org.locationtech.jts.geom.GeometryFactory;
|
import org.locationtech.jts.geom.GeometryFactory;
|
||||||
import org.locationtech.jts.geom.Point;
|
import org.locationtech.jts.geom.Point;
|
||||||
import org.locationtech.jts.geom.Polygon;
|
import org.locationtech.jts.geom.Polygon;
|
||||||
|
import org.locationtech.jts.io.ParseException;
|
||||||
import org.locationtech.jts.io.geojson.GeoJsonReader;
|
import org.locationtech.jts.io.geojson.GeoJsonReader;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
@@ -36,9 +37,10 @@ public class VoronoiZoneDetector {
|
|||||||
private static final GeometryFactory geometryFactory = new GeometryFactory();
|
private static final GeometryFactory geometryFactory = new GeometryFactory();
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
public VoronoiZoneDetector(String geojsonUrl, boolean isUrl) throws Exception {
|
private VoronoiZoneDetector(String geojsonUrl, boolean isUrl) {
|
||||||
String geojsonStr;
|
String geojsonStr;
|
||||||
|
|
||||||
|
try {
|
||||||
if(isUrl) {
|
if(isUrl) {
|
||||||
try(InputStream is = URL.of(URI.create(geojsonUrl), null).openStream()) {
|
try(InputStream is = URL.of(URI.create(geojsonUrl), null).openStream()) {
|
||||||
geojsonStr = new String(is.readAllBytes());
|
geojsonStr = new String(is.readAllBytes());
|
||||||
@@ -46,6 +48,10 @@ public class VoronoiZoneDetector {
|
|||||||
} else {
|
} else {
|
||||||
geojsonStr = Files.readString(new File(geojsonUrl).toPath());
|
geojsonStr = Files.readString(new File(geojsonUrl).toPath());
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Constants.LOGGER.error("⚠️ Error al cargar el GeoJSON: " + e.getMessage());
|
||||||
|
throw new RuntimeException("Error al cargar el GeoJSON", e);
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject root = JsonParser.parseString(geojsonStr).getAsJsonObject();
|
JsonObject root = JsonParser.parseString(geojsonStr).getAsJsonObject();
|
||||||
JsonArray features = root.getAsJsonArray("features");
|
JsonArray features = root.getAsJsonArray("features");
|
||||||
@@ -62,7 +68,13 @@ public class VoronoiZoneDetector {
|
|||||||
JsonObject geometryJson = feature.getAsJsonObject("geometry");
|
JsonObject geometryJson = feature.getAsJsonObject("geometry");
|
||||||
String geometryStr = gson.toJson(geometryJson);
|
String geometryStr = gson.toJson(geometryJson);
|
||||||
|
|
||||||
Geometry geometry = reader.read(geometryStr);
|
Geometry geometry = null;
|
||||||
|
try {
|
||||||
|
geometry = reader.read(geometryStr);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
// TODO Auto-generated catch block
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
if (geometry instanceof Polygon polygon) {
|
if (geometry instanceof Polygon polygon) {
|
||||||
zones.add(new Zone(polygon, groupId));
|
zones.add(new Zone(polygon, groupId));
|
||||||
@@ -72,7 +84,11 @@ public class VoronoiZoneDetector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer getZoneForPoint(double lon, double lat) {
|
public static VoronoiZoneDetector create(String geojsonUrl, boolean isUrl) {
|
||||||
|
return new VoronoiZoneDetector(geojsonUrl, isUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getZoneForPoint(double lon, double lat) {
|
||||||
Point p = geometryFactory.createPoint(new Coordinate(lon, lat));
|
Point p = geometryFactory.createPoint(new Coordinate(lon, lat));
|
||||||
|
|
||||||
for (Zone z : zones) {
|
for (Zone z : zones) {
|
||||||
@@ -83,4 +99,18 @@ public class VoronoiZoneDetector {
|
|||||||
|
|
||||||
return null; // no está dentro de ninguna zona
|
return null; // no está dentro de ninguna zona
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
VoronoiZoneDetector detector = new VoronoiZoneDetector("https://miarma.net/files/voronoi_sevilla_geovoronoi.geojson", true);
|
||||||
|
|
||||||
|
double lon = -5.9752;
|
||||||
|
double lat = 37.3887;
|
||||||
|
|
||||||
|
Integer actuatorId = detector.getZoneForPoint(lon, lat);
|
||||||
|
if (actuatorId != null) {
|
||||||
|
System.out.println("📍 El punto pertenece al actuator: " + actuatorId);
|
||||||
|
} else {
|
||||||
|
System.out.println("🚫 El punto no pertenece a ninguna zona");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ public class ActuatorDAO implements DataAccessObject<Actuator, Integer>{
|
|||||||
public Future<Actuator> update(Actuator t) {
|
public Future<Actuator> update(Actuator t) {
|
||||||
Promise<Actuator> promise = Promise.promise();
|
Promise<Actuator> promise = Promise.promise();
|
||||||
String query = QueryBuilder.update(t).build();
|
String query = QueryBuilder.update(t).build();
|
||||||
|
System.out.println();
|
||||||
|
|
||||||
db.execute(query, Actuator.class,
|
db.execute(query, Actuator.class,
|
||||||
list -> promise.complete(list.isEmpty() ? null : list.get(0)),
|
list -> promise.complete(list.isEmpty() ? null : list.get(0)),
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ public class QueryBuilder {
|
|||||||
String fieldName = field.getName();
|
String fieldName = field.getName();
|
||||||
Object value = extractValue(fieldValue);
|
Object value = extractValue(fieldValue);
|
||||||
|
|
||||||
if (fieldName.endsWith("_id")) {
|
if (fieldName.endsWith("Id")) {
|
||||||
idField = field;
|
idField = field;
|
||||||
whereJoiner.add(fieldName + " = " + (value instanceof String
|
whereJoiner.add(fieldName + " = " + (value instanceof String
|
||||||
|| value instanceof LocalDateTime ? "'" + value + "'" : value));
|
|| value instanceof LocalDateTime ? "'" + value + "'" : value));
|
||||||
@@ -258,7 +258,7 @@ public class QueryBuilder {
|
|||||||
String fieldName = field.getName();
|
String fieldName = field.getName();
|
||||||
Object fieldValue = field.get(object);
|
Object fieldValue = field.get(object);
|
||||||
|
|
||||||
if (fieldName.endsWith("_id")) {
|
if (fieldName.endsWith("Id")) {
|
||||||
idField = field;
|
idField = field;
|
||||||
Object value = extractValue(fieldValue);
|
Object value = extractValue(fieldValue);
|
||||||
whereJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value));
|
whereJoiner.add(fieldName + " = " + (value instanceof String || value instanceof LocalDateTime ? "'" + value + "'" : value));
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ public class DataLayerAPIVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
deviceDAO.getByIdAndGroupId(deviceId, groupId).compose(device -> {
|
deviceDAO.getByIdAndGroupId(deviceId, groupId).compose(device -> {
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
return Future.succeededFuture(null);
|
return Future.failedFuture(new RuntimeException("Dispositivo no encontrado"));
|
||||||
}
|
}
|
||||||
return actuatorDAO.getByIdAndDeviceId(actuatorId, device.getDeviceId());
|
return actuatorDAO.getByIdAndDeviceId(actuatorId, device.getDeviceId());
|
||||||
}).onSuccess(actuator -> {
|
}).onSuccess(actuator -> {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import io.vertx.mqtt.MqttClientOptions;
|
|||||||
import net.miarma.contaminus.common.ConfigManager;
|
import net.miarma.contaminus.common.ConfigManager;
|
||||||
import net.miarma.contaminus.common.Constants;
|
import net.miarma.contaminus.common.Constants;
|
||||||
import net.miarma.contaminus.common.VoronoiZoneDetector;
|
import net.miarma.contaminus.common.VoronoiZoneDetector;
|
||||||
|
import net.miarma.contaminus.entities.Actuator;
|
||||||
import net.miarma.contaminus.entities.COValue;
|
import net.miarma.contaminus.entities.COValue;
|
||||||
import net.miarma.contaminus.entities.Device;
|
import net.miarma.contaminus.entities.Device;
|
||||||
import net.miarma.contaminus.entities.GpsValue;
|
import net.miarma.contaminus.entities.GpsValue;
|
||||||
@@ -41,6 +42,7 @@ public class LogicLayerAPIVerticle extends AbstractVerticle {
|
|||||||
private final Gson gson = new GsonBuilder().serializeNulls().create();
|
private final Gson gson = new GsonBuilder().serializeNulls().create();
|
||||||
private RestClientUtil restClient;
|
private RestClientUtil restClient;
|
||||||
private MqttClient mqttClient;
|
private MqttClient mqttClient;
|
||||||
|
private VoronoiZoneDetector detector;
|
||||||
|
|
||||||
public LogicLayerAPIVerticle() {
|
public LogicLayerAPIVerticle() {
|
||||||
this.configManager = ConfigManager.getInstance();
|
this.configManager = ConfigManager.getInstance();
|
||||||
@@ -53,6 +55,7 @@ public class LogicLayerAPIVerticle extends AbstractVerticle {
|
|||||||
.setUsername("contaminus")
|
.setUsername("contaminus")
|
||||||
.setPassword("contaminus")
|
.setPassword("contaminus")
|
||||||
);
|
);
|
||||||
|
this.detector = VoronoiZoneDetector.create("https://miarma.net/files/voronoi_sevilla_geovoronoi.geojson", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -76,6 +79,8 @@ public class LogicLayerAPIVerticle extends AbstractVerticle {
|
|||||||
router.route(HttpMethod.GET, Constants.POLLUTION_MAP).handler(this::getDevicePollutionMap);
|
router.route(HttpMethod.GET, Constants.POLLUTION_MAP).handler(this::getDevicePollutionMap);
|
||||||
router.route(HttpMethod.GET, Constants.HISTORY).handler(this::getDeviceHistory);
|
router.route(HttpMethod.GET, Constants.HISTORY).handler(this::getDeviceHistory);
|
||||||
router.route(HttpMethod.GET, Constants.SENSOR_VALUES).handler(this::getSensorValues);
|
router.route(HttpMethod.GET, Constants.SENSOR_VALUES).handler(this::getSensorValues);
|
||||||
|
router.route(HttpMethod.GET, Constants.ACTUATOR_STATUS).handler(this::getActuatorStatus);
|
||||||
|
router.route(HttpMethod.POST, Constants.ACTUATOR_STATUS).handler(this::postActuatorStatus);
|
||||||
|
|
||||||
mqttClient.connect(1883, "localhost", ar -> {
|
mqttClient.connect(1883, "localhost", ar -> {
|
||||||
if (ar.succeeded()) {
|
if (ar.succeeded()) {
|
||||||
@@ -152,80 +157,165 @@ public class LogicLayerAPIVerticle extends AbstractVerticle {
|
|||||||
|
|
||||||
private void addBatch(RoutingContext context) {
|
private void addBatch(RoutingContext context) {
|
||||||
JsonObject body = context.body().asJsonObject();
|
JsonObject body = context.body().asJsonObject();
|
||||||
if (body == null) {
|
|
||||||
context.response().setStatusCode(400).end("Missing JSON body");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String groupId = body.getString("groupId");
|
String groupId = body.getString("groupId");
|
||||||
String deviceId = body.getString("deviceId");
|
String deviceId = body.getString("deviceId");
|
||||||
|
|
||||||
JsonObject gps = body.getJsonObject("gps");
|
JsonObject gpsJson = body.getJsonObject("gps");
|
||||||
JsonObject weather = body.getJsonObject("weather");
|
JsonObject weatherJson = body.getJsonObject("weather");
|
||||||
JsonObject co = body.getJsonObject("co");
|
JsonObject coJson = body.getJsonObject("co");
|
||||||
|
|
||||||
if (deviceId == null || gps == null || weather == null || co == null) {
|
if (groupId == null || deviceId == null || gpsJson == null || weatherJson == null || coJson == null) {
|
||||||
context.response().setStatusCode(400).end("Missing required fields");
|
sendError(context, 400, "Missing required fields");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GpsValue gpsValue = gson.fromJson(gps.toString(), GpsValue.class);
|
GpsValue gpsValue = gson.fromJson(gpsJson.toString(), GpsValue.class);
|
||||||
WeatherValue weatherValue = gson.fromJson(weather.toString(), WeatherValue.class);
|
WeatherValue weatherValue = gson.fromJson(weatherJson.toString(), WeatherValue.class);
|
||||||
COValue coValue = gson.fromJson(co.toString(), COValue.class);
|
COValue coValue = gson.fromJson(coJson.toString(), COValue.class);
|
||||||
|
|
||||||
if(!VoronoiZoneDetector.getZoneForPoint(gpsValue.getLat(), gpsValue.getLon())
|
if (!isInCorrectZone(gpsValue, groupId)) {
|
||||||
.equals(Integer.valueOf(groupId))) {
|
sendZoneWarning(context);
|
||||||
Constants.LOGGER.info("El dispositivo no ha medido en su zona");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String host = "http://" + configManager.getHost();
|
handleActuators(groupId, coValue.getValue());
|
||||||
int port = configManager.getDataApiPort();
|
|
||||||
String gpsPath = Constants.ADD_GPS_VALUE.replace(":groupId", groupId).replace(":deviceId", deviceId);
|
|
||||||
String weatherPath = Constants.ADD_WEATHER_VALUE.replace(":groupId", groupId).replace(":deviceId", deviceId);
|
|
||||||
String coPath = Constants.ADD_CO_VALUE.replace(":groupId", groupId).replace(":deviceId", deviceId);
|
|
||||||
String devicesPath = Constants.DEVICES.replace(":groupId", groupId);
|
|
||||||
|
|
||||||
restClient.getRequest(port, host, devicesPath, Device[].class)
|
|
||||||
.onSuccess(ar -> {
|
|
||||||
Arrays.stream(ar)
|
|
||||||
.filter(d -> d.getDeviceRole().equals(Constants.ACTUATOR_ROLE))
|
|
||||||
.forEach(d -> {
|
|
||||||
float coAmount = coValue.getValue();
|
|
||||||
Constants.LOGGER.info("CO amount received: " + coAmount);
|
|
||||||
String topic = buildTopic(Integer.parseInt(groupId), d.getDeviceId(), "matrix");
|
|
||||||
Constants.LOGGER.info("Topic: " + topic);
|
|
||||||
if (mqttClient.isConnected()) {
|
|
||||||
Constants.LOGGER.info("🟢 Publishing to MQTT topic: " + topic + " with value: " + coAmount);
|
|
||||||
mqttClient.publish(topic, Buffer.buffer(coAmount >= 80.0f ? "ECO" : "GAS"),
|
|
||||||
MqttQoS.AT_LEAST_ONCE, false, false);
|
|
||||||
Constants.LOGGER.info("🟢 Message published to MQTT topic: " + topic);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
.onFailure(err -> {
|
|
||||||
context.fail(500, err);
|
|
||||||
});
|
|
||||||
|
|
||||||
gpsValue.setDeviceId(deviceId);
|
gpsValue.setDeviceId(deviceId);
|
||||||
weatherValue.setDeviceId(deviceId);
|
weatherValue.setDeviceId(deviceId);
|
||||||
coValue.setDeviceId(deviceId);
|
coValue.setDeviceId(deviceId);
|
||||||
|
|
||||||
restClient.postRequest(port, host, gpsPath, gpsValue, GpsValue.class)
|
storeMeasurements(context, groupId, deviceId, gpsValue, weatherValue, coValue);
|
||||||
.compose(_ -> restClient.postRequest(port, host, weatherPath, weatherValue, WeatherValue.class))
|
|
||||||
.compose(_ -> restClient.postRequest(port, host, coPath, coValue, COValue.class))
|
|
||||||
.onSuccess(_ -> {
|
|
||||||
context.response()
|
|
||||||
.setStatusCode(201)
|
|
||||||
.putHeader("Content-Type", "application/json")
|
|
||||||
.end(new JsonObject().put("status", "success").put("inserted", 3).encode());
|
|
||||||
})
|
|
||||||
.onFailure(err -> context.fail(500, err));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildTopic(int groupId, String deviceId, String topic)
|
private void getActuatorStatus(RoutingContext context) {
|
||||||
{
|
String groupId = context.request().getParam("groupId");
|
||||||
|
String deviceId = context.request().getParam("deviceId");
|
||||||
|
String actuatorId = context.request().getParam("actuatorId");
|
||||||
|
|
||||||
|
String host = "http://" + configManager.getHost();
|
||||||
|
int port = configManager.getDataApiPort();
|
||||||
|
String actuatorPath = Constants.ACTUATOR
|
||||||
|
.replace(":groupId", groupId)
|
||||||
|
.replace(":deviceId", deviceId)
|
||||||
|
.replace(":actuatorId", actuatorId);
|
||||||
|
|
||||||
|
restClient.getRequest(port, host, actuatorPath, Actuator.class)
|
||||||
|
.onSuccess(actuator -> {
|
||||||
|
String actuatorStatus = actuator.getStatus() == 0 ? "Solo vehiculos electricos/hibridos" : "Todo tipo de vehiculos";
|
||||||
|
|
||||||
|
context.response()
|
||||||
|
.setStatusCode(200)
|
||||||
|
.putHeader("Content-Type", "application/json")
|
||||||
|
.end(new JsonObject().put("status", "success").put("actuatorStatus", actuatorStatus).encode());
|
||||||
|
})
|
||||||
|
.onFailure(_ -> sendError(context, 500, "Failed to retrieve actuator status"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void postActuatorStatus(RoutingContext context) {
|
||||||
|
String groupId = context.request().getParam("groupId");
|
||||||
|
String deviceId = context.request().getParam("deviceId");
|
||||||
|
String actuatorId = context.request().getParam("actuatorId");
|
||||||
|
|
||||||
|
JsonObject body = context.body().asJsonObject();
|
||||||
|
String actuatorStatus = body.getString("status");
|
||||||
|
|
||||||
|
String host = "http://" + configManager.getHost();
|
||||||
|
int port = configManager.getDataApiPort();
|
||||||
|
String actuatorPath = Constants.ACTUATOR
|
||||||
|
.replace(":groupId", groupId)
|
||||||
|
.replace(":deviceId", deviceId)
|
||||||
|
.replace(":actuatorId", actuatorId);
|
||||||
|
|
||||||
|
Actuator updatedActuator = new Actuator(null, null, Integer.valueOf(actuatorStatus), null); // Assuming status 1 is the desired state
|
||||||
|
|
||||||
|
restClient.putRequest(port, host, actuatorPath, updatedActuator, Actuator.class)
|
||||||
|
.onSuccess(_ -> {
|
||||||
|
context.response()
|
||||||
|
.setStatusCode(200)
|
||||||
|
.putHeader("Content-Type", "application/json")
|
||||||
|
.end(new JsonObject().put("status", "success").put("message", "Actuator status updated").encode());
|
||||||
|
})
|
||||||
|
.onFailure(_ -> sendError(context, 500, "Failed to update actuator status"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendError(RoutingContext ctx, int status, String msg) {
|
||||||
|
ctx.response().setStatusCode(status).end(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInCorrectZone(GpsValue gps, String expectedZone) {
|
||||||
|
Integer actualZone = detector.getZoneForPoint(gps.getLon(), gps.getLat());
|
||||||
|
Constants.LOGGER.info(gps.getLat() + ", " + gps.getLon() + " -> Zone: " + actualZone);
|
||||||
|
return actualZone.equals(Integer.valueOf(expectedZone));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendZoneWarning(RoutingContext ctx) {
|
||||||
|
Constants.LOGGER.info("El dispositivo no ha medido en su zona");
|
||||||
|
ctx.response()
|
||||||
|
.setStatusCode(200)
|
||||||
|
.putHeader("Content-Type", "application/json")
|
||||||
|
.end(new JsonObject().put("status", "success").put("message", "Device did not measure in its zone").encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleActuators(String groupId, float coAmount) {
|
||||||
|
String host = "http://" + configManager.getHost();
|
||||||
|
int port = configManager.getDataApiPort();
|
||||||
|
String devicesPath = Constants.DEVICES.replace(":groupId", groupId);
|
||||||
|
|
||||||
|
restClient.getRequest(port, host, devicesPath, Device[].class)
|
||||||
|
.onSuccess(devices -> Arrays.stream(devices)
|
||||||
|
.filter(d -> Constants.ACTUATOR_ROLE.equals(d.getDeviceRole()))
|
||||||
|
.forEach(d -> {
|
||||||
|
String topic = buildTopic(Integer.parseInt(groupId), d.getDeviceId(), "matrix");
|
||||||
|
publishMQTT(topic, coAmount);
|
||||||
|
|
||||||
|
String actuatorsPath = Constants.ACTUATORS
|
||||||
|
.replace(":groupId", groupId)
|
||||||
|
.replace(":deviceId", d.getDeviceId());
|
||||||
|
|
||||||
|
restClient.getRequest(port, host, actuatorsPath, Actuator[].class)
|
||||||
|
.onSuccess(actuators -> Arrays.stream(actuators).forEach(a -> {
|
||||||
|
String actuatorPath = Constants.ACTUATOR
|
||||||
|
.replace(":groupId", groupId)
|
||||||
|
.replace(":deviceId", d.getDeviceId())
|
||||||
|
.replace(":actuatorId", String.valueOf(a.getActuatorId()));
|
||||||
|
Actuator updated = new Actuator(a.getActuatorId(), d.getDeviceId(), coAmount >= 80.0f ? 0 : 1, null);
|
||||||
|
restClient.putRequest(port, host, actuatorPath, updated, Actuator.class);
|
||||||
|
}))
|
||||||
|
.onFailure(err -> Constants.LOGGER.error("Failed to update actuator", err));
|
||||||
|
}))
|
||||||
|
.onFailure(err -> Constants.LOGGER.error("Failed to retrieve devices", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishMQTT(String topic, float coAmount) {
|
||||||
|
if (mqttClient.isConnected()) {
|
||||||
|
Constants.LOGGER.info("Publishing to MQTT topic: " + topic);
|
||||||
|
mqttClient.publish(topic, Buffer.buffer(coAmount >= 80.0f ? "ECO" : "GAS"),
|
||||||
|
MqttQoS.AT_LEAST_ONCE, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeMeasurements(RoutingContext ctx, String groupId, String deviceId,
|
||||||
|
GpsValue gps, WeatherValue weather, COValue co) {
|
||||||
|
|
||||||
|
String host = "http://" + configManager.getHost();
|
||||||
|
int port = configManager.getDataApiPort();
|
||||||
|
|
||||||
|
String gpsPath = Constants.ADD_GPS_VALUE.replace(":groupId", groupId).replace(":deviceId", deviceId);
|
||||||
|
String weatherPath = Constants.ADD_WEATHER_VALUE.replace(":groupId", groupId).replace(":deviceId", deviceId);
|
||||||
|
String coPath = Constants.ADD_CO_VALUE.replace(":groupId", groupId).replace(":deviceId", deviceId);
|
||||||
|
|
||||||
|
restClient.postRequest(port, host, gpsPath, gps, GpsValue.class)
|
||||||
|
.compose(_ -> restClient.postRequest(port, host, weatherPath, weather, WeatherValue.class))
|
||||||
|
.compose(_ -> restClient.postRequest(port, host, coPath, co, COValue.class))
|
||||||
|
.onSuccess(_ -> ctx.response()
|
||||||
|
.setStatusCode(201)
|
||||||
|
.putHeader("Content-Type", "application/json")
|
||||||
|
.end(new JsonObject().put("status", "success").put("inserted", 3).encode()))
|
||||||
|
.onFailure(err -> ctx.fail(500, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private String buildTopic(int groupId, String deviceId, String topic) {
|
||||||
String topicString = "group/" + groupId + "/device/" + deviceId + "/" + topic;
|
String topicString = "group/" + groupId + "/device/" + deviceId + "/" + topic;
|
||||||
return topicString;
|
return topicString;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ dp.poolSize=5
|
|||||||
|
|
||||||
# HTTP Server Configuration
|
# HTTP Server Configuration
|
||||||
inet.host=localhost
|
inet.host=localhost
|
||||||
|
mqtt.host=localhost
|
||||||
webserver.port=8080
|
webserver.port=8080
|
||||||
data-api.port=8081
|
data-api.port=8081
|
||||||
logic-api.port=8082
|
logic-api.port=8082
|
||||||
@@ -183,7 +183,7 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Operación exitosa
|
description: Operación exitosa
|
||||||
/api/raw/v1/groups/{groupId}/devices/{deviceId}/actuators/{actuator_id}:
|
/api/raw/v1/groups/{groupId}/devices/{deviceId}/actuators/{actuatorId}:
|
||||||
get:
|
get:
|
||||||
summary: Información de un actuador
|
summary: Información de un actuador
|
||||||
parameters:
|
parameters:
|
||||||
@@ -197,7 +197,7 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: actuator_id
|
- name: actuatorId
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
@@ -205,7 +205,7 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Operación exitosa
|
description: Operación exitosa
|
||||||
/api/v1/groups/{groupId}/devices/{deviceId}/actuators/{actuator_id}/status:
|
/api/v1/groups/{groupId}/devices/{deviceId}/actuators/{actuatorId}/status:
|
||||||
get:
|
get:
|
||||||
summary: Estado de un actuador
|
summary: Estado de un actuador
|
||||||
parameters:
|
parameters:
|
||||||
@@ -219,7 +219,7 @@ paths:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: actuator_id
|
- name: actuatorId
|
||||||
in: path
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
|
|||||||
@@ -28,10 +28,10 @@
|
|||||||
"GET_SENSOR_VALUES": "/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
|
"GET_SENSOR_VALUES": "/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
|
||||||
|
|
||||||
"GET_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
"GET_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
||||||
"GET_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
|
"GET_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuatorId",
|
||||||
"POST_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
"POST_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
||||||
"PUT_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
|
"PUT_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuatorId",
|
||||||
"GET_ACTUATOR_STATUS": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id/status",
|
"GET_ACTUATOR_STATUS": "/groups/:groupId/devices/:deviceId/actuators/:actuatorId/status",
|
||||||
|
|
||||||
"VIEW_LATEST_VALUES": "/v_latest_values",
|
"VIEW_LATEST_VALUES": "/v_latest_values",
|
||||||
"VIEW_POLLUTION_MAP": "/v_pollution_map",
|
"VIEW_POLLUTION_MAP": "/v_pollution_map",
|
||||||
|
|||||||
@@ -28,10 +28,10 @@
|
|||||||
"GET_SENSOR_VALUES": "/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
|
"GET_SENSOR_VALUES": "/groups/:groupId/devices/:deviceId/sensors/:sensorId/values",
|
||||||
|
|
||||||
"GET_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
"GET_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
||||||
"GET_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
|
"GET_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuatorId",
|
||||||
"POST_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
"POST_ACTUATORS": "/groups/:groupId/devices/:deviceId/actuators",
|
||||||
"PUT_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id",
|
"PUT_ACTUATOR_BY_ID": "/groups/:groupId/devices/:deviceId/actuators/:actuatorId",
|
||||||
"GET_ACTUATOR_STATUS": "/groups/:groupId/devices/:deviceId/actuators/:actuator_id/status",
|
"GET_ACTUATOR_STATUS": "/groups/:groupId/devices/:deviceId/actuators/:actuatorId/status",
|
||||||
|
|
||||||
"VIEW_LATEST_VALUES": "/v_latest_values",
|
"VIEW_LATEST_VALUES": "/v_latest_values",
|
||||||
"VIEW_POLLUTION_MAP": "/v_pollution_map",
|
"VIEW_POLLUTION_MAP": "/v_pollution_map",
|
||||||
|
|||||||
Binary file not shown.
@@ -41,7 +41,7 @@ const Card = ({
|
|||||||
const cardContent = (
|
const cardContent = (
|
||||||
<div
|
<div
|
||||||
ref={cardRef}
|
ref={cardRef}
|
||||||
className={`card p-3 w-100 ${theme} ${className ?? ""}`}
|
className={`card p-3 w-100 h-100 ${theme} ${className ?? ""}`}
|
||||||
style={styleMode === "override" ? style : {}}
|
style={styleMode === "override" ? style : {}}
|
||||||
>
|
>
|
||||||
<h3 className="text-center">
|
<h3 className="text-center">
|
||||||
@@ -49,18 +49,24 @@ const Card = ({
|
|||||||
{shortTitle}
|
{shortTitle}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="card-content">
|
|
||||||
{marquee ? (
|
{marquee && (
|
||||||
<marquee>
|
<div className="contenedor-con-efecto card-content rounded-4 h-100 d-flex align-items-center justify-content-center" style={{ backgroundColor: "black"}}>
|
||||||
<p className="card-text text-center">{children}</p>
|
<marquee scrollamount="30">
|
||||||
|
<p className="card-text text-center m-0">{children}</p>
|
||||||
</marquee>
|
</marquee>
|
||||||
) : text ? (
|
</div>
|
||||||
<p className="card-text text-center">{children}</p>
|
|
||||||
) : (
|
|
||||||
<div className="my-2">{children}</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!marquee && (
|
||||||
|
<div className="card-content h-100" >
|
||||||
|
{text ? (
|
||||||
|
<p className="card-text text-center">{children}</p>
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{status && <span className="status text-center mt-2">{status}</span>}
|
{status && <span className="status text-center mt-2">{status}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card.light > span.status {
|
.card.light > span.status {
|
||||||
background: #E0E0E0;
|
background: #D0D0D0;
|
||||||
|
color: #505050;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.dark {
|
.card.dark {
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
|
|
||||||
.card.dark > span.status {
|
.card.dark > span.status {
|
||||||
background: #505050;
|
background: #505050;
|
||||||
|
color: #D0D0D0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
.card:hover {
|
||||||
@@ -48,12 +50,20 @@
|
|||||||
|
|
||||||
.card.led marquee > p.card-text {
|
.card.led marquee > p.card-text {
|
||||||
font-family: "LEDBOARD" !important;
|
font-family: "LEDBOARD" !important;
|
||||||
color: rgb(38, 60, 229) !important;
|
color: #263ce5 !important;
|
||||||
font-size: 2.5em !important;
|
font-size: 6em !important;
|
||||||
text-transform: uppercase !important;
|
text-transform: uppercase !important;
|
||||||
letter-spacing: 1px !important;
|
letter-spacing: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contenedor-con-efecto {
|
||||||
|
background-color: #000; /* Fondo oscuro, similar al de la imagen */
|
||||||
|
background-image: radial-gradient(#313131aa 3px, transparent 0.5px); /* Puntos muy sutiles, casi invisibles */
|
||||||
|
background-size: 8px 8px; /* Controla la separación de los puntos */
|
||||||
|
/* Ajusta estos valores para cambiar el tamaño y la densidad de los puntos */
|
||||||
|
/* Los colores de los puntos pueden ser un azul muy oscuro, casi negro, o el mismo negro del fondo para un efecto muy sutil. */
|
||||||
|
}
|
||||||
|
|
||||||
p.card-text {
|
p.card-text {
|
||||||
font-size: 2.2em;
|
font-size: 2.2em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import { useEffect, useState } from "react";
|
|||||||
import { DataProvider } from "@/context/DataContext";
|
import { DataProvider } from "@/context/DataContext";
|
||||||
|
|
||||||
import { MapContainer, TileLayer, Marker } from 'react-leaflet';
|
import { MapContainer, TileLayer, Marker } from 'react-leaflet';
|
||||||
import L, { map } from 'leaflet';
|
import L from 'leaflet';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { text } from "@fortawesome/fontawesome-svg-core";
|
|
||||||
|
|
||||||
// Icono de marcador por defecto (porque Leaflet no lo carga bien en algunos setups)
|
// Icono de marcador por defecto (porque Leaflet no lo carga bien en algunos setups)
|
||||||
const markerIcon = new L.Icon({
|
const markerIcon = new L.Icon({
|
||||||
@@ -24,13 +23,15 @@ const markerIcon = new L.Icon({
|
|||||||
|
|
||||||
const MiniMap = ({ lat, lon }) => (
|
const MiniMap = ({ lat, lon }) => (
|
||||||
<MapContainer
|
<MapContainer
|
||||||
|
|
||||||
center={[lat, lon]}
|
center={[lat, lon]}
|
||||||
zoom={15}
|
zoom={15}
|
||||||
scrollWheelZoom={false}
|
scrollWheelZoom={false}
|
||||||
dragging={false}
|
dragging={false}
|
||||||
doubleClickZoom={false}
|
doubleClickZoom={false}
|
||||||
zoomControl={false}
|
zoomControl={false}
|
||||||
style={{ height: '150px', width: '100%', borderRadius: '10px' }}
|
className="rounded-4"
|
||||||
|
style={{ height: '180px', width: '100%'}}
|
||||||
>
|
>
|
||||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||||
<Marker position={[lat, lon]} icon={markerIcon} />
|
<Marker position={[lat, lon]} icon={markerIcon} />
|
||||||
@@ -65,9 +66,11 @@ const GroupViewContent = () => {
|
|||||||
const { data, dataLoading, dataError, getData } = useDataContext();
|
const { data, dataLoading, dataError, getData } = useDataContext();
|
||||||
const { groupId } = useParams();
|
const { groupId } = useParams();
|
||||||
const [latestData, setLatestData] = useState({});
|
const [latestData, setLatestData] = useState({});
|
||||||
|
const [actuatorStatus, setActuatorStatus] = useState({});
|
||||||
|
|
||||||
const { config } = useConfig(); // lo pillamos por si acaso
|
const { config } = useConfig(); // lo pillamos por si acaso
|
||||||
const latestValuesUrl = config.appConfig.endpoints.LOGIC_URL + config.appConfig.endpoints.GET_DEVICE_LATEST_VALUES;
|
const latestValuesUrl = config.appConfig.endpoints.LOGIC_URL + config.appConfig.endpoints.GET_DEVICE_LATEST_VALUES;
|
||||||
|
const actuatorStatusUrl = config.appConfig.endpoints.LOGIC_URL + config.appConfig.endpoints.GET_ACTUATOR_STATUS;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data || data.length === 0) return;
|
if (!data || data.length === 0) return;
|
||||||
@@ -94,6 +97,52 @@ const GroupViewContent = () => {
|
|||||||
fetchLatestData();
|
fetchLatestData();
|
||||||
}, [data, groupId]);
|
}, [data, groupId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data || data.length === 0) return;
|
||||||
|
|
||||||
|
const fetchActuatorStatus = async () => {
|
||||||
|
const statusByDevice = {};
|
||||||
|
|
||||||
|
await Promise.all(data.map(async device => {
|
||||||
|
const actuatorListUrl = config.appConfig.endpoints.DATA_URL +
|
||||||
|
config.appConfig.endpoints.GET_ACTUATORS
|
||||||
|
.replace(':groupId', groupId)
|
||||||
|
.replace(':deviceId', device.deviceId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getData(actuatorListUrl);
|
||||||
|
const actuators = Array.isArray(response?.data) ? response.data : [];
|
||||||
|
|
||||||
|
const statuses = await Promise.all(
|
||||||
|
actuators.map(async (actuator) => {
|
||||||
|
const statusUrl = actuatorStatusUrl
|
||||||
|
.replace(':groupId', groupId)
|
||||||
|
.replace(':deviceId', device.deviceId)
|
||||||
|
.replace(':actuatorId', actuator.actuatorId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const status = await getData(statusUrl); // { status: ... }
|
||||||
|
return { actuatorId: actuator.actuatorId, status };
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error al obtener status del actuator ${actuator.actuatorId}:`, err);
|
||||||
|
return { actuatorId: actuator.actuatorId, status: null };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
statusByDevice[device.deviceId] = statuses;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error al obtener actuadores de ${device.deviceId}:`, err);
|
||||||
|
statusByDevice[device.deviceId] = [];
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
setActuatorStatus(statusByDevice);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchActuatorStatus();
|
||||||
|
}, [data, groupId]);
|
||||||
|
|
||||||
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
if (dataLoading) return <p className="text-center my-5"><LoadingIcon /></p>;
|
||||||
if (dataError) return <p className="text-center my-5">Error al cargar datos: {dataError}</p>;
|
if (dataError) return <p className="text-center my-5">Error al cargar datos: {dataError}</p>;
|
||||||
|
|
||||||
@@ -104,13 +153,19 @@ const GroupViewContent = () => {
|
|||||||
const gpsSensor = latest?.data[0];
|
const gpsSensor = latest?.data[0];
|
||||||
const mapPreview = <MiniMap lat={gpsSensor?.lat} lon={gpsSensor?.lon} />;
|
const mapPreview = <MiniMap lat={gpsSensor?.lat} lon={gpsSensor?.lon} />;
|
||||||
|
|
||||||
|
const actuatorById = actuatorStatus[device.deviceId] || [];
|
||||||
|
const firstStatus = actuatorById.length > 0 ? actuatorById[0].status : null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: device.deviceName,
|
title: device.deviceName,
|
||||||
status: `ID: ${device.deviceId}`,
|
status: `ID: ${device.deviceId}`,
|
||||||
link: gpsSensor != undefined,
|
link: gpsSensor != undefined,
|
||||||
text: gpsSensor == undefined,
|
text: gpsSensor == undefined,
|
||||||
marquee: gpsSensor == undefined,
|
marquee: gpsSensor == undefined,
|
||||||
content: gpsSensor == undefined ? "TODO TIPO DE VEHICULOS" : mapPreview,
|
|
||||||
|
content: gpsSensor == undefined
|
||||||
|
? (firstStatus?.data?.actuatorStatus || 'Sin estado')
|
||||||
|
: mapPreview,
|
||||||
to: `/groups/${groupId}/devices/${device.deviceId}`,
|
to: `/groups/${groupId}/devices/${device.deviceId}`,
|
||||||
className: `col-12 col-md-6 col-lg-4 ${gpsSensor == undefined ? "led" : ""}`,
|
className: `col-12 col-md-6 col-lg-4 ${gpsSensor == undefined ? "led" : ""}`,
|
||||||
};
|
};
|
||||||
|
|||||||
4
hardware/.vscode/settings.json
vendored
4
hardware/.vscode/settings.json
vendored
@@ -16,7 +16,9 @@
|
|||||||
"string_view": "cpp",
|
"string_view": "cpp",
|
||||||
"initializer_list": "cpp",
|
"initializer_list": "cpp",
|
||||||
"system_error": "cpp",
|
"system_error": "cpp",
|
||||||
"cmath": "cpp"
|
"cmath": "cpp",
|
||||||
|
"random": "cpp",
|
||||||
|
"limits": "cpp"
|
||||||
},
|
},
|
||||||
"github.copilot.enable": {
|
"github.copilot.enable": {
|
||||||
"*": true,
|
"*": true,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "BME280.hpp"
|
#include "BME280.hpp"
|
||||||
#include "MQ7v2.hpp"
|
#include "MQ7v2.hpp"
|
||||||
#include "GPS.hpp"
|
#include "GPS.hpp"
|
||||||
|
#include "MAX7219.hpp"
|
||||||
|
|
||||||
String serializeSensorValue(
|
String serializeSensorValue(
|
||||||
int groupId,
|
int groupId,
|
||||||
@@ -16,12 +17,4 @@ String serializeSensorValue(
|
|||||||
const MQ7Data_t &mq7,
|
const MQ7Data_t &mq7,
|
||||||
const GPSData_t &gps);
|
const GPSData_t &gps);
|
||||||
|
|
||||||
String serializeActuatorStatus(
|
MAX7219Status_t deserializeActuatorStatus(HTTPClient &http, int httpResponseCode);
|
||||||
int actuatorId,
|
|
||||||
const String &deviceId,
|
|
||||||
int status,
|
|
||||||
long timestamp);
|
|
||||||
|
|
||||||
void deserializeSensorValue(HTTPClient &http, int httpResponseCode);
|
|
||||||
void deserializeActuatorStatus(HTTPClient &http, int httpResponseCode);
|
|
||||||
void deserializeDevice(HTTPClient &http, int httpResponseCode);
|
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
#define CS_PIN 18
|
#define CS_PIN 18
|
||||||
#define CLK_PIN 17
|
#define CLK_PIN 17
|
||||||
|
|
||||||
|
struct MAX7219Status_t
|
||||||
|
{
|
||||||
|
String status;
|
||||||
|
String actuatorStatus;
|
||||||
|
};
|
||||||
|
|
||||||
void MAX7219_Init();
|
void MAX7219_Init();
|
||||||
void MAX7219_DisplayText(const char *text, textPosition_t align, uint16_t speed, uint16_t pause);
|
void MAX7219_DisplayText(const char *text, textPosition_t align, uint16_t speed, uint16_t pause);
|
||||||
bool MAX7219_Animate();
|
bool MAX7219_Animate();
|
||||||
|
|||||||
@@ -15,3 +15,4 @@ struct MQ7Data_t
|
|||||||
|
|
||||||
void MQ7_Init();
|
void MQ7_Init();
|
||||||
MQ7Data_t MQ7_Read();
|
MQ7Data_t MQ7_Read();
|
||||||
|
MQ7Data_t MQ7_Read_Fake();
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <PubSubClient.h>
|
#include <PubSubClient.h>
|
||||||
|
#include "RestClient.hpp"
|
||||||
|
|
||||||
#define USER "contaminus"
|
#define USER "contaminus"
|
||||||
#define MQTT_PASSWORD "contaminus"
|
#define MQTT_PASSWORD "contaminus"
|
||||||
|
|||||||
@@ -4,3 +4,4 @@
|
|||||||
|
|
||||||
void getRequest(const String url, String &response);
|
void getRequest(const String url, String &response);
|
||||||
void postRequest(const String url, const String &payload, String &response);
|
void postRequest(const String url, const String &payload, String &response);
|
||||||
|
void putRequest(const String url, const String &payload, String &response);
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#define DEVICE_ROLE ACTUATOR // se cambia entre SENSOR y ACTUATOR
|
||||||
|
|
||||||
#define MQTT_URI "miarma.net"
|
#define MQTT_URI "miarma.net"
|
||||||
#define API_URI "https://contaminus.miarma.net/api/v1/"
|
#define API_URI "https://contaminus.miarma.net/api/v1/"
|
||||||
#define REST_PORT 443
|
#define REST_PORT 443
|
||||||
@@ -10,13 +12,11 @@
|
|||||||
#define GPS_ID 1
|
#define GPS_ID 1
|
||||||
#define MAX7219_ID 1
|
#define MAX7219_ID 1
|
||||||
|
|
||||||
#define ECO "Solo vehiculos electricos/hibridos"
|
|
||||||
#define ALL "Todo tipo de vehiculos"
|
|
||||||
|
|
||||||
#define DEBUG
|
|
||||||
|
|
||||||
#define SENSOR 0
|
#define SENSOR 0
|
||||||
#define ACTUATOR 1
|
#define ACTUATOR 1
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
extern const uint32_t DEVICE_ID;
|
extern const uint32_t DEVICE_ID;
|
||||||
extern const int GROUP_ID;
|
extern const int GROUP_ID;
|
||||||
|
extern String currentMessage;
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
#include "globals.hpp"
|
#include "globals.hpp"
|
||||||
|
|
||||||
#define DEVICE_ROLE SENSOR // se cambia entre SENSOR y ACTUATOR
|
|
||||||
|
|
||||||
#if DEVICE_ROLE == SENSOR
|
#if DEVICE_ROLE == SENSOR
|
||||||
#warning "Compilando firmware para SENSOR"
|
#warning "Compilando firmware para SENSOR"
|
||||||
#elif DEVICE_ROLE == ACTUATOR
|
#elif DEVICE_ROLE == ACTUATOR
|
||||||
|
|||||||
@@ -36,158 +36,53 @@ String serializeSensorValue(
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
String serializeActuatorStatus(const int actuatorId, const String &deviceId, const int status, const long timestamp)
|
MAX7219Status_t deserializeActuatorStatus(HTTPClient &http, int httpResponseCode)
|
||||||
{
|
|
||||||
DynamicJsonDocument doc(512);
|
|
||||||
|
|
||||||
doc["actuatorId"] = actuatorId;
|
|
||||||
doc["deviceId"] = deviceId;
|
|
||||||
doc["status"] = status;
|
|
||||||
doc["timestamp"] = timestamp;
|
|
||||||
|
|
||||||
String output;
|
|
||||||
serializeJson(doc, output);
|
|
||||||
Serial.println(output);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
String serializeDevice(const String &deviceId, int groupId, const String &deviceName)
|
|
||||||
{
|
|
||||||
DynamicJsonDocument doc(512);
|
|
||||||
|
|
||||||
doc["deviceId"] = deviceId;
|
|
||||||
doc["groupId"] = groupId;
|
|
||||||
doc["deviceName"] = deviceName;
|
|
||||||
|
|
||||||
String output;
|
|
||||||
serializeJson(doc, output);
|
|
||||||
Serial.println(output);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
void deserializeSensorValue(HTTPClient &http, int httpResponseCode)
|
|
||||||
{
|
{
|
||||||
if (httpResponseCode > 0)
|
if (httpResponseCode > 0)
|
||||||
{
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
Serial.print("HTTP Response code: ");
|
Serial.print("HTTP Response code: ");
|
||||||
Serial.println(httpResponseCode);
|
Serial.println(httpResponseCode);
|
||||||
String responseJson = http.getString();
|
#endif
|
||||||
|
|
||||||
DynamicJsonDocument doc(ESP.getMaxAllocHeap());
|
|
||||||
DeserializationError error = deserializeJson(doc, responseJson);
|
|
||||||
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
Serial.print(F("deserializeJson() failed: "));
|
|
||||||
Serial.println(error.f_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String groupId = doc["groupId"];
|
|
||||||
String deviceId = doc["deviceId"];
|
|
||||||
|
|
||||||
JsonObject gps = doc["gps"];
|
|
||||||
int gpsId = gps["sensorId"];
|
|
||||||
float lat = gps["lat"];
|
|
||||||
float lon = gps["lon"];
|
|
||||||
|
|
||||||
JsonObject weather = doc["weather"];
|
|
||||||
int weatherId = weather["sensorId"];
|
|
||||||
float temp = weather["temperature"];
|
|
||||||
float hum = weather["humidity"];
|
|
||||||
float pres = weather["pressure"];
|
|
||||||
|
|
||||||
JsonObject co = doc["co"];
|
|
||||||
int coId = co["sensorId"];
|
|
||||||
float coVal = co["value"];
|
|
||||||
|
|
||||||
Serial.println("🛰 GPS:");
|
|
||||||
Serial.printf(" Sensor ID: %d\n Lat: %.6f Lon: %.6f\n", gpsId, lat, lon);
|
|
||||||
|
|
||||||
Serial.println("🌤 Weather:");
|
|
||||||
Serial.printf(" Sensor ID: %d\n Temp: %.2f°C Hum: %.2f%% Pressure: %.2f hPa\n", weatherId, temp, hum, pres);
|
|
||||||
|
|
||||||
Serial.println("🧪 CO:");
|
|
||||||
Serial.printf(" Sensor ID: %d\n CO: %.2f ppm\n", coId, coVal);
|
|
||||||
|
|
||||||
Serial.printf("🧾 Group ID: %s\n", groupId.c_str());
|
|
||||||
Serial.printf("🧾 Device ID: %s\n", deviceId.c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.print("Error code: ");
|
|
||||||
Serial.println(httpResponseCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void deserializeActuatorStatus(HTTPClient &http, int httpResponseCode)
|
|
||||||
{
|
|
||||||
if (httpResponseCode > 0)
|
|
||||||
{
|
|
||||||
Serial.print("HTTP Response code: ");
|
|
||||||
Serial.println(httpResponseCode);
|
|
||||||
String responseJson = http.getString();
|
String responseJson = http.getString();
|
||||||
DynamicJsonDocument doc(ESP.getMaxAllocHeap());
|
DynamicJsonDocument doc(ESP.getMaxAllocHeap());
|
||||||
DeserializationError error = deserializeJson(doc, responseJson);
|
DeserializationError error = deserializeJson(doc, responseJson);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
Serial.print(F("deserializeJson() failed: "));
|
Serial.print(F("deserializeJson() failed: "));
|
||||||
Serial.println(error.f_str());
|
Serial.println(error.f_str());
|
||||||
return;
|
#endif
|
||||||
|
return {
|
||||||
|
.status = "error",
|
||||||
|
.actuatorStatus = "Error"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonArray array = doc.as<JsonArray>();
|
String status = doc["status"] | "error";
|
||||||
for (JsonObject actuator : array)
|
String actuatorStatus = doc["actuatorStatus"] | "Unknown";
|
||||||
{
|
|
||||||
int actuatorId = actuator["actuatorId"];
|
|
||||||
String deviceId = actuator["deviceId"];
|
|
||||||
int status = actuator["status"];
|
|
||||||
long timestamp = actuator["timestamp"];
|
|
||||||
|
|
||||||
Serial.println("Actuator deserialized:");
|
#ifdef DEBUG
|
||||||
Serial.printf(" ID: %d\n Device: %s\n Status: %d\n Time: %ld\n\n",
|
Serial.println("Actuator status deserialized:");
|
||||||
actuatorId, deviceId.c_str(), status, timestamp);
|
Serial.printf(" Status: %s\n Actuator Status: %s\n\n", status.c_str(), actuatorStatus.c_str());
|
||||||
}
|
#endif
|
||||||
}
|
|
||||||
else
|
return {
|
||||||
{
|
.status = status,
|
||||||
Serial.print("Error code: ");
|
.actuatorStatus = actuatorStatus
|
||||||
Serial.println(httpResponseCode);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void deserializeDevice(HTTPClient &http, int httpResponseCode)
|
|
||||||
{
|
|
||||||
if (httpResponseCode > 0)
|
|
||||||
{
|
|
||||||
Serial.print("HTTP Response code: ");
|
|
||||||
Serial.println(httpResponseCode);
|
|
||||||
String responseJson = http.getString();
|
|
||||||
DynamicJsonDocument doc(ESP.getMaxAllocHeap());
|
|
||||||
DeserializationError error = deserializeJson(doc, responseJson);
|
|
||||||
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
Serial.print(F("deserializeJson() failed: "));
|
|
||||||
Serial.println(error.f_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonArray array = doc.as<JsonArray>();
|
|
||||||
for (JsonObject device : array)
|
|
||||||
{
|
|
||||||
String deviceId = device["deviceId"];
|
|
||||||
int groupId = device["groupId"];
|
|
||||||
String deviceName = device["deviceName"];
|
|
||||||
|
|
||||||
Serial.println("Device deserialized:");
|
|
||||||
Serial.printf(" ID: %s\n Group: %d\n Name: %s\n\n", deviceId.c_str(), groupId, deviceName.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
#ifdef DEBUG
|
||||||
Serial.print("Error code: ");
|
Serial.print("Error code: ");
|
||||||
Serial.println(httpResponseCode);
|
Serial.println(httpResponseCode);
|
||||||
|
#endif
|
||||||
|
return {
|
||||||
|
.status = "error",
|
||||||
|
.actuatorStatus = "HTTP error"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,3 +32,19 @@ void postRequest(const String url, const String &payload, String &response)
|
|||||||
}
|
}
|
||||||
httpClient.end();
|
httpClient.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void putRequest(const String url, const String &payload, String &response)
|
||||||
|
{
|
||||||
|
httpClient.begin(url);
|
||||||
|
httpClient.addHeader("Content-Type", "application/json");
|
||||||
|
int httpCode = httpClient.PUT(payload);
|
||||||
|
if (httpCode > 0)
|
||||||
|
{
|
||||||
|
response = httpClient.getString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response = "Error: " + String(httpCode);
|
||||||
|
}
|
||||||
|
httpClient.end();
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "MqttClient.hpp"
|
#include "MqttClient.hpp"
|
||||||
|
|
||||||
extern WiFiClient wifiClient;
|
extern WiFiClient wifiClient;
|
||||||
extern const char *currentMessage;
|
extern HTTPClient httpClient;
|
||||||
|
|
||||||
PubSubClient client(wifiClient);
|
PubSubClient client(wifiClient);
|
||||||
|
|
||||||
@@ -20,14 +20,20 @@ void MQTT_OnReceived(char *topic, byte *payload, unsigned int length)
|
|||||||
content.concat((char)payload[i]);
|
content.concat((char)payload[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.trim();
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
Serial.println(content);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if DEVICE_ROLE == ACTUATOR
|
#if DEVICE_ROLE == ACTUATOR
|
||||||
if(content == "ECO")
|
if (content.equals("ECO"))
|
||||||
{
|
{
|
||||||
currentMessage = ECO;
|
currentMessage = "Vehiculos electricos/hibridos";
|
||||||
}
|
}
|
||||||
else
|
else if (content.equals("GAS"))
|
||||||
{
|
{
|
||||||
currentMessage = ALL;
|
currentMessage = "Todo tipo de vehiculos";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -67,7 +73,7 @@ void MQTT_Handle(const char *MQTTClientName)
|
|||||||
client.loop();
|
client.loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
String buildTopic(int groupId, const String& deviceId, const String& topic)
|
String buildTopic(int groupId, const String &deviceId, const String &topic)
|
||||||
{
|
{
|
||||||
String topicString = "group/" + String(groupId) + "/device/" + deviceId + "/" + topic;
|
String topicString = "group/" + String(groupId) + "/device/" + deviceId + "/" + topic;
|
||||||
return topicString;
|
return topicString;
|
||||||
|
|||||||
@@ -29,5 +29,5 @@ GPSData_t GPS_Read()
|
|||||||
GPSData_t GPS_Read_Fake()
|
GPSData_t GPS_Read_Fake()
|
||||||
{
|
{
|
||||||
float rnd = random(-0.0005, 0.0005);
|
float rnd = random(-0.0005, 0.0005);
|
||||||
return {37.358201f + rnd, -5.986640f + rnd};
|
return {37.326371f + rnd, -5.966001f + rnd};
|
||||||
}
|
}
|
||||||
@@ -18,3 +18,19 @@ MQ7Data_t MQ7_Read()
|
|||||||
|
|
||||||
return {ppm, d0};
|
return {ppm, d0};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MQ7Data_t MQ7_Read_Fake()
|
||||||
|
{
|
||||||
|
float ppm;
|
||||||
|
bool d0;
|
||||||
|
|
||||||
|
if (random(0, 100) < 50) {
|
||||||
|
ppm = random(80, 500); // valores entre 101 y 500 ppm
|
||||||
|
d0 = true;
|
||||||
|
} else {
|
||||||
|
ppm = random(10, 79); // valores entre 10 y 99 ppm
|
||||||
|
d0 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ppm, d0};
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ TaskTimer mqttTimer{0, 5000};
|
|||||||
|
|
||||||
#if DEVICE_ROLE == ACTUATOR
|
#if DEVICE_ROLE == ACTUATOR
|
||||||
TaskTimer matrixTimer{0, 25};
|
TaskTimer matrixTimer{0, 25};
|
||||||
const char *currentMessage = ALL;
|
TaskTimer displayTimer{0, 1000};
|
||||||
|
String currentMessage = "";
|
||||||
|
String lastMessage = "";
|
||||||
extern MD_Parola display;
|
extern MD_Parola display;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -48,7 +50,12 @@ void setup()
|
|||||||
#if DEVICE_ROLE == ACTUATOR
|
#if DEVICE_ROLE == ACTUATOR
|
||||||
MAX7219_Init();
|
MAX7219_Init();
|
||||||
Serial.println("Display inicializado");
|
Serial.println("Display inicializado");
|
||||||
writeMatrix(currentMessage);
|
writeMatrix(currentMessage.c_str());
|
||||||
|
|
||||||
|
String url = String(API_URI) + "groups/" + GROUP_ID + "/devices/" + String(DEVICE_ID, HEX) + "/actuators/" + MAX7219_ID + "/status";
|
||||||
|
getRequest(url, response);
|
||||||
|
MAX7219Status_t statusData = deserializeActuatorStatus(httpClient, httpClient.GET());
|
||||||
|
currentMessage = statusData.actuatorStatus;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
catch (const char *e)
|
catch (const char *e)
|
||||||
@@ -70,6 +77,19 @@ void loop()
|
|||||||
}
|
}
|
||||||
matrixTimer.lastRun = now;
|
matrixTimer.lastRun = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (now - displayTimer.lastRun >= displayTimer.interval)
|
||||||
|
{
|
||||||
|
if (currentMessage != lastMessage)
|
||||||
|
{
|
||||||
|
writeMatrix(currentMessage.c_str());
|
||||||
|
lastMessage = currentMessage;
|
||||||
|
#ifdef DEBUG
|
||||||
|
Serial.println("Display actualizado");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
displayTimer.lastRun = now;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (now - globalTimer.lastRun >= globalTimer.interval)
|
if (now - globalTimer.lastRun >= globalTimer.interval)
|
||||||
@@ -99,9 +119,8 @@ void loop()
|
|||||||
void writeMatrix(const char *message)
|
void writeMatrix(const char *message)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
Serial.println("Escribiendo en el display...");
|
Serial.println("Escribiendo mensaje: "); Serial.print(message);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
MAX7219_DisplayText(message, PA_LEFT, 50, 0);
|
MAX7219_DisplayText(message, PA_LEFT, 50, 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -110,7 +129,7 @@ void writeMatrix(const char *message)
|
|||||||
void readMQ7()
|
void readMQ7()
|
||||||
{
|
{
|
||||||
const float CO_THRESHOLD = 100.0f;
|
const float CO_THRESHOLD = 100.0f;
|
||||||
mq7Data = MQ7_Read();
|
mq7Data = MQ7_Read_Fake();
|
||||||
}
|
}
|
||||||
|
|
||||||
void readBME280()
|
void readBME280()
|
||||||
|
|||||||
Reference in New Issue
Block a user