Compare commits
13 Commits
00f11f8c08
...
master
Author | SHA1 | Date | |
---|---|---|---|
16ea6dafe3 | |||
2e580b44d2 | |||
4068b8bb01 | |||
a9a8cf2745 | |||
85e6ab01ae | |||
31585c4b6b | |||
1de19b208a | |||
72800546a8 | |||
287a28d065 | |||
efc56aae40 | |||
a127bcfbbd | |||
f5e1d73988 | |||
90456f5800 |
7
package-lock.json
generated
7
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ztimson/utils": "^0.21.6",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"i2c-bus": "^5.2.3",
|
"i2c-bus": "^5.2.3",
|
||||||
@ -220,6 +221,12 @@
|
|||||||
"url": "https://opencollective.com/serialport/donate"
|
"url": "https://opencollective.com/serialport/donate"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ztimson/utils": {
|
||||||
|
"version": "0.21.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ztimson/utils/-/utils-0.21.6.tgz",
|
||||||
|
"integrity": "sha512-wOjwd9N6m73NgsuCIWr1yrmWwp7CLoDu6vdlhE0CiXhEqzC9qRhkOcP8Sgn7n0nK3RANlwDtYKJl02iuRAcoJw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "1.3.8",
|
"version": "1.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/main.js"
|
"apollo": "node src/main.js"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
21
src/bme.js
21
src/bme.js
@ -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());
|
|
@ -3,17 +3,32 @@ import i2c from 'i2c-bus';
|
|||||||
export async function bme(address = 0x76) {
|
export async function bme(address = 0x76) {
|
||||||
const i2cBus = await i2c.openPromisified(1);
|
const i2cBus = await i2c.openPromisified(1);
|
||||||
const data = await Promise.all([
|
const data = await Promise.all([
|
||||||
i2cBus.readByte(address, 0xFA),
|
i2cBus.readByte(address, 0xFA), // Temp MSB
|
||||||
i2cBus.readByte(address, 0xFB),
|
i2cBus.readByte(address, 0xFB), // Temp LSB
|
||||||
i2cBus.readByte(address, 0xFC),
|
i2cBus.readByte(address, 0xFC), // Temp XLSB
|
||||||
i2cBus.readByte(address, 0xF7),
|
i2cBus.readByte(address, 0xF7), // Pressure MSB
|
||||||
i2cBus.readByte(address, 0xF8),
|
i2cBus.readByte(address, 0xF8), // Pressure LSB
|
||||||
i2cBus.readByte(address, 0xF9),
|
i2cBus.readByte(address, 0xF9), // Pressure XLSB
|
||||||
i2cBus.readByte(address, 0xFD),
|
i2cBus.readByte(address, 0xFD), // Humidity MSB
|
||||||
i2cBus.readByte(address, 0xFE),
|
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 {
|
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,
|
pressure: ((data[3] << 12) | (data[4] << 4) | (data[5] >> 4)) / 25600,
|
||||||
humidity: ((data[6] << 8) | data[7]) / 1024.0,
|
humidity: ((data[6] << 8) | data[7]) / 1024.0,
|
||||||
};
|
};
|
||||||
|
@ -9,6 +9,7 @@ export async function bms(address = 0x57) {
|
|||||||
i2cBus.readByte(address, 0x23),
|
i2cBus.readByte(address, 0x23),
|
||||||
i2cBus.readByte(address, 0x2a),
|
i2cBus.readByte(address, 0x2a),
|
||||||
]);
|
]);
|
||||||
|
await i2cBus.close();
|
||||||
return {
|
return {
|
||||||
charging: !!((data[0] >> 7) & 1),
|
charging: !!((data[0] >> 7) & 1),
|
||||||
percentage: data[4] / 100,
|
percentage: data[4] / 100,
|
||||||
|
71
src/bno080.js
Normal file
71
src/bno080.js
Normal 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 },
|
||||||
|
};
|
||||||
|
}
|
13
src/main.js
13
src/main.js
@ -30,8 +30,11 @@ function run(cmd) {
|
|||||||
else if(cmd.startsWith('remote')) {
|
else if(cmd.startsWith('remote')) {
|
||||||
remote = cmd.split(' ').pop();
|
remote = cmd.split(' ').pop();
|
||||||
return `Remote Set: ${remote}`;
|
return `Remote Set: ${remote}`;
|
||||||
} else if(cmd.startsWith('start')) new Daemon(+cmd.split(' ').pop() || 1969);
|
} else if(cmd.startsWith('start')) {
|
||||||
else return fetch(`${remote.startsWith('http') ? '' : 'http://'}${remote}/api/${cmd}`).then(resp => {
|
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'))
|
if(resp.ok && resp.headers['Content-Type']?.includes('json'))
|
||||||
return resp.json();
|
return resp.json();
|
||||||
else return resp.text();
|
else return resp.text();
|
||||||
@ -42,8 +45,10 @@ function run(cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(command) console.log(run(command));
|
if(command) console.log(run(command));
|
||||||
else console.log(help());
|
else {
|
||||||
while(true) {
|
console.log(help());
|
||||||
|
while(true) {
|
||||||
const cmd = await ask('> ');
|
const cmd = await ask('> ');
|
||||||
console.log(await run(cmd), '\n');
|
console.log(await run(cmd), '\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {adjustedInterval, sleep} from './misc.js';
|
import {adjustedInterval, sleep} from '@ztimson/utils';
|
||||||
import {bms} from './bms.js';
|
import {bms} from './bms.js';
|
||||||
import {bme} from './bme280.js';
|
import {bme} from './bme280.js';
|
||||||
|
import {imu} from './bno080.js';
|
||||||
|
|
||||||
export default class SensorSuite {
|
export default class SensorSuite {
|
||||||
data = {
|
data = {
|
||||||
@ -25,11 +26,15 @@ export default class SensorSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
|
// Movement
|
||||||
|
this.intervals.push(adjustedInterval(async () => {
|
||||||
|
this.data.movement = await this.statusWrapper(imu(), 'imu');
|
||||||
|
}, 1000));
|
||||||
|
// Environment
|
||||||
this.intervals.push(adjustedInterval(async () => {
|
this.intervals.push(adjustedInterval(async () => {
|
||||||
this.data.environment = await this.statusWrapper(bme(), 'bme280');
|
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));
|
}, 1000));
|
||||||
|
// Battery
|
||||||
await sleep(500); // Offset reading sensor data
|
await sleep(500); // Offset reading sensor data
|
||||||
this.intervals.push(adjustedInterval(async () => {
|
this.intervals.push(adjustedInterval(async () => {
|
||||||
this.data.battery = await this.statusWrapper(bms(), 'bms');
|
this.data.battery = await this.statusWrapper(bms(), 'bms');
|
||||||
|
BIN
ui/assets/navball-cursor.png
Normal file
BIN
ui/assets/navball-cursor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
ui/assets/navball.png
Normal file
BIN
ui/assets/navball.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
BIN
ui/assets/navball2.png
Normal file
BIN
ui/assets/navball2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
@ -141,7 +141,7 @@
|
|||||||
<div class="status status-bat">BAT</div>
|
<div class="status status-bat">BAT</div>
|
||||||
<div class="status status-env">ENV</div>
|
<div class="status status-env">ENV</div>
|
||||||
<div class="status status-gps">GPS</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 class="status status-tel">TEL</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -180,10 +180,10 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const remote = localStorage.getItem('remote') || 'localhost:1969';
|
const remote = localStorage.getItem('remote') || '';
|
||||||
|
|
||||||
function run(cmd) {
|
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();
|
const value = await resp.text();
|
||||||
try {
|
try {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
@ -196,12 +196,15 @@
|
|||||||
const battery = document.querySelectorAll('.battery');
|
const battery = document.querySelectorAll('.battery');
|
||||||
const batteryTemp = document.querySelectorAll('.battery-temperature');
|
const batteryTemp = document.querySelectorAll('.battery-temperature');
|
||||||
const batteryIcon = document.querySelectorAll('.battery-icon');
|
const batteryIcon = document.querySelectorAll('.battery-icon');
|
||||||
|
const humidity = document.querySelectorAll('.humidity');
|
||||||
const navball = document.querySelectorAll('.navball');
|
const navball = document.querySelectorAll('.navball');
|
||||||
const statusAgm = document.querySelectorAll('.status-agm');
|
const pressure = document.querySelectorAll('.pressure');
|
||||||
const statusBat = document.querySelectorAll('.status-bat');
|
const statusBat = document.querySelectorAll('.status-bat');
|
||||||
const statusEnv = document.querySelectorAll('.status-env');
|
const statusEnv = document.querySelectorAll('.status-env');
|
||||||
|
const statusImu = document.querySelectorAll('.status-imu');
|
||||||
const statusGps = document.querySelectorAll('.status-gps');
|
const statusGps = document.querySelectorAll('.status-gps');
|
||||||
const statusTel = document.querySelectorAll('.status-tel');
|
const statusTel = document.querySelectorAll('.status-tel');
|
||||||
|
const temperature = document.querySelectorAll('.temperature');
|
||||||
const time = document.querySelectorAll('.time');
|
const time = document.querySelectorAll('.time');
|
||||||
const tPlus = document.querySelectorAll('.t-plus');
|
const tPlus = document.querySelectorAll('.t-plus');
|
||||||
const voltage = document.querySelectorAll('.voltage');
|
const voltage = document.querySelectorAll('.voltage');
|
||||||
@ -217,12 +220,16 @@
|
|||||||
battery.forEach(b => b.innerHTML = (sensors.battery?.percentage || 0) * 100);
|
battery.forEach(b => b.innerHTML = (sensors.battery?.percentage || 0) * 100);
|
||||||
batteryTemp.forEach(bt => bt.innerHTML = sensors.battery?.temperature || 0);
|
batteryTemp.forEach(bt => bt.innerHTML = sensors.battery?.temperature || 0);
|
||||||
batteryIcon.forEach(bi => bi.className = sensors.battery?.charging ? 'fa fa-bolt' : 'fa fa-battery');
|
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);
|
voltage.forEach(v => v.innerHTML = sensors.battery?.voltage || 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
run('status').then(status => {
|
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');
|
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)`);
|
navball.forEach(n => n.style.transform = `rotate(${count * 5}deg)`);
|
||||||
|
Reference in New Issue
Block a user