| 
									
										
										
										
											2022-03-01 17:17:56 +00:00
										 |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | // Copyright (C) 2021 Jon Beniston, M7RCE                                        //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // Based on code and docs by einergehtnochrein, rs1729 and bazjo                 //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // This program is free software; you can redistribute it and/or modify          //
 | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by          //
 | 
					
						
							|  |  |  | // the Free Software Foundation as version 3 of the License, or                  //
 | 
					
						
							|  |  |  | // (at your option) any later version.                                           //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful,               //
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of                //
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
 | 
					
						
							|  |  |  | // GNU General Public License V3 for more details.                               //
 | 
					
						
							|  |  |  | //                                                                               //
 | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License             //
 | 
					
						
							|  |  |  | // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | 
					
						
							|  |  |  | ///////////////////////////////////////////////////////////////////////////////////
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QDateTime>
 | 
					
						
							|  |  |  | #include <QVector3D>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "util/radiosonde.h"
 | 
					
						
							|  |  |  | #include "util/coordinates.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RS41Frame::RS41Frame(const QByteArray ba) : | 
					
						
							|  |  |  |     m_statusValid(false), | 
					
						
							|  |  |  |     m_batteryVoltage(0.0), | 
					
						
							|  |  |  |     m_pcbTemperature(0), | 
					
						
							|  |  |  |     m_humiditySensorHeating(0), | 
					
						
							|  |  |  |     m_transmitPower(0), | 
					
						
							|  |  |  |     m_maxSubframeNumber(0), | 
					
						
							|  |  |  |     m_subframeNumber(0), | 
					
						
							|  |  |  |     m_measValid(false), | 
					
						
							|  |  |  |     m_gpsInfoValid(false), | 
					
						
							|  |  |  |     m_posValid(false), | 
					
						
							|  |  |  |     m_latitude(0.0), | 
					
						
							|  |  |  |     m_longitude(0.0), | 
					
						
							|  |  |  |     m_height(0.0), | 
					
						
							|  |  |  |     m_bytes(ba), | 
					
						
							|  |  |  |     m_pressureCalibrated(false), | 
					
						
							| 
									
										
										
										
											2022-03-01 17:48:36 +00:00
										 |  |  |     m_temperatureCalibrated(false), | 
					
						
							| 
									
										
										
										
											2022-03-01 17:17:56 +00:00
										 |  |  |     m_humidityTemperatureCalibrated(false), | 
					
						
							|  |  |  |     m_humidityCalibrated(false) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     int length = getFrameLength(ba[RS41_OFFSET_FRAME_TYPE]); | 
					
						
							|  |  |  |     for (int i = RS41_OFFSET_BLOCK_0; i < length; ) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         uint8_t blockID = ba[i+0]; | 
					
						
							|  |  |  |         uint8_t blockLength = ba[i+1]; | 
					
						
							|  |  |  |         switch (blockID) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |         case RS41_ID_STATUS: | 
					
						
							|  |  |  |             decodeStatus(ba.mid(i+2, blockLength)); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case RS41_ID_MEAS: | 
					
						
							|  |  |  |             decodeMeas(ba.mid(i+2, blockLength)); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case RS41_ID_GPSINFO: | 
					
						
							|  |  |  |             decodeGPSInfo(ba.mid(i+2, blockLength)); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case RS41_ID_GPSRAW: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case RS41_ID_GPSPOS: | 
					
						
							|  |  |  |             decodeGPSPos(ba.mid(i+2, blockLength)); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case RS41_ID_EMPTY: | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         i += 2 + blockLength + 2; // ID, length, data, CRC
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString RS41Frame::toHex() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_bytes.toHex(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | uint16_t RS41Frame::getUInt16(const QByteArray ba, int offset) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return (ba[offset] & 0xff) | 
					
						
							|  |  |  |         | ((ba[offset+1] & 0xff) << 8); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | uint32_t RS41Frame::getUInt24(const QByteArray ba, int offset) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return (ba[offset] & 0xff) | 
					
						
							|  |  |  |         | ((ba[offset+1] & 0xff) << 8) | 
					
						
							|  |  |  |         | ((ba[offset+2] & 0xff) << 16); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | uint32_t RS41Frame::getUInt32(const QByteArray ba, int offset) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return (ba[offset] & 0xff) | 
					
						
							|  |  |  |         | ((ba[offset+1] & 0xff) << 8) | 
					
						
							|  |  |  |         | ((ba[offset+2] & 0xff) << 16) | 
					
						
							|  |  |  |         | ((ba[offset+3] & 0xff) << 24); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::decodeStatus(const QByteArray ba) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_statusValid = true; | 
					
						
							|  |  |  |     m_frameNumber = getUInt16(ba, 0); | 
					
						
							|  |  |  |     m_serial = QString(ba.mid(0x2, 8)); | 
					
						
							|  |  |  |     m_batteryVoltage = (ba[0xa] & 0xff) / 10.0; | 
					
						
							|  |  |  |     QStringList phases = {"Ground", "Ascent", "0x2", "Descent"}; | 
					
						
							|  |  |  |     int phase = ba[0xd] & 0x3; | 
					
						
							|  |  |  |     m_flightPhase = phases[phase]; | 
					
						
							|  |  |  |     m_batteryStatus = (ba[0xe] & 0x10) == 0 ? "OK" : "Low"; | 
					
						
							|  |  |  |     m_pcbTemperature = (ba[0x10] & 0xff); | 
					
						
							|  |  |  |     m_humiditySensorHeating = getUInt16(ba, 0x13); | 
					
						
							|  |  |  |     m_transmitPower = ba[0x15] & 0xff; | 
					
						
							|  |  |  |     m_maxSubframeNumber = ba[0x16] & 0xff; | 
					
						
							|  |  |  |     m_subframeNumber = ba[0x17] & 0xff; | 
					
						
							|  |  |  |     m_subframe = ba.mid(0x18, 16); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::decodeMeas(const QByteArray ba) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_measValid = true; | 
					
						
							|  |  |  |     m_tempMain = getUInt24(ba, 0x0); | 
					
						
							|  |  |  |     m_tempRef1 = getUInt24(ba, 0x3); | 
					
						
							|  |  |  |     m_tempRef2 = getUInt24(ba, 0x6); | 
					
						
							|  |  |  |     m_humidityMain = getUInt24(ba, 0x9); | 
					
						
							|  |  |  |     m_humidityRef1 = getUInt24(ba, 0xc); | 
					
						
							|  |  |  |     m_humidityRef2 = getUInt24(ba, 0xf); | 
					
						
							|  |  |  |     m_humidityTempMain = getUInt24(ba, 0x12); | 
					
						
							|  |  |  |     m_humidityTempRef1 = getUInt24(ba, 0x15); | 
					
						
							|  |  |  |     m_humidityTempRef2 = getUInt24(ba, 0x18); | 
					
						
							|  |  |  |     m_pressureMain = getUInt24(ba, 0x1b); | 
					
						
							|  |  |  |     m_pressureRef1 = getUInt24(ba, 0x1e); | 
					
						
							|  |  |  |     m_pressureRef2 = getUInt24(ba, 0x21); | 
					
						
							|  |  |  |     m_pressureTemp = getUInt16(ba, 0x26) / 100.0f; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::decodeGPSInfo(const QByteArray ba) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_gpsInfoValid = true; | 
					
						
							|  |  |  |     uint16_t gpsWeek = getUInt16(ba, 0x0); | 
					
						
							|  |  |  |     uint32_t gpsTimeOfWeek = getUInt32(ba, 0x2);  // Milliseconds
 | 
					
						
							|  |  |  |     QDateTime epoch(QDate(1980, 1, 6), QTime(0, 0, 0), Qt::OffsetFromUTC, 18); // GPS doesn't count leap seconds
 | 
					
						
							|  |  |  |     m_gpsDateTime = epoch.addDays(gpsWeek*7).addMSecs(gpsTimeOfWeek); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::decodeGPSPos(const QByteArray ba) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_satellitesUsed = ba[0x12] & 0xff; | 
					
						
							|  |  |  |     if (m_satellitesUsed > 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_posValid = true; | 
					
						
							|  |  |  |         int32_t ecefX = (int32_t)getUInt32(ba, 0x0); | 
					
						
							|  |  |  |         int32_t ecefY = (int32_t)getUInt32(ba, 0x4); | 
					
						
							|  |  |  |         int32_t ecefZ = (int32_t)getUInt32(ba, 0x8); | 
					
						
							|  |  |  |         // Convert cm to m
 | 
					
						
							|  |  |  |         // Convert to latitude, longitude and altitude
 | 
					
						
							|  |  |  |         Coordinates::ecefToGeodetic(ecefX / 100.0, ecefY / 100.0, ecefZ / 100.0, m_latitude, m_longitude, m_height); | 
					
						
							|  |  |  |         int32_t velX = (int16_t)getUInt16(ba, 0xc); | 
					
						
							|  |  |  |         int32_t velY = (int16_t)getUInt16(ba, 0xe); | 
					
						
							|  |  |  |         int32_t velZ = (int16_t)getUInt16(ba, 0x10); | 
					
						
							|  |  |  |         // Convert cm/s to m/s
 | 
					
						
							|  |  |  |         // Calculate speed / heading
 | 
					
						
							|  |  |  |         Coordinates::ecefVelToSpeedHeading(m_latitude, m_longitude, velX / 100.0, velY / 100.0, velZ / 100.0, m_speed, m_verticalRate, m_heading); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Find the water vapor saturation pressure for a given temperature.
 | 
					
						
							|  |  |  | float waterVapourSaturationPressure(float tCelsius) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // Convert to Kelvin
 | 
					
						
							|  |  |  |     float T = tCelsius + 273.15f; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Correction
 | 
					
						
							|  |  |  |     T = - 0.4931358f | 
					
						
							|  |  |  |         + (1.0f + 4.6094296e-3f) * T | 
					
						
							|  |  |  |         - 1.3746454e-5f * T * T | 
					
						
							|  |  |  |         + 1.2743214e-8f * T * T * T; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Hyland and Wexler equation
 | 
					
						
							|  |  |  |     float p = expf(-5800.2206f / T | 
					
						
							|  |  |  |                   + 1.3914993f | 
					
						
							|  |  |  |                   + 6.5459673f * logf(T) | 
					
						
							|  |  |  |                   - 4.8640239e-2f * T | 
					
						
							|  |  |  |                   + 4.1764768e-5f * T * T | 
					
						
							|  |  |  |                   - 1.4452093e-8f * T * T * T); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Scale result to hPa
 | 
					
						
							|  |  |  |     return p / 100.0f; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float calcT(int f, int f1, int f2, float r1, float r2, float *poly, float *cal) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     /*float g = (float)(f2-f1) / (r2-r1);       // gain
 | 
					
						
							|  |  |  |     float Rb = (f1*r2-f2*r1) / (float)(f2-f1); // offset
 | 
					
						
							|  |  |  |     float Rc = f/g - Rb; | 
					
						
							|  |  |  |     float R = Rc * cal[0]; | 
					
						
							|  |  |  |     float T = (poly[0] + poly[1]*R + poly[2]*R*R + cal[1])*(1.0 + cal[2]); | 
					
						
							|  |  |  |     return T; | 
					
						
							|  |  |  |     */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Convert integer measurement to scale factor
 | 
					
						
							|  |  |  |     float s = (f-f1)/(float)(f2-f1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Calculate resistance (scale between two reference resistors)
 | 
					
						
							|  |  |  |     float rUncal = r1 + (r2 - r1) * s; | 
					
						
							|  |  |  |     float r = rUncal * cal[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Convert resistance to temperature
 | 
					
						
							|  |  |  |     float tUncal = poly[0] + poly[1]*r + poly[2]*r*r; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Correct temperature (5th order polynomial)
 | 
					
						
							|  |  |  |     float tCal = 0.0f; | 
					
						
							|  |  |  |     for (int i = 6; i > 0; i--) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         tCal *= tUncal; | 
					
						
							|  |  |  |         tCal += cal[i]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     tCal += tUncal; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return tCal; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float calcU(int cInt, int cMin, int cMax, float c1, float c2, float T, float HT, float *capCal, float *matrixCal) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     //qDebug() << "cInt " << cInt << " cMin " << cMin << " cMax " << cMax << " c1 " << c1 << " c2 " << c2 << " T " << T << " HT " << HT << " capCal[0] " << capCal[0] << " capCal[1] " << capCal[1];
 | 
					
						
							|  |  |  |   /*
 | 
					
						
							|  |  |  |     float a0 = 7.5f; | 
					
						
							|  |  |  |     float a1 = 350.0f / capCal[0]; | 
					
						
							|  |  |  |     float fh = (cInt-cMin) / (float)(cMax-cMin); | 
					
						
							|  |  |  |     float rh = 100.0f * (a1*fh - a0); | 
					
						
							|  |  |  |     float T0 = 0.0f; | 
					
						
							|  |  |  |     float T1 = -25.0f; | 
					
						
							|  |  |  |     rh += T0 - T/5.5; | 
					
						
							|  |  |  |     if (T < T1) { | 
					
						
							|  |  |  |         rh *= 1.0 + (T1-T)/90.0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (rh < 0.0) { | 
					
						
							|  |  |  |         rh = 0.0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (rh > 100.0) { | 
					
						
							|  |  |  |         rh = 100.0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (T < -273.0) { | 
					
						
							|  |  |  |         rh = -1.0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     qDebug() << "RH old method: " << rh;  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Convert integer measurement to scale factor
 | 
					
						
							|  |  |  |     float s = (cInt - cMin) / (float)(cMax-cMin); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Calculate capacitance (scale between two reference caps)
 | 
					
						
							|  |  |  |     float cUncal = c1 + (c2 - c1) * s; | 
					
						
							|  |  |  |     float cCal = (cUncal / capCal[0] - 1.0f) * capCal[1]; | 
					
						
							|  |  |  |     float uUncal = 0.0f; | 
					
						
							|  |  |  |     float t = (HT - 20.0f) / 180.0f; | 
					
						
							|  |  |  |     float f1 = 1.0f; | 
					
						
							|  |  |  |     for (int i = 0; i < 7; i++) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         float f2 = 1.0; | 
					
						
							|  |  |  |         for (int j = 0; j < 6; j++) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             uUncal += f1 * f2 * matrixCal[i*6+j]; | 
					
						
							|  |  |  |             f2 *= t; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         f1 *= cCal; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Adjust for difference in outside air temperature and the humidty sensor temperature
 | 
					
						
							|  |  |  |     float uCal = uUncal * waterVapourSaturationPressure(T) / waterVapourSaturationPressure(HT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Ensure within range of 0..100%
 | 
					
						
							|  |  |  |     uCal = std::min(100.0f, uCal); | 
					
						
							|  |  |  |     uCal = std::max(0.0f, uCal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return uCal; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float calcP(int f, int f1, int f2, float pressureTemp, float *cal) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     // Convert integer measurement to scale factor
 | 
					
						
							|  |  |  |     float s = (f-f1) / (float)(f2-f1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     float t = pressureTemp; | 
					
						
							|  |  |  |     float t2 = t * t; | 
					
						
							|  |  |  |     float t3 = t2 * t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     float poly[6]; | 
					
						
							|  |  |  |     poly[0] = cal[0] + cal[7] * t + cal[11] * t2 + cal[15] * t3; | 
					
						
							|  |  |  |     poly[1] = cal[1] + cal[8] * t + cal[12] * t2 + cal[16] * t3; | 
					
						
							|  |  |  |     poly[2] = cal[2] + cal[9] * t + cal[13] * t2 + cal[17] * t3; | 
					
						
							|  |  |  |     poly[3] = cal[3] + cal[10] * t + cal[14] * t2; | 
					
						
							|  |  |  |     poly[4] = cal[4]; | 
					
						
							|  |  |  |     poly[5] = cal[5]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     float p = cal[6] / s; | 
					
						
							|  |  |  |     float p2 = p * p; | 
					
						
							|  |  |  |     float p3 = p2 * p; | 
					
						
							|  |  |  |     float p4 = p3 * p; | 
					
						
							|  |  |  |     float p5 = p4 * p; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     float pCal = poly[0] + poly[1] * p + poly[2] * p2 + poly[3] * p3 + poly[4] * p4 + poly[5] * p5; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return pCal; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float RS41Frame::getPressureFloat(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_pressureCalibrated) { | 
					
						
							|  |  |  |         calcPressure(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_pressure; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString RS41Frame::getPressureString(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_pressureCalibrated) { | 
					
						
							|  |  |  |         calcPressure(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_pressureString; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float RS41Frame::getTemperatureFloat(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_temperatureCalibrated) { | 
					
						
							|  |  |  |         calcTemperature(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_temperature; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString RS41Frame::getTemperatureString(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_temperatureCalibrated) { | 
					
						
							|  |  |  |         calcTemperature(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_temperatureString; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::calcPressure(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     float cal[18]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_pressureMain == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_pressure = 0.0f; | 
					
						
							|  |  |  |         m_pressureString = ""; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_pressureCalibrated = subframe->getPressureCal(cal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_pressure = calcP(m_pressureMain, m_pressureRef1, m_pressureRef2, m_pressureTemp, cal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // RS41 pressure resolution of 0.01hPa
 | 
					
						
							|  |  |  |     m_pressureString = QString::number(m_pressure, 'f', 2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!m_pressureCalibrated) { | 
					
						
							|  |  |  |         m_pressureString = m_pressureString + "U"; // U for uncalibrated
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::calcTemperature(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     float r1, r2; | 
					
						
							|  |  |  |     float poly[3]; | 
					
						
							|  |  |  |     float cal[7]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_tempMain == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_temperature = 0.0f; | 
					
						
							|  |  |  |         m_temperatureString = ""; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_temperatureCalibrated = subframe->getTempCal(r1, r2, poly, cal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_temperature = calcT(m_tempMain, m_tempRef1, m_tempRef2, | 
					
						
							|  |  |  |                           r1, r2, | 
					
						
							|  |  |  |                           poly, cal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // RS41 temperature resolution of 0.01C
 | 
					
						
							|  |  |  |     m_temperatureString = QString::number(m_temperature, 'f', 2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!m_temperatureCalibrated) { | 
					
						
							|  |  |  |         m_temperatureString = m_temperatureString + "U"; // U for uncalibrated
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float RS41Frame::getHumidityTemperatureFloat(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_humidityTemperatureCalibrated) { | 
					
						
							|  |  |  |         calcHumidityTemperature(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_humidityTemperature; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::calcHumidityTemperature(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     float r1, r2; | 
					
						
							|  |  |  |     float poly[3]; | 
					
						
							|  |  |  |     float cal[7]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_humidityTempMain == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_humidityTemperature = 0.0f; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_humidityTemperatureCalibrated = subframe->getHumidityTempCal(r1, r2, poly, cal); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_humidityTemperature = calcT(m_humidityTempMain, m_humidityTempRef1, m_humidityTempRef2, | 
					
						
							|  |  |  |                                   r1, r2, | 
					
						
							|  |  |  |                                   poly, cal); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float RS41Frame::getHumidityFloat(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_humidityCalibrated) { | 
					
						
							|  |  |  |         calcHumidity(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_humidity; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString RS41Frame::getHumidityString(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (!m_humidityCalibrated) { | 
					
						
							|  |  |  |         calcHumidity(subframe); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return m_humidityString; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void RS41Frame::calcHumidity(const RS41Subframe *subframe) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     float c1, c2; | 
					
						
							|  |  |  |     float capCal[2]; | 
					
						
							|  |  |  |     float calMatrix[7*6]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_humidityMain == 0) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         m_humidity = 0.0f; | 
					
						
							|  |  |  |         m_humidityString = ""; | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     float temperature = getTemperatureFloat(subframe); | 
					
						
							|  |  |  |     float humidityTemperature = getHumidityTemperatureFloat(subframe); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     bool humidityCalibrated = subframe->getHumidityCal(c1, c2, capCal, calMatrix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_humidityCalibrated = m_temperatureCalibrated && m_humidityTemperatureCalibrated && humidityCalibrated; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_humidity = calcU(m_humidityMain, m_humidityRef1, m_humidityRef2, | 
					
						
							|  |  |  |                        c1, c2, | 
					
						
							|  |  |  |                        temperature, humidityTemperature, | 
					
						
							|  |  |  |                        capCal, calMatrix); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // RS41 humidity resolution of 0.1%
 | 
					
						
							|  |  |  |     m_humidityString = QString::number(m_humidity, 'f', 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!m_humidityCalibrated) { | 
					
						
							|  |  |  |         m_humidityString = m_humidityString + "U"; // U for uncalibrated
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RS41Frame* RS41Frame::decode(const QByteArray ba) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return new RS41Frame(ba); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int RS41Frame::getFrameLength(int frameType) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return frameType == RS41_FRAME_STD ? RS41_LENGTH_STD : RS41_LENGTH_EXT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RS41Subframe::RS41Subframe() : | 
					
						
							|  |  |  |     m_subframe(51*16, (char)0) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     for (int i = 0; i < 51; i++) { | 
					
						
							|  |  |  |         m_subframeValid[i] = false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Update subframe with subframe data from received message
 | 
					
						
							|  |  |  | void RS41Subframe::update(RS41Frame *message) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_subframeValid[message->m_subframeNumber] = true; | 
					
						
							|  |  |  |     int offset = message->m_subframeNumber * 16; | 
					
						
							|  |  |  |     for (int i = 0; i < 16; i++) { | 
					
						
							|  |  |  |         m_subframe[offset+i] = message->m_subframe[i]; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Indicate if we have all the required temperature calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::hasTempCal() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_subframeValid[3] && m_subframeValid[4] && m_subframeValid[5] && m_subframeValid[6] && m_subframeValid[7]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Get temperature calibration data
 | 
					
						
							|  |  |  | // r1, r2 - Temperature reference resistances (Ohms)
 | 
					
						
							|  |  |  | // poly - Resistance to temperature 2nd order polynomial
 | 
					
						
							|  |  |  | bool RS41Subframe::getTempCal(float &r1, float &r2, float *poly, float *cal) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (hasTempCal()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         r1 = getFloat(0x3d); | 
					
						
							|  |  |  |         r2 = getFloat(0x41); | 
					
						
							|  |  |  |         for (int i = 0; i < 3; i++) { | 
					
						
							|  |  |  |             poly[i] = getFloat(0x4d + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (int i = 0; i < 7; i++) { | 
					
						
							|  |  |  |             cal[i] = getFloat(0x59 + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Use default values
 | 
					
						
							|  |  |  |         r1 = 750.0f; | 
					
						
							|  |  |  |         r2 = 1100.0f; | 
					
						
							|  |  |  |         poly[0] = -243.9108f; | 
					
						
							|  |  |  |         poly[1] = 0.187654f; | 
					
						
							|  |  |  |         poly[2] = 8.2e-06f; | 
					
						
							|  |  |  |         cal[0] = 1.279928f; | 
					
						
							|  |  |  |         for (int i = 1; i < 7; i++) { | 
					
						
							|  |  |  |             cal[i] = 0.0f; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Indicate if we have all the required humidty calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::hasHumidityCal() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_subframeValid[4] && m_subframeValid[7] | 
					
						
							|  |  |  |             && m_subframeValid[8] && m_subframeValid[9] && m_subframeValid[0xa] && m_subframeValid[0xb] | 
					
						
							|  |  |  |             && m_subframeValid[0xc] && m_subframeValid[0xd] && m_subframeValid[0xe] && m_subframeValid[0xf] | 
					
						
							|  |  |  |             && m_subframeValid[0x10] && m_subframeValid[0x11] && m_subframeValid[0x12]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Get humidty calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::getHumidityCal(float &c1, float &c2, float *capCal, float *calMatrix) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (hasHumidityCal()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         c1 = getFloat(0x45); | 
					
						
							|  |  |  |         c2 = getFloat(0x49); | 
					
						
							|  |  |  |         for (int i = 0; i < 2; i++) { | 
					
						
							|  |  |  |             capCal[i] = getFloat(0x75 + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (int i = 0; i < 7*6; i++) { | 
					
						
							|  |  |  |             calMatrix[i] = getFloat(0x7d + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Use default values
 | 
					
						
							|  |  |  |         c1 = 0.0f; | 
					
						
							|  |  |  |         c2 = 47.0f; | 
					
						
							|  |  |  |         capCal[0] = 45.9068f; | 
					
						
							|  |  |  |         capCal[1] = 4.92924f; | 
					
						
							|  |  |  |         static const float calMatrixDefault[7*6] = { | 
					
						
							|  |  |  |              -0.002586f, -2.24367f,   9.92294f,   -3.61913f,   54.3554f,    -93.3012f, | 
					
						
							|  |  |  |              51.7056f,   38.8709f,    209.437f,   -378.437f,   9.17326f,    19.5301f, | 
					
						
							|  |  |  |              150.257f,   -150.907f,   -280.315f,  182.293f,    3247.39f,    4083.65f, | 
					
						
							|  |  |  |              -233.568f,  345.375f,    200.217f,   -388.246f,   -3617.66f,   0.0f, | 
					
						
							|  |  |  |              225.841f,   -233.051f,   0.0f,       0.0f,        0.0f,        0.0f, | 
					
						
							|  |  |  |              -93.0635f,  0.0f,        0.0f,       0.0f,        0.0f,        0.0f, | 
					
						
							|  |  |  |               0.0f,      0.0f,        0.0f,       0.0f,        0.0f,        0.0f | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |         std::copy(calMatrixDefault, calMatrixDefault + 7*6, calMatrix); | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Indicate if we have all the required humidty temperature sensor calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::hasHumidityTempCal() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_subframeValid[3] && m_subframeValid[4] && m_subframeValid[0x12] && m_subframeValid[0x13] && m_subframeValid[0x14]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Get humidty temperature sensor calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::getHumidityTempCal(float &r1, float &r2, float *poly, float *cal) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (hasHumidityTempCal()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         r1 = getFloat(0x3d); | 
					
						
							|  |  |  |         r2 = getFloat(0x41); | 
					
						
							|  |  |  |         for (int i = 0; i < 3; i++) { | 
					
						
							|  |  |  |              poly[i] = getFloat(0x125 + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         for (int i = 0; i < 7; i++) { | 
					
						
							|  |  |  |             cal[i] = getFloat(0x131 + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Use default values
 | 
					
						
							|  |  |  |         r1 = 750.0f; | 
					
						
							|  |  |  |         r2 = 1100.0f; | 
					
						
							|  |  |  |         poly[0] = -243.9108f; | 
					
						
							|  |  |  |         poly[1] = 0.187654f; | 
					
						
							|  |  |  |         poly[2] = 8.2e-06f; | 
					
						
							|  |  |  |         cal[0] = 1.279928f; | 
					
						
							|  |  |  |         for (int i = 1; i < 7; i++) { | 
					
						
							|  |  |  |             cal[i] = 0.0f; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Indicate if we have all the required pressure calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::hasPressureCal() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return m_subframeValid[0x25] && m_subframeValid[0x26] && m_subframeValid[0x27] | 
					
						
							|  |  |  |             && m_subframeValid[0x28] && m_subframeValid[0x29] && m_subframeValid[0x2a]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Get pressure calibration data
 | 
					
						
							|  |  |  | bool RS41Subframe::getPressureCal(float *cal) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (hasPressureCal()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         for (int i = 0; i < 18; i++) { | 
					
						
							|  |  |  |             cal[i] = getFloat(0x25e + i * 4); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // Use default values - TODO: Need to obtain from inflight device
 | 
					
						
							|  |  |  |         for (int i = 0; i < 18; i++) { | 
					
						
							|  |  |  |             cal[i] = 0.0f; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Get type of RS41. E.g. "RS41-SGP"
 | 
					
						
							|  |  |  | QString RS41Subframe::getType() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_subframeValid[0x21] & m_subframeValid[0x22]) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return QString(m_subframe.mid(0x218, 10)).trimmed(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return "RS41"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Get transmission frequency in MHz
 | 
					
						
							|  |  |  | QString RS41Subframe::getFrequencyMHz() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_subframeValid[0]) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         uint8_t lower = m_subframe[2] & 0xff; | 
					
						
							|  |  |  |         uint8_t upper = m_subframe[3] & 0xff; | 
					
						
							|  |  |  |         float freq = 400.0 + (upper + (lower / 255.0)) * 0.04; | 
					
						
							|  |  |  |         return QString::number(freq, 'f', 3); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return ""; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString RS41Subframe::getBurstKillStatus() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_subframeValid[2]) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         uint8_t status = m_subframe[0x2b]; | 
					
						
							|  |  |  |         return status == 0 ? "Inactive" : "Active"; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return ""; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Seconds until power-off once active
 | 
					
						
							|  |  |  | QString RS41Subframe::getBurstKillTimer() const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_subframeValid[0x31]) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         uint16_t secs = getUInt16(0x316); | 
					
						
							|  |  |  |         QTime t(0, 0, 0); | 
					
						
							|  |  |  |         t = t.addSecs(secs); | 
					
						
							|  |  |  |         return t.toString("hh:mm:ss"); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return ""; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | uint16_t RS41Subframe::getUInt16(int offset) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     return (m_subframe[offset] & 0xff) | ((m_subframe[offset+1] & 0xff) << 8); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | float RS41Subframe::getFloat(int offset) const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     float f; | 
					
						
							|  |  |  |     // Assumes host is little endian with 32-bit float
 | 
					
						
							|  |  |  |     memcpy(&f, m_subframe.data() + offset, 4); | 
					
						
							|  |  |  |     return f; | 
					
						
							|  |  |  | } |