Versión Inicial

This commit is contained in:
2025-04-08 18:38:27 +02:00
parent 661c1a69af
commit 58b7e6ebae
6 changed files with 546 additions and 0 deletions

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"
}
}