diff --git a/config.js b/config.js new file mode 100644 index 0000000..12d3343 --- /dev/null +++ b/config.js @@ -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 + } +} \ No newline at end of file diff --git a/libs/api.js b/libs/api.js new file mode 100644 index 0000000..560bcd6 --- /dev/null +++ b/libs/api.js @@ -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} 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(); + + diff --git a/libs/utils.js b/libs/utils.js new file mode 100644 index 0000000..9d24287 --- /dev/null +++ b/libs/utils.js @@ -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} + */ +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 +} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..6966e59 --- /dev/null +++ b/main.js @@ -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); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b8a46b0 --- /dev/null +++ b/package-lock.json @@ -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" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b107921 --- /dev/null +++ b/package.json @@ -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" + } +}