Datos no disponibles.
;
return (
- Datos no disponibles.
;
const CardsData = [
- { id: 1, title: "Temperatura", content: "N/A", status: "Esperando datos...", titleIcon: '🌡 ' },
- { id: 2, title: "Humedad", content: "N/A", status: "Esperando datos...", titleIcon: '💦 ' },
- { id: 3, title: "Presión", content: "N/A", status: "Esperando datos...", titleIcon: '⏲ ' },
- { id: 4, title: "Nivel de CO", content: "N/A", status: "Esperando datos...", titleIcon: '☁ ' }
+ {
+ id: 1,
+ title: "Temperatura",
+ content: "N/A",
+ status: "Esperando datos...",
+ titleIcon: '🌡 ',
+ className: "col-12 col-md-6 col-lg-3",
+ link: false,
+ text: true
+ },
+
+ {
+ id: 2,
+ title: "Humedad",
+ content: "N/A",
+ status: "Esperando datos...",
+ titleIcon: '💦 ',
+ className: "col-12 col-md-6 col-lg-3",
+ link: false,
+ text: true
+ },
+
+ {
+ id: 3,
+ title: "Presión",
+ content: "N/A",
+ status: "Esperando datos...",
+ titleIcon: '⏲ ',
+ className: "col-12 col-md-6 col-lg-3",
+ link: false,
+ text: true
+ },
+
+ {
+ id: 4,
+ title: "Nivel de CO",
+ content: "N/A",
+ status: "Esperando datos...",
+ titleIcon: '☁ ',
+ className: "col-12 col-md-6 col-lg-3",
+ link: false,
+ text: true
+ }
];
+
if (data) {
let coData = data[2];
let tempData = data[1];
@@ -61,7 +101,7 @@ const SummaryCardsContent = () => {
}
return (
-
-
-
- {titleIcon}
- {shortTitle}
-
-
{children}
- {status ?
{status} : null}
+
+ {titleIcon}
+ {shortTitle}
+
+
+
+ {marquee ? (
+
+ ) : text ? (
+
{children}
+ ) : (
+
{children}
+ )}
+
+
+ {status &&
{status}}
);
+
+ return link && to
+ ?
{cardContent}
+ : cardContent;
};
Card.propTypes = {
title: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
- styleMode: PropTypes.oneOf(["override", ""]),
- className: PropTypes.string,
+ styleMode: PropTypes.oneOf(["override", ""]),
+ className: PropTypes.string,
titleIcon: PropTypes.node,
style: PropTypes.object,
+ link: PropTypes.bool,
+ to: PropTypes.string,
+ text: PropTypes.bool,
};
Card.defaultProps = {
styleMode: "",
+ className: "",
+ style: {},
+ link: false,
+ to: "",
+ text: false,
};
export default Card;
diff --git a/frontend/src/components/layout/CardContainer.jsx b/frontend/src/components/layout/CardContainer.jsx
index 1e5253c..5f369ee 100644
--- a/frontend/src/components/layout/CardContainer.jsx
+++ b/frontend/src/components/layout/CardContainer.jsx
@@ -1,41 +1,33 @@
import Card from "./Card.jsx";
import PropTypes from "prop-types";
-import { Link } from "react-router-dom";
-const CardContainer = ({ links, cards, className, text }) => {
+const CardContainer = ({ cards, className }) => {
return (
-
+
{cards.map((card, index) => (
- links ? (
-
-
- {text
- ? {card.content}
- : {card.content}
- }
-
-
- ) : (
-
- {text
- ? {card.content}
- : {card.content}
- }
+
+
+ {card.content}
- )
+
))}
);
};
CardContainer.propTypes = {
- links: Boolean,
- text: Boolean,
cards: PropTypes.arrayOf(
PropTypes.shape({
title: PropTypes.string.isRequired,
content: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
+ className: PropTypes.string,
+ styleMode: PropTypes.string,
+ style: PropTypes.object,
+ titleIcon: PropTypes.node,
+ link: PropTypes.bool,
+ to: PropTypes.string,
+ text: PropTypes.bool,
})
).isRequired,
className: PropTypes.string,
diff --git a/frontend/src/components/layout/Header.jsx b/frontend/src/components/layout/Header.jsx
index 51ea32d..d0dfab0 100644
--- a/frontend/src/components/layout/Header.jsx
+++ b/frontend/src/components/layout/Header.jsx
@@ -7,17 +7,13 @@ const Header = ({ subtitle }) => {
const { theme } = useTheme();
return (
-
+
{subtitle}
- {/* */}
);
}
diff --git a/frontend/src/css/Card.css b/frontend/src/css/Card.css
index 90c96cc..eae7d0e 100644
--- a/frontend/src/css/Card.css
+++ b/frontend/src/css/Card.css
@@ -46,6 +46,14 @@
margin-right: 10px;
}
+.card.led marquee > p.card-text {
+ font-family: "LEDBOARD" !important;
+ color: rgb(38, 60, 229) !important;
+ font-size: 2.5em !important;
+ text-transform: uppercase !important;
+ letter-spacing: 1px !important;
+}
+
p.card-text {
font-size: 2.2em;
font-weight: 600;
diff --git a/frontend/src/css/Header.css b/frontend/src/css/Header.css
index f30a968..f35991c 100644
--- a/frontend/src/css/Header.css
+++ b/frontend/src/css/Header.css
@@ -34,6 +34,21 @@ header > .subtitle {
animation: fadeIn 2s ease-in-out;
}
+.animated-header {
+ animation: pulseHeader 6s ease-in-out infinite;
+}
+
+@keyframes pulseHeader {
+ 0%, 100% {
+ opacity: 0.96;
+ transform: translateY(3px);
+ }
+ 50% {
+ opacity: 1;
+ transform: translateY(-3px);
+ }
+}
+
@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
diff --git a/frontend/src/css/index.css b/frontend/src/css/index.css
index 2cd4958..e1f0169 100644
--- a/frontend/src/css/index.css
+++ b/frontend/src/css/index.css
@@ -12,42 +12,52 @@
--card-gradient-secondary: #353535;
}
-/* latin-ext */
@font-face {
- font-family: 'Poppins';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/poppins/v22/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format('woff2');
- unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ font-family: "Open Sans";
+ src: url('/fonts/OpenSans.ttf');
}
-/* latin */
@font-face {
- font-family: 'Poppins';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/poppins/v22/pxiEyp8kv8JHgFVrJJfecg.woff2) format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Product Sans";
+ src: url('/fonts/ProductSansRegular.ttf');
}
-/* latin-ext */
@font-face {
- font-family: 'Poppins';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/poppins/v22/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format('woff2');
- unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+ font-family: "Product Sans Italic";
+ src: url('/fonts/ProductSansItalic.ttf');
}
-/* latin */
@font-face {
- font-family: 'Poppins';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: url(https://fonts.gstatic.com/s/poppins/v22/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format('woff2');
- unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ font-family: "Product Sans Italic Bold";
+ src: url('/fonts/ProductSansBoldItalic.ttf');
}
+
+@font-face {
+ font-family: "Product Sans Bold";
+ src: url('/fonts/ProductSansBold.ttf');
+}
+
+@font-face {
+ font-family: "LEDBOARD";
+ src: url('/fonts/LEDBOARD.ttf');
+}
+
+/* Tipografía global */
+div,
+label,
+input,
+p,
+span,
+a,
+button {
+ font-family: "Open Sans", sans-serif;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Product Sans", sans-serif;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx
index f5ca237..6c50e2e 100644
--- a/frontend/src/pages/Dashboard.jsx
+++ b/frontend/src/pages/Dashboard.jsx
@@ -8,7 +8,7 @@ const Dashboard = () => {
const { groupId, deviceId } = useParams();
return (
-
+
diff --git a/frontend/src/pages/GroupView.jsx b/frontend/src/pages/GroupView.jsx
index ee7b6c4..1f85910 100644
--- a/frontend/src/pages/GroupView.jsx
+++ b/frontend/src/pages/GroupView.jsx
@@ -9,8 +9,10 @@ import { useEffect, useState } from "react";
import { DataProvider } from "@/context/DataContext";
import { MapContainer, TileLayer, Marker } from 'react-leaflet';
-import L from 'leaflet';
+import L, { map } from 'leaflet';
import 'leaflet/dist/leaflet.css';
+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)
const markerIcon = new L.Icon({
@@ -19,6 +21,7 @@ const markerIcon = new L.Icon({
iconAnchor: [12, 41],
});
+
const MiniMap = ({ lat, lon }) => (
(
);
+MiniMap.propTypes = {
+ lat: PropTypes.number.isRequired,
+ lon: PropTypes.number.isRequired,
+};
+
const GroupView = () => {
const { groupId } = useParams();
const { config, configLoading } = useConfig();
@@ -91,21 +99,20 @@ const GroupViewContent = () => {
return (
{
const latest = latestData[device.deviceId];
const gpsSensor = latest?.data[0];
- const mapPreview = gpsSensor?.lat && gpsSensor?.lon
- ?
- : "Sin posición";
+ const mapPreview = ;
return {
title: device.deviceName,
status: `ID: ${device.deviceId}`,
- content: mapPreview,
+ link: gpsSensor != undefined,
+ text: gpsSensor == undefined,
+ marquee: gpsSensor == undefined,
+ content: gpsSensor == undefined ? "SOLO VEHICULOS ELECTRICOS" : mapPreview,
to: `/groups/${groupId}/devices/${device.deviceId}`,
- styleMode: "override",
- className: "col-12 col-md-6 col-lg-4"
+ className: `col-12 col-md-6 col-lg-4 ${gpsSensor == undefined ? "led" : ""}`,
};
})}
diff --git a/frontend/src/pages/Groups.jsx b/frontend/src/pages/Groups.jsx
index 6454032..591d46a 100644
--- a/frontend/src/pages/Groups.jsx
+++ b/frontend/src/pages/Groups.jsx
@@ -60,17 +60,16 @@ const GroupsContent = ({ config }) => {
return (
{
const groupDevices = devices[group.groupId]?.data;
const deviceCount = groupDevices?.length;
return {
title: group.groupName,
+ link: true,
+ text: true,
status: `ID: ${group.groupId}`,
to: `/groups/${group.groupId}`,
- styleMode: "override",
content: deviceCount != null
? (deviceCount === 1 ? "1 dispositivo" : `${deviceCount} dispositivos`)
: "Cargando dispositivos...",
diff --git a/hardware/include/WifiConnection.hpp b/hardware/include/WifiConnection.hpp
index ace4661..35b1f7e 100644
--- a/hardware/include/WifiConnection.hpp
+++ b/hardware/include/WifiConnection.hpp
@@ -3,7 +3,7 @@
#include
#include
-#define SSID "iPhone de Álvaro"
-#define WIFI_PASSWORD "alvarito123"
+#define SSID "DIGIFIBRA-D2ys"
+#define WIFI_PASSWORD "4EEATsyTcZ"
-int setupWifi();
\ No newline at end of file
+int WiFi_Init();
\ No newline at end of file
diff --git a/hardware/include/globals.hpp b/hardware/include/globals.hpp
index bfe14bf..15968d5 100644
--- a/hardware/include/globals.hpp
+++ b/hardware/include/globals.hpp
@@ -10,7 +10,13 @@
#define GPS_ID 1
#define MAX7219_ID 1
+#define ECO "Solo vehiculos electricos/hibridos"
+#define ALL "Todo tipo de vehiculos"
+
#define DEBUG
+#define SENSOR 0
+#define ACTUATOR 1
+
extern const uint32_t DEVICE_ID;
extern const int GROUP_ID;
\ No newline at end of file
diff --git a/hardware/include/main.hpp b/hardware/include/main.hpp
index 0fe0e85..7fa4211 100644
--- a/hardware/include/main.hpp
+++ b/hardware/include/main.hpp
@@ -1,14 +1,29 @@
#pragma once
#include "globals.hpp"
+
+#define DEVICE_ROLE SENSOR // se cambia entre SENSOR y ACTUATOR
+
+#if DEVICE_ROLE == SENSOR
+ #warning "Compilando firmware para SENSOR"
+#elif DEVICE_ROLE == ACTUATOR
+ #warning "Compilando firmware para ACTUATOR"
+#else
+ #warning "DEVICE_ROLE no definido correctamente"
+#endif
+
#include "JsonTools.hpp"
#include "RestClient.hpp"
#include "WifiConnection.hpp"
#include "MqttClient.hpp"
+#if DEVICE_ROLE == SENSOR
#include "BME280.hpp"
#include "GPS.hpp"
-#include "MAX7219.hpp"
#include "MQ7v2.hpp"
+#endif
+#if DEVICE_ROLE == ACTUATOR
+#include "MAX7219.hpp"
+#endif
struct TaskTimer
{
diff --git a/hardware/src/lib/inet/MqttClient.cpp b/hardware/src/lib/inet/MqttClient.cpp
index f7c9af3..a1982ce 100644
--- a/hardware/src/lib/inet/MqttClient.cpp
+++ b/hardware/src/lib/inet/MqttClient.cpp
@@ -20,14 +20,16 @@ void MQTT_OnReceived(char *topic, byte *payload, unsigned int length)
content.concat((char)payload[i]);
}
+#if DEVICE_ROLE == ACTUATOR
if(content == "ECO")
{
- currentMessage = "Solo vehiculos electricos/hibridos";
+ currentMessage = ECO;
}
else
{
- currentMessage = "Todo tipo de vehiculos";
+ currentMessage = ALL;
}
+#endif
}
void MQTT_Init(const char *MQTTServerAddress, uint16_t MQTTServerPort)
diff --git a/hardware/src/lib/inet/WifiConnection.cpp b/hardware/src/lib/inet/WifiConnection.cpp
index 627272b..010f9ed 100644
--- a/hardware/src/lib/inet/WifiConnection.cpp
+++ b/hardware/src/lib/inet/WifiConnection.cpp
@@ -34,7 +34,7 @@ void hueCycle(uint8_t pos)
setColor(r, g, b);
}
-int setupWifi()
+int WiFi_Init()
{
setupLED();
diff --git a/hardware/src/main.cpp b/hardware/src/main.cpp
index 2f80d0f..4dbce40 100644
--- a/hardware/src/main.cpp
+++ b/hardware/src/main.cpp
@@ -1,47 +1,67 @@
#include "main.hpp"
const uint32_t DEVICE_ID = getChipID();
+const String mqttId = "CUS-" + String(DEVICE_ID, HEX);
const int GROUP_ID = 1;
-const char *currentMessage = nullptr;
-const String id = "CUS-" + String(DEVICE_ID, HEX);
-TaskTimer matrixTimer{0, 25};
TaskTimer globalTimer{0, 60000};
TaskTimer mqttTimer{0, 5000};
+#if DEVICE_ROLE == ACTUATOR
+TaskTimer matrixTimer{0, 25};
+const char *currentMessage = ALL;
+extern MD_Parola display;
+#endif
+
extern HTTPClient httpClient;
String response;
-extern MD_Parola display;
+#if DEVICE_ROLE == SENSOR
MQ7Data_t mq7Data;
BME280Data_t bme280Data;
GPSData_t gpsData;
+#endif
void setup()
{
Serial.begin(115200);
+#ifdef DEBUG
Serial.println("Iniciando...");
+#endif
- setupWifi();
+ WiFi_Init();
MQTT_Init(MQTT_URI, MQTT_PORT);
- BME280_Init();
- Serial.println("Sensor BME280 inicializado");
- GPS_Init();
- Serial.println("Sensor GPS inicializado");
- MQ7_Init();
- Serial.println("Sensor MQ7 inicializado");
- MAX7219_Init();
- Serial.println("Display inicializado");
+ try
+ {
+
+#if DEVICE_ROLE == SENSOR
+ BME280_Init();
+ Serial.println("Sensor BME280 inicializado");
+ GPS_Init();
+ Serial.println("Sensor GPS inicializado");
+ MQ7_Init();
+ Serial.println("Sensor MQ7 inicializado");
+#endif
- writeMatrix(currentMessage);
+#if DEVICE_ROLE == ACTUATOR
+ MAX7219_Init();
+ Serial.println("Display inicializado");
+ writeMatrix(currentMessage);
+#endif
+ }
+ catch (const char *e)
+ {
+ Serial.println(e);
+ }
}
void loop()
{
uint32_t now = millis();
+#if DEVICE_ROLE == ACTUATOR
if (now - matrixTimer.lastRun >= matrixTimer.interval)
{
if (MAX7219_Animate())
@@ -50,9 +70,11 @@ void loop()
}
matrixTimer.lastRun = now;
}
+#endif
if (now - globalTimer.lastRun >= globalTimer.interval)
{
+#if DEVICE_ROLE == SENSOR
readBME280();
readGPS();
readMQ7();
@@ -62,17 +84,29 @@ void loop()
#endif
sendSensorData();
-
+#endif
globalTimer.lastRun = now;
}
if (now - mqttTimer.lastRun >= mqttTimer.interval)
{
- MQTT_Handle(id.c_str());
+ MQTT_Handle(mqttId.c_str());
mqttTimer.lastRun = now;
}
}
+#if DEVICE_ROLE == ACTUATOR
+void writeMatrix(const char *message)
+{
+#ifdef DEBUG
+ Serial.println("Escribiendo en el display...");
+#endif
+
+ MAX7219_DisplayText(message, PA_LEFT, 50, 0);
+}
+#endif
+
+#if DEVICE_ROLE == SENSOR
void readMQ7()
{
const float CO_THRESHOLD = 100.0f;
@@ -89,15 +123,6 @@ void readGPS()
gpsData = GPS_Read_Fake();
}
-void writeMatrix(const char *message)
-{
-#ifdef DEBUG
- Serial.println("Escribiendo en el display...");
-#endif
-
- MAX7219_DisplayText(message, PA_LEFT, 50, 0);
-}
-
void printAllData()
{
Serial.println("---------------------");
@@ -130,7 +155,6 @@ void sendSensorData()
{
const String deviceId = String(DEVICE_ID, HEX);
- // Validaciones básicas (puedes añadir más si quieres)
bool gpsValid = gpsData.lat != 0.0f && gpsData.lon != 0.0f;
bool weatherValid = bme280Data.temperature != 0.0f &&
bme280Data.humidity != 0.0f &&
@@ -156,10 +180,11 @@ void sendSensorData()
postRequest(String(API_URI) + "/batch", json, response);
#ifdef DEBUG
- Serial.println("📬 Respuesta del servidor:");
+ Serial.println("📥 Respuesta del servidor:");
Serial.println(response);
#endif
}
+#endif
uint32_t getChipID()
{