diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp index 3f64b3b3e2..5175a26809 100644 --- a/src/ButtonThread.cpp +++ b/src/ButtonThread.cpp @@ -190,6 +190,20 @@ int32_t ButtonThread::runOnce() case 4: digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); break; +#endif +#if defined(RAK_4631) + // 5 clicks: start accelerometer/magenetometer calibration for 30 seconds + case 5: + if (accelerometerThread) { + accelerometerThread->calibrate(30); + } + break; + // 6 clicks: start accelerometer/magenetometer calibration for 60 seconds + case 6: + if (accelerometerThread) { + accelerometerThread->calibrate(60); + } + break; #endif // No valid multipress action default: diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 3cb39e8ec0..ce416156fd 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -278,6 +278,10 @@ class Screen : public concurrency::OSThread bool hasHeading() { return hasCompass; } long getHeading() { return compassHeading; } + + void setEndCalibration(uint32_t _endCalibrationAt) { endCalibrationAt = _endCalibrationAt; } + uint32_t getEndCalibration() { return endCalibrationAt; } + // functions for display brightness void increaseBrightness(); void decreaseBrightness(); @@ -673,6 +677,8 @@ class Screen : public concurrency::OSThread bool hasCompass = false; float compassHeading; + uint32_t endCalibrationAt; + /// Holds state for debug information DebugInfo debugInfo; diff --git a/src/motion/AccelerometerThread.h b/src/motion/AccelerometerThread.h index 95f09910fd..6e517d6b0f 100755 --- a/src/motion/AccelerometerThread.h +++ b/src/motion/AccelerometerThread.h @@ -48,6 +48,13 @@ class AccelerometerThread : public concurrency::OSThread setIntervalFromNow(0); }; + void calibrate(uint16_t forSeconds) + { + if (sensor) { + sensor->calibrate(forSeconds); + } + } + protected: int32_t runOnce() override { diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp index 6562a651c6..d88ccefc2d 100755 --- a/src/motion/BMX160Sensor.cpp +++ b/src/motion/BMX160Sensor.cpp @@ -16,7 +16,23 @@ bool BMX160Sensor::init() if (sensor.begin()) { // set output data rate sensor.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); - LOG_DEBUG("BMX160 init ok"); + sensor.setGyroRange(eGyroRange_500DPS); + sensor.setAccelRange(eAccelRange_2G); + + // default location for the BMX160 is on the rear of the board with Z negative + sensorConfig.orientation.x = -1; + sensorConfig.orientation.y = -1; + sensorConfig.orientation.z = 1; + + loadState(); + + LOG_INFO("BMX160 MAG calibration center_x: %.4f, center_Y: %.4f, center_Z: %.4f", sensorConfig.mAccel.x, + sensorConfig.mAccel.y, sensorConfig.mAccel.z); + LOG_INFO("BMX160 GYRO calibration center_x: %.4f, center_Y: %.4f, center_Z: %.4f", sensorConfig.gyroAccel.x, + sensorConfig.gyroAccel.y, sensorConfig.gyroAccel.z); + LOG_INFO("BMX160 ORIENT calibration: x=%i, y=%i, z=%i", sensorConfig.orientation.x, sensorConfig.orientation.y, + sensorConfig.orientation.z); + return true; } LOG_DEBUG("BMX160 init failed"); @@ -25,54 +41,42 @@ bool BMX160Sensor::init() int32_t BMX160Sensor::runOnce() { +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) sBmx160SensorData_t magAccel; + sBmx160SensorData_t gyroAccel; sBmx160SensorData_t gAccel; /* Get a new sensor event */ - sensor.getAllData(&magAccel, NULL, &gAccel); - -#if !defined(MESHTASTIC_EXCLUDE_SCREEN) - // experimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 12 * 1000 && millis() < 30 * 1000) { - if (!showingScreen) { - showingScreen = true; - screen->startAlert((FrameCallback)drawFrameCalibration); - } - if (magAccel.x > highestX) - highestX = magAccel.x; - if (magAccel.x < lowestX) - lowestX = magAccel.x; - if (magAccel.y > highestY) - highestY = magAccel.y; - if (magAccel.y < lowestY) - lowestY = magAccel.y; - if (magAccel.z > highestZ) - highestZ = magAccel.z; - if (magAccel.z < lowestZ) - lowestZ = magAccel.z; - } else if (showingScreen && millis() >= 30 * 1000) { - showingScreen = false; - screen->endAlert(); + sensor.getAllData(&magAccel, &gyroAccel, &gAccel); + + if (doMagCalibration) { + getMagCalibrationData(magAccel.x, magAccel.y, magAccel.z); + } else if (doGyroWarning) { + gyroCalibrationWarning(); + } else if (doGyroCalibration) { + getGyroCalibrationData(gyroAccel.x, gyroAccel.y, gyroAccel.z, gAccel.x, gAccel.y, gAccel.z); } - int highestRealX = highestX - (highestX + lowestX) / 2; + // int highestRealX = sensorConfig.mAccel.max.x - (sensorConfig.mAccel.max.x + sensorConfig.mAccel.min.x) / 2; - magAccel.x -= (highestX + lowestX) / 2; - magAccel.y -= (highestY + lowestY) / 2; - magAccel.z -= (highestZ + lowestZ) / 2; - FusionVector ga, ma; - ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board - ga.axis.y = -gAccel.y; - ga.axis.z = gAccel.z; - ma.axis.x = -magAccel.x; - ma.axis.y = -magAccel.y; - ma.axis.z = magAccel.z * 3; + magAccel.x -= sensorConfig.mAccel.x; + magAccel.y -= sensorConfig.mAccel.y; + magAccel.z -= sensorConfig.mAccel.z; + FusionVector ga, ma; + ga.axis.x = gAccel.x * sensorConfig.orientation.x; + ga.axis.y = gAccel.y * sensorConfig.orientation.y; + ga.axis.z = gAccel.z * sensorConfig.orientation.z; + ma.axis.x = magAccel.x * sensorConfig.orientation.x; + ma.axis.y = magAccel.y * sensorConfig.orientation.y; + ma.axis.z = magAccel.z * sensorConfig.orientation.z * 3; + + // Use calibration orientation instead of swap based on CompassOrientation definition // If we're set to one of the inverted positions - if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { - ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); - ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); - } + // if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { + // ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); + // ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); + // } float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); @@ -93,12 +97,31 @@ int32_t BMX160Sensor::runOnce() heading += 270; break; } + + // LOG_DEBUG("MAG Sensor Data: X=%.4f, Y=%.4f, Z=%.4f", magAccel.x, magAccel.y, magAccel.z); + // LOG_DEBUG("ACCEL Sensor Data: X=%.4f, Y=%.4f, Z=%.4f", gAccel.x, gAccel.y, gAccel.z); + // LOG_DEBUG("HEADING Sensor Data: %.1f deg", heading); + // LOG_DEBUG("Gyro Sensor Data: X=%.4f, Y=%.4f, Z=%.4f", gyroAccel.x, gyroAccel.y, gyroAccel.z); + screen->setHeading(heading); #endif return MOTION_SENSOR_CHECK_INTERVAL_MS; } +void BMX160Sensor::calibrate(uint16_t forSeconds) +{ +#if !defined(MESHTASTIC_EXCLUDE_SCREEN) + LOG_INFO("BMX160 calibration started for %is", forSeconds); + + doMagCalibration = true; + firstCalibrationRead = true; + uint16_t calibrateFor = forSeconds * 1000; // calibrate for seconds provided + endMagCalibrationAt = millis() + calibrateFor; + screen->setEndCalibration(endMagCalibrationAt); +#endif +} + #endif #endif \ No newline at end of file diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h index 26f4772719..4cd4d5d1a4 100755 --- a/src/motion/BMX160Sensor.h +++ b/src/motion/BMX160Sensor.h @@ -16,13 +16,12 @@ class BMX160Sensor : public MotionSensor { private: RAK_BMX160 sensor; - bool showingScreen = false; - float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; public: explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice); virtual bool init() override; virtual int32_t runOnce() override; + virtual void calibrate(uint16_t forSeconds) override; }; #else diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp index 242e3709f8..f69389c66f 100755 --- a/src/motion/MotionSensor.cpp +++ b/src/motion/MotionSensor.cpp @@ -2,6 +2,8 @@ #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +char timeRemainingBuffer[20]; + // screen is defined in main.cpp extern graphics::Screen *screen; @@ -29,6 +31,29 @@ ScanI2C::I2CPort MotionSensor::devicePort() return device.address.port; } +#if !MESHTASTIC_EXCLUDE_POWER_FSM +void MotionSensor::wakeScreen() +{ + if (powerFSM.getState() == &stateDARK) { + LOG_DEBUG("Motion wakeScreen detected"); + powerFSM.trigger(EVENT_INPUT); + } +} + +void MotionSensor::buttonPress() +{ + LOG_DEBUG("Motion buttonPress detected"); + powerFSM.trigger(EVENT_PRESS); +} + +#else + +void MotionSensor::wakeScreen() {} + +void MotionSensor::buttonPress() {} + +#endif + #if defined(RAK_4631) & !MESHTASTIC_EXCLUDE_SCREEN void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { @@ -37,6 +62,12 @@ void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_MEDIUM); display->drawString(x, y, "Calibrating\nCompass"); + + uint8_t timeRemaining = (screen->getEndCalibration() - millis()) / 1000; + sprintf(timeRemainingBuffer, "( %02d )", timeRemaining); + display->setFont(FONT_SMALL); + display->drawString(x, y + 40, timeRemainingBuffer); + int16_t compassX = 0, compassY = 0; uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); @@ -51,28 +82,258 @@ void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState display->drawCircle(compassX, compassY, compassDiam / 2); screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); } -#endif -#if !MESHTASTIC_EXCLUDE_POWER_FSM -void MotionSensor::wakeScreen() +void MotionSensor::drawFrameGyroWarning(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { - if (powerFSM.getState() == &stateDARK) { - LOG_DEBUG("Motion wakeScreen detected"); - powerFSM.trigger(EVENT_INPUT); + // int x_offset = display->width() / 2; + // int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->drawString(x, y, "Place Screen Face Up\n& Keep Still"); + + uint8_t timeRemaining = (screen->getEndCalibration() - millis()) / 1000; + sprintf(timeRemainingBuffer, "Starting in ( %02d )", timeRemaining); + display->drawString(x, y + 40, timeRemainingBuffer); +} + +void MotionSensor::drawFrameGyroCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // int x_offset = display->width() / 2; + // int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + display->drawString(x, y, "Calibrating\nGyroscope"); + + uint8_t timeRemaining = (screen->getEndCalibration() - millis()) / 1000; + sprintf(timeRemainingBuffer, "Keep Still ( %02d )", timeRemaining); + display->setFont(FONT_SMALL); + display->drawString(x, y + 40, timeRemainingBuffer); +} + +void MotionSensor::getMagCalibrationData(float x, float y, float z) +{ + if (!showingScreen) { + powerFSM.trigger(EVENT_PRESS); // keep screen alive during calibration + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameCalibration); + } + + if (firstCalibrationRead) { + magCalibrationMinMax.min.x = x; + magCalibrationMinMax.max.x = x; + magCalibrationMinMax.min.y = y; + magCalibrationMinMax.max.y = y; + magCalibrationMinMax.min.z = z; + magCalibrationMinMax.max.z = z; + firstCalibrationRead = false; + } else { + if (x > magCalibrationMinMax.max.x) + magCalibrationMinMax.max.x = x; + if (x < magCalibrationMinMax.min.x) + magCalibrationMinMax.min.x = x; + if (y > magCalibrationMinMax.max.y) + magCalibrationMinMax.max.y = y; + if (y < magCalibrationMinMax.min.y) + magCalibrationMinMax.min.y = y; + if (z > magCalibrationMinMax.max.z) + magCalibrationMinMax.max.z = z; + if (z < magCalibrationMinMax.min.z) + magCalibrationMinMax.min.z = z; + } + + uint32_t now = millis(); + if (now > endMagCalibrationAt) { + sensorConfig.mAccel.x = (magCalibrationMinMax.max.x + magCalibrationMinMax.min.x) / 2; + sensorConfig.mAccel.y = (magCalibrationMinMax.max.y + magCalibrationMinMax.min.y) / 2; + sensorConfig.mAccel.z = (magCalibrationMinMax.max.z + magCalibrationMinMax.min.z) / 2; + + doMagCalibration = false; + endMagCalibrationAt = 0; + magCalibrationMinMax.min.x = 0; + magCalibrationMinMax.max.x = 0; + magCalibrationMinMax.min.y = 0; + magCalibrationMinMax.max.y = 0; + magCalibrationMinMax.min.z = 0; + magCalibrationMinMax.max.z = 0; + showingScreen = false; + screen->endAlert(); + + doGyroWarning = true; + endGyroWarningAt = now + 10000; + screen->setEndCalibration(endGyroWarningAt); } } -void MotionSensor::buttonPress() +void MotionSensor::gyroCalibrationWarning() { - LOG_DEBUG("Motion buttonPress detected"); - powerFSM.trigger(EVENT_PRESS); + if (!showingScreen) { + powerFSM.trigger(EVENT_PRESS); // keep screen alive during calibration + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameGyroWarning); + } + + uint32_t now = millis(); + if (now > endGyroWarningAt) { + doGyroWarning = false; + endGyroWarningAt = 0; + showingScreen = false; + screen->endAlert(); + + doGyroCalibration = true; + endGyroCalibrationAt = now + 10000; + screen->setEndCalibration(endGyroCalibrationAt); + } } +void MotionSensor::getGyroCalibrationData(float g_x, float g_y, float g_z, float a_x, float a_y, float a_z) +{ + if (!showingScreen) { + powerFSM.trigger(EVENT_PRESS); // keep screen alive during calibration + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameGyroCalibration); + } + + gyroCalibrationSum.x += g_x; + gyroCalibrationSum.y += g_y; + gyroCalibrationSum.z += g_z; + + // increment x, y, or z based on greatest accel vector to identify down direction + if (abs(a_x) > abs(a_y) && abs(a_x) > abs(a_z)) { + if (a_x >= 0) { + accelCalibrationSum.x += 1; + } else { + accelCalibrationSum.x += -1; + } + } else if (abs(a_y) > abs(a_x) && abs(a_y) > abs(a_z)) { + if (a_y >= 0) { + accelCalibrationSum.y += 1; + } else { + accelCalibrationSum.y += -1; + } + } else if (abs(a_z) > abs(a_x) && abs(a_z) > abs(a_y)) { + if (a_z >= 0) { + accelCalibrationSum.z += 1; + } else { + accelCalibrationSum.z += -1; + } + } + calibrationCount++; + + LOG_DEBUG("Accel calibration x: %i, y: %i, z: %i", accelCalibrationSum.x, accelCalibrationSum.y, accelCalibrationSum.z); + + uint32_t now = millis(); + if (now > endGyroCalibrationAt) { + sensorConfig.gyroAccel.x = gyroCalibrationSum.x / calibrationCount; + sensorConfig.gyroAccel.y = gyroCalibrationSum.y / calibrationCount; + sensorConfig.gyroAccel.z = gyroCalibrationSum.z / calibrationCount; + + // determine orientation multipliers based on down direction + if (abs(accelCalibrationSum.x) > abs(accelCalibrationSum.y) && abs(accelCalibrationSum.x) > abs(accelCalibrationSum.z)) { + if (accelCalibrationSum.x >= 0) { + // X axis oriented with down positive + sensorConfig.orientation.x = 1; + sensorConfig.orientation.y = 1; + sensorConfig.orientation.z = 1; + } else { + // X axis oriented with down negative + sensorConfig.orientation.x = 1; + sensorConfig.orientation.y = -1; + sensorConfig.orientation.z = -1; + } + } else if (abs(accelCalibrationSum.y) > abs(accelCalibrationSum.x) && + abs(accelCalibrationSum.y) > abs(accelCalibrationSum.z)) { + if (accelCalibrationSum.y >= 0) { + // Y axis oriented with down positive + sensorConfig.orientation.x = 1; + sensorConfig.orientation.y = 1; + sensorConfig.orientation.z = 1; + } else { + // Y axis oriented with down negative + sensorConfig.orientation.x = -1; + sensorConfig.orientation.y = 1; + sensorConfig.orientation.z = -1; + } + } else if (abs(accelCalibrationSum.z) > abs(accelCalibrationSum.x) && + abs(accelCalibrationSum.z) > abs(accelCalibrationSum.y)) { + if (accelCalibrationSum.z >= 0) { + // Z axis oriented with down positive + sensorConfig.orientation.x = 1; + sensorConfig.orientation.y = 1; + sensorConfig.orientation.z = 1; + } else { + // Z axis oriented with down negative + sensorConfig.orientation.x = -1; + sensorConfig.orientation.y = -1; + sensorConfig.orientation.z = 1; + } + } + + LOG_INFO("Gyro center x: %.4f, y: %.4f, z: %.4f", sensorConfig.gyroAccel.x, sensorConfig.gyroAccel.y, + sensorConfig.gyroAccel.z); + LOG_INFO("Orientation vector x: %i, y: %i, z: %i", sensorConfig.orientation.x, sensorConfig.orientation.y, + sensorConfig.orientation.z); + + saveState(); + doGyroCalibration = false; + endGyroCalibrationAt = 0; + accelCalibrationSum.x = 0; + accelCalibrationSum.y = 0; + accelCalibrationSum.z = 0; + gyroCalibrationSum.x = 0; + gyroCalibrationSum.y = 0; + gyroCalibrationSum.z = 0; + showingScreen = false; + screen->endAlert(); + } +} + +void MotionSensor::loadState() +{ +#ifdef FSCom + auto file = FSCom.open(configFileName, FILE_O_READ); + if (file) { + file.read((uint8_t *)&sensorState, MAX_STATE_BLOB_SIZE); + file.close(); + + memcpy(&sensorConfig, &sensorState, sizeof(SensorConfig)); + + LOG_INFO("Motion Sensor config state read from %s", configFileName); + } else { + LOG_INFO("No Motion Sensor config state found (File: %s)", configFileName); + } #else + LOG_ERROR("ERROR: Filesystem not implemented"); +#endif +} -void MotionSensor::wakeScreen() {} +void MotionSensor::saveState() +{ +#ifdef FSCom + memcpy(&sensorState, &sensorConfig, sizeof(SensorConfig)); -void MotionSensor::buttonPress() {} + LOG_INFO("Save MAG calibration center_x: %.4f, center_Y: %.4f, center_Z: %.4f", sensorConfig.mAccel.x, sensorConfig.mAccel.y, + sensorConfig.mAccel.z); + LOG_INFO("Save GYRO calibration center_x: %.4f, center_Y: %.4f, center_Z: %.4f", sensorConfig.gyroAccel.x, + sensorConfig.gyroAccel.y, sensorConfig.gyroAccel.z); + LOG_INFO("Save ORIENT calibration: x=%i, y=%i, z=%i", sensorConfig.orientation.x, sensorConfig.orientation.y, + sensorConfig.orientation.z); + + if (FSCom.exists(configFileName) && !FSCom.remove(configFileName)) { + LOG_WARN("Can't remove old Motion Sensor config state file"); + } + auto file = FSCom.open(configFileName, FILE_O_WRITE); + if (file) { + LOG_INFO("Write Motion Sensor config state to %s", configFileName); + file.write((uint8_t *)&sensorState, MAX_STATE_BLOB_SIZE); + file.flush(); + file.close(); + } else { + LOG_INFO("Can't write Motion Sensor config state (File: %s)", configFileName); + } +#else + LOG_ERROR("ERROR: Filesystem not implemented"); +#endif +} #endif diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h index 78eec54cec..5f5c7b81d8 100755 --- a/src/motion/MotionSensor.h +++ b/src/motion/MotionSensor.h @@ -14,8 +14,32 @@ #include "../graphics/Screen.h" #include "../graphics/ScreenFonts.h" #include "../power.h" +#include "FSCommon.h" #include "Wire.h" +#define MAX_STATE_BLOB_SIZE (256) // pad size to allow for additional saved config parameters (accel, gyro, etc) + +struct xyzInt { + int x = 0; + int y = 0; + int z = 0; +}; +struct xyzFloat { + float x = 0.0; + float y = 0.0; + float z = 0.0; +}; +struct minMaxXYZ { + xyzFloat min; + xyzFloat max; +}; +struct SensorConfig { + xyzFloat mAccel; + xyzFloat gyroAccel; + // xyzFloat gAccel; + xyzInt orientation; +}; + // Base class for motion processing class MotionSensor { @@ -40,6 +64,8 @@ class MotionSensor // Refer to /src/concurrency/OSThread.h for more information inline virtual int32_t runOnce() { return MOTION_SENSOR_CHECK_INTERVAL_MS; }; + virtual void calibrate(uint16_t forSeconds){}; + protected: // Turn on the screen when a tap or motion is detected virtual void wakeScreen(); @@ -47,12 +73,37 @@ class MotionSensor // Register a button press when a double-tap is detected virtual void buttonPress(); + ScanI2C::FoundDevice device; + + SensorConfig sensorConfig; + bool showingScreen = false; + bool doMagCalibration = false; + bool doGyroWarning = false; + bool doGyroCalibration = false; + bool firstCalibrationRead = false; + uint32_t endMagCalibrationAt = 0; + uint32_t endGyroWarningAt = 0; + uint32_t endGyroCalibrationAt = 0; + xyzFloat gyroCalibrationSum; + xyzInt accelCalibrationSum; + minMaxXYZ magCalibrationMinMax; + uint16_t calibrationCount = 0; + + const char *configFileName = "/prefs/motionSensor.dat"; + uint8_t sensorState[MAX_STATE_BLOB_SIZE] = {0}; + #if defined(RAK_4631) & !MESHTASTIC_EXCLUDE_SCREEN // draw an OLED frame (currently only used by the RAK4631 BMX160 sensor) static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawFrameGyroWarning(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + static void drawFrameGyroCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); + + void getMagCalibrationData(float x, float y, float z); + void gyroCalibrationWarning(); + void getGyroCalibrationData(float g_x, float g_y, float g_z, float a_x, float a_y, float a_z); + void loadState(); + void saveState(); #endif - - ScanI2C::FoundDevice device; }; namespace MotionSensorI2C