Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17f60f1a09 | |||
| d1c2e5f447 | |||
| da87d12588 | |||
| 9b4b939b1b | |||
| c70e43126f | |||
| cfc686e984 | |||
| 27efdbb69d | |||
| 741e9e7f3f | |||
| c47d579fa2 | |||
| 8da09d4959 | |||
| 2c2c300d60 | |||
| 7a27678e39 | |||
| 5efe17c204 | |||
| 5b6fed69c0 | |||
| f78a0f50bd | |||
| 52b81554a3 | |||
| 88ae331c96 | |||
| 70eb5b916c | |||
| d43e98d06b | |||
| ffb2bc8749 | |||
| 19b7da85fe | |||
| 12ffd0ce59 | |||
| 70c798a2cc | |||
| 6c61227623 | |||
| 7d2f384898 | |||
| 8a80559ea7 | |||
| 20e9c305ce | |||
| 1954094b68 | |||
| 1f1fb421e9 | |||
| 7c3dd2d15b | |||
| 135c7b0c94 | |||
| 06d457f4e5 | |||
| 67c5936399 | |||
| f1751f2589 | |||
| 48c0b01f93 |
@@ -1,16 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onDestroy, onMount } from 'svelte'
|
import { onDestroy, onMount } from 'svelte'
|
||||||
import {
|
import {
|
||||||
BufferGeometry,
|
|
||||||
Line,
|
|
||||||
LineBasicMaterial,
|
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
type Object3D,
|
type Object3D,
|
||||||
SphereGeometry,
|
SphereGeometry,
|
||||||
Vector3,
|
Vector3,
|
||||||
type NormalBufferAttributes,
|
type Object3DEventMap,
|
||||||
type Object3DEventMap
|
Color
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import {
|
import {
|
||||||
ModesEnum,
|
ModesEnum,
|
||||||
@@ -26,12 +23,7 @@
|
|||||||
walkGait,
|
walkGait,
|
||||||
walkGaitToMode
|
walkGaitToMode
|
||||||
} from '$lib/stores'
|
} from '$lib/stores'
|
||||||
import {
|
import { populateModelCache, throttler, getToeWorldPositions } from '$lib/utilities'
|
||||||
extractFootColor,
|
|
||||||
populateModelCache,
|
|
||||||
throttler,
|
|
||||||
getToeWorldPositions
|
|
||||||
} from '$lib/utilities'
|
|
||||||
import SceneBuilder from '$lib/sceneBuilder'
|
import SceneBuilder from '$lib/sceneBuilder'
|
||||||
import { lerp, degToRad } from 'three/src/math/MathUtils'
|
import { lerp, degToRad } from 'three/src/math/MathUtils'
|
||||||
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
|
||||||
@@ -42,14 +34,20 @@
|
|||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
sky?: boolean
|
defaultColor?: string | null
|
||||||
orbit?: boolean
|
orbit?: boolean
|
||||||
panel?: boolean
|
panel?: boolean
|
||||||
debug?: boolean
|
debug?: boolean
|
||||||
ground?: boolean
|
ground?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
let { sky = true, orbit = false, panel = true, debug = false, ground = true }: Props = $props()
|
let {
|
||||||
|
defaultColor = '#0091ff',
|
||||||
|
orbit = false,
|
||||||
|
panel = true,
|
||||||
|
debug = false,
|
||||||
|
ground = true
|
||||||
|
}: Props = $props()
|
||||||
|
|
||||||
let sceneManager = $state(new SceneBuilder())
|
let sceneManager = $state(new SceneBuilder())
|
||||||
let canvas: HTMLCanvasElement
|
let canvas: HTMLCanvasElement
|
||||||
@@ -108,7 +106,7 @@
|
|||||||
xm: 0,
|
xm: 0,
|
||||||
ym: 0.7,
|
ym: 0.7,
|
||||||
zm: 0,
|
zm: 0,
|
||||||
Background: 'black'
|
Background: defaultColor
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -153,7 +151,7 @@
|
|||||||
visibility.add(settings, 'Trace points', 1, 1000, 1)
|
visibility.add(settings, 'Trace points', 1, 1000, 1)
|
||||||
visibility.add(settings, 'Target position')
|
visibility.add(settings, 'Target position')
|
||||||
visibility.add(settings, 'Smooth motion')
|
visibility.add(settings, 'Smooth motion')
|
||||||
visibility.addColor(settings, 'Background')
|
visibility.addColor(settings, 'Background').onChange(setSceneBackground).listen()
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateKinematicPosition = () => {
|
const updateKinematicPosition = () => {
|
||||||
@@ -167,6 +165,8 @@
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setSceneBackground = (c: string | null) => (sceneManager.scene.background = new Color(c!))
|
||||||
|
|
||||||
const updateAngles = (name: string, angle: number) => {
|
const updateAngles = (name: string, angle: number) => {
|
||||||
modelTargetAngles[$jointNames.indexOf(name)] = angle * (180 / Math.PI)
|
modelTargetAngles[$jointNames.indexOf(name)] = angle * (180 / Math.PI)
|
||||||
Throttler.throttle(
|
Throttler.throttle(
|
||||||
@@ -197,13 +197,13 @@
|
|||||||
sceneManager.scene.add(target)
|
sceneManager.scene.add(target)
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
sceneManager.addDragControl((angles: Record<string, number>) => {
|
sceneManager.addDragControl(angles => {
|
||||||
Object.entries(angles).forEach(([name, angle]) => {
|
Object.entries(angles).forEach(([name, angle]) => {
|
||||||
updateAngles(name, angle)
|
updateAngles(name, angle)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (sky) sceneManager.addSky()
|
if (defaultColor) setSceneBackground(settings['Background'] || defaultColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculate_kinematics = () => {
|
const calculate_kinematics = () => {
|
||||||
|
|||||||
@@ -17,18 +17,16 @@ import {
|
|||||||
MeshPhongMaterial,
|
MeshPhongMaterial,
|
||||||
EquirectangularReflectionMapping,
|
EquirectangularReflectionMapping,
|
||||||
ACESFilmicToneMapping,
|
ACESFilmicToneMapping,
|
||||||
MathUtils,
|
|
||||||
Group,
|
Group,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
RepeatWrapping
|
RepeatWrapping,
|
||||||
|
Object3D
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import { Sky } from 'three/addons/objects/Sky.js'
|
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||||
import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
|
import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
|
||||||
import { Reflector } from 'three/examples/jsm/objects/Reflector.js'
|
import { Reflector } from 'three/examples/jsm/objects/Reflector.js'
|
||||||
import { type URDFJoint, type URDFMimicJoint, type URDFRobot } from 'urdf-loader'
|
import { type URDFJoint, type URDFMimicJoint, type URDFRobot } from 'urdf-loader'
|
||||||
import { PointerURDFDragControls } from 'urdf-loader/src/URDFDragControls'
|
import { PointerURDFDragControls } from 'urdf-loader/src/URDFDragControls'
|
||||||
import { sunCalculator } from './utilities/position-utilities'
|
|
||||||
|
|
||||||
export const addScene = () => new Scene()
|
export const addScene = () => new Scene()
|
||||||
|
|
||||||
@@ -65,8 +63,6 @@ export default class SceneBuilder {
|
|||||||
private fog!: FogExp2
|
private fog!: FogExp2
|
||||||
private isLoaded: boolean = false
|
private isLoaded: boolean = false
|
||||||
public isDragging: boolean = false
|
public isDragging: boolean = false
|
||||||
highlightMaterial: MeshPhongMaterial
|
|
||||||
sky!: Sky
|
|
||||||
transformControl: TransformControls
|
transformControl: TransformControls
|
||||||
public modelGroup!: Group
|
public modelGroup!: Group
|
||||||
|
|
||||||
@@ -89,34 +85,6 @@ export default class SceneBuilder {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
public addSky = () => {
|
|
||||||
this.sky = new Sky()
|
|
||||||
this.sky.scale.setScalar(450000)
|
|
||||||
this.scene.add(this.sky)
|
|
||||||
const effectController = {
|
|
||||||
turbidity: 10,
|
|
||||||
rayleigh: 3,
|
|
||||||
mieCoefficient: 0.005,
|
|
||||||
mieDirectionalG: 0.7,
|
|
||||||
elevation: sunCalculator.calculateSunElevation(),
|
|
||||||
azimuth: 200,
|
|
||||||
exposure: this.renderer.toneMappingExposure
|
|
||||||
}
|
|
||||||
const uniforms = this.sky.material.uniforms
|
|
||||||
uniforms['turbidity'].value = effectController.turbidity
|
|
||||||
uniforms['rayleigh'].value = effectController.rayleigh
|
|
||||||
uniforms['mieCoefficient'].value = effectController.mieCoefficient
|
|
||||||
uniforms['mieDirectionalG'].value = effectController.mieDirectionalG
|
|
||||||
this.renderer.toneMappingExposure = 0.5
|
|
||||||
const phi = MathUtils.degToRad(90 - effectController.elevation)
|
|
||||||
const theta = MathUtils.degToRad(effectController.azimuth)
|
|
||||||
const sun = new Vector3()
|
|
||||||
|
|
||||||
sun.setFromSphericalCoords(1, phi, theta)
|
|
||||||
uniforms['sunPosition'].value.copy(sun)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
public addPerspectiveCamera = (options: position) => {
|
public addPerspectiveCamera = (options: position) => {
|
||||||
this.camera = new PerspectiveCamera()
|
this.camera = new PerspectiveCamera()
|
||||||
this.camera.position.set(options.x ?? 0, options.y ?? 2.7, options.z ?? 0)
|
this.camera.position.set(options.x ?? 0, options.y ?? 2.7, options.z ?? 0)
|
||||||
@@ -334,7 +302,7 @@ export default class SceneBuilder {
|
|||||||
)
|
)
|
||||||
dragControls.updateJoint = (joint: URDFMimicJoint, angle: number) => {
|
dragControls.updateJoint = (joint: URDFMimicJoint, angle: number) => {
|
||||||
this.setJointValue(joint.name, angle)
|
this.setJointValue(joint.name, angle)
|
||||||
updateAngle(joint.name, angle)
|
updateAngle({ [joint.name]: angle })
|
||||||
}
|
}
|
||||||
dragControls.onDragStart = () => {
|
dragControls.onDragStart = () => {
|
||||||
this.orbit.enabled = false
|
this.orbit.enabled = false
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
import type { IMU } from '$lib/types/models'
|
import type { IMUMsg } from '$lib/types/models'
|
||||||
|
|
||||||
const maxIMUData = 100
|
const maxIMUData = 100
|
||||||
|
|
||||||
@@ -14,11 +14,24 @@ export const imu = (() => {
|
|||||||
bmp_temp: [] as number[]
|
bmp_temp: [] as number[]
|
||||||
})
|
})
|
||||||
|
|
||||||
const addData = (content: IMU) => {
|
const addData = (content: IMUMsg) => {
|
||||||
update(data => {
|
update(data => {
|
||||||
;(Object.keys(content) as (keyof IMU)[]).forEach(key => {
|
if (content.imu && content.imu[4]) {
|
||||||
data[key] = [...data[key], content[key]].slice(-maxIMUData)
|
data.x = [...data.x, content.imu[0]].slice(-maxIMUData)
|
||||||
})
|
data.y = [...data.y, content.imu[1]].slice(-maxIMUData)
|
||||||
|
data.z = [...data.z, content.imu[2]].slice(-maxIMUData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.mag && content.mag[4]) {
|
||||||
|
data.heading = [...data.heading, content.mag[3]].slice(-maxIMUData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.bmp && content.bmp[3]) {
|
||||||
|
data.pressure = [...data.pressure, content.bmp[0]].slice(-maxIMUData)
|
||||||
|
data.altitude = [...data.altitude, content.bmp[1]].slice(-maxIMUData)
|
||||||
|
data.bmp_temp = [...data.bmp_temp, content.bmp[2]].slice(-maxIMUData)
|
||||||
|
}
|
||||||
|
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,6 +154,12 @@ export type IMU = {
|
|||||||
pressure: number
|
pressure: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IMUMsg = {
|
||||||
|
imu: [number, number, number, number, boolean]
|
||||||
|
mag: [number, number, number, number, boolean]
|
||||||
|
bmp: [number, number, number, boolean]
|
||||||
|
}
|
||||||
|
|
||||||
export interface I2CDevice {
|
export interface I2CDevice {
|
||||||
address: number
|
address: number
|
||||||
part_number: string
|
part_number: string
|
||||||
|
|||||||
@@ -4,6 +4,5 @@ export * from './svelte-utilities'
|
|||||||
export * from './math-utilities'
|
export * from './math-utilities'
|
||||||
export * from './buffer-utilities'
|
export * from './buffer-utilities'
|
||||||
export * from './model-utilities'
|
export * from './model-utilities'
|
||||||
export * from './position-utilities'
|
|
||||||
export * from './string-utilities'
|
export * from './string-utilities'
|
||||||
export * from './color-utilities'
|
export * from './color-utilities'
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
class SunCalculator {
|
|
||||||
calculateSunElevation(lat: number = 55, lon: number = 12) {
|
|
||||||
const now = new Date()
|
|
||||||
const JD = this.getJulianDate(now)
|
|
||||||
const solarDec = this.getSolarDeclination(JD)
|
|
||||||
const solarTime = this.getSolarTime(now, lon)
|
|
||||||
|
|
||||||
const hourAngle = (solarTime - 12) * 15
|
|
||||||
const elevation = Math.asin(
|
|
||||||
Math.sin(this.degToRad(lat)) * Math.sin(solarDec) +
|
|
||||||
Math.cos(this.degToRad(lat)) *
|
|
||||||
Math.cos(solarDec) *
|
|
||||||
Math.cos(this.degToRad(hourAngle))
|
|
||||||
)
|
|
||||||
|
|
||||||
return this.radToDeg(elevation)
|
|
||||||
}
|
|
||||||
|
|
||||||
getJulianDate(date: Date) {
|
|
||||||
const Y = date.getUTCFullYear()
|
|
||||||
const M = date.getUTCMonth() + 1
|
|
||||||
const D =
|
|
||||||
date.getUTCDate() +
|
|
||||||
date.getUTCHours() / 24 +
|
|
||||||
date.getUTCMinutes() / 1440 +
|
|
||||||
date.getUTCSeconds() / 86400
|
|
||||||
const A = Math.floor((14 - M) / 12)
|
|
||||||
const Y1 = Y + 4800 - A
|
|
||||||
const M1 = M + 12 * A - 3
|
|
||||||
return (
|
|
||||||
D +
|
|
||||||
Math.floor((153 * M1 + 2) / 5) +
|
|
||||||
365 * Y1 +
|
|
||||||
Math.floor(Y1 / 4) -
|
|
||||||
Math.floor(Y1 / 100) +
|
|
||||||
Math.floor(Y1 / 400) -
|
|
||||||
32045
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
getSolarDeclination(JulianDate: number) {
|
|
||||||
const n = JulianDate - 2451545
|
|
||||||
const L = (280.46 + 0.9856474 * n) % 360
|
|
||||||
const g = this.degToRad((357.528 + 0.9856003 * n) % 360)
|
|
||||||
const lambda = this.degToRad(L + 1.915 * Math.sin(g) + 0.02 * Math.sin(2 * g))
|
|
||||||
return Math.asin(Math.sin(lambda) * Math.sin(this.degToRad(23.44)))
|
|
||||||
}
|
|
||||||
|
|
||||||
getSolarTime(date: Date, lon: number) {
|
|
||||||
const EoT = this.getEquationOfTime(date)
|
|
||||||
const offset = date.getTimezoneOffset() / 60
|
|
||||||
const standardMeridian = Math.round(lon / 15) * 15
|
|
||||||
const solarTime =
|
|
||||||
date.getUTCHours() +
|
|
||||||
(date.getUTCMinutes() + (4 * (standardMeridian - lon) + EoT)) / 60 -
|
|
||||||
offset
|
|
||||||
return (solarTime + 24) % 24
|
|
||||||
}
|
|
||||||
|
|
||||||
getEquationOfTime(date: Date) {
|
|
||||||
const JD = this.getJulianDate(date)
|
|
||||||
const n = JD - 2451545
|
|
||||||
const g = this.degToRad((357.528 + 0.9856003 * n) % 360)
|
|
||||||
const q = this.degToRad((280.46 + 0.9856474 * n) % 360)
|
|
||||||
return (
|
|
||||||
4 *
|
|
||||||
this.radToDeg(
|
|
||||||
0.000075 +
|
|
||||||
0.001868 * Math.cos(q) -
|
|
||||||
0.032077 * Math.sin(g) -
|
|
||||||
0.014615 * Math.cos(2 * q) -
|
|
||||||
0.040849 * Math.sin(2 * g)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
degToRad(deg: number) {
|
|
||||||
return deg * (Math.PI / 180)
|
|
||||||
}
|
|
||||||
|
|
||||||
radToDeg(rad: number) {
|
|
||||||
return rad * (180 / Math.PI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sunCalculator = new SunCalculator()
|
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="hero bg-base-100 h-screen">
|
<div class="hero bg-base-100 h-screen">
|
||||||
<div class="card md:card-side bg-base-200 shadow-2xl flex justify-center items-center">
|
<div class="card md:card-side bg-base-200 shadow-2xl flex justify-center items-center">
|
||||||
<div class="w-64 h-64">
|
<div class="w-64 h-64">
|
||||||
<Visualization sky={false} orbit panel={false} ground={false} />
|
<Visualization defaultColor={null} orbit panel={false} ground={false} />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body w-80">
|
<div class="card-body w-80">
|
||||||
<h2 class="card-title text-center text-2xl">Begin you journey</h2>
|
<h2 class="card-title text-center text-2xl">Begin you journey</h2>
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
part_number: 'MPU6050',
|
part_number: 'MPU6050',
|
||||||
name: 'Six-Axis (Gyro + Accelerometer) MEMS MotionTracking™ Devices'
|
name: 'Six-Axis (Gyro + Accelerometer) MEMS MotionTracking™ Devices'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
address: 105,
|
||||||
|
part_number: 'ICM20948',
|
||||||
|
name: 'Nine-Axis (Gyro + Accelerometer + Magnetometer) MEMS MotionTracking™ Device'
|
||||||
|
},
|
||||||
{ address: 115, part_number: 'PAJ7620U2', name: 'Gesture sensor' },
|
{ address: 115, part_number: 'PAJ7620U2', name: 'Gesture sensor' },
|
||||||
{ address: 119, part_number: 'BMP085', name: 'Temp/Barometric' }
|
{ address: 119, part_number: 'BMP085', name: 'Temp/Barometric' }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { slide } from 'svelte/transition'
|
import { slide } from 'svelte/transition'
|
||||||
import { onDestroy, onMount } from 'svelte'
|
import { onDestroy, onMount } from 'svelte'
|
||||||
import { socket } from '$lib/stores'
|
import { socket } from '$lib/stores'
|
||||||
import { MessageTopic, type IMU } from '$lib/types/models'
|
import { MessageTopic, type IMUMsg } from '$lib/types/models'
|
||||||
import { useFeatureFlags } from '$lib/stores/featureFlags'
|
import { useFeatureFlags } from '$lib/stores/featureFlags'
|
||||||
import { Rotate3d } from '$lib/components/icons'
|
import { Rotate3d } from '$lib/components/icons'
|
||||||
|
|
||||||
@@ -18,10 +18,12 @@
|
|||||||
let angleChartElement: HTMLCanvasElement
|
let angleChartElement: HTMLCanvasElement
|
||||||
let tempChartElement: HTMLCanvasElement
|
let tempChartElement: HTMLCanvasElement
|
||||||
let altitudeChartElement: HTMLCanvasElement
|
let altitudeChartElement: HTMLCanvasElement
|
||||||
|
let magnetometerChartElement: HTMLCanvasElement
|
||||||
|
|
||||||
let angleChart: Chart
|
let angleChart: Chart
|
||||||
let tempChart: Chart
|
let tempChart: Chart
|
||||||
let altitudeChart: Chart
|
let altitudeChart: Chart
|
||||||
|
let magnetometerChart: Chart
|
||||||
|
|
||||||
const getChartColors = () => {
|
const getChartColors = () => {
|
||||||
const style = getComputedStyle(document.body)
|
const style = getComputedStyle(document.body)
|
||||||
@@ -171,6 +173,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
magnetometerChart = new Chart(magnetometerChartElement, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Heading',
|
||||||
|
borderColor: colors.primary,
|
||||||
|
backgroundColor: colors.primary,
|
||||||
|
borderWidth: 2,
|
||||||
|
data: $imu.heading,
|
||||||
|
yAxisID: 'y'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
...baseConfig,
|
||||||
|
scales: {
|
||||||
|
...baseConfig.scales,
|
||||||
|
y: {
|
||||||
|
...baseConfig.scales.y,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Heading [°]',
|
||||||
|
color: colors.background,
|
||||||
|
font: { size: 16, weight: 'bold' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateChartData = (chart: Chart, data: number[]) => {
|
const updateChartData = (chart: Chart, data: number[]) => {
|
||||||
@@ -194,6 +227,10 @@
|
|||||||
angleChart.update('none')
|
angleChart.update('none')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($features.mag) {
|
||||||
|
updateChartData(magnetometerChart, $imu.heading)
|
||||||
|
}
|
||||||
|
|
||||||
if ($features.bmp) {
|
if ($features.bmp) {
|
||||||
updateChartData(tempChart, $imu.bmp_temp)
|
updateChartData(tempChart, $imu.bmp_temp)
|
||||||
updateChartData(altitudeChart, $imu.altitude)
|
updateChartData(altitudeChart, $imu.altitude)
|
||||||
@@ -201,7 +238,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
socket.on(MessageTopic.imu, (data: IMU) => {
|
socket.on(MessageTopic.imu, (data: IMUMsg) => {
|
||||||
console.log(data)
|
console.log(data)
|
||||||
imu.addData(data)
|
imu.addData(data)
|
||||||
})
|
})
|
||||||
@@ -235,6 +272,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if $features.mag}
|
||||||
|
<div class="w-full overflow-x-auto">
|
||||||
|
<div
|
||||||
|
class="flex w-full flex-col space-y-1 h-60"
|
||||||
|
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
||||||
|
>
|
||||||
|
<canvas bind:this={magnetometerChartElement}></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $features.bmp}
|
{#if $features.bmp}
|
||||||
<div class="w-full overflow-x-auto">
|
<div class="w-full overflow-x-auto">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,22 @@
|
|||||||
# Running the spot
|
# Running the spot
|
||||||
|
|
||||||
|
### Start the Development Server
|
||||||
|
|
||||||
|
Use the following commands to launch the development server with Vite, enabling instant updates:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd app
|
||||||
|
pnpm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that this example uses the `pnpm` package manager. For any other package manager such as `npm` or `yarn`, please adjust the command accordingly to run the dev command.
|
||||||
|
|
||||||
|
[Download the pnpm package manager from pnpm.io](https://pnpm.io/installation)
|
||||||
|
### Access the Vite server
|
||||||
|
|
||||||
|
Access the frontend via the browser link provided by Vite.<br>
|
||||||
|
Vite typically runs on port 5173, and can be accessed locally at [localhost:5173](http://localhost:5173/)
|
||||||
|
|
||||||
> *Prerequsition*: You have successfully built, flashed, and configured your robot.
|
> *Prerequsition*: You have successfully built, flashed, and configured your robot.
|
||||||
|
|
||||||
Navigate to `/controller`
|
Navigate to `/controller`
|
||||||
|
|||||||
@@ -22,13 +22,4 @@ server: {
|
|||||||
|
|
||||||
> Changes require a restart of the development server.
|
> Changes require a restart of the development server.
|
||||||
|
|
||||||
### Start the Development Server
|
|
||||||
|
|
||||||
Use the following commands to launch the development server with Vite, enabling instant updates:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cd app
|
|
||||||
pnpm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Access the frontend via the provided browser link.
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ build_flags =
|
|||||||
-D USE_HMC5883=0
|
-D USE_HMC5883=0
|
||||||
-D USE_BMP180=0
|
-D USE_BMP180=0
|
||||||
-D USE_MPU6050=0
|
-D USE_MPU6050=0
|
||||||
|
-D USE_ICM20948=1
|
||||||
|
-D USE_ICM20948_SPIMODE=1
|
||||||
-D USE_WS2812=1
|
-D USE_WS2812=1
|
||||||
-D USE_BNO055=0
|
-D USE_BNO055=0
|
||||||
-D USE_USS=0
|
-D USE_USS=0
|
||||||
|
|||||||
@@ -38,16 +38,16 @@ class CommAdapterBase {
|
|||||||
array.add(event);
|
array.add(event);
|
||||||
array.add(payload);
|
array.add(payload);
|
||||||
|
|
||||||
// TODO: Only send to subscribed
|
|
||||||
|
|
||||||
#if USE_MSGPACK
|
#if USE_MSGPACK
|
||||||
std::string bin;
|
std::string bin;
|
||||||
serializeMsgPack(doc, bin);
|
serializeMsgPack(doc, bin);
|
||||||
send(reinterpret_cast<const uint8_t *>(bin.data()), bin.size(), -1); // TODO: Make CID dynamic
|
xSemaphoreGive(mutex_);
|
||||||
|
send(reinterpret_cast<const uint8_t *>(bin.data()), bin.size(), -1);
|
||||||
#else
|
#else
|
||||||
String out;
|
String out;
|
||||||
serializeJson(doc, out);
|
serializeJson(doc, out);
|
||||||
send(out.c_str(), cid);
|
xSemaphoreGive(mutex_);
|
||||||
|
send(out.c_str(), -1);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
#include <communication/comm_base.hpp>
|
#include <communication/comm_base.hpp>
|
||||||
|
|
||||||
class Websocket : CommAdapterBase {
|
class Websocket : public CommAdapterBase {
|
||||||
public:
|
public:
|
||||||
Websocket(PsychicHttpServer &server, const char *route = "/api/ws");
|
Websocket(PsychicHttpServer &server, const char *route = "/api/ws");
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define USE_CAMERA 0
|
#define USE_CAMERA 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ESP32 IMU on by default
|
// ESP32 IMU off by default
|
||||||
#ifndef USE_MPU6050
|
#ifndef USE_MPU6050
|
||||||
#define USE_MPU6050 0
|
#define USE_MPU6050 0
|
||||||
#endif
|
#endif
|
||||||
@@ -22,6 +22,14 @@
|
|||||||
#define USE_BNO055 1
|
#define USE_BNO055 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// ESP32 IMU off by default
|
||||||
|
#ifndef USE_ICM20948
|
||||||
|
#define USE_ICM20948 0
|
||||||
|
#endif
|
||||||
|
#ifndef USE_ICM20948_SPIMODE // I2C on by default
|
||||||
|
#define USE_ICM20948_SPIMODE 0
|
||||||
|
#endif
|
||||||
|
|
||||||
// ESP32 magnetometer on by default
|
// ESP32 magnetometer on by default
|
||||||
#ifndef USE_HMC5883
|
#ifndef USE_HMC5883
|
||||||
#define USE_HMC5883 0
|
#define USE_HMC5883 0
|
||||||
|
|||||||
@@ -36,37 +36,33 @@ struct BarometerMsg : public SensorMessageBase {
|
|||||||
friend void toJson(JsonVariant v, BarometerMsg const& a) { a.toJson(v); }
|
friend void toJson(JsonVariant v, BarometerMsg const& a) { a.toJson(v); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Barometer {
|
class Barometer : public SensorBase<BarometerMsg> {
|
||||||
public:
|
public:
|
||||||
bool initialize() {
|
bool initialize(void* _) override {
|
||||||
bmp_success = _bmp.begin();
|
_msg.success = _bmp.begin();
|
||||||
return bmp_success;
|
return _msg.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool readBarometer() {
|
bool update() override {
|
||||||
if (!bmp_success) return false;
|
if (!_msg.success) return false;
|
||||||
_bmp.getTemperature(&temperature);
|
_bmp.getTemperature(&_msg.temperature);
|
||||||
sensors_event_t event;
|
sensors_event_t event;
|
||||||
_bmp.getEvent(&event);
|
_bmp.getEvent(&event);
|
||||||
pressure = event.pressure;
|
_msg.pressure = event.pressure;
|
||||||
altitude = _bmp.pressureToAltitude(seaLevelPressure, pressure);
|
_msg.altitude = _bmp.pressureToAltitude(seaLevelPressure, _msg.pressure);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float getPressure() { return pressure; }
|
float getPressure() { return _msg.pressure; }
|
||||||
|
|
||||||
float getAltitude() { return altitude; }
|
float getAltitude() { return _msg.altitude; }
|
||||||
|
|
||||||
float getTemperature() { return temperature; }
|
float getTemperature() { return _msg.temperature; }
|
||||||
|
|
||||||
bool active() { return bmp_success; }
|
bool active() { return _msg.success; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Adafruit_BMP085_Unified _bmp {10085};
|
Adafruit_BMP085_Unified _bmp {10085};
|
||||||
bool bmp_success {false};
|
|
||||||
float pressure {0};
|
|
||||||
float altitude {0};
|
|
||||||
float temperature {0};
|
|
||||||
|
|
||||||
const float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
|
const float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
|
||||||
};
|
};
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <utils/math_utils.h>
|
#include <utils/math_utils.h>
|
||||||
|
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#include "ICM_20948.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if FT_ENABLED(USE_MPU6050)
|
#if FT_ENABLED(USE_MPU6050)
|
||||||
#include <MPU6050_6Axis_MotionApps612.h>
|
#include <MPU6050_6Axis_MotionApps612.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -44,46 +48,112 @@ struct IMUAnglesMsg : public SensorMessageBase {
|
|||||||
|
|
||||||
class IMU : public SensorBase<IMUAnglesMsg> {
|
class IMU : public SensorBase<IMUAnglesMsg> {
|
||||||
public:
|
public:
|
||||||
bool initialize() override {
|
bool initialize(void* _arg = nullptr) override {
|
||||||
#if FT_ENABLED(USE_MPU6050)
|
#if FT_ENABLED(USE_MPU6050)
|
||||||
_imu.initialize();
|
_imu.initialize();
|
||||||
_msg.success = _imu.testConnection();
|
_msg.success = _imu.testConnection();
|
||||||
if (!_msg.success) return false;
|
if (!_msg.success) {
|
||||||
|
ESP_LOGE("IMU", "MPU6050 connection test failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
devStatus = _imu.dmpInitialize();
|
devStatus = _imu.dmpInitialize();
|
||||||
if (devStatus == 0) {
|
if (devStatus == 0) {
|
||||||
_imu.setDMPEnabled(false);
|
_imu.setXGyroOffset(0);
|
||||||
_imu.setDMPConfig1(0x03);
|
_imu.setYGyroOffset(0);
|
||||||
_imu.setDMPEnabled(true);
|
_imu.setZGyroOffset(0);
|
||||||
|
_imu.setXAccelOffset(0);
|
||||||
|
_imu.setYAccelOffset(0);
|
||||||
|
_imu.setZAccelOffset(0);
|
||||||
|
|
||||||
_imu.setI2CMasterModeEnabled(false);
|
_imu.setI2CMasterModeEnabled(false);
|
||||||
_imu.setI2CBypassEnabled(true);
|
_imu.setI2CBypassEnabled(true);
|
||||||
_imu.setSleepEnabled(false);
|
_imu.setSleepEnabled(false);
|
||||||
|
_imu.setRate(1);
|
||||||
|
_imu.resetFIFO();
|
||||||
|
_imu.setDMPEnabled(true);
|
||||||
|
|
||||||
|
ESP_LOGI("IMU", "MPU6050 DMP initialized successfully");
|
||||||
} else {
|
} else {
|
||||||
return false;
|
ESP_LOGE("IMU", "DMP initialization failed (code %d)", devStatus);
|
||||||
|
_msg.success = false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_BNO055)
|
#if FT_ENABLED(USE_BNO055)
|
||||||
_msg.success = _imu.begin();
|
_msg.success = _imu.begin();
|
||||||
if (!_msg.success) {
|
if (!_msg.success) {
|
||||||
|
ESP_LOGE("IMU", "BNO055 connection test failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_imu.setExtCrystalUse(true);
|
_imu.setExtCrystalUse(true);
|
||||||
#endif
|
#endif
|
||||||
return true;
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
#define ICM_20948_USE_DMP // TODO: Move to features.ini
|
||||||
|
SPI_PORT.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1); // TODO: Move to global spi start
|
||||||
|
_imu = (ICM_20948_SPI*)_arg;
|
||||||
|
#ifndef ICM20948_ALIVE
|
||||||
|
#define ICM20948_ALIVE
|
||||||
|
_imu->begin(ICM20948_SPI_CS, SPI_PORT); ESP_LOGI("IMU", "Beginning ICM20948 in SPI mode");
|
||||||
|
_imu->initializeDMP();
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
_imu = (ICM_20948_I2C*)_arg;
|
||||||
|
#ifndef ICM20948_ALIVE
|
||||||
|
#define ICM20948_ALIVE
|
||||||
|
_imu->begin(Wire, 1, 0xFF); ESP_LOGI("IMU", "Beginning ICM20948 in I2C mode");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: begin failed"); return false; }
|
||||||
|
|
||||||
|
_imu->setSampleMode((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), ICM_20948_Sample_Mode_Continuous);
|
||||||
|
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: set sample failed"); return false; }
|
||||||
|
|
||||||
|
ICM_20948_fss_t myFSS;
|
||||||
|
myFSS.a = gpm2;
|
||||||
|
myFSS.g = dps250;
|
||||||
|
_imu->setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS);
|
||||||
|
if (_imu->status != ICM_20948_Stat_Ok){ ESP_LOGW("IMU", "Failed to start ICM20948: set full scale failed"); return false; }
|
||||||
|
// TODO: Setup low pass filter config
|
||||||
|
_msg.success = true;
|
||||||
|
#endif
|
||||||
|
return _msg.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool update() override {
|
bool update() override {
|
||||||
if (!_msg.success) return false;
|
|
||||||
#if FT_ENABLED(USE_MPU6050)
|
#if FT_ENABLED(USE_MPU6050)
|
||||||
if (_imu.dmpPacketAvailable()) {
|
uint16_t fifoCount = _imu.getFIFOCount();
|
||||||
|
uint8_t intStatus = _imu.getIntStatus();
|
||||||
|
|
||||||
|
if (intStatus & 0x10) {
|
||||||
|
_imu.resetFIFO();
|
||||||
|
ESP_LOGW("IMU", "FIFO overflow, resetting");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (_imu.dmpGetCurrentFIFOPacket(fifoBuffer)) {
|
if (_imu.dmpGetCurrentFIFOPacket(fifoBuffer)) {
|
||||||
_imu.dmpGetQuaternion(&q, fifoBuffer);
|
_imu.dmpGetQuaternion(&q, fifoBuffer);
|
||||||
_imu.dmpGetGravity(&gravity, &q);
|
_imu.dmpGetGravity(&gravity, &q);
|
||||||
_imu.dmpGetYawPitchRoll(_msg.rpy, &q, &gravity);
|
_imu.dmpGetYawPitchRoll(_msg.rpy, &q, &gravity);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
|
||||||
|
#ifndef ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||||
|
#define ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||||
|
if (_imu->dataReady())
|
||||||
|
{
|
||||||
|
_imu->getAGMT();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_msg.rpy[0] = _imu->accX();
|
||||||
|
_msg.rpy[1] = _imu->accY();
|
||||||
|
_msg.rpy[2] = _imu->accZ();
|
||||||
|
#endif
|
||||||
#if FT_ENABLED(USE_BNO055)
|
#if FT_ENABLED(USE_BNO055)
|
||||||
sensors_event_t event;
|
sensors_event_t event;
|
||||||
_imu.getEvent(&event);
|
_imu.getEvent(&event);
|
||||||
@@ -105,7 +175,7 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
|||||||
private:
|
private:
|
||||||
#if FT_ENABLED(USE_MPU6050)
|
#if FT_ENABLED(USE_MPU6050)
|
||||||
MPU6050 _imu;
|
MPU6050 _imu;
|
||||||
uint8_t devStatus {false};
|
uint8_t devStatus {0};
|
||||||
Quaternion q;
|
Quaternion q;
|
||||||
uint8_t fifoBuffer[64];
|
uint8_t fifoBuffer[64];
|
||||||
VectorFloat gravity;
|
VectorFloat gravity;
|
||||||
@@ -113,4 +183,12 @@ class IMU : public SensorBase<IMUAnglesMsg> {
|
|||||||
#if FT_ENABLED(USE_BNO055)
|
#if FT_ENABLED(USE_BNO055)
|
||||||
Adafruit_BNO055 _imu {55, 0x29};
|
Adafruit_BNO055 _imu {55, 0x29};
|
||||||
#endif
|
#endif
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
ICM_20948_SPI* _imu;
|
||||||
|
#else
|
||||||
|
//#define WIRE_PORT Wire
|
||||||
|
ICM_20948_I2C* _imu;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include <peripherals/sensor.hpp>
|
#include <peripherals/sensor.hpp>
|
||||||
|
|
||||||
|
|
||||||
struct MagnetometerMsg : public SensorMessageBase {
|
struct MagnetometerMsg : public SensorMessageBase {
|
||||||
float rpy[3] {0, 0, 0};
|
float rpy[3] {0, 0, 0};
|
||||||
float heading {-1};
|
float heading {-1};
|
||||||
@@ -38,37 +39,87 @@ struct MagnetometerMsg : public SensorMessageBase {
|
|||||||
|
|
||||||
class Magnetometer : public SensorBase<MagnetometerMsg> {
|
class Magnetometer : public SensorBase<MagnetometerMsg> {
|
||||||
public:
|
public:
|
||||||
bool initialize() {
|
bool initialize(void* _arg) override {
|
||||||
msg.success = _mag.begin();
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
return msg.success;
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
SPI_PORT.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1);
|
||||||
|
_mag = (ICM_20948_SPI*)_arg;
|
||||||
|
#ifndef ICM20948_ALIVE
|
||||||
|
#define ICM20948_ALIVE
|
||||||
|
_imu->begin(ICM20948_SPI_CS, SPI_PORT); ESP_LOGI("Magnetometer", "Beginning ICM20948 in SPI mode");
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
_mag = (ICM_20948_I2C*)_arg;
|
||||||
|
#ifndef ICM20948_ALIVE
|
||||||
|
#define ICM20948_ALIVE
|
||||||
|
if (!_mag->isConnected()) { _mag->begin(Wire, 1, 0xFF); ESP_LOGI("Magnetometer", "Beginning ICM20948 in I2C mode"); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
|
|
||||||
|
_mag->startupMagnetometer();
|
||||||
|
if (_mag->status != ICM_20948_Stat_Ok){ return false; }
|
||||||
|
_msg.success = true;
|
||||||
|
#elif FT_ENABLED(USE_HMC5883)
|
||||||
|
_msg.success = _mag.begin();
|
||||||
|
#endif
|
||||||
|
return _msg.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool update() {
|
bool update() override {
|
||||||
if (!msg.success) return false;
|
if (!_msg.success) return false;
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#ifndef ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||||
|
#define ICM20948_GET_AGMT_UPDATED_ONCE_PER_LOOP
|
||||||
|
if (_imu->dataReady())
|
||||||
|
{
|
||||||
|
_imu->getAGMT();
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_msg.rpy[0] = _mag->magX();
|
||||||
|
_msg.rpy[1] = _mag->magY();
|
||||||
|
_msg.rpy[2] = _mag->magZ();
|
||||||
|
|
||||||
|
#elif FT_ENABLED(USE_HMC5883)
|
||||||
sensors_event_t event;
|
sensors_event_t event;
|
||||||
bool updated = _mag.getEvent(&event);
|
bool updated = _mag.getEvent(&event);
|
||||||
if (!updated) return false;
|
if (!updated) return false;
|
||||||
msg.rpy[0] = event.magnetic.x;
|
_msg.rpy[0] = event.magnetic.x;
|
||||||
msg.rpy[1] = event.magnetic.y;
|
_msg.rpy[1] = event.magnetic.y;
|
||||||
msg.rpy[2] = event.magnetic.z;
|
_msg.rpy[2] = event.magnetic.z;
|
||||||
msg.heading = atan2(event.magnetic.y, event.magnetic.x);
|
#endif
|
||||||
msg.heading += declinationAngle;
|
_msg.heading = atan2(_msg.rpy[1], _msg.rpy[0]); // atan2(y, x)
|
||||||
if (msg.heading < 0) msg.heading += 2 * PI;
|
_msg.heading += declinationAngle;
|
||||||
if (msg.heading > 2 * PI) msg.heading -= 2 * PI;
|
if (_msg.heading < 0) _msg.heading += 2 * PI;
|
||||||
msg.heading *= 180 / M_PI;
|
if (_msg.heading > 2 * PI) _msg.heading -= 2 * PI;
|
||||||
|
_msg.heading *= 180 / M_PI;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
float getMagX() { return msg.rpy[0]; }
|
float getMagX() { return _msg.rpy[0]; }
|
||||||
|
|
||||||
float getMagY() { return msg.rpy[1]; }
|
float getMagY() { return _msg.rpy[1]; }
|
||||||
|
|
||||||
float getMagZ() { return msg.rpy[2]; }
|
float getMagZ() { return _msg.rpy[2]; }
|
||||||
|
|
||||||
float getHeading() { return msg.heading; }
|
float getHeading() { return _msg.heading; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
#define SPI_PORT SPI // TODO in periphearals_seetings.h
|
||||||
|
#define CS_PIN 2
|
||||||
|
ICM_20948_SPI* _mag;
|
||||||
|
#else
|
||||||
|
//#define WIRE_PORT Wire
|
||||||
|
ICM_20948_I2C* _mag;
|
||||||
|
#endif
|
||||||
|
#elif FT_ENABLED(USE_HMC5883)
|
||||||
Adafruit_HMC5883_Unified _mag {12345};
|
Adafruit_HMC5883_Unified _mag {12345};
|
||||||
MagnetometerMsg msg;
|
#endif
|
||||||
const float declinationAngle = 0.22;
|
const float declinationAngle = 0.22;
|
||||||
};
|
};
|
||||||
@@ -87,10 +87,10 @@ class Peripherals : public StatefulService<PeripheralsConfiguration> {
|
|||||||
|
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
char message[MAX_ESP_IMU_SIZE];
|
char message[MAX_ESP_IMU_SIZE];
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||||
IMU _imu;
|
IMU _imu;
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_HMC5883)
|
#if FT_ENABLED(USE_HMC5883) || FT_ENABLED(USE_ICM20948)
|
||||||
Magnetometer _mag;
|
Magnetometer _mag;
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_BMP180)
|
#if FT_ENABLED(USE_BMP180)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class SensorBase {
|
|||||||
public:
|
public:
|
||||||
SensorBase() {}
|
SensorBase() {}
|
||||||
|
|
||||||
virtual bool initialize() = 0;
|
virtual bool initialize(void* _arg) = 0;
|
||||||
|
|
||||||
virtual bool update() = 0;
|
virtual bool update() = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#define SCL_PIN SCL
|
#define SCL_PIN SCL
|
||||||
#endif
|
#endif
|
||||||
#ifndef I2C_FREQUENCY
|
#ifndef I2C_FREQUENCY
|
||||||
#define I2C_FREQUENCY 1000000UL
|
#define I2C_FREQUENCY 400000UL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class PinConfig {
|
class PinConfig {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <PsychicHttp.h>
|
#include <PsychicHttp.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
// #include <communication/websocket_adapter.h>
|
#include <communication/websocket_adapter.h>
|
||||||
#include <filesystem.h>
|
#include <filesystem.h>
|
||||||
#include <global.h>
|
#include <global.h>
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
@@ -25,7 +25,7 @@ void sleep();
|
|||||||
void status(JsonObject &root);
|
void status(JsonObject &root);
|
||||||
void metrics(JsonObject &root);
|
void metrics(JsonObject &root);
|
||||||
|
|
||||||
void emitMetrics();
|
void emitMetrics(Websocket &socket);
|
||||||
|
|
||||||
const char *resetReason(esp_reset_reason_t reason);
|
const char *resetReason(esp_reset_reason_t reason);
|
||||||
} // namespace system_service
|
} // namespace system_service
|
||||||
@@ -39,3 +39,49 @@
|
|||||||
name##_count = 0; \
|
name##_count = 0; \
|
||||||
last_time = esp_timer_get_time() / 1000; \
|
last_time = esp_timer_get_time() / 1000; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CALLS_PER_SECOND_TIMED_START_TICK(name, function) \
|
||||||
|
static uint64_t name##_##function##_start = 0; \
|
||||||
|
static uint64_t name##_##function##_total_time = 0; \
|
||||||
|
static uint64_t name##_##function##_call_count = 0; \
|
||||||
|
name##_##function##_start = esp_timer_get_time();
|
||||||
|
|
||||||
|
#define CALLS_PER_SECOND_TIMED_END_TICK(name, function) \
|
||||||
|
name##_##function##_total_time += (esp_timer_get_time() - name##_##function##_start); \
|
||||||
|
name##_##function##_call_count++;
|
||||||
|
|
||||||
|
#define CALLS_PER_SECOND_TIMED_CALL(name, function, call) \
|
||||||
|
static uint64_t name##_##function##_total_time = 0; \
|
||||||
|
static uint64_t name##_##function##_call_count = 0; \
|
||||||
|
do { \
|
||||||
|
uint64_t name##_##function##_start = esp_timer_get_time(); \
|
||||||
|
call; \
|
||||||
|
name##_##function##_total_time += (esp_timer_get_time() - name##_##function##_start); \
|
||||||
|
name##_##function##_call_count++; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CALLS_PER_SECOND_TIMED_FUNC_PRINT(name, function) \
|
||||||
|
if (name##_##function##_call_count > 0) { \
|
||||||
|
uint64_t avg = name##_##function##_total_time / name##_##function##_call_count; \
|
||||||
|
if (avg < 1000) { \
|
||||||
|
ESP_LOGI("Timing", " %s: %llu us (avg over %llu calls)", \
|
||||||
|
#function, avg, name##_##function##_call_count); \
|
||||||
|
} else { \
|
||||||
|
ESP_LOGI("Timing", " %s: %llu ms (avg over %llu calls)", \
|
||||||
|
#function, avg / 1000, name##_##function##_call_count); \
|
||||||
|
} \
|
||||||
|
name##_##function##_total_time = 0; \
|
||||||
|
name##_##function##_call_count = 0; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CALLS_PER_SECOND_TIMED(name, ...) \
|
||||||
|
do { \
|
||||||
|
static uint64_t name##_last_print = 0; \
|
||||||
|
uint64_t name##_current_time = esp_timer_get_time() / 1000; \
|
||||||
|
if (name##_current_time - name##_last_print >= 1000) { \
|
||||||
|
ESP_LOGI("Timing", "=== %s Average Timings ===", #name); \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
name##_last_print = name##_current_time; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ from pathlib import Path
|
|||||||
|
|
||||||
Import("env")
|
Import("env")
|
||||||
|
|
||||||
filesystem_dir = env["PROJECT_DIR"] + "/data"
|
filesystem_dir = env["PROJECT_DIR"] + "/esp32/data"
|
||||||
|
|
||||||
Path(filesystem_dir).mkdir(exist_ok=True)
|
Path(filesystem_dir).mkdir(exist_ok=True)
|
||||||
@@ -12,11 +12,14 @@ void printFeatureConfiguration() {
|
|||||||
ESP_LOGI("Features", "USE_MOTION: %s", USE_MOTION ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_MOTION: %s", USE_MOTION ? "enabled" : "disabled");
|
||||||
|
|
||||||
// Sensors
|
// Sensors
|
||||||
|
ESP_LOGI("Features", "USE_ICM20948: %s", USE_ICM20948 ? "enabled" : "disabled");
|
||||||
ESP_LOGI("Features", "USE_BNO055: %s", USE_BNO055 ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_BNO055: %s", USE_BNO055 ? "enabled" : "disabled");
|
||||||
ESP_LOGI("Features", "USE_MPU6050: %s", USE_MPU6050 ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_MPU6050: %s", USE_MPU6050 ? "enabled" : "disabled");
|
||||||
ESP_LOGI("Features", "USE_HMC5883: %s", USE_HMC5883 ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_HMC5883: %s", USE_HMC5883 ? "enabled" : "disabled");
|
||||||
ESP_LOGI("Features", "USE_BMP180: %s", USE_BMP180 ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_BMP180: %s", USE_BMP180 ? "enabled" : "disabled");
|
||||||
ESP_LOGI("Features", "USE_USS: %s", USE_USS ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_USS: %s", USE_USS ? "enabled" : "disabled");
|
||||||
|
ESP_LOGI("Features", "USE_PAJ7620U2: %s", USE_PAJ7620U2 ? "enabled" : "disabled");
|
||||||
|
|
||||||
|
|
||||||
// Peripherals
|
// Peripherals
|
||||||
ESP_LOGI("Features", "USE_PCA9685: %s", USE_PCA9685 ? "enabled" : "disabled");
|
ESP_LOGI("Features", "USE_PCA9685: %s", USE_PCA9685 ? "enabled" : "disabled");
|
||||||
@@ -31,8 +34,8 @@ void printFeatureConfiguration() {
|
|||||||
|
|
||||||
void features(JsonObject &root) {
|
void features(JsonObject &root) {
|
||||||
root["camera"] = USE_CAMERA ? true : false;
|
root["camera"] = USE_CAMERA ? true : false;
|
||||||
root["imu"] = (USE_MPU6050 || USE_BNO055) ? true : false;
|
root["imu"] = (USE_MPU6050 || USE_BNO055 || USE_ICM20948) ? true : false;
|
||||||
root["mag"] = (USE_HMC5883 || USE_BNO055) ? true : false;
|
root["mag"] = (USE_HMC5883 || USE_BNO055 || USE_ICM20948) ? true : false;
|
||||||
root["bmp"] = USE_BMP180 ? true : false;
|
root["bmp"] = USE_BMP180 ? true : false;
|
||||||
root["sonar"] = USE_USS ? true : false;
|
root["sonar"] = USE_USS ? true : false;
|
||||||
root["servo"] = USE_PCA9685 ? true : false;
|
root["servo"] = USE_PCA9685 ? true : false;
|
||||||
|
|||||||
+22
-7
@@ -124,15 +124,20 @@ void IRAM_ATTR SpotControlLoopEntry(void *) {
|
|||||||
motionService.begin();
|
motionService.begin();
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
CALLS_PER_SECOND(SpotControlLoopEntry);
|
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, peripherals_update, peripherals.update());
|
||||||
peripherals.update();
|
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, motionService_update, motionService.update(&peripherals));
|
||||||
motionService.update(&peripherals);
|
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, servoController_setAngles, servoController.setAngles(motionService.getAngles()));
|
||||||
servoController.setAngles(motionService.getAngles());
|
CALLS_PER_SECOND_TIMED_CALL(SpotControlLoopEntry, servoController_update, servoController.update());
|
||||||
servoController.update();
|
|
||||||
#if FT_ENABLED(USE_WS2812)
|
#if FT_ENABLED(USE_WS2812)
|
||||||
ledService.loop();
|
ledService.loop();
|
||||||
#endif
|
#endif
|
||||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
// CALLS_PER_SECOND_TIMED(SpotControlLoopEntry,
|
||||||
|
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, peripherals_update)
|
||||||
|
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, motionService_update)
|
||||||
|
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, servoController_setAngles)
|
||||||
|
// CALLS_PER_SECOND_TIMED_FUNC_PRINT(SpotControlLoopEntry, servoController_update)
|
||||||
|
// );
|
||||||
|
// vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +149,10 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
|||||||
MDNS.setInstanceName(APP_NAME);
|
MDNS.setInstanceName(APP_NAME);
|
||||||
apService.begin();
|
apService.begin();
|
||||||
|
|
||||||
|
#if FT_ENABLED(USE_CAMERA)
|
||||||
|
cameraService.begin();
|
||||||
|
#endif
|
||||||
|
|
||||||
setupServer();
|
setupServer();
|
||||||
|
|
||||||
socket.begin();
|
socket.begin();
|
||||||
@@ -153,7 +162,13 @@ void IRAM_ATTR serviceLoopEntry(void *) {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
wifiService.loop();
|
wifiService.loop();
|
||||||
apService.loop();
|
apService.loop();
|
||||||
EXECUTE_EVERY_N_MS(2000, system_service::emitMetrics());
|
EXECUTE_EVERY_N_MS(2000, system_service::emitMetrics(socket));
|
||||||
|
EXECUTE_EVERY_N_MS(500, {
|
||||||
|
JsonDocument doc;
|
||||||
|
JsonVariant results = doc.to<JsonVariant>();
|
||||||
|
peripherals.getIMUResult(results);
|
||||||
|
socket.emit(EVENT_IMU, results);
|
||||||
|
});
|
||||||
|
|
||||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,18 +15,40 @@ void Peripherals::begin() {
|
|||||||
|
|
||||||
updatePins();
|
updatePins();
|
||||||
|
|
||||||
|
#if FT_ENABLED(USE_ICM20948)
|
||||||
|
#if FT_ENABLED(USE_ICM20948_SPIMODE) > 0
|
||||||
|
ICM_20948_SPI* icm20948 = new ICM_20948_SPI;
|
||||||
|
#else
|
||||||
|
ICM_20948_I2C* icm20948 = new ICM_20948_I2C;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// --- IMU ---
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
||||||
if (!_imu.initialize()) ESP_LOGE("IMUService", "IMU initialize failed");
|
if (!_imu.initialize(nullptr)) ESP_LOGE("Peripherals", "IMU initialize failed");
|
||||||
|
#elif FT_ENABLED(USE_ICM20948)
|
||||||
|
if (!_imu.initialize(icm20948)) ESP_LOGE("Peripherals", "IMU initialize failed (ICM20948)");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// --- MAGNETOMETER ---
|
||||||
#if FT_ENABLED(USE_HMC5883)
|
#if FT_ENABLED(USE_HMC5883)
|
||||||
if (!_mag.initialize()) ESP_LOGE("IMUService", "MAG initialize failed");
|
if (!_mag.initialize(nullptr)) ESP_LOGE("Peripherals", "MAG initialize failed");
|
||||||
|
#elif FT_ENABLED(USE_ICM20948)
|
||||||
|
if (!_mag.initialize(icm20948)) ESP_LOGE("Peripherals", "MAG initialize failed (ICM20948)");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// --- BMP ---
|
||||||
#if FT_ENABLED(USE_BMP180)
|
#if FT_ENABLED(USE_BMP180)
|
||||||
if (!_bmp.initialize()) ESP_LOGE("IMUService", "BMP initialize failed");
|
if (!_bmp.initialize(nullptr)) ESP_LOGE("Peripherals", "BMP initialize failed");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// --- GESTURE ---
|
||||||
#if FT_ENABLED(USE_PAJ7620U2)
|
#if FT_ENABLED(USE_PAJ7620U2)
|
||||||
if (!_gesture.initialize()) ESP_LOGE("IMUService", "Gesture initialize failed");
|
if (!_gesture.initialize(nullptr)) ESP_LOGE("Peripherals", "Gesture initialize failed");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// --- SONAR ---
|
||||||
#if FT_ENABLED(USE_USS)
|
#if FT_ENABLED(USE_USS)
|
||||||
_left_sonar = std::make_unique<NewPing>(USS_LEFT_PIN, USS_LEFT_PIN, MAX_DISTANCE);
|
_left_sonar = std::make_unique<NewPing>(USS_LEFT_PIN, USS_LEFT_PIN, MAX_DISTANCE);
|
||||||
_right_sonar = std::make_unique<NewPing>(USS_RIGHT_PIN, USS_RIGHT_PIN, MAX_DISTANCE);
|
_right_sonar = std::make_unique<NewPing>(USS_RIGHT_PIN, USS_RIGHT_PIN, MAX_DISTANCE);
|
||||||
@@ -34,11 +56,25 @@ void Peripherals::begin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Peripherals::update() {
|
void Peripherals::update() {
|
||||||
readImu();
|
bool res = true;
|
||||||
readMag();
|
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_imu, res = readImu());
|
||||||
EXECUTE_EVERY_N_MS(100, { readGesture(); });
|
#ifdef FT_ENABLED(USE_ICM20948)
|
||||||
EXECUTE_EVERY_N_MS(500, { readBMP(); });
|
// IF ICM_20948 fails to get IMU, it means that mag also does not have new data
|
||||||
EXECUTE_EVERY_N_MS(500, { readSonar(); });
|
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_mag, if (res) { res = readMag(); } );
|
||||||
|
#else
|
||||||
|
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_mag, res = readMag());
|
||||||
|
#endif
|
||||||
|
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_gesture, EXECUTE_EVERY_N_MS(100, { readGesture(); }) );
|
||||||
|
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_bmp, EXECUTE_EVERY_N_MS(500, { readBMP(); }) );
|
||||||
|
CALLS_PER_SECOND_TIMED_CALL(Peripherals_update, read_sonar, EXECUTE_EVERY_N_MS(500, { readSonar(); }) );
|
||||||
|
|
||||||
|
CALLS_PER_SECOND_TIMED(Peripherals_update,
|
||||||
|
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_imu)
|
||||||
|
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_mag)
|
||||||
|
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_gesture)
|
||||||
|
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_bmp)
|
||||||
|
CALLS_PER_SECOND_TIMED_FUNC_PRINT(Peripherals_update, read_sonar)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Peripherals::updatePins() {
|
void Peripherals::updatePins() {
|
||||||
@@ -48,6 +84,7 @@ void Peripherals::updatePins() {
|
|||||||
|
|
||||||
if (state().sda != -1 && state().scl != -1) {
|
if (state().sda != -1 && state().scl != -1) {
|
||||||
Wire.begin(state().sda, state().scl, state().frequency);
|
Wire.begin(state().sda, state().scl, state().frequency);
|
||||||
|
ESP_LOGI("Peripherals", "Starting Wire with SDA=%d, SCL=%d, FREQ=%d", state().sda, state().scl, state().frequency);
|
||||||
i2c_active = true;
|
i2c_active = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +116,7 @@ void Peripherals::scanI2C(uint8_t lower, uint8_t higher) {
|
|||||||
/* IMU FUNCTIONS */
|
/* IMU FUNCTIONS */
|
||||||
bool Peripherals::readImu() {
|
bool Peripherals::readImu() {
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||||
beginTransaction();
|
beginTransaction();
|
||||||
updated = _imu.update();
|
updated = _imu.update();
|
||||||
endTransaction();
|
endTransaction();
|
||||||
@@ -89,7 +126,7 @@ bool Peripherals::readImu() {
|
|||||||
|
|
||||||
bool Peripherals::readMag() {
|
bool Peripherals::readMag() {
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
#if FT_ENABLED(USE_HMC5883)
|
#if FT_ENABLED(USE_HMC5883 || USE_ICM20948)
|
||||||
beginTransaction();
|
beginTransaction();
|
||||||
updated = _mag.update();
|
updated = _mag.update();
|
||||||
endTransaction();
|
endTransaction();
|
||||||
@@ -101,7 +138,7 @@ bool Peripherals::readBMP() {
|
|||||||
bool updated = false;
|
bool updated = false;
|
||||||
#if FT_ENABLED(USE_BMP180)
|
#if FT_ENABLED(USE_BMP180)
|
||||||
beginTransaction();
|
beginTransaction();
|
||||||
updated = _bmp.readBarometer();
|
updated = _bmp.update();
|
||||||
endTransaction();
|
endTransaction();
|
||||||
#endif
|
#endif
|
||||||
return updated;
|
return updated;
|
||||||
@@ -127,7 +164,7 @@ void Peripherals::readSonar() {
|
|||||||
|
|
||||||
float Peripherals::angleX() {
|
float Peripherals::angleX() {
|
||||||
return
|
return
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||||
_imu.getAngleX();
|
_imu.getAngleX();
|
||||||
#else
|
#else
|
||||||
0;
|
0;
|
||||||
@@ -136,7 +173,7 @@ float Peripherals::angleX() {
|
|||||||
|
|
||||||
float Peripherals::angleY() {
|
float Peripherals::angleY() {
|
||||||
return
|
return
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||||
_imu.getAngleY();
|
_imu.getAngleY();
|
||||||
#else
|
#else
|
||||||
0;
|
0;
|
||||||
@@ -145,7 +182,7 @@ float Peripherals::angleY() {
|
|||||||
|
|
||||||
float Peripherals::angleZ() {
|
float Peripherals::angleZ() {
|
||||||
return
|
return
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||||
_imu.getAngleZ();
|
_imu.getAngleZ();
|
||||||
#else
|
#else
|
||||||
0;
|
0;
|
||||||
@@ -165,14 +202,17 @@ float Peripherals::leftDistance() { return _left_distance; }
|
|||||||
float Peripherals::rightDistance() { return _right_distance; }
|
float Peripherals::rightDistance() { return _right_distance; }
|
||||||
|
|
||||||
void Peripherals::getIMUResult(JsonVariant &root) {
|
void Peripherals::getIMUResult(JsonVariant &root) {
|
||||||
#if FT_ENABLED(USE_MPU6050 || USE_BNO055)
|
#if FT_ENABLED(USE_MPU6050 || USE_BNO055 || USE_ICM20948)
|
||||||
_imu.getResults(root);
|
JsonVariant imu = root["imu"].to<JsonVariant>();
|
||||||
|
_imu.getResults(imu);
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_HMC5883)
|
#if FT_ENABLED(USE_HMC5883 || USE_ICM20948) // TODO:
|
||||||
_mag.getResults(root);
|
JsonVariant mag = root["mag"].to<JsonVariant>();
|
||||||
|
_mag.getResults(mag);
|
||||||
#endif
|
#endif
|
||||||
#if FT_ENABLED(USE_BMP180)
|
#if FT_ENABLED(USE_BMP180)
|
||||||
_bmp.getResults(root);
|
JsonVariant bmp = root["bmp"].to<JsonVariant>();
|
||||||
|
_bmp.getResults(bmp);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,13 +121,14 @@ void metrics(JsonObject &root) {
|
|||||||
root["core_temp"] = temperatureRead();
|
root["core_temp"] = temperatureRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitMetrics() {
|
void emitMetrics(Websocket &socket) {
|
||||||
// if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
|
if (!socket.hasSubscribers(EVENT_ANALYTICS)) return;
|
||||||
// analyticsDoc.clear();
|
|
||||||
// JsonObject root = analyticsDoc.to<JsonObject>();
|
JsonDocument doc;
|
||||||
// system_service::metrics(root);
|
JsonObject root = doc.to<JsonObject>();
|
||||||
// JsonVariant data = analyticsDoc.as<JsonVariant>();
|
system_service::metrics(root);
|
||||||
// socket.emit(EVENT_ANALYTICS, data);
|
JsonVariant data = doc.as<JsonVariant>();
|
||||||
|
socket.emit(EVENT_ANALYTICS, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *resetReason(esp_reset_reason_t reason) {
|
const char *resetReason(esp_reset_reason_t reason) {
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ build_flags =
|
|||||||
-D USS_RIGHT_PIN=14
|
-D USS_RIGHT_PIN=14
|
||||||
-D SDA_PIN=47
|
-D SDA_PIN=47
|
||||||
-D SCL_PIN=21
|
-D SCL_PIN=21
|
||||||
|
-D SPI_SCK=41
|
||||||
|
-D SPI_MISO=19
|
||||||
|
-D SPI_MOSI=20
|
||||||
|
-D ICM20948_SPI_CS=2 # Only needed if ICM20948 is in SPI mode
|
||||||
|
|
||||||
[env:seeed-xiao-esp32s3]
|
[env:seeed-xiao-esp32s3]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
@@ -90,6 +94,7 @@ build_flags =
|
|||||||
${factory_settings.build_flags}
|
${factory_settings.build_flags}
|
||||||
${features.build_flags}
|
${features.build_flags}
|
||||||
${build_settings.build_flags}
|
${build_settings.build_flags}
|
||||||
|
-D SPI_PORT=SPI # Define which spi port to use for external components
|
||||||
-D CORE_DEBUG_LEVEL=4
|
-D CORE_DEBUG_LEVEL=4
|
||||||
-D register=
|
-D register=
|
||||||
-std=gnu++2a
|
-std=gnu++2a
|
||||||
@@ -116,6 +121,7 @@ lib_deps =
|
|||||||
adafruit/Adafruit Unified Sensor@^1.1.14
|
adafruit/Adafruit Unified Sensor@^1.1.14
|
||||||
adafruit/Adafruit BNO055@^1.6.4
|
adafruit/Adafruit BNO055@^1.6.4
|
||||||
FastLED@3.5.0
|
FastLED@3.5.0
|
||||||
|
sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@^1.3.2
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:esp32/scripts/pre_build.py
|
pre:esp32/scripts/pre_build.py
|
||||||
pre:esp32/scripts/build_app.py
|
pre:esp32/scripts/build_app.py
|
||||||
|
|||||||
+22
-4
@@ -15,7 +15,11 @@ from src.controllers import Controller, GUIController, WebSocketController
|
|||||||
|
|
||||||
class SpotMicroSimulation:
|
class SpotMicroSimulation:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, controller: Controller, env: Optional[QuadrupedEnv] = None, terrain_type: TerrainType = TerrainType.FLAT
|
self,
|
||||||
|
controller: Controller,
|
||||||
|
env: Optional[QuadrupedEnv] = None,
|
||||||
|
terrain_type: TerrainType = TerrainType.FLAT,
|
||||||
|
dt: float = 1.0 / 240,
|
||||||
):
|
):
|
||||||
print("Initializing Spot Micro simulation...")
|
print("Initializing Spot Micro simulation...")
|
||||||
try:
|
try:
|
||||||
@@ -23,7 +27,7 @@ class SpotMicroSimulation:
|
|||||||
self.env = env
|
self.env = env
|
||||||
print("Using existing environment")
|
print("Using existing environment")
|
||||||
else:
|
else:
|
||||||
self.env = QuadrupedEnv(terrain_type=terrain_type)
|
self.env = QuadrupedEnv(terrain_type=terrain_type, dt=dt)
|
||||||
print("Environment created successfully")
|
print("Environment created successfully")
|
||||||
|
|
||||||
print(f"Robot ID: {self.env.robot.robot_id}")
|
print(f"Robot ID: {self.env.robot.robot_id}")
|
||||||
@@ -78,7 +82,7 @@ class SpotMicroSimulation:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.gait = GaitController(standby)
|
self.gait = GaitController(standby)
|
||||||
self.dt = 1.0 / 240
|
self.dt = dt
|
||||||
|
|
||||||
def step(self):
|
def step(self):
|
||||||
self.controller.update(self.body_state, self.gait_state, self.dt)
|
self.controller.update(self.body_state, self.gait_state, self.dt)
|
||||||
@@ -87,10 +91,24 @@ class SpotMicroSimulation:
|
|||||||
joints = self.kinematics.inverse_kinematics(self.body_state)
|
joints = self.kinematics.inverse_kinematics(self.body_state)
|
||||||
joints = joints * self.joint_directions
|
joints = joints * self.joint_directions
|
||||||
|
|
||||||
_, _, done, truncated, _ = self.env.step(joints)
|
obs, _, done, truncated, _ = self.env.step(joints)
|
||||||
|
|
||||||
|
self._print_mpu6050_data(obs)
|
||||||
|
|
||||||
return joints, done, truncated
|
return joints, done, truncated
|
||||||
|
|
||||||
|
def _print_mpu6050_data(self, obs):
|
||||||
|
accel = obs[0:3]
|
||||||
|
gyro = obs[3:6]
|
||||||
|
heading = obs[6]
|
||||||
|
altitude = obs[7]
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"MPU6050: Accel({accel[0]:8.3f}, {accel[1]:8.3f}, {accel[2]:8.3f}) "
|
||||||
|
f"Gyro({gyro[0]:8.3f}, {gyro[1]:8.3f}, {gyro[2]:8.3f}) "
|
||||||
|
f"Mag({heading:8.3f}) Baro({altitude:8.3f})"
|
||||||
|
)
|
||||||
|
|
||||||
def run_sync(self):
|
def run_sync(self):
|
||||||
try:
|
try:
|
||||||
while self.controller.is_running():
|
while self.controller.is_running():
|
||||||
|
|||||||
@@ -38,22 +38,31 @@ class QuadrupedRobot:
|
|||||||
return [p.getJointInfo(self.robot_id, idx)[1].decode("utf-8") for idx in self.movable_joint_indices]
|
return [p.getJointInfo(self.robot_id, idx)[1].decode("utf-8") for idx in self.movable_joint_indices]
|
||||||
|
|
||||||
def get_observation(self):
|
def get_observation(self):
|
||||||
position, orientation = p.getBasePositionAndOrientation(self.robot_id)
|
pos_w, quat_wb = p.getBasePositionAndOrientation(self.robot_id)
|
||||||
orientation = p.getEulerFromQuaternion(orientation)
|
v_w, w_w = p.getBaseVelocity(self.robot_id)
|
||||||
velocity, angular_velocity = p.getBaseVelocity(self.robot_id)
|
|
||||||
joint_states = p.getJointStates(self.robot_id, self.movable_joint_indices)
|
R = np.array(p.getMatrixFromQuaternion(quat_wb), dtype=np.float32).reshape(3, 3)
|
||||||
joint_positions = [state[0] for state in joint_states]
|
|
||||||
joint_velocities = [state[1] for state in joint_states]
|
if hasattr(self, "prev_velocity") and self.prev_velocity is not None:
|
||||||
return np.concatenate(
|
dt = 1.0 / 240.0
|
||||||
[
|
accel_world = (v_w - self.prev_velocity) / dt
|
||||||
position,
|
else:
|
||||||
orientation,
|
accel_world = np.array([0.0, 0.0, 0.0])
|
||||||
velocity,
|
|
||||||
angular_velocity,
|
accel_body = R.T @ np.asarray(accel_world, dtype=np.float32)
|
||||||
joint_positions,
|
gravity_body = R.T @ np.array([0, 0, -9.81], dtype=np.float32)
|
||||||
joint_velocities,
|
accel_body += gravity_body
|
||||||
]
|
|
||||||
).astype(np.float32)
|
gyro_body = np.degrees(R.T @ np.asarray(w_w, dtype=np.float32))
|
||||||
|
|
||||||
|
euler = p.getEulerFromQuaternion(quat_wb)
|
||||||
|
heading = np.degrees(euler[2])
|
||||||
|
|
||||||
|
altitude = np.array([pos_w[2]], dtype=np.float32)
|
||||||
|
|
||||||
|
self.prev_velocity = np.array(v_w)
|
||||||
|
|
||||||
|
return np.concatenate([accel_body, gyro_body, [heading], altitude]).astype(np.float32)
|
||||||
|
|
||||||
def apply_action(self, action):
|
def apply_action(self, action):
|
||||||
for i, position in enumerate(action):
|
for i, position in enumerate(action):
|
||||||
@@ -78,6 +87,7 @@ class QuadrupedEnv(gym.Env):
|
|||||||
target_velocity: float = 0.5,
|
target_velocity: float = 0.5,
|
||||||
max_steps: int = 1000,
|
max_steps: int = 1000,
|
||||||
distance_limit: float = 10.0,
|
distance_limit: float = 10.0,
|
||||||
|
dt: float = 1.0 / 240,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
if render_mode == "human":
|
if render_mode == "human":
|
||||||
@@ -85,7 +95,7 @@ class QuadrupedEnv(gym.Env):
|
|||||||
else:
|
else:
|
||||||
p.connect(p.DIRECT)
|
p.connect(p.DIRECT)
|
||||||
|
|
||||||
self.observation_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(36,), dtype=np.float32)
|
self.observation_space = gym.spaces.Box(low=-np.inf, high=np.inf, shape=(8,), dtype=np.float32)
|
||||||
self.action_space = gym.spaces.Box(low=-1, high=1, shape=(12,), dtype=np.float32)
|
self.action_space = gym.spaces.Box(low=-1, high=1, shape=(12,), dtype=np.float32)
|
||||||
p.setAdditionalSearchPath(pybullet_data.getDataPath())
|
p.setAdditionalSearchPath(pybullet_data.getDataPath())
|
||||||
|
|
||||||
@@ -93,9 +103,10 @@ class QuadrupedEnv(gym.Env):
|
|||||||
self.render_mode = render_mode
|
self.render_mode = render_mode
|
||||||
self.target_velocity = target_velocity
|
self.target_velocity = target_velocity
|
||||||
self.max_steps = max_steps
|
self.max_steps = max_steps
|
||||||
|
self.prev_accel = np.zeros(3)
|
||||||
|
self.estimated_velocity = np.zeros(3)
|
||||||
self.current_step = 0
|
self.current_step = 0
|
||||||
|
self.dt = dt
|
||||||
self.prev_velocity = None
|
|
||||||
|
|
||||||
self._setup_world()
|
self._setup_world()
|
||||||
if render_mode == "human":
|
if render_mode == "human":
|
||||||
@@ -107,7 +118,7 @@ class QuadrupedEnv(gym.Env):
|
|||||||
self.robot = QuadrupedRobot("src/resources/spot.urdf")
|
self.robot = QuadrupedRobot("src/resources/spot.urdf")
|
||||||
self._load_terrain(self.terrain_type)
|
self._load_terrain(self.terrain_type)
|
||||||
p.setGravity(0, 0, -9.8)
|
p.setGravity(0, 0, -9.8)
|
||||||
p.setTimeStep(1 / 240)
|
p.setTimeStep(self.dt)
|
||||||
if self.render_mode == "human":
|
if self.render_mode == "human":
|
||||||
self.gui = GUI(self.robot.robot_id)
|
self.gui = GUI(self.robot.robot_id)
|
||||||
else:
|
else:
|
||||||
@@ -157,7 +168,7 @@ class QuadrupedEnv(gym.Env):
|
|||||||
|
|
||||||
obs = self.robot.get_observation()
|
obs = self.robot.get_observation()
|
||||||
reward = self.calculate_reward(obs)
|
reward = self.calculate_reward(obs)
|
||||||
done = self.is_done(obs)
|
done = self.is_done()
|
||||||
truncated = self.current_step >= self.max_steps
|
truncated = self.current_step >= self.max_steps
|
||||||
|
|
||||||
return obs, reward, done, truncated, {}
|
return obs, reward, done, truncated, {}
|
||||||
@@ -167,48 +178,42 @@ class QuadrupedEnv(gym.Env):
|
|||||||
# p.disconnect()
|
# p.disconnect()
|
||||||
|
|
||||||
def calculate_reward(self, obs):
|
def calculate_reward(self, obs):
|
||||||
position = obs[:3]
|
accel = obs[0:3]
|
||||||
orientation = obs[3:6]
|
gyro = obs[3:6]
|
||||||
velocity = obs[6:9]
|
heading = obs[6]
|
||||||
angular_velocity = obs[9:12]
|
altitude = obs[7]
|
||||||
|
|
||||||
forward_velocity = velocity[0]
|
self.estimated_velocity = self.estimated_velocity + self.prev_accel * self.dt
|
||||||
|
|
||||||
|
self.prev_accel = accel.copy()
|
||||||
|
|
||||||
|
forward_velocity = self.estimated_velocity[0]
|
||||||
velocity_reward = -abs(forward_velocity - self.target_velocity)
|
velocity_reward = -abs(forward_velocity - self.target_velocity)
|
||||||
|
|
||||||
height_penalty = -abs(position[2] - 0.3) * 0.5
|
height_penalty = -abs(altitude - 0.3) * 0.5
|
||||||
|
|
||||||
roll, pitch, yaw = orientation
|
orientation_penalty = -(abs(gyro[0]) + abs(gyro[1])) * 0.1
|
||||||
orientation_penalty = -(abs(roll) + abs(pitch)) * 1.0
|
|
||||||
|
|
||||||
angular_penalty = -np.sum(np.square(angular_velocity)) * 0.05
|
angular_penalty = -np.sum(np.square(gyro)) * 0.01
|
||||||
|
|
||||||
sideways_velocity_penalty = -abs(velocity[1]) * 0.3
|
lateral_acc_penalty = -abs(accel[1]) * 0.01
|
||||||
|
|
||||||
if self.prev_velocity is not None:
|
vertical_acc_penalty = -abs(accel[2] + 9.81) * 0.01
|
||||||
dt = 1.0 / 240.0
|
|
||||||
acceleration = (velocity - self.prev_velocity) / dt
|
|
||||||
lateral_acc_penalty = -abs(acceleration[1]) * 0.01
|
|
||||||
vertical_acc_penalty = -abs(acceleration[2]) * 0.01
|
|
||||||
else:
|
|
||||||
lateral_acc_penalty = 0
|
|
||||||
vertical_acc_penalty = 0
|
|
||||||
|
|
||||||
self.prev_velocity = velocity.copy()
|
|
||||||
|
|
||||||
total_reward = (
|
total_reward = (
|
||||||
velocity_reward
|
velocity_reward
|
||||||
+ height_penalty
|
+ height_penalty
|
||||||
+ orientation_penalty
|
+ orientation_penalty
|
||||||
+ angular_penalty
|
+ angular_penalty
|
||||||
+ sideways_velocity_penalty
|
|
||||||
+ lateral_acc_penalty
|
+ lateral_acc_penalty
|
||||||
+ vertical_acc_penalty
|
+ vertical_acc_penalty
|
||||||
)
|
)
|
||||||
return total_reward
|
return total_reward
|
||||||
|
|
||||||
def is_done(self, obs):
|
def is_done(self):
|
||||||
position = obs[:3]
|
position, orientation = p.getBasePositionAndOrientation(self.robot.robot_id)
|
||||||
orientation = obs[3:6]
|
orientation = p.getEulerFromQuaternion(orientation)
|
||||||
|
|
||||||
return self._is_fallen(orientation) or self._is_distance_limit_exceeded(position)
|
return self._is_fallen(orientation) or self._is_distance_limit_exceeded(position)
|
||||||
|
|
||||||
def _is_distance_limit_exceeded(self, position):
|
def _is_distance_limit_exceeded(self, position):
|
||||||
@@ -216,9 +221,4 @@ class QuadrupedEnv(gym.Env):
|
|||||||
return distance > self._distance_limit
|
return distance > self._distance_limit
|
||||||
|
|
||||||
def _is_fallen(self, orientation):
|
def _is_fallen(self, orientation):
|
||||||
# orientation = self.spot.GetBaseOrientation()
|
|
||||||
# rot_mat = self._pybullet_client.getMatrixFromQuaternion(orientation)
|
|
||||||
# local_up = rot_mat[6:]
|
|
||||||
# pos = self.spot.GetBasePosition()
|
|
||||||
# return (np.dot(np.asarray([0, 0, 1]), np.asarray(local_up)) < 0.55)
|
|
||||||
return abs(orientation[0]) > 0.85 or abs(orientation[1]) > 0.85
|
return abs(orientation[0]) > 0.85 or abs(orientation[1]) > 0.85
|
||||||
|
|||||||
Reference in New Issue
Block a user