Compare commits

...

2 Commits

Author SHA1 Message Date
e130b84d0b Instrucciones de ejecución 2025-04-08 18:47:07 +02:00
58b7e6ebae Versión Inicial 2025-04-08 18:38:27 +02:00
7 changed files with 583 additions and 0 deletions

View File

@@ -1,2 +1,39 @@
# CanSat # CanSat
Este documento describe el proceso para ejecutar el código que gestionará la recepción de datos del CanSat.
## Requisitos Previos
Es indispensable contar con [Node.js](https://nodejs.org/) instalado en el equipo. Se recomienda descargar la versión LTS (Long Term Support) para garantizar la estabilidad del proyecto.
## Verificación de la Instalación de Node.js
Para confirmar que Node.js y npm se han instalado correctamente, abra una terminal y ejecute los siguientes comandos:
```bash
node -v
npm -v
```
Si los comandos no muestran la versión correspondiente, es posible que la instalación no haya finalizado correctamente. En ese caso, consulte las [instrucciones oficiales de Node.js](https://nodejs.org/) para completar la instalación.
## Preparación del Código
1. Descargue el código del proyecto en formato ZIP desde el repositorio o, alternativamente, realice un clon mediante Git.
2. Una vez ubicado en la carpeta del proyecto, ejecute en la terminal el siguiente comando para instalar las dependencias:
```bash
npm install
```
## Ejecución del Código
Para iniciar la ejecución del programa, utilice el siguiente comando en la terminal:
```bash
node main.js
```
## Configuración
Antes de ejecutar la aplicación, asegúrese de configurar correctamente las opciones necesarias en el archivo `config.js`.

16
config.js Normal file
View File

@@ -0,0 +1,16 @@
export default {
// Configuración de ThingSpeak
"thingSpeak": {
"channelId": "",
"writeKey": ""
},
// Configuración del CanSat
"canSat": {
},
// Configuración del Serial
"serial": {
"path": "/dev/tty.usbserial-0001",
"baudRate": 9600
}
}

96
libs/api.js Normal file
View File

@@ -0,0 +1,96 @@
// Importamos las utilidades que necesitemos y la configuración del programa
import { sleep } from './utils.js';
import config from '../config.js';
// Creamos una lista de elementos que contendrá las peticiones acumuladas para enviarlas de golpe
const peticionesRetrasadas = [];
/**
* Añade el dato a la lista de peticiones en el formato que necesita la api
* @param {object} datos Objeto con los datos a subir
*/
async function subirDato(datos) {
if (datos === null || datos === undefined) {
return;
}
const datosParaEnviar = {
created_at: datos.datetime,
delta_t: 1,
field1: datos.paquetes,
field2: datos.temperatura,
field3: datos.presion,
field4: datos.altitudSegunPresion,
latitude: datos.latitud,
longitude: datos.longitud
};
//console.log(datosParaEnviar);
peticionesRetrasadas.push(datosParaEnviar);
}
/**
* Sube los datos de golpe al ThingSpeak
* @param {Array} datosParaSubir Array de objetos con los datos a subir
* @returns {Promise<boolean>} True si se subieron los datos correctamente, false si no
*/
async function subirDatosDeGolpe(datosParaSubir) {
//console.log("Subiendo datos de golpe...");
// Si no hay datos, no subimos nada
if(datosParaSubir.length === 0) {
return false;
}
// Preparamos los datos para subir añadiendo la apiKey
const datosParaEnviar = {
write_api_key: config.thingSpeak.writeKey,
updates: datosParaSubir
}
//console.log(datosParaEnviar);
// Hacemos la petición a la API de ThingSpeak
let resultado = false;
await fetch(`https://api.thingspeak.com/channels/${config.thingSpeak.channelId}/bulk_update.json`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(datosParaEnviar) // Convertimos el objeto a JSON para enviarlo
})
.then(response => response.json())
.then(data => {
//console.log(data);
resultado = true;
})
.catch(error => {
console.error('Error al subir los datos:', error);
})
return resultado;
}
async function bucleSubidaDatos() {
while(true) {
await sleep(20000);
console.log("Lista por subir: " + peticionesRetrasadas.length);
const datosQueSeVanASubir = JSON.parse(JSON.stringify(peticionesRetrasadas))
if (await subirDatosDeGolpe(datosQueSeVanASubir)) { // Con eso creamos una copia de los datos del array
if (peticionesRetrasadas.length == datosQueSeVanASubir.length) {
peticionesRetrasadas.length = 0;
} else {
peticionesRetrasadas.splice(0, datosQueSeVanASubir.length);
//console.log("Datos que se hubieran perdido: " + peticionesRetrasadas)
}
}
}
}
export {
subirDato
}
bucleSubidaDatos();

51
libs/utils.js Normal file
View File

@@ -0,0 +1,51 @@
/**
* Convierte una línea de datos en un objeto
* @param {string} line Línea de datos para convertir a objeto
* @returns {object} Objeto con los datos convertidos
*/
function dataLineToObject(line) {
//Separamos nuestra línea en diferentes trozos usando como separador el ';'
const lineArray = line.split(";");
//Devolvemos un objeto con sus propiedades convertidas
//Usamos el trim() para eliminar los espacios delante y detrás de los valores
//Usamos el parseFloat para convertir esos valores a decimales, ya que son cadenas de texto
//Usamos el parseInt para convertir esos valores a enteros, ya que son cadenas de texto
try {
return {
paquetes: parseInt(lineArray[0].trim()),
temperatura: parseFloat(lineArray[1].trim()),
presion: parseFloat(lineArray[2].trim()),
altitudSegunPresion: parseFloat(lineArray[3].trim()),
nombreEstacion: lineArray[4].trim(),
latitud: parseFloat(lineArray[5].trim()),
longitud: parseFloat(lineArray[6].trim()),
altitudMetros: parseFloat(lineArray[7].trim()),
velocidadKmph: parseFloat(lineArray[8].trim()),
direccionONose: parseFloat(lineArray[9].trim()),
numSatelites: parseInt(lineArray[10].trim()),
datetime: new Date(lineArray[11].trim().replace("Date/Time: ", ""))
};
} catch (error) {
console.error('Error al convertir los datos:', error);
return null;
}
}
/**
* Espera un cierto tiempo, esto sirve para crear temporizadores
* @param {number} ms Milisegundos que hay que esperar
* @returns {Promise<void>}
*/
async function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
// Exportamos las funciones para poder usarlas en otras partes del programa
export {
dataLineToObject,
sleep
}

38
main.js Normal file
View File

@@ -0,0 +1,38 @@
// Importamos las librerias necesarias
import { SerialPort } from 'serialport';
import { ReadlineParser } from '@serialport/parser-readline';
// Importamos las funciones de utilidad que vayamos a usar
import { dataLineToObject } from './libs/utils.js';
// Importamos las funciones de la API
import { subirDato } from './libs/api.js';
// Importamos toda la configuración
import config from './config.js';
// Creamos una conexión con el puerto serial
const port = new SerialPort({
path: config.serial.path,
baudRate: config.serial.baudRate,
autoOpen: true
});
// Le establecemos que procese los datos recibidos con el parser de readline y le decimos que diferencie las líneas por saltos de línea
const parser = port.pipe(new ReadlineParser({ delimiter: '\n' }));
// Esperamos a que se abra el puerto y cuando lo haga ejecutamos un console log para indicar que se ha abierto correctamente
port.on('open', () => {
console.log(`Puerto ${config.serial.path} conectado correctamente.`);
});
// Cada vez que recibamos un dato, le decimos al programa que lo procese sin bloquear la ejecución principal
parser.on('data', async (data) => {
// Convertimos la línea recibida en un objeto y lo mandamos a la api para que se encargue de la subida
subirDato(dataLineToObject(data));
});
// Si hay un error en el puerto, lo mostramos en la consola
port.on('error', (err) => {
console.error('Error en el puerto:', err.message);
});

328
package-lock.json generated Normal file
View File

@@ -0,0 +1,328 @@
{
"name": "cansat",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cansat",
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"@serialport/parser-readline": "^13.0.0",
"path": "^0.12.7",
"serialport": "^13.0.0"
}
},
"node_modules/@serialport/binding-mock": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz",
"integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==",
"license": "MIT",
"dependencies": {
"@serialport/bindings-interface": "^1.2.1",
"debug": "^4.3.3"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/@serialport/bindings-cpp": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-13.0.0.tgz",
"integrity": "sha512-r25o4Bk/vaO1LyUfY/ulR6hCg/aWiN6Wo2ljVlb4Pj5bqWGcSRC4Vse4a9AcapuAu/FeBzHCbKMvRQeCuKjzIQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@serialport/bindings-interface": "1.2.2",
"@serialport/parser-readline": "12.0.0",
"debug": "4.4.0",
"node-addon-api": "8.3.0",
"node-gyp-build": "4.8.4"
},
"engines": {
"node": ">=18.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz",
"integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz",
"integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==",
"license": "MIT",
"dependencies": {
"@serialport/parser-delimiter": "12.0.0"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/bindings-interface": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz",
"integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==",
"license": "MIT",
"engines": {
"node": "^12.22 || ^14.13 || >=16"
}
},
"node_modules/@serialport/parser-byte-length": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-13.0.0.tgz",
"integrity": "sha512-32yvqeTAqJzAEtX5zCrN1Mej56GJ5h/cVFsCDPbF9S1ZSC9FWjOqNAgtByseHfFTSTs/4ZBQZZcZBpolt8sUng==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-cctalk": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-13.0.0.tgz",
"integrity": "sha512-RErAe57g9gvnlieVYGIn1xymb1bzNXb2QtUQd14FpmbQQYlcrmuRnJwKa1BgTCujoCkhtaTtgHlbBWOxm8U2uA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-delimiter": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-13.0.0.tgz",
"integrity": "sha512-Qqyb0FX1avs3XabQqNaZSivyVbl/yl0jywImp7ePvfZKLwx7jBZjvL+Hawt9wIG6tfq6zbFM24vzCCK7REMUig==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-inter-byte-timeout": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-13.0.0.tgz",
"integrity": "sha512-a0w0WecTW7bD2YHWrpTz1uyiWA2fDNym0kjmPeNSwZ2XCP+JbirZt31l43m2ey6qXItTYVuQBthm75sPVeHnGA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-packet-length": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-13.0.0.tgz",
"integrity": "sha512-60ZDDIqYRi0Xs2SPZUo4Jr5LLIjtb+rvzPKMJCohrO6tAqSDponcNpcB1O4W21mKTxYjqInSz+eMrtk0LLfZIg==",
"license": "MIT",
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/@serialport/parser-readline": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-13.0.0.tgz",
"integrity": "sha512-dov3zYoyf0dt1Sudd1q42VVYQ4WlliF0MYvAMA3MOyiU1IeG4hl0J6buBA2w4gl3DOCC05tGgLDN/3yIL81gsA==",
"license": "MIT",
"dependencies": {
"@serialport/parser-delimiter": "13.0.0"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-ready": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz",
"integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-regex": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz",
"integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-slip-encoder": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz",
"integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/parser-spacepacket": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz",
"integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/@serialport/stream": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz",
"integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==",
"license": "MIT",
"dependencies": {
"@serialport/bindings-interface": "1.2.2",
"debug": "4.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
"license": "ISC"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz",
"integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/path": {
"version": "0.12.7",
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
"integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
"license": "MIT",
"dependencies": {
"process": "^0.11.1",
"util": "^0.10.3"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/serialport": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/serialport/-/serialport-13.0.0.tgz",
"integrity": "sha512-PHpnTd8isMGPfFTZNCzOZp9m4mAJSNWle9Jxu6BPTcWq7YXl5qN7tp8Sgn0h+WIGcD6JFz5QDgixC2s4VW7vzg==",
"license": "MIT",
"dependencies": {
"@serialport/binding-mock": "10.2.2",
"@serialport/bindings-cpp": "13.0.0",
"@serialport/parser-byte-length": "13.0.0",
"@serialport/parser-cctalk": "13.0.0",
"@serialport/parser-delimiter": "13.0.0",
"@serialport/parser-inter-byte-timeout": "13.0.0",
"@serialport/parser-packet-length": "13.0.0",
"@serialport/parser-readline": "13.0.0",
"@serialport/parser-ready": "13.0.0",
"@serialport/parser-regex": "13.0.0",
"@serialport/parser-slip-encoder": "13.0.0",
"@serialport/parser-spacepacket": "13.0.0",
"@serialport/stream": "13.0.0",
"debug": "4.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://opencollective.com/serialport/donate"
}
},
"node_modules/util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
"license": "MIT",
"dependencies": {
"inherits": "2.0.3"
}
}
}
}

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "cansat",
"version": "0.0.1",
"description": "Programa que recibe los datos por serial y los manda a ThingSpeak para mostrarlos luego en un dashboard.",
"license": "MIT",
"author": "",
"type": "module",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@serialport/parser-readline": "^13.0.0",
"path": "^0.12.7",
"serialport": "^13.0.0"
}
}