init
This commit is contained in:
commit
b2dcc571c2
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.idea
|
||||||
|
node_modules
|
23
apollo.js
Normal file
23
apollo.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import SensorSuite from './sensor-suite.js'
|
||||||
|
|
||||||
|
export default class Apollo {
|
||||||
|
sensor;
|
||||||
|
onStop;
|
||||||
|
|
||||||
|
get status() {
|
||||||
|
return {...this.sensor.status};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.sensor = new SensorSuite();
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.sensor.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
this.sensor.stop();
|
||||||
|
if(this.onStop) this.onStop();
|
||||||
|
}
|
||||||
|
}
|
17
cli.js
Normal file
17
cli.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Controller from './controller.js';
|
||||||
|
import {ask} from './misc.js';
|
||||||
|
|
||||||
|
export default class Cli extends Controller {
|
||||||
|
constructor(apollo) {
|
||||||
|
super(apollo);
|
||||||
|
console.log(this.help());
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
while(true) {
|
||||||
|
const cmd = await ask('> ');
|
||||||
|
console.log(this.run(cmd));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
controller.js
Normal file
37
controller.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {$} from './misc.js';
|
||||||
|
|
||||||
|
export default class Controller {
|
||||||
|
apollo;
|
||||||
|
|
||||||
|
constructor(apollo) {
|
||||||
|
this.apollo = apollo;
|
||||||
|
}
|
||||||
|
|
||||||
|
help() {
|
||||||
|
return `
|
||||||
|
Apollo v0.0.0
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
exit - Stop Apollo
|
||||||
|
help - Display manual
|
||||||
|
reboot - Reboot System
|
||||||
|
sensors - All sensor data
|
||||||
|
shutdown - Shutdown System
|
||||||
|
stop - Stop Apollo
|
||||||
|
status - Subsystem status
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
run(cmd) {
|
||||||
|
cmd = cmd.toLowerCase();
|
||||||
|
if(cmd == 'help') return this.help();
|
||||||
|
else if(cmd == 'reboot') $`reboot now`;
|
||||||
|
else if(cmd == 'sensors') return this.apollo.sensor.data;
|
||||||
|
else if(cmd == 'shutdown') {
|
||||||
|
$`shutdown now`;
|
||||||
|
this.apollo.stop();
|
||||||
|
} else if(cmd == 'status') return this.apollo.status;
|
||||||
|
else if(cmd == 'stop' || cmd == 'exit') process.exit();
|
||||||
|
else return `Unknown Command: ${cmd}`;
|
||||||
|
}
|
||||||
|
}
|
25
http.js
Normal file
25
http.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import Controller from './controller.js';
|
||||||
|
|
||||||
|
export default class Http extends Controller {
|
||||||
|
express;
|
||||||
|
|
||||||
|
constructor(apollo, port = 8000) {
|
||||||
|
super(apollo);
|
||||||
|
this.express = express();
|
||||||
|
this.express.get('*', (req, res) => {
|
||||||
|
let p = req.params['0'];
|
||||||
|
if(!p || p == '/') p = 'index.html';
|
||||||
|
const absolute = path.join(import.meta.url, '/../', p).replace('file:', '');
|
||||||
|
res.sendFile(absolute);
|
||||||
|
});
|
||||||
|
this.express.get('/api/*', async (req, res) => {
|
||||||
|
const cmd = req.params['0'];
|
||||||
|
console.log(cmd);
|
||||||
|
res.json(await this.run(cmd));
|
||||||
|
});
|
||||||
|
this.express.listen(port);
|
||||||
|
}
|
||||||
|
}
|
11
index.html
Normal file
11
index.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!Doctype html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Apollo v0.0.0</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Apollo v0.0.0</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
index.js
Normal file
14
index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Apollo from './apollo.js';
|
||||||
|
import Cli from './cli.js';
|
||||||
|
import Http from './http.js';
|
||||||
|
import Serial from './serial.js';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const apollo = new Apollo();
|
||||||
|
const cli = new Cli(apollo);
|
||||||
|
const serial = new Serial(apollo);
|
||||||
|
const http = new Http(apollo);
|
||||||
|
await apollo.start();
|
||||||
|
cli.start();
|
||||||
|
// serial.start();
|
||||||
|
})();
|
71
misc.js
Normal file
71
misc.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import * as readline from 'node:readline';
|
||||||
|
import {exec} from 'child_process';
|
||||||
|
|
||||||
|
export function $(str, ...args) {
|
||||||
|
let cmd = str.reduce((acc, part, i) => acc + part + (args[i] || ''), '');
|
||||||
|
return new Promise((res, rej) => exec(cmd, (err, stdout, stderr) => {
|
||||||
|
if(err || stderr) return rej(err || stderr);
|
||||||
|
return res(stdout);
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ask(prompt, hide = false) {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
terminal: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!hide) {
|
||||||
|
rl.question(prompt, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rl.output.write(prompt);
|
||||||
|
let input = '';
|
||||||
|
|
||||||
|
// Listen for 'keypress' to handle masking
|
||||||
|
rl.input.on('keypress', (char, key) => {
|
||||||
|
if (key && key.name === 'return') {
|
||||||
|
rl.output.write('\n'); // Submit on new line
|
||||||
|
rl.close();
|
||||||
|
resolve(input);
|
||||||
|
} else if (key && key.name === 'backspace') {
|
||||||
|
if (input.length > 0) {
|
||||||
|
input = input.slice(0, -1);
|
||||||
|
rl.output.write(`\r${prompt}${input.replaceAll(/./g, '*')} \b`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input += char;
|
||||||
|
rl.output.write('\b*'); // Mask the input with '*'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Restore settings
|
||||||
|
rl.input.setRawMode(true);
|
||||||
|
rl.input.resume();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function poll(cb, ms) {
|
||||||
|
let cancel = false, timeout = null;
|
||||||
|
const p = async () => {
|
||||||
|
if(cancel) return;
|
||||||
|
const start = new Date().getTime();
|
||||||
|
await cb();
|
||||||
|
const end = new Date().getTime();
|
||||||
|
timeout = setTimeout(() => p(), ms - (end - start));
|
||||||
|
};
|
||||||
|
p();
|
||||||
|
return () => {
|
||||||
|
cancel = true;
|
||||||
|
if(timeout) clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sleep(ms) {
|
||||||
|
return new Promise(res => setTimeout(res, ms));
|
||||||
|
}
|
1132
package-lock.json
generated
Normal file
1132
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "apollo",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Apollo Sensor Suite",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bme280-sensor": "^0.1.7",
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"i2c-bus": "^5.2.3",
|
||||||
|
"serialport": "^12.0.0"
|
||||||
|
}
|
||||||
|
}
|
46
sensor-suite.js
Normal file
46
sensor-suite.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import BME280 from 'bme280-sensor';
|
||||||
|
import {poll} from './misc.js';
|
||||||
|
|
||||||
|
export default class SensorSuite {
|
||||||
|
bme280;
|
||||||
|
stopBme280;
|
||||||
|
|
||||||
|
_data = {
|
||||||
|
acceleration: [null, null, null],
|
||||||
|
altitude: null,
|
||||||
|
battery: null,
|
||||||
|
gpsStrength: null,
|
||||||
|
gyro: [null, null, null],
|
||||||
|
humidity: null,
|
||||||
|
magnetometer: [null, null, null],
|
||||||
|
position: [null, null],
|
||||||
|
pressure: null,
|
||||||
|
temperature: null,
|
||||||
|
voltage: null,
|
||||||
|
}
|
||||||
|
set data(d) { this._data = d; }
|
||||||
|
get data() { return {timestamp: new Date().getTime(), ...this._data}; }
|
||||||
|
|
||||||
|
get status() { return { sensors: 'ok' }; }
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.bme280 = new BME280({i2cBusNo: 1, i2cAddress: 0x76});
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.bme280.init();
|
||||||
|
|
||||||
|
// Poll environmental data
|
||||||
|
this.stopBme280 = poll(async () => {
|
||||||
|
const d = await this.bme280.readSensorData();
|
||||||
|
this._data.humidity = d.humidity / 100;
|
||||||
|
this._data.temperature = d.temperature_C;
|
||||||
|
this._data.pressure = d.pressure_hPa;
|
||||||
|
this._data.altitude = BME280.calculateAltitudeMeters(this.data.pressure);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if(this.stopBme280) this.stopBme280();
|
||||||
|
}
|
||||||
|
}
|
29
serial.js
Normal file
29
serial.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import {SerialPort} from 'serialport';
|
||||||
|
import Controller from './controller.js';
|
||||||
|
|
||||||
|
export default class Serial extends Controller {
|
||||||
|
baud;
|
||||||
|
interval;
|
||||||
|
port;
|
||||||
|
|
||||||
|
constructor(apollo, options) {
|
||||||
|
super(apollo);
|
||||||
|
this.options = {
|
||||||
|
baud: 9600,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
this.interval = setInterval(async () => {
|
||||||
|
const cons = (await SerialPort.list()).map(p => p.path);
|
||||||
|
if(this.ports.length > 0) {
|
||||||
|
this.port = new SerialPort({path: cons[0], baudRate: this.options.baud});
|
||||||
|
this.port.on('open', () => this.help());
|
||||||
|
this.port.on('data', cmd => port.write(JSON.stringify(this.run(cmd))));
|
||||||
|
this.port.on('close', () => this.start());
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user