51 if (value == 0 || (mask & value) == 0)
57 imuConnection = value;
160 recreateFusionFilter();
168 return callHandshake();
179 return callHandshake();
215 recreateFusionFilter();
241bool IMU::ISNewNumber(
const char *dev,
const char *name,
double values[],
char *names[],
int n)
320 double sin_lat = sin(lat_rad);
321 double cos_lat = cos(lat_rad);
325 enuVector.
x = -skyVector.
y;
326 enuVector.
y = (skyVector.
z - (skyVector.
x * sin_lat / cos_lat)) / (cos_lat + (sin_lat * sin_lat / cos_lat));
327 enuVector.
z = (skyVector.
x + enuVector.
y * sin_lat) / cos_lat;
333 enuVector.
x = -skyVector.
y;
334 enuVector.
y = skyVector.
x;
335 enuVector.
z = skyVector.
z;
346 targetIMUVector = enuVector;
350 targetIMUVector.
x = enuVector.
y;
351 targetIMUVector.
y = -enuVector.
x;
352 targetIMUVector.
z = enuVector.
z;
356 targetIMUVector.
x = -enuVector.
y;
357 targetIMUVector.
y = -enuVector.
x;
358 targetIMUVector.
z = enuVector.
z;
367 double qw = last_raw_q_w, qx = last_raw_q_i, qy = last_raw_q_j, qz = last_raw_q_k;
369 double current_x = vx * (1 - 2 * qy * qy - 2 * qz * qz) + vy * (2 * qx * qy - 2 * qz * qw) + vz *
370 (2 * qx * qz + 2 * qy * qw);
371 double current_y = vx * (2 * qx * qy + 2 * qz * qw) + vy * (1 - 2 * qx * qx - 2 * qz * qz) + vz *
372 (2 * qy * qz - 2 * qx * qw);
373 double current_z = vx * (2 * qx * qz - 2 * qy * qw) + vy * (2 * qy * qz + 2 * qx * qw) + vz *
374 (1 - 2 * qx * qx - 2 * qy * qy);
379 double dotProduct = std::max(-1.0, std::min(1.0, currentIMUVector ^ targetIMUVector));
380 double rotationAngle = acos(dotProduct);
382 if (rotationAngle < 1e-6)
395 double rawRollRad, rawPitchRad, rawYawRad;
398 rawRollRad *= rollMultiplier;
399 rawPitchRad *= pitchMultiplier;
400 rawYawRad *= yawMultiplier;
402 double multiplied_i, multiplied_j, multiplied_k, multiplied_w;
403 EulerToQuaternion(rawRollRad, rawPitchRad, rawYawRad, multiplied_i, multiplied_j, multiplied_k, multiplied_w);
405 double mag_i, mag_j, mag_k, mag_w;
413 double dot = v ^ targetIMUVector;
416 double angle = acos(dot);
418 double s = sin(angle / 2.0);
419 double target_orientation_w = cos(angle / 2.0);
420 double target_orientation_i = axis.
x * s;
421 double target_orientation_j = axis.
y * s;
422 double target_orientation_k = axis.
z * s;
429 double conj_mag_i = -mag_i, conj_mag_j = -mag_j, conj_mag_k = -mag_k, conj_mag_w = mag_w;
431 double conj_mult_i = -multiplied_i, conj_mult_j = -multiplied_j, conj_mult_k = -multiplied_k, conj_mult_w = multiplied_w;
434 double temp_w = conj_mag_w * target_orientation_w - conj_mag_i * target_orientation_i - conj_mag_j * target_orientation_j -
435 conj_mag_k * target_orientation_k;
436 double temp_i = conj_mag_w * target_orientation_i + conj_mag_i * target_orientation_w + conj_mag_j * target_orientation_k -
437 conj_mag_k * target_orientation_j;
438 double temp_j = conj_mag_w * target_orientation_j - conj_mag_i * target_orientation_k + conj_mag_j * target_orientation_w +
439 conj_mag_k * target_orientation_i;
440 double temp_k = conj_mag_w * target_orientation_k + conj_mag_i * target_orientation_j - conj_mag_j * target_orientation_i +
441 conj_mag_k * target_orientation_w;
444 double offset_w = temp_w * conj_mult_w - temp_i * conj_mult_i - temp_j * conj_mult_j - temp_k * conj_mult_k;
445 double offset_i = temp_w * conj_mult_i + temp_i * conj_mult_w + temp_j * conj_mult_k - temp_k * conj_mult_j;
446 double offset_j = temp_w * conj_mult_j - temp_i * conj_mult_k + temp_j * conj_mult_w + temp_k * conj_mult_i;
447 double offset_k = temp_w * conj_mult_k + temp_i * conj_mult_j - temp_j * conj_mult_i + temp_k * conj_mult_w;
450 double roll, pitch, yaw;
464 targetIMUVector.
y, targetIMUVector.
z);
466 currentIMUVector.y, currentIMUVector.z);
468 DEBUGF(
Logger::DBG_DEBUG,
"IMU Sync: New Offset Quaternion: i=%.4f, j=%.4f, k=%.4f, w=%.4f", offset_i, offset_j,
496 auto *f =
dynamic_cast<MahonyFilter *
>(m_SensorFusion.get());
511 LOGF_INFO(
"IMU: Fusion parameters updated (param1=%.4f, param2=%.4f).", p1, p2);
553 recreateFusionFilter();
554 LOG_INFO(
"IMU: Software sensor fusion enabled.");
558 LOG_INFO(
"IMU: Software sensor fusion disabled.");
570 recreateFusionFilter();
579bool IMU::ISNewText(
const char *dev,
const char *name,
char *texts[],
char *names[],
int n)
617bool IMU::callHandshake()
619 if (imuConnection > 0)
635 double sinr_cosp = 2 * (w * i + j * k);
636 double cosr_cosp = 1 - 2 * (i * i + j * j);
637 roll = std::atan2(sinr_cosp, cosr_cosp);
640 double sinp = 2 * (w * j - k * i);
641 if (std::abs(sinp) >= 1)
642 pitch = std::copysign(
M_PI / 2, sinp);
644 pitch = std::asin(sinp);
647 double siny_cosp = 2 * (w * k + i * j);
648 double cosy_cosp = 1 - 2 * (j * j + k * k);
649 yaw = std::atan2(siny_cosp, cosy_cosp);
655 double cy = cos(yaw * 0.5);
656 double sy = sin(yaw * 0.5);
657 double cp = cos(pitch * 0.5);
658 double sp = sin(pitch * 0.5);
659 double cr = cos(roll * 0.5);
660 double sr = sin(roll * 0.5);
662 w = cr * cp * cy + sr * sp * sy;
663 i = sr * cp * cy - cr * sp * sy;
664 j = cr * sp * cy + sr * cp * sy;
665 k = cr * cp * sy - sr * sp * cy;
710 DEBUGF(
Logger::DBG_DEBUG,
"IMU: Recalculating Astro Coordinates from stored Quaternion: i=%.4f, j=%.4f, k=%.4f, w=%.4f",
711 last_q_i, last_q_j, last_q_k, last_q_w);
713 double rollRad, pitchRad, yawRad;
714 QuaternionToEuler(last_q_i, last_q_j, last_q_k, last_q_w, rollRad, pitchRad, yawRad);
725 double qw = last_q_w;
726 double qx = last_q_i;
727 double qy = last_q_j;
728 double qz = last_q_k;
764 rollMultiplier, pitchMultiplier, yawMultiplier);
766 rollOffset, pitchOffset, yawOffset);
786 double x_hor = vx * (1 - 2 * qy * qy - 2 * qz * qz) + vy * (2 * qx * qy - 2 * qz * qw) + vz * (2 * qx * qz + 2 * qy * qw);
787 double y_hor = vx * (2 * qx * qy + 2 * qz * qw) + vy * (1 - 2 * qx * qx - 2 * qz * qz) + vz * (2 * qy * qz - 2 * qx * qw);
788 double z_hor = vx * (2 * qx * qz - 2 * qy * qw) + vy * (2 * qy * qz + 2 * qx * qw) + vz * (1 - 2 * qx * qx - 2 * qy * qy);
793 DEBUGF(
Logger::DBG_DEBUG,
"IMU: IMU Vector (before frame conversion): X=%.4f, Y=%.4f, Z=%.4f", x_hor, y_hor, z_hor);
821 horizontal_vector = imu_vector;
828 horizontal_vector.
x = imu_vector.y;
829 horizontal_vector.
y = -imu_vector.x;
830 horizontal_vector.
z = imu_vector.z;
837 horizontal_vector.
x = -imu_vector.y;
838 horizontal_vector.
y = -imu_vector.x;
839 horizontal_vector.
z = imu_vector.z;
845 horizontal_vector.
z);
886 double sin_lat = sin(lat_rad);
887 double cos_lat = cos(lat_rad);
890 DEBUGF(
Logger::DBG_DEBUG,
"IMU: Horizontal→Equatorial transformation: lat_rad=%.6f, sin_lat=%.6f, cos_lat=%.6f",
891 lat_rad, sin_lat, cos_lat);
894 double x_eq = (horizontal_vector.
z * cos_lat) + (horizontal_vector.
x * sin_lat);
895 double y_eq = horizontal_vector.
y;
896 double z_eq = (horizontal_vector.
z * sin_lat) + (horizontal_vector.
x * cos_lat);
931 "IMU: LocalHourAngleDeclinationFromTelescopeDirectionVector returned: HA=%.6f hours, Dec=%.6f deg",
969 horizontal_vector.
x, horizontal_vector.
y, horizontal_vector.
z);
1076 double rawRollRad, rawPitchRad, rawYawRad;
1080 rawRollRad *= rollMultiplier;
1081 rawPitchRad *= pitchMultiplier;
1082 rawYawRad *= yawMultiplier;
1085 double temp_i, temp_j, temp_k, temp_w;
1086 EulerToQuaternion(rawRollRad, rawPitchRad, rawYawRad, temp_i, temp_j, temp_k, temp_w);
1100 double offset_i, offset_j, offset_k, offset_w;
1101 EulerToQuaternion(rollOffset, pitchOffset, yawOffset, offset_i, offset_j, offset_k, offset_w);
1104 double mag_i, mag_j, mag_k, mag_w;
1135 double adjusted_w = offset_w * temp_w - offset_i * temp_i - offset_j * temp_j - offset_k * temp_k;
1136 double adjusted_i = offset_w * temp_i + offset_i * temp_w + offset_j * temp_k - offset_k * temp_j;
1137 double adjusted_j = offset_w * temp_j - offset_i * temp_k + offset_j * temp_w + offset_k * temp_i;
1138 double adjusted_k = offset_w * temp_k + offset_i * temp_j - offset_j * temp_i + offset_k * temp_w;
1141 last_q_w = mag_w * adjusted_w - mag_i * adjusted_i - mag_j * adjusted_j - mag_k * adjusted_k;
1142 last_q_i = mag_w * adjusted_i + mag_i * adjusted_w + mag_j * adjusted_k - mag_k * adjusted_j;
1143 last_q_j = mag_w * adjusted_j - mag_i * adjusted_k + mag_j * adjusted_w + mag_k * adjusted_i;
1144 last_q_k = mag_w * adjusted_k + mag_i * adjusted_j - mag_j * adjusted_i + mag_k * adjusted_w;
1156 double adjustedRollRad, adjustedPitchRad, adjustedYawRad;
1157 QuaternionToEuler(last_q_i, last_q_j, last_q_k, last_q_w, adjustedRollRad, adjustedPitchRad, adjustedYawRad);
1210 m_RawAccel = {x, y, z,
true};
1223 m_RawGyro = {x, y, z,
true};
1229 if (isFusionEnabled() && m_SensorFusion && m_RawAccel.valid)
1232 auto now = std::chrono::steady_clock::now();
1234 if (!m_FusionTimerInitialized)
1237 m_FusionTimerInitialized =
true;
1241 dt = std::chrono::duration<double>(now - m_LastFusionTime).count();
1243 m_LastFusionTime = now;
1250 double gx = gyroInDegrees ?
DEG_TO_RAD(x) : x;
1251 double gy = gyroInDegrees ?
DEG_TO_RAD(y) : y;
1252 double gz = gyroInDegrees ?
DEG_TO_RAD(z) : z;
1255 bool useMag = m_RawMag.valid;
1256 m_SensorFusion->update(
1258 m_RawAccel.x, m_RawAccel.y, m_RawAccel.z,
1259 m_RawMag.x, m_RawMag.y, m_RawMag.z,
1264 double qw, qi, qj, qk;
1265 m_SensorFusion->getQuaternion(qw, qi, qj, qk);
1268 "IMU Fusion: dt=%.4fs q=(w=%.4f, i=%.4f, j=%.4f, k=%.4f) useMag=%d",
1269 dt, qw, qi, qj, qk, useMag);
1285 m_RawMag = {x, y, z,
true};
1299bool IMU::isFusionEnabled()
const
1308void IMU::recreateFusionFilter()
1314 m_SensorFusion = std::make_unique<MahonyFilter>(p1, p2);
1316 m_SensorFusion = std::make_unique<MadgwickFilter>(p1);
1319 m_RawAccel.valid =
false;
1320 m_RawGyro.valid =
false;
1321 m_RawMag.valid =
false;
1322 m_FusionTimerInitialized =
false;
The I2C class manages connection with I2C devices on Linux systems. I2C communication is commonly use...
void setDefaultBusPath(const char *path)
setDefaultBusPath Set default I2C bus path. Call this function in initProperties() of your driver if ...
void registerHandshake(std::function< bool()> callback)
registerHandshake Register a handshake function to be called once the initial connection to the devic...
The Serial class manages connection with serial devices including Bluetooth. Serial communication is ...
These functions are used to convert different coordinate systems to and from the telescope direction ...
const TelescopeDirectionVector TelescopeDirectionVectorFromAltitudeAzimuth(INDI::IHorizontalCoordinates HorizontalCoordinates)
Calculates a normalised direction vector from the supplied altitude and azimuth.
const TelescopeDirectionVector TelescopeDirectionVectorFromLocalHourAngleDeclination(INDI::IEquatorialCoordinates EquatorialCoordinates)
Calculates a telescope direction vector from the supplied local hour angle and declination.
void LocalHourAngleDeclinationFromTelescopeDirectionVector(const TelescopeDirectionVector TelescopeDirectionVector, INDI::IEquatorialCoordinates &EquatorialCoordinates)
Calculates a local hour angle and declination from the supplied telescope direction vector and declin...
void AltitudeAzimuthFromTelescopeDirectionVector(const TelescopeDirectionVector TelescopeDirectionVector, INDI::IHorizontalCoordinates &HorizontalCoordinates)
Calculates an altitude and azimuth from the supplied normalised direction vector and declination.
const char * getDeviceName() const
bool updateProperty(PropertyType &property, ValueType *values, char *names[], int n, std::function< bool()> updater, bool saveConfig=false)
Generic helper function to update an INDI property based on an external operation.
virtual bool updateProperties()
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool saveConfig(bool silent=false, const char *property=nullptr)
Save the current properties in a configuration file.
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n)
Process the client newSwitch command.
void registerConnection(Connection::Interface *newConnection)
registerConnection Add new connection plugin to the existing connection pool. The connection type sha...
virtual bool deleteProperty(const char *propertyName)
Delete a property and unregister it. It will also be deleted from all clients.
void defineProperty(INumberVectorProperty *property)
virtual bool saveConfigItems(FILE *fp)
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
virtual bool initProperties()
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n)
Process the client newNumber command.
Connection::Interface * getActiveConnection()
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n)
Process the client newSwitch command.
bool processSwitch(const std::string &dev, const std::string &name, ISState *states, char *names[], int n)
Process IMU switch properties.
const std::string IMU_TAB
bool saveConfigItems(FILE *fp)
saveConfigItems Save IMU properties in config file
bool processText(const std::string &dev, const std::string &name, char *texts[], char *names[], int n)
Process IMU text properties.
INDI::PropertyNumber OrientationNP
INDI::PropertySwitch AngularUnitsSP
uint32_t GetCapability() const
GetIMUCapability returns the capability of the IMU device.
@ ORIENTATION_QUATERNION_W
INDI::PropertyNumber AccelerationNP
INDI::PropertyNumber GyroscopeNP
const std::string COORDINATES_TAB
bool updateProperties()
updateProperties Define or Delete IMU properties based on the connection status of the base device
bool processNumber(const std::string &dev, const std::string &name, double values[], char *names[], int n)
Process IMU number properties.
INDI::PropertyNumber MagnetometerNP
void SetCapability(uint32_t cap)
SetIMUCapability sets the IMU capabilities. All capabilities must be initialized.
void initProperties(const std::string &groupName)
Initialize IMU properties. It is recommended to call this function within initProperties() of your pr...
virtual bool SetOperationMode(const std::string &mode) override
SetOperationMode Set the operation mode of the IMU sensor.
INDI::PropertySwitch IMUFrameSP
virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n) override
Process the client newSwitch command.
INDI::PropertySwitch AstroCoordsTypeSP
bool SetAccelerationData(double x, double y, double z) override
SetAccelerationData Set the linear acceleration data.
INDI::PropertyNumber MagneticDeclinationNP
INDI::PropertyNumber FusionParamsNP
Algorithm tuning parameters (β for Madgwick; Kp & Ki for Mahony)
virtual bool SetUpdateRate(double rate) override
SetUpdateRate Set the sensor polling frequency.
void RecalculateAstroCoordinates()
Connection::Serial * serialConnection
INDI::PropertySwitch FusionTypeSP
Select fusion algorithm: Madgwick or Mahony.
bool SetGyroscopeData(double x, double y, double z) override
SetGyroscopeData Set the angular velocity data.
virtual bool saveConfigItems(FILE *fp) override
saveConfigItems Save specific properties in the provide config file handler. Child class usually over...
void EulerToQuaternion(double roll, double pitch, double yaw, double &i, double &j, double &k, double &w)
virtual bool ISNewNumber(const char *dev, const char *name, double values[], char *names[], int n) override
Process the client newNumber command.
Connection::I2C * i2cConnection
virtual bool LoadCalibrationData() override
LoadCalibrationData Load previously saved calibration data.
virtual bool SaveCalibrationData() override
SaveCalibrationData Save the current calibration data.
virtual bool StartCalibration() override
StartCalibration Initiate the calibration sequence.
bool SetMagnetometerData(double x, double y, double z) override
SetMagnetometerData Set the magnetic field strength data.
void QuaternionToEuler(double i, double j, double k, double w, double &roll, double &pitch, double &yaw)
INDI::PropertyNumber GeographicCoordNP
virtual bool Handshake()
Performs a device-specific handshake after a connection is established. This method should be overrid...
virtual bool updateProperties() override
updateProperties is called whenever there is a change in the CONNECTION status of the driver....
virtual bool SetDistanceUnits(bool metric) override
SetUnits Set the units for sensor data.
INDI::PropertyNumber TelescopeVectorNP
virtual bool SetPowerMode(const std::string &mode) override
SetPowerMode Set the power mode of the IMU sensor.
INDI::PropertyNumber AstroCoordinatesNP
void setSupportedConnections(const uint8_t &value)
INDI::PropertyNumber SyncAxisNP
virtual bool initProperties() override
Initialize properties initial state and value. The child class must implement this function.
virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n) override
Process the client newSwitch command.
INDI::PropertySwitch SensorFusionSP
Enable / Disable software sensor fusion.
INDI::PropertyNumber OrientationAdjustmentsNP
virtual bool ResetCalibration() override
ResetCalibration Reset calibration data.
virtual bool SetAngularUnits(bool degrees) override
SetAngularUnits Set the angular units for sensor data.
bool SetOrientationData(double roll, double pitch, double yaw, double w=0.0) override
SetOrientationData Set the orientation data (Roll, Pitch, Yaw, Quaternion W)
@ FUSION_PARAM_1
β for Madgwick; Kp for Mahony
@ FUSION_PARAM_2
unused for Madgwick; Ki for Mahony
Madgwick AHRS filter (9-DOF and 6-DOF).
void setBeta(double beta)
Set the filter gain β.
Mahony AHRS filter (9-DOF and 6-DOF).
void setKp(double kp)
Set proportional gain Kp.
void setState(IPState state)
void apply(const char *format,...) const ATTRIBUTE_FORMAT_PRINTF(2
bool load()
load Attempt to load property values from configuration file.
void save(FILE *f) const
save Save property to configuration file.
bool isNameMatch(const char *otherName) const
bool update(const double values[], const char *const names[], int n)
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, double timeout, IPState state)
bool update(const ISState states[], const char *const names[], int n)
INDI::WidgetViewSwitch * findOnSwitch() const
int findOnSwitchIndex() const
void fill(const char *device, const char *name, const char *label, const char *group, IPerm permission, ISRule rule, double timeout, IPState state)
const char * MAIN_CONTROL_TAB
MAIN_CONTROL_TAB Where all the primary controls for the device are located.
Implementations for common driver routines.
#define LOGF_INFO(fmt,...)
#define DEBUG(priority, msg)
Macro to print log messages. Example of usage of the Logger: DEBUG(DBG_DEBUG, "hello " << "world");.
#define DEBUGF(priority, msg,...)
Namespace to encapsulate INDI client, drivers, and mediator classes.
Holds a normalised direction vector (direction cosines)
void Normalise()
Normalise the vector.