Compare commits

..

15 Commits

Author SHA1 Message Date
16ea6dafe3 Fixed IMU address 2024-10-30 11:29:35 -04:00
2e580b44d2 Added IMU status light 2024-10-30 11:23:54 -04:00
4068b8bb01 Added IMU 2024-10-30 11:22:19 -04:00
a9a8cf2745 Calibrate the temp sensor 2024-10-30 11:16:24 -04:00
85e6ab01ae Fixed startup command 2024-10-30 11:06:19 -04:00
31585c4b6b Log server start 2024-10-30 11:05:21 -04:00
1de19b208a Temp fix 2024-10-30 11:03:30 -04:00
72800546a8 Added environment data to dashboard 2024-10-30 10:41:03 -04:00
287a28d065 Fixed ENV status light 2024-10-30 10:37:08 -04:00
efc56aae40 Fix API URL 2024-10-30 10:35:09 -04:00
a127bcfbbd Merge branch 'master' of https://git.zakscode.com/ztimson/Apollo 2024-10-30 10:28:21 -04:00
f5e1d73988 Fixed moved utilies 2024-10-30 14:27:55 +00:00
90456f5800 Added missing ui 2024-10-30 10:27:33 -04:00
00f11f8c08 Merge 2024-10-30 14:22:07 +00:00
3e06856c70 Added cors 2024-10-30 14:20:55 +00:00
13 changed files with 1284 additions and 45 deletions

1152
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,12 @@
"main": "index.js",
"type": "module",
"scripts": {
"start": "node src/main.js"
"apollo": "node src/main.js"
},
"author": "",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"@ztimson/utils": "^0.21.6",
"express": "^4.21.1",
"i2c-bus": "^5.2.3",

View File

@ -1,21 +0,0 @@
import i2c from 'i2c-bus';
export async function bme(address = 0x76) {
const i2cBus = await i2c.openPromisified(1);
const data = await Promise.all([
i2cBus.readByte(address, 0xFA),
i2cBus.readByte(address, 0xFB),
i2cBus.readByte(address, 0xFC),
i2cBus.readByte(address, 0xF7),
i2cBus.readByte(address, 0xF8),
i2cBus.readByte(address, 0xF9),
i2cBus.readByte(address, 0xFD),
i2cBus.readByte(address, 0xFE),
]);
return {
temperature: (((data[0] << 12) | (data[1] << 4) | (data[2] >> 4)) / 16384.0 - 5120.0) / 100,
pressure: ((data[3] << 12) | (data[4] << 4) | (data[5] >> 4)) / 25600,
humidity: ((data[6] << 8) | data[7]) / 1024.0,
};
}
console.log(await bme());

View File

@ -3,17 +3,32 @@ import i2c from 'i2c-bus';
export async function bme(address = 0x76) {
const i2cBus = await i2c.openPromisified(1);
const data = await Promise.all([
i2cBus.readByte(address, 0xFA),
i2cBus.readByte(address, 0xFB),
i2cBus.readByte(address, 0xFC),
i2cBus.readByte(address, 0xF7),
i2cBus.readByte(address, 0xF8),
i2cBus.readByte(address, 0xF9),
i2cBus.readByte(address, 0xFD),
i2cBus.readByte(address, 0xFE),
i2cBus.readByte(address, 0xFA), // Temp MSB
i2cBus.readByte(address, 0xFB), // Temp LSB
i2cBus.readByte(address, 0xFC), // Temp XLSB
i2cBus.readByte(address, 0xF7), // Pressure MSB
i2cBus.readByte(address, 0xF8), // Pressure LSB
i2cBus.readByte(address, 0xF9), // Pressure XLSB
i2cBus.readByte(address, 0xFD), // Humidity MSB
i2cBus.readByte(address, 0xFE), // Humidity LSB
]);
// Calibration registers
const CALIBRATION_LENGTH = 24;
const calibBuffer = Buffer.alloc(CALIBRATION_LENGTH);
await i2cBus.readI2cBlock(address, 0x88, CALIBRATION_LENGTH, calibBuffer);
const T1 = calibBuffer.readUInt16LE(0);
const T2 = calibBuffer.readInt16LE(2);
const T3 = calibBuffer.readInt16LE(4);
const rawTemp = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
const var1 = (((rawTemp >> 3) - (T1 << 1)) * T2) / 2048;
const var2 = (((((rawTemp >> 4) - T1) * ((rawTemp >> 4) - T1)) >> 12) * T3) / 16384;
const temp = (var1 + var2) / 5120.0;
await i2cBus.close();
return {
temperature: (((data[0] << 12) | (data[1] << 4) | (data[2] >> 4)) / 16384.0 - 5120.0) / 100,
temperature: temp,
pressure: ((data[3] << 12) | (data[4] << 4) | (data[5] >> 4)) / 25600,
humidity: ((data[6] << 8) | data[7]) / 1024.0,
};

View File

@ -9,6 +9,7 @@ export async function bms(address = 0x57) {
i2cBus.readByte(address, 0x23),
i2cBus.readByte(address, 0x2a),
]);
await i2cBus.close();
return {
charging: !!((data[0] >> 7) & 1),
percentage: data[4] / 100,

71
src/bno080.js Normal file
View File

@ -0,0 +1,71 @@
import i2c from 'i2c-bus';
export async function imu(address = 0x4B) { // BNO080 default I2C address
const i2cBus = await i2c.openPromisified(1);
// Register addresses for accelerometer, gyroscope, and magnetometer data (assumes configuration is done)
const ACCEL_X_LSB = 0x08;
const ACCEL_X_MSB = 0x09;
const ACCEL_Y_LSB = 0x0A;
const ACCEL_Y_MSB = 0x0B;
const ACCEL_Z_LSB = 0x0C;
const ACCEL_Z_MSB = 0x0D;
const GYRO_X_LSB = 0x10;
const GYRO_X_MSB = 0x11;
const GYRO_Y_LSB = 0x12;
const GYRO_Y_MSB = 0x13;
const GYRO_Z_LSB = 0x14;
const GYRO_Z_MSB = 0x15;
const MAG_X_LSB = 0x16; // Hypothetical register for magnetometer X LSB
const MAG_X_MSB = 0x17; // Hypothetical register for magnetometer X MSB
const MAG_Y_LSB = 0x18; // Hypothetical register for magnetometer Y LSB
const MAG_Y_MSB = 0x19; // Hypothetical register for magnetometer Y MSB
const MAG_Z_LSB = 0x1A; // Hypothetical register for magnetometer Z LSB
const MAG_Z_MSB = 0x1B; // Hypothetical register for magnetometer Z MSB
// Read data from the sensor registers
const data = await Promise.all([
i2cBus.readByte(address, ACCEL_X_LSB),
i2cBus.readByte(address, ACCEL_X_MSB),
i2cBus.readByte(address, ACCEL_Y_LSB),
i2cBus.readByte(address, ACCEL_Y_MSB),
i2cBus.readByte(address, ACCEL_Z_LSB),
i2cBus.readByte(address, ACCEL_Z_MSB),
i2cBus.readByte(address, GYRO_X_LSB),
i2cBus.readByte(address, GYRO_X_MSB),
i2cBus.readByte(address, GYRO_Y_LSB),
i2cBus.readByte(address, GYRO_Y_MSB),
i2cBus.readByte(address, GYRO_Z_LSB),
i2cBus.readByte(address, GYRO_Z_MSB),
i2cBus.readByte(address, MAG_X_LSB),
i2cBus.readByte(address, MAG_X_MSB),
i2cBus.readByte(address, MAG_Y_LSB),
i2cBus.readByte(address, MAG_Y_MSB),
i2cBus.readByte(address, MAG_Z_LSB),
i2cBus.readByte(address, MAG_Z_MSB),
]);
// Convert accelerometer data
const accelX = ((data[1] << 8) | data[0]) / 1000;
const accelY = ((data[3] << 8) | data[2]) / 1000;
const accelZ = ((data[5] << 8) | data[4]) / 1000;
// Convert gyroscope data
const gyroX = ((data[7] << 8) | data[6]) / 1000;
const gyroY = ((data[9] << 8) | data[8]) / 1000;
const gyroZ = ((data[11] << 8) | data[10]) / 1000;
// Convert magnetometer data
const magX = ((data[13] << 8) | data[12]) / 1000; // Hypothetical conversion factor
const magY = ((data[15] << 8) | data[14]) / 1000;
const magZ = ((data[17] << 8) | data[16]) / 1000;
await i2cBus.close();
return {
acceleration: { x: accelX, y: accelY, z: accelZ },
gyroscope: { x: gyroX, y: gyroY, z: gyroZ },
magnetometer: { x: magX, y: magY, z: magZ },
};
}

View File

@ -1,3 +1,4 @@
import cors from 'cors';
import express from 'express';
import path from 'path';
import APOLLO from './apollo.js';
@ -13,6 +14,8 @@ export default class Daemon {
this.express = express();
this.express.use(cors('*'));
this.express.get('/api/*', async (req, res) => {
const cmd = req.params['0'];
res.json(await this.run(cmd));

View File

@ -30,8 +30,11 @@ function run(cmd) {
else if(cmd.startsWith('remote')) {
remote = cmd.split(' ').pop();
return `Remote Set: ${remote}`;
} else if(cmd.startsWith('start')) new Daemon(+cmd.split(' ').pop() || 1969);
else return fetch(`${remote.startsWith('http') ? '' : 'http://'}${remote}/api/${cmd}`).then(resp => {
} else if(cmd.startsWith('start')) {
const port = +cmd.split(' ').pop() || 1969;
new Daemon(port);
return `Listening on: http://localhost:${port}`;
} else return fetch(`${remote.startsWith('http') ? '' : 'http://'}${remote}/api/${cmd}`).then(resp => {
if(resp.ok && resp.headers['Content-Type']?.includes('json'))
return resp.json();
else return resp.text();
@ -42,8 +45,10 @@ function run(cmd) {
}
if(command) console.log(run(command));
else console.log(help());
while(true) {
else {
console.log(help());
while(true) {
const cmd = await ask('> ');
console.log(await run(cmd), '\n');
}
}

View File

@ -1,6 +1,7 @@
import {adjustedInterval, sleep} from './misc.js';
import {adjustedInterval, sleep} from '@ztimson/utils';
import {bms} from './bms.js';
import {bme} from './bme280.js';
import {imu} from './bno080.js';
export default class SensorSuite {
data = {
@ -25,11 +26,15 @@ export default class SensorSuite {
}
async start() {
// Movement
this.intervals.push(adjustedInterval(async () => {
this.data.movement = await this.statusWrapper(imu(), 'imu');
}, 1000));
// Environment
this.intervals.push(adjustedInterval(async () => {
this.data.environment = await this.statusWrapper(bme(), 'bme280');
if(this.data.environment?.pressure != null)
this.data.environment.altitude = SensorSuite.#hPaToAltitude(this.data.environment.pressure);
}, 1000));
// Battery
await sleep(500); // Offset reading sensor data
this.intervals.push(adjustedInterval(async () => {
this.data.battery = await this.statusWrapper(bms(), 'bms');

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

BIN
ui/assets/navball.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
ui/assets/navball2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -141,7 +141,7 @@
<div class="status status-bat">BAT</div>
<div class="status status-env">ENV</div>
<div class="status status-gps">GPS</div>
<div class="status status-agm">IMU</div>
<div class="status status-imu">IMU</div>
<div class="status status-tel">TEL</div>
</div>
@ -180,10 +180,10 @@
</dialog>
<script>
const remote = localStorage.getItem('remote') || 'localhost:1969';
const remote = localStorage.getItem('remote') || '';
function run(cmd) {
return fetch(`${remote.startsWith('http') ? '' : 'http://'}${remote}/api/${cmd}`).then(async resp => {
return fetch(`${remote}/api/${cmd}`).then(async resp => {
const value = await resp.text();
try {
return JSON.parse(value);
@ -196,12 +196,15 @@
const battery = document.querySelectorAll('.battery');
const batteryTemp = document.querySelectorAll('.battery-temperature');
const batteryIcon = document.querySelectorAll('.battery-icon');
const humidity = document.querySelectorAll('.humidity');
const navball = document.querySelectorAll('.navball');
const statusAgm = document.querySelectorAll('.status-agm');
const pressure = document.querySelectorAll('.pressure');
const statusBat = document.querySelectorAll('.status-bat');
const statusEnv = document.querySelectorAll('.status-env');
const statusImu = document.querySelectorAll('.status-imu');
const statusGps = document.querySelectorAll('.status-gps');
const statusTel = document.querySelectorAll('.status-tel');
const temperature = document.querySelectorAll('.temperature');
const time = document.querySelectorAll('.time');
const tPlus = document.querySelectorAll('.t-plus');
const voltage = document.querySelectorAll('.voltage');
@ -217,12 +220,16 @@
battery.forEach(b => b.innerHTML = (sensors.battery?.percentage || 0) * 100);
batteryTemp.forEach(bt => bt.innerHTML = sensors.battery?.temperature || 0);
batteryIcon.forEach(bi => bi.className = sensors.battery?.charging ? 'fa fa-bolt' : 'fa fa-battery');
humidity.forEach(h => h.innerHTML = sensors.environment?.humidity ?? 0);
pressure.forEach(p => p.innerHTML = sensors.environment?.pressure ?? 0);
temperature.forEach(t => t.innerHTML = sensors.environment?.temperature ?? 0);
voltage.forEach(v => v.innerHTML = sensors.battery?.voltage || 0);
});
run('status').then(status => {
statusEnv.forEach(se => se.style.background = status['bme'] === 'ok' ? 'green' : 'red');
statusBat.forEach(sb => sb.style.background = status['bms'] === 'ok' ? 'green' : 'red');
statusEnv.forEach(se => se.style.background = status['bme280'] === 'ok' ? 'green' : 'red');
statusImu.forEach(sb => sb.style.background = status['imu'] === 'ok' ? 'green' : 'red');
});
navball.forEach(n => n.style.transform = `rotate(${count * 5}deg)`);