|
| 1 | +import 'dart:math'; |
| 2 | +import 'package:pslab/communication/peripherals/i2c.dart'; |
| 3 | +import 'package:pslab/communication/science_lab.dart'; |
| 4 | +import 'package:pslab/others/logger_service.dart'; |
| 5 | + |
| 6 | +import '../../l10n/app_localizations.dart'; |
| 7 | +import '../../providers/locator.dart'; |
| 8 | + |
| 9 | +class BMP180 { |
| 10 | + AppLocalizations appLocalizations = getIt.get<AppLocalizations>(); |
| 11 | + |
| 12 | + static const String tag = "BMP180"; |
| 13 | + |
| 14 | + static const int address = 0x77; |
| 15 | + |
| 16 | + static const int ultraLowPower = 0; |
| 17 | + static const int standard = 1; |
| 18 | + static const int highRes = 2; |
| 19 | + static const int ultraHighRes = 3; |
| 20 | + |
| 21 | + static const int calAC1 = 0xAA; |
| 22 | + static const int calAC2 = 0xAC; |
| 23 | + static const int calAC3 = 0xAE; |
| 24 | + static const int calAC4 = 0xB0; |
| 25 | + static const int calAC5 = 0xB2; |
| 26 | + static const int calAC6 = 0xB4; |
| 27 | + static const int calB1 = 0xB6; |
| 28 | + static const int calB2 = 0xB8; |
| 29 | + static const int calMB = 0xBA; |
| 30 | + static const int calMC = 0xBC; |
| 31 | + static const int calMD = 0xBE; |
| 32 | + static const int control = 0xF4; |
| 33 | + static const int tempData = 0xF6; |
| 34 | + static const int pressData = 0xF6; |
| 35 | + |
| 36 | + static const int readTempCmd = 0x2E; |
| 37 | + static const int readPressureCmd = 0x34; |
| 38 | + |
| 39 | + int mode = highRes; |
| 40 | + int oversampling = highRes; |
| 41 | + |
| 42 | + static const int numPlots = 3; |
| 43 | + static const List<String> plotNames = ["Temperature", "Pressure", "Altitude"]; |
| 44 | + static const String name = "Altimeter BMP180"; |
| 45 | + |
| 46 | + final I2C i2c; |
| 47 | + late int ac1, ac2, ac3, ac4, ac5, ac6, b1, b2, mb, mc, md; |
| 48 | + double temperature = 0.0; |
| 49 | + double pressure = 0.0; |
| 50 | + |
| 51 | + static const double seaLevelPressure = 101325.0; |
| 52 | + |
| 53 | + BMP180._(this.i2c); |
| 54 | + bool _validateCalibrationValues() { |
| 55 | + if (ac1 == 0 || ac2 == 0 || ac3 == 0 || ac4 == 0 || ac5 == 0 || ac6 == 0) { |
| 56 | + return false; |
| 57 | + } |
| 58 | + if (ac1 < -32768 || ac1 > 32767) return false; |
| 59 | + if (ac2 < -32768 || ac2 > 32767) return false; |
| 60 | + if (ac3 < -32768 || ac3 > 32767) return false; |
| 61 | + if (ac4 < 0 || ac4 > 65535) return false; |
| 62 | + if (ac5 < 0 || ac5 > 65535) return false; |
| 63 | + if (ac6 < 0 || ac6 > 65535) return false; |
| 64 | + if (b1 < -32768 || b1 > 32767) return false; |
| 65 | + if (b2 < -32768 || b2 > 32767) return false; |
| 66 | + if (mb < -32768 || mb > 32767) return false; |
| 67 | + if (mc < -32768 || mc > 32767) return false; |
| 68 | + if (md < -32768 || md > 32767) return false; |
| 69 | + return true; |
| 70 | + } |
| 71 | + |
| 72 | + static Future<BMP180> create(I2C i2c, ScienceLab scienceLab) async { |
| 73 | + final bmp180 = BMP180._(i2c); |
| 74 | + await bmp180._initializeCalibrationValues(scienceLab); |
| 75 | + if (!bmp180._validateCalibrationValues()) { |
| 76 | + throw Exception('BMP180 calibration values are invalid or out of range.'); |
| 77 | + } |
| 78 | + return bmp180; |
| 79 | + } |
| 80 | + |
| 81 | + Future<void> _initializeCalibrationValues(ScienceLab scienceLab) async { |
| 82 | + if (!scienceLab.isConnected()) { |
| 83 | + throw Exception("ScienceLab not connected"); |
| 84 | + } |
| 85 | + |
| 86 | + try { |
| 87 | + ac1 = await readInt16(calAC1); |
| 88 | + ac2 = await readInt16(calAC2); |
| 89 | + ac3 = await readInt16(calAC3); |
| 90 | + ac4 = await readUInt16(calAC4); |
| 91 | + ac5 = await readUInt16(calAC5); |
| 92 | + ac6 = await readUInt16(calAC6); |
| 93 | + b1 = await readInt16(calB1); |
| 94 | + b2 = await readInt16(calB2); |
| 95 | + mb = await readInt16(calMB); |
| 96 | + mc = await readInt16(calMC); |
| 97 | + md = await readInt16(calMD); |
| 98 | + |
| 99 | + logger.d( |
| 100 | + "Calibration values: [$ac1, $ac2, $ac3, $ac4, $ac5, $ac6, $b1, $b2, $mb, $mc, $md]"); |
| 101 | + } catch (e) { |
| 102 | + logger.e("Error initializing BMP180 calibration values: $e"); |
| 103 | + rethrow; |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + Future<int> readInt16(int registerAddress) async { |
| 108 | + try { |
| 109 | + List<int> data = await i2c.readBulk(address, registerAddress, 2); |
| 110 | + if (data.length < 2) { |
| 111 | + throw Exception( |
| 112 | + "Expected 2 bytes but got ${data.length} from register $registerAddress"); |
| 113 | + } |
| 114 | + int value = ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); |
| 115 | + if (value >= 0x8000) value -= 0x10000; |
| 116 | + return value; |
| 117 | + } catch (e) { |
| 118 | + logger.e("Error reading int16 from register $registerAddress: $e"); |
| 119 | + rethrow; |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + Future<int> readUInt16(int registerAddress) async { |
| 124 | + try { |
| 125 | + List<int> data = await i2c.readBulk(address, registerAddress, 2); |
| 126 | + if (data.length < 2) { |
| 127 | + throw Exception( |
| 128 | + "Expected 2 bytes but got ${data.length} from register $registerAddress"); |
| 129 | + } |
| 130 | + return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); |
| 131 | + } catch (e) { |
| 132 | + logger.e("Error reading uint16 from register $registerAddress: $e"); |
| 133 | + rethrow; |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + Future<int> readRawTemperature() async { |
| 138 | + try { |
| 139 | + await i2c.write(address, [readTempCmd], control); |
| 140 | + await Future.delayed(const Duration(milliseconds: 5)); |
| 141 | + int raw = await readUInt16(tempData); |
| 142 | + return raw; |
| 143 | + } catch (e) { |
| 144 | + logger.e("Error reading raw temperature: $e"); |
| 145 | + rethrow; |
| 146 | + } |
| 147 | + } |
| 148 | + |
| 149 | + Future<double> readTemperature({int? rawTemp}) async { |
| 150 | + try { |
| 151 | + int ut = rawTemp ?? await readRawTemperature(); |
| 152 | + |
| 153 | + int x1 = ((ut - ac6) * ac5) >> 15; |
| 154 | + int x2 = (mc << 11) ~/ (x1 + md); |
| 155 | + int b5 = x1 + x2; |
| 156 | + temperature = ((b5 + 8) >> 4) / 10.0; |
| 157 | + |
| 158 | + return temperature; |
| 159 | + } catch (e) { |
| 160 | + logger.e("Error reading temperature: $e"); |
| 161 | + rethrow; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + void setOversampling(int num) { |
| 166 | + oversampling = num; |
| 167 | + } |
| 168 | + |
| 169 | + Future<int> readRawPressure() async { |
| 170 | + try { |
| 171 | + List<int> delays = [5, 8, 14, 26]; |
| 172 | + int safeOversampling = oversampling.clamp(0, 3); |
| 173 | + await i2c.write(address, [readPressureCmd + (mode << 6)], control); |
| 174 | + await Future.delayed(Duration(milliseconds: delays[safeOversampling])); |
| 175 | + |
| 176 | + List<int> data = await i2c.readBulk(address, pressData, 3); |
| 177 | + if (data.length < 3) { |
| 178 | + throw Exception( |
| 179 | + "Expected 3 bytes but got ${data.length} from pressure data"); |
| 180 | + } |
| 181 | + int msb = data[0] & 0xFF; |
| 182 | + int lsb = data[1] & 0xFF; |
| 183 | + int xlsb = data[2] & 0xFF; |
| 184 | + |
| 185 | + return ((msb << 16) + (lsb << 8) + xlsb) >> (8 - mode); |
| 186 | + } catch (e) { |
| 187 | + logger.e("Error reading raw pressure: $e"); |
| 188 | + rethrow; |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + Future<double> readPressure({int? rawTemp}) async { |
| 193 | + try { |
| 194 | + int ut = rawTemp ?? await readRawTemperature(); |
| 195 | + int up = await readRawPressure(); |
| 196 | + |
| 197 | + int x1 = ((ut - ac6) * ac5) >> 15; |
| 198 | + int x2 = (mc << 11) ~/ (x1 + md); |
| 199 | + int b5 = x1 + x2; |
| 200 | + |
| 201 | + int b6 = b5 - 4000; |
| 202 | + x1 = (b2 * (b6 * b6) >> 12) >> 11; |
| 203 | + x2 = (ac2 * b6) >> 11; |
| 204 | + int x3 = x1 + x2; |
| 205 | + int b3 = (((ac1 * 4 + x3) << mode) + 2) ~/ 4; |
| 206 | + |
| 207 | + x1 = (ac3 * b6) >> 13; |
| 208 | + x2 = (b1 * ((b6 * b6) >> 12)) >> 16; |
| 209 | + x3 = ((x1 + x2) + 2) >> 2; |
| 210 | + int b4 = (ac4 * (x3 + 32768)) >> 15; |
| 211 | + int b7 = (up - b3) * (50000 >> mode); |
| 212 | + |
| 213 | + int p; |
| 214 | + if (b7 < 0x80000000) { |
| 215 | + p = (b7 * 2) ~/ b4; |
| 216 | + } else { |
| 217 | + p = (b7 ~/ b4) * 2; |
| 218 | + } |
| 219 | + |
| 220 | + x1 = (p >> 8) * (p >> 8); |
| 221 | + x1 = (x1 * 3038) >> 16; |
| 222 | + x2 = (-7357 * p) >> 16; |
| 223 | + pressure = (p + ((x1 + x2 + 3791) >> 4)).toDouble(); |
| 224 | + |
| 225 | + return pressure; |
| 226 | + } catch (e) { |
| 227 | + logger.e("Error reading pressure: $e"); |
| 228 | + rethrow; |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + double altitude() { |
| 233 | + return 44330.0 * (1 - pow(pressure / seaLevelPressure, 1 / 5.255)); |
| 234 | + } |
| 235 | + |
| 236 | + double seaLevel(double pressure, double altitude) { |
| 237 | + return pressure / pow(1 - (altitude / 44330.0), 5.255); |
| 238 | + } |
| 239 | + |
| 240 | + Future<Map<String, double>> getRawData() async { |
| 241 | + try { |
| 242 | + int rawTemp = await readRawTemperature(); |
| 243 | + temperature = await readTemperature(rawTemp: rawTemp); |
| 244 | + pressure = await readPressure(rawTemp: rawTemp); |
| 245 | + double alt = altitude(); |
| 246 | + |
| 247 | + return { |
| 248 | + 'temperature': temperature, |
| 249 | + 'pressure': pressure, |
| 250 | + 'altitude': alt, |
| 251 | + }; |
| 252 | + } catch (e) { |
| 253 | + logger.e("Error getting raw data: $e"); |
| 254 | + rethrow; |
| 255 | + } |
| 256 | + } |
| 257 | +} |
0 commit comments