35 Commits 28f3e15715 ... a74eac56bb

Author SHA1 Message Date
  subDesTagesMitExtraKaese a74eac56bb bugfix 2 years ago
  subDesTagesMitExtraKaese a7da29b8d7 Merge remote-tracking branch 'wokoeck/master' 2 years ago
  wokoeck_ch 2bfdd525dd Files bzgl optischer Kalibrierung 2 years ago
  wokoeck_ch 18a570cc73 Update Magnetische kalib. 2 years ago
  wokoeck_ch aaa78c8a23 Menu updated; Magnetic sensor offsets groundwork & shell script for easy program start 3 years ago
  Christian Woköck 1f207bc307 Delete 'acustic tracking.jpg' 3 years ago
  Christian Woköck 552af03f77 Delete 'screenrecord_thumb.jpg' 3 years ago
  wokoeck_ch 2d862d9048 deleted more duplicates + gyroscope calibration ready for testing 3 years ago
  wokoeck_ch 14188666ab old mainWIndow.py deleted and moved new one back to it's place :) 3 years ago
  wokoeck_ch d28ab95aeb Updated config.ini & c_cpp_properties.json & (Re-)started implementing the magnetic sensor calibration 3 years ago
  Janek 7fedbe4f27 Merge branch 'master' of https://gogs.es-lab.de/wokoeck_ch/lern-tracking-system 3 years ago
  Janek cd11a07c96 mainWindow + magnetic Sensor 3 years ago
  wokoeck_ch de61b5b541 update.. again 3 years ago
  Christian Woköck c47b27b120 Delete 'magneticSensor.py' 3 years ago
  Christian Woköck 02c994209b Delete 'mainWindow.py' 3 years ago
  Janek 925a8b71c4 Magnetdummysetup 3 years ago
  Janek 00b4b7bae2 Gui Änderung 3 years ago
  Janek 49569057ff Gui Änderung 3 years ago
  wokoeck_ch 1570c2acdc Merge branch 'master' of http://gogs.es-lab.de/wokoeck_ch/lern-tracking-system 3 years ago
  wokoeck_ch aaea7a84fb test 3 years ago
  Janek Fabian Franz 5781cd9312 Upload files to '' 3 years ago
  Janek Fabian Franz 926c574c54 weitere gui updates 3 years ago
  Janek Fabian Franz 5c2a7a49f4 Calibrate-Button für Magnetsensor 3 years ago
  Janek Fabian Franz 83123d1b3d Vorlage Wertanzeige Magnetsensor 3 years ago
  Christian Woköck 1efedb346c Update 'raspberry-pi/sensors/opticalSensor.py' 3 years ago
  Christian Woköck 68923f4de3 added magnetic sensor to main 3 years ago
  Christian Woköck 5028c7899d Preparing magnetic Sensor 3 years ago
  Christian Woköck 94fa8a54c2 Upload files to 'raspberry-pi/gui' 3 years ago
  Christian Woköck 0e5c99c39d Menu 3 years ago
  Christian Woköck b9569cb8dd Menu 3 years ago
  Janek Fabian Franz e0b08dd2f8 'raspberry-pi/gui/mainWindow.py' ändern 3 years ago
  Janek Fabian Franz 073951db08 'raspberry-pi/gui/mainWindow.py' ändern 3 years ago
  Christian Woköck 3ee279210a .. 3 years ago
  Christian Woköck f14f189be3 testing menu and magnetic sensor 3 years ago
  Christian Woköck 1630f3dc35 Upload files to '' 3 years ago
90 changed files with 4082 additions and 51 deletions
  1. 6 2
      .gitignore
  2. 1 0
      .pio/build/project.checksum
  3. 1 0
      .pio/build/uno/idedata.json
  4. 5 0
      .pio/libdeps/uno/BMP280_DEV/.editorconfig
  5. 32 0
      .pio/libdeps/uno/BMP280_DEV/.gitignore
  6. 1 0
      .pio/libdeps/uno/BMP280_DEV/.piopm
  7. 306 0
      .pio/libdeps/uno/BMP280_DEV/BMP280_DEV.cpp
  8. 218 0
      .pio/libdeps/uno/BMP280_DEV/BMP280_DEV.h
  9. 176 0
      .pio/libdeps/uno/BMP280_DEV/Device.cpp
  10. 86 0
      .pio/libdeps/uno/BMP280_DEV/Device.h
  11. 21 0
      .pio/libdeps/uno/BMP280_DEV/LICENSE
  12. 343 0
      .pio/libdeps/uno/BMP280_DEV/README.md
  13. 45 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_HSPI_Normal/BMP280_ESP32_HSPI_Normal.ino
  14. 29 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_I2C_Normal_DefinedPins/BMP280_ESP32_I2C_Normal_DefinedPins.ino
  15. 29 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP8266_I2C_Normal_DefinedPins/BMP280_ESP8266_I2C_Normal_DefinedPins.ino
  16. 32 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Alt_Normal/BMP280_I2C_Alt_Normal.ino
  17. 31 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Forced/BMP280_I2C_Forced.ino
  18. 32 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Normal/BMP280_I2C_Normal.ino
  19. 31 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Forced/BMP280_SPI_Forced.ino
  20. 32 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal/BMP280_SPI_Normal.ino
  21. 44 0
      .pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal_Multiple/BMP280_SPI_Normal_Multiple.ino
  22. 64 0
      .pio/libdeps/uno/BMP280_DEV/keywords.txt
  23. 9 0
      .pio/libdeps/uno/BMP280_DEV/library.properties
  24. 1 0
      .pio/libdeps/uno/MPU9250/.piopm
  25. 21 0
      .pio/libdeps/uno/MPU9250/LICENSE
  26. 954 0
      .pio/libdeps/uno/MPU9250/MPU9250.h
  27. 151 0
      .pio/libdeps/uno/MPU9250/MPU9250/MPU9250RegisterMap.h
  28. 223 0
      .pio/libdeps/uno/MPU9250/MPU9250/QuaternionFilter.h
  29. 230 0
      .pio/libdeps/uno/MPU9250/README.md
  30. 22 0
      .pio/libdeps/uno/MPU9250/examples/calibration/calibration.ino
  31. 29 0
      .pio/libdeps/uno/MPU9250/examples/calibration_eeprom/calibration_eeprom.ino
  32. 150 0
      .pio/libdeps/uno/MPU9250/examples/calibration_eeprom/eeprom_utils.h
  33. 71 0
      .pio/libdeps/uno/MPU9250/examples/connection_check/connection_check.ino
  34. 17 0
      .pio/libdeps/uno/MPU9250/examples/simple/simple.ino
  35. 20 0
      .pio/libdeps/uno/MPU9250/library.json
  36. 9 0
      .pio/libdeps/uno/MPU9250/library.properties
  37. 60 0
      .vscode/c_cpp_properties.json
  38. 10 0
      .vscode/extensions.json
  39. 44 0
      .vscode/launch.json
  40. 0 0
      LICENSE
  41. BIN
      Projektarbeit_final_Presentation.odp
  42. 0 0
      README.md
  43. 0 0
      arduino/.gitignore
  44. 0 0
      arduino/.vscode/extensions.json
  45. 0 0
      arduino/include/README
  46. 0 0
      arduino/include/ultrasonic.hpp
  47. 0 0
      arduino/lib/README
  48. 0 0
      arduino/platformio.ini
  49. 12 3
      arduino/src/main.cpp
  50. 0 0
      arduino/src/ultrasonic.cpp
  51. 0 0
      arduino/test/README
  52. BIN
      hntrgrnd.jpg
  53. 0 0
      images/Aufgabenstellung.jpg
  54. 0 0
      images/BOM.png
  55. 0 0
      images/Projektarbeit_final_Presentation.odp
  56. 0 0
      images/acustic tracking.jpg
  57. 0 0
      images/cave.jpg
  58. 0 0
      images/full_system.jpg
  59. 0 0
      images/item2.png
  60. 0 0
      images/screenrecord_thumb.jpg
  61. 0 0
      images/tracking.png
  62. BIN
      markers.png
  63. 19 0
      platformio.ini
  64. 0 0
      raspberry-pi/.vscode/settings.json
  65. BIN
      raspberry-pi/SourceSansPro-Semibold.otf
  66. 11 3
      raspberry-pi/config.ini
  67. 0 0
      raspberry-pi/gui/SourceSansPro-Semibold.otf
  68. 0 0
      raspberry-pi/gui/graph.py
  69. 0 0
      raspberry-pi/gui/logScreen.py
  70. 132 21
      raspberry-pi/gui/mainWindow.py
  71. 0 0
      raspberry-pi/gui/popup.py
  72. 0 0
      raspberry-pi/logHandler.py
  73. 7 5
      raspberry-pi/main.py
  74. 0 0
      raspberry-pi/markers.png
  75. 0 0
      raspberry-pi/requirements.txt
  76. 1 0
      raspberry-pi/sensors/acousticSensor.py
  77. 0 0
      raspberry-pi/sensors/calibration.py
  78. 21 0
      raspberry-pi/sensors/camera_calibration-master/LICENSE
  79. 18 0
      raspberry-pi/sensors/camera_calibration-master/README.md
  80. BIN
      raspberry-pi/sensors/camera_calibration-master/aruco_marker_board.pdf
  81. 115 0
      raspberry-pi/sensors/camera_calibration-master/camera_calibration.py
  82. 28 0
      raspberry-pi/sensors/camera_calibration-master/data_generation.py
  83. 11 0
      raspberry-pi/sensors/camera_calibration-master/importante.txt
  84. BIN
      raspberry-pi/sensors/camera_calibration-master/markers.png
  85. 9 9
      raspberry-pi/sensors/connection.py
  86. 97 8
      raspberry-pi/sensors/magneticSensor.py
  87. 0 0
      raspberry-pi/sensors/opticalSensor.py
  88. 12 0
      raspberry-pi/sensors/workspace.code-workspace
  89. 3 0
      start.sh
  90. 0 0
      ultrasound-tests.ods

+ 6 - 2
.gitignore

@@ -1,5 +1,9 @@
-.vscode/c_cpp_properties.json
 .venv
 .vscode/settings.json
 
-__pycache__
+__pycache__
+.vscode/c_cpp_properties.json
+.pio/build/project.checksum
+.pio/build/uno/idedata.json
+.vscode/c_cpp_properties.json
+.vscode/launch.json

+ 1 - 0
.pio/build/project.checksum

@@ -0,0 +1 @@
+fb7487a38c33ab8f5ddac2d3de9ed44f3bd3ef87

File diff suppressed because it is too large
+ 1 - 0
.pio/build/uno/idedata.json


+ 5 - 0
.pio/libdeps/uno/BMP280_DEV/.editorconfig

@@ -0,0 +1,5 @@
+root = true
+
+[*]
+indent_style = tab
+indent_size = 2

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/.gitignore

@@ -0,0 +1,32 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app

+ 1 - 0
.pio/libdeps/uno/BMP280_DEV/.piopm

@@ -0,0 +1 @@
+{"type": "library", "name": "BMP280_DEV", "version": "1.0.18", "spec": {"owner": "martinl1", "id": 6223, "name": "BMP280_DEV", "requirements": null, "url": null}}

+ 306 - 0
.pio/libdeps/uno/BMP280_DEV/BMP280_DEV.cpp

@@ -0,0 +1,306 @@
+/*
+  BMP280_DEV is an I2C/SPI compatible library for the Bosch BMP280 barometer.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release 	
+	V1.0.1 -- Added ESP32 HSPI support and change library to unique name
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Changed library name in the library.properties file
+	V1.0.5 -- Fixed bug in BMP280_DEV::getTemperature() function, thanks to Jon M.
+	V1.0.6 -- Merged multiple instances and initialisation pull requests by sensslen
+	V1.0.8 -- Used default arguments for begin() member function and 
+						added example using multiple BMP280 devices with SPI comms in NORMAL mode
+	V1.0.9 -- Moved writeMask to Device class and improved measurement detection code
+	V1.0.10 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.12 -- Allow sea level pressure calibration using setSeaLevelPressure() function
+	V1.0.14 -- Fix uninitialised structures, thanks to David Jade investigating and 
+						 flagging up this issue
+	V1.0.16 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.17 -- Added getCurrentTemperature(), getCurrentPressure(), getCurrentTempPres() 
+						 getCurrentAltitude() and getCurrentMeasurements() functions,
+						 to allow the BMP280 to be read directly without checking the measuring bit
+	V1.0.18 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#include <BMP280_DEV.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Class Constructors
+////////////////////////////////////////////////////////////////////////////////
+
+BMP280_DEV::BMP280_DEV() { setI2CAddress(BMP280_I2C_ADDR); }		// Constructor for I2C communications		
+#ifdef ARDUINO_ARCH_ESP8266
+BMP280_DEV::BMP280_DEV(uint8_t sda, uint8_t scl) : Device(sda, scl) { setI2CAddress(BMP280_I2C_ADDR); } 	// Constructor for I2C comms on ESP8266
+#endif
+BMP280_DEV::BMP280_DEV(uint8_t cs) : Device(cs) {}			   			// Constructor for SPI communications
+#ifdef ARDUINO_ARCH_ESP32 																			
+BMP280_DEV::BMP280_DEV(uint8_t sda, uint8_t scl) : Device(sda, scl) { setI2CAddress(BMP280_I2C_ADDR); } 	// Constructor for I2C comms on ESP32
+BMP280_DEV::BMP280_DEV(uint8_t cs, uint8_t spiPort, SPIClass& spiClass) : Device(cs, spiPort, spiClass) {} // Constructor for SPI communications on the ESP32
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Public Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+uint8_t BMP280_DEV::begin(Mode mode, 															// Initialise BMP280 device settings
+											Oversampling presOversampling, 
+											Oversampling tempOversampling,
+											IIRFilter iirFilter,
+											TimeStandby timeStandby)
+{
+	initialise();																											// Call the Device base class "initialise" function
+	if (readByte(BMP280_DEVICE_ID) != DEVICE_ID)              				// Check the device ID
+  {
+    return 0;                                                     	// If the ID is incorrect return 0
+  }	
+  reset();                                                          // Reset the BMP280 barometer
+  readBytes(BMP280_TRIM_PARAMS, (uint8_t*)&params, sizeof(params)); // Read the trim parameters into the params structure
+	setConfigRegister(iirFilter, timeStandby); 												// Initialise the BMP280 configuration register
+	setCtrlMeasRegister(mode, presOversampling, tempOversampling);		// Initialise the BMP280 control and measurement register	
+	return 1;																													// Report successful initialisation
+}
+
+uint8_t BMP280_DEV::begin(Mode mode, uint8_t addr)									// Initialise BMP280 with default settings, but selected mode and
+{																																		// I2C address
+	setI2CAddress(addr);
+	return begin(mode);
+}
+
+uint8_t BMP280_DEV::begin(uint8_t addr)															// Initialise BMP280 with default settings and selected I2C address
+{
+	setI2CAddress(addr);
+	return begin();
+}
+
+void BMP280_DEV::reset()																						// Reset the BMP280 barometer
+{
+	writeByte(BMP280_RESET, RESET_CODE);                     									
+  delay(10);                                                                
+}
+
+void BMP280_DEV::startNormalConversion() { setMode(NORMAL_MODE); }	// Start continuous measurement in NORMAL_MODE
+
+void BMP280_DEV::startForcedConversion() 														// Start a one shot measurement in FORCED_MODE
+{ 
+	ctrl_meas.reg = readByte(BMP280_CTRL_MEAS);											 	// Read the control and measurement register
+	if (ctrl_meas.bit.mode == SLEEP_MODE)															// Only set FORCED_MODE if we're already in SLEEP_MODE
+	{
+		setMode(FORCED_MODE);
+	}	
+}			
+
+void BMP280_DEV::stopConversion() { setMode(SLEEP_MODE); }					// Stop the conversion and return to SLEEP_MODE
+
+void BMP280_DEV::setPresOversampling(Oversampling presOversampling)	// Set the pressure oversampling rate
+{
+	ctrl_meas.bit.osrs_p = presOversampling;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
+}
+
+void BMP280_DEV::setTempOversampling(Oversampling tempOversampling)	// Set the temperature oversampling rate
+{
+	ctrl_meas.bit.osrs_t = tempOversampling;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
+}
+
+void BMP280_DEV::setIIRFilter(IIRFilter iirFilter)									// Set the IIR filter setting
+{
+	config.bit.filter = iirFilter;
+	writeByte(BMP280_CONFIG, config.reg);
+}
+
+void BMP280_DEV::setTimeStandby(TimeStandby timeStandby)						// Set the time standby measurement interval
+{
+	config.bit.t_sb = timeStandby;
+	writeByte(BMP280_CONFIG, config.reg);
+}
+
+void BMP280_DEV::setSeaLevelPressure(float pressure)								// Set the sea level pressure value
+{
+	sea_level_pressure = pressure;
+}
+
+void BMP280_DEV::getCurrentTemperature(float &temperature)					// Get the current temperature without checking the measuring bit
+{
+	uint8_t data[3];                                                  // Create a data buffer
+	readBytes(BMP280_TEMP_MSB, &data[0], 3);             							// Read the temperature and pressure data
+	int32_t adcTemp = (int32_t)data[0] << 12 | (int32_t)data[1] << 4 | (int32_t)data[2] >> 4;  // Copy the temperature and pressure data into the adc variables
+	int32_t temp = bmp280_compensate_T_int32(adcTemp);                // Temperature compensation (function from BMP280 datasheet)
+	temperature = (float)temp / 100.0f;                               // Calculate the temperature in degrees Celsius
+}
+
+uint8_t BMP280_DEV::getTemperature(float &temperature)							// Get the temperature with measurement check
+{
+	if (!dataReady())																									// Check if a measurement is ready
+	{
+		return 0;
+	}
+	getCurrentTemperature(temperature);																// Get the current temperature
+	return 1;
+}
+
+void BMP280_DEV::getCurrentPressure(float &pressure)								// Get the current pressure without checking the measuring bit
+{
+	float temperature;
+	getCurrentTempPres(temperature, pressure);
+}
+
+uint8_t BMP280_DEV::getPressure(float &pressure)										// Get the pressure
+{
+	float temperature;
+	return getTempPres(temperature, pressure);
+}
+
+void BMP280_DEV::getCurrentTempPres(float &temperature, float &pressure)	// Get the current temperature and pressure without checking the measuring bit
+{
+	uint8_t data[6];                                                  // Create a data buffer
+	readBytes(BMP280_PRES_MSB, &data[0], 6);             							// Read the temperature and pressure data
+	int32_t adcTemp = (int32_t)data[3] << 12 | (int32_t)data[4] << 4 | (int32_t)data[5] >> 4;  // Copy the temperature and pressure data into the adc variables
+	int32_t adcPres = (int32_t)data[0] << 12 | (int32_t)data[1] << 4 | (int32_t)data[2] >> 4;
+	int32_t temp = bmp280_compensate_T_int32(adcTemp);                // Temperature compensation (function from BMP280 datasheet)
+	uint32_t pres = bmp280_compensate_P_int64(adcPres);               // Pressure compensation (function from BMP280 datasheet)
+	temperature = (float)temp / 100.0f;                               // Calculate the temperature in degrees Celsius
+	pressure = (float)pres / 256.0f / 100.0f;                         // Calculate the pressure in millibar
+}
+
+uint8_t BMP280_DEV::getTempPres(float &temperature, float &pressure)	// Get the temperature and pressure
+{
+	if (!dataReady())																									// Check if a measurement is ready
+	{
+		return 0;
+	}
+	getCurrentTempPres(temperature, pressure);												// Get the current temperature and pressure
+	return 1;
+}
+
+void BMP280_DEV::getCurrentAltitude(float &altitude)								// Get the current altitude without checking the measuring bit
+{
+	float temperature, pressure;
+	getCurrentMeasurements(temperature, pressure, altitude);
+}
+
+uint8_t BMP280_DEV::getAltitude(float &altitude)										// Get the altitude
+{
+	float temperature, pressure;
+	return getMeasurements(temperature, pressure, altitude);
+}
+
+void BMP280_DEV::getCurrentMeasurements(float &temperature, float &pressure, float &altitude) // Get all the measurements without checking the measuring bit
+{
+	getCurrentTempPres(temperature, pressure);
+	altitude = ((float)powf(sea_level_pressure / pressure, 0.190223f) - 1.0f) * (temperature + 273.15f) / 0.0065f; // Calculate the altitude in metres 
+}
+
+uint8_t BMP280_DEV::getMeasurements(float &temperature, float &pressure, float &altitude)		// Get all measurements temperature, pressue and altitude
+{  
+	if (getTempPres(temperature, pressure))
+	{
+		altitude = ((float)powf(sea_level_pressure / pressure, 0.190223f) - 1.0f) * (temperature + 273.15f) / 0.0065f; // Calculate the altitude in metres 
+		return 1;
+	}
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Private Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+void BMP280_DEV::setMode(Mode mode)																	// Set the BMP280's mode
+{
+	ctrl_meas.bit.mode = mode;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);
+}
+
+// Set the BMP280 control and measurement register 
+void BMP280_DEV::setCtrlMeasRegister(Mode mode, Oversampling presOversampling, Oversampling tempOversampling)
+{
+	ctrl_meas.reg = tempOversampling << 5 | presOversampling << 2 | mode;
+	writeByte(BMP280_CTRL_MEAS, ctrl_meas.reg);                              
+}
+
+// Set the BMP280 configuration register
+void BMP280_DEV::setConfigRegister(IIRFilter iirFilter, TimeStandby timeStandby)
+{
+	config.reg = timeStandby << 5 | iirFilter << 2;
+	writeByte(BMP280_CONFIG, config.reg);                              
+}
+
+uint8_t BMP280_DEV::dataReady()																			// Check if a measurement is ready
+{			
+	if (ctrl_meas.bit.mode == SLEEP_MODE)														 	// If we're in SLEEP_MODE return immediately
+	{
+		return 0;
+	}
+	status.reg = readByte(BMP280_STATUS);															// Read the status register				
+	if (status.bit.measuring ^ previous_measuring)						 				// Edge detection: check if the measurement bit has been changed
+	{
+		previous_measuring = status.bit.measuring;											// Update the previous measuring flag
+		if (!status.bit.measuring)																			// Check if the measuring bit has been cleared
+		{
+			if (ctrl_meas.bit.mode == FORCED_MODE)												// If we're in FORCED_MODE switch back to SLEEP_MODE
+			{
+				ctrl_meas.bit.mode = SLEEP_MODE;												
+			}
+			return 1;																											// A measurement is ready
+		}
+	}
+	return 0;																													// A measurement is still pending
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Bosch BMP280_DEV (Private) Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
+// t_fine carries fine temperature as global value
+int32_t BMP280_DEV::bmp280_compensate_T_int32(int32_t adc_T)
+{
+  int32_t var1, var2, T;
+  var1 = ((((adc_T >> 3) - ((int32_t)params.dig_T1 << 1))) * ((int32_t)params.dig_T2)) >> 11;
+  var2 = (((((adc_T >> 4) - ((int32_t)params.dig_T1)) * ((adc_T >> 4) - ((int32_t)params.dig_T1))) >> 12) *
+  ((int32_t)params.dig_T3)) >> 14;
+  t_fine = var1 + var2;
+  T = (t_fine * 5 + 128) >> 8;
+  return T;
+}
+
+// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
+// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
+uint32_t BMP280_DEV::bmp280_compensate_P_int64(int32_t adc_P)
+{
+  int64_t var1, var2, p;
+  var1 = ((int64_t)t_fine) - 128000;
+  var2 = var1 * var1 * (int64_t)params.dig_P6;
+  var2 = var2 + ((var1 * (int64_t)params.dig_P5) << 17);
+  var2 = var2 + (((int64_t)params.dig_P4) << 35);
+  var1 = ((var1 * var1 * (int64_t)params.dig_P3) >> 8) + ((var1 * (int64_t)params.dig_P2) << 12);
+  var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)params.dig_P1) >> 33;
+  if (var1 == 0)
+  {
+    return 0; // avoid exception caused by division by zero
+  }
+  p = 1048576 - adc_P;
+  p = (((p << 31) - var2) * 3125) / var1;
+  var1 = (((int64_t)params.dig_P9) * (p >> 13) * (p>>13)) >> 25;
+  var2 = (((int64_t)params.dig_P8) * p) >> 19;
+  p = ((p + var1 + var2) >> 8) + (((int64_t)params.dig_P7) << 4);
+  return (uint32_t)p;
+}

+ 218 - 0
.pio/libdeps/uno/BMP280_DEV/BMP280_DEV.h

@@ -0,0 +1,218 @@
+/*
+  BMP280_DEV is an I2C/SPI compatible library for the Bosch BMP280 barometer.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release 		
+	V1.0.1 -- Added ESP32 HSPI support and change library to unique name
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Changed library name in the library.properties file
+	V1.0.5 -- Fixed bug in BMP280_DEV::getTemperature() function, thanks to Jon M.
+	V1.0.6 -- Merged multiple instances and initialisation pull requests by sensslen
+	V1.0.8 -- Used default arguments for begin() member function and 
+						added example using multiple BMP280 devices with SPI comms in NORMAL mode	
+	V1.0.9 -- Moved writeMask to Device class and improved measurement detection code
+	V1.0.10 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.12 -- Allow sea level pressure calibration using setSeaLevelPressure() function
+	V1.0.14 -- Fix uninitialised structures, thanks to David Jade investigating and 
+						 flagging up this issue
+	V1.0.16 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.17 -- Added getCurrentTemperature(), getCurrentPressure(), getCurrentTempPres() 
+						 getCurrentAltitude() and getCurrentMeasurements() functions,
+						 to allow the BMP280 to be read directly without checking the measuring bit
+	V1.0.18 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#ifndef BMP280_DEV_h
+#define BMP280_DEV_h
+
+#include <Device.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Definitions
+////////////////////////////////////////////////////////////////////////////////
+
+#define BMP280_I2C_ADDR		 		0x77				// The BMP280 I2C address
+#define BMP280_I2C_ALT_ADDR 	0x76				// The BMP280 I2C alternate address
+#define DEVICE_ID 						0x58				// The BMP280 device ID
+#define RESET_CODE						0xB6				// The BMP280 reset code
+
+enum SPIPort { BMP280_SPI0, BMP280_SPI1 };
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Registers
+////////////////////////////////////////////////////////////////////////////////
+
+enum {
+	BMP280_TRIM_PARAMS		 = 0x88,          // Trim parameter registers' base sub-address
+	BMP280_DEVICE_ID 			 = 0xD0,          // Device ID register sub-address
+	BMP280_RESET 					 = 0xE0,          // Reset register sub-address
+	BMP280_STATUS      		 = 0xF3,          // Status register sub-address
+	BMP280_CTRL_MEAS   		 = 0xF4,          // Control and measurement register sub-address
+	BMP280_CONFIG       	 = 0xF5,          // Configuration register sub-address
+	BMP280_PRES_MSB    		 = 0xF7,          // Pressure Most Significant Byte (MSB) register sub-address
+	BMP280_PRES_LSB    		 = 0xF8,          // Pressure Least Significant Byte (LSB) register sub-address
+	BMP280_PRES_XLSB   		 = 0xF9,          // Pressure eXtended Least Significant Byte (XLSB) register sub-address
+	BMP280_TEMP_MSB    		 = 0xFA,          // Pressure Most Significant Byte (MSB) register sub-address
+	BMP280_TEMP_LSB    	 	 = 0xFB,          // Pressure Least Significant Byte (LSB) register sub-address
+	BMP280_TEMP_XLSB    	 = 0xFC 					// Pressure eXtended Least Significant Byte (XLSB) register sub-address
+};          
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Modes
+////////////////////////////////////////////////////////////////////////////////
+
+enum Mode {
+	SLEEP_MODE          	 = 0x00,          // Device mode bitfield in the control and measurement register 
+	FORCED_MODE         	 = 0x01,
+	NORMAL_MODE         	 = 0x03
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Register bit field Definitions
+////////////////////////////////////////////////////////////////////////////////
+
+enum Oversampling {
+	OVERSAMPLING_SKIP 		 = 0x00,     			// Oversampling bit fields in the control and measurement register
+	OVERSAMPLING_X1   		 = 0x01,
+	OVERSAMPLING_X2   		 = 0x02,
+	OVERSAMPLING_X4  		   = 0x03,
+	OVERSAMPLING_X8    		 = 0x04,
+	OVERSAMPLING_X16   	 	 = 0x05
+};
+
+enum IIRFilter {
+	IIR_FILTER_OFF  			 = 0x00,     			// Infinite Impulse Response (IIR) filter bit field in the configuration register
+	IIR_FILTER_2    			 = 0x01,
+	IIR_FILTER_4           = 0x02,
+	IIR_FILTER_8           = 0x03,
+	IIR_FILTER_16          = 0x04
+};
+
+enum TimeStandby {
+	TIME_STANDBY_05MS      = 0x00,     		  // Time standby bit field in the configuration register
+	TIME_STANDBY_62MS      = 0x01,
+	TIME_STANDBY_125MS     = 0x02,
+	TIME_STANDBY_250MS     = 0x03,
+	TIME_STANDBY_500MS     = 0x04,
+	TIME_STANDBY_1000MS    = 0x05,
+	TIME_STANDBY_2000MS    = 0x06,
+	TIME_STANDBY_4000MS    = 0x07
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV Class definition
+////////////////////////////////////////////////////////////////////////////////
+
+class BMP280_DEV : public Device {															// Derive the BMP280_DEV class from the Device class
+	public:
+		BMP280_DEV();																								// BMP280_DEV object for I2C operation
+#ifdef ARDUINO_ARCH_ESP8266
+		BMP280_DEV(uint8_t sda, uint8_t scl);												// BMP280_DEV object for ESP8266 I2C operation with user-defined pins
+#endif
+		BMP280_DEV(uint8_t cs);																			// BMP280_DEV object for SPI operation
+#ifdef ARDUINO_ARCH_ESP32
+		BMP280_DEV(uint8_t sda, uint8_t scl);												// BMP280_DEV object for ESP32 I2C operation with user-defined pins
+		BMP280_DEV(uint8_t cs, uint8_t spiPort, SPIClass& spiClass);	// BMP280_DEV object for SPI1 with supplied SPIClass object
+#endif
+		uint8_t begin(Mode mode = SLEEP_MODE, 												// Initialise the barometer with arguments
+									Oversampling presOversampling = OVERSAMPLING_X16, 
+									Oversampling tempOversampling = OVERSAMPLING_X2, 
+									IIRFilter iirFilter = IIR_FILTER_OFF, 
+									TimeStandby timeStandby = TIME_STANDBY_05MS);
+		uint8_t begin(Mode mode, uint8_t addr);											// Initialise the barometer specifying start mode and I2C addrss
+		uint8_t begin(uint8_t addr);																// Initialise the barometer specifying I2C address with default initialisation
+		void reset();																								// Soft reset the barometer		
+		void startNormalConversion();																// Start continuous measurement in NORMAL_MODE
+		void startForcedConversion();															  // Start a one shot measurement in FORCED_MODE
+		void stopConversion();																			// Stop the conversion and return to SLEEP_MODE
+		void setPresOversampling(Oversampling presOversampling);		// Set the pressure oversampling: OFF, X1, X2, X4, X8, X16
+		void setTempOversampling(Oversampling tempOversampling);		// Set the temperature oversampling: OFF, X1, X2, X4, X8, X16
+		void setIIRFilter(IIRFilter iirFilter);											// Set the IIR filter setting: OFF, 2, 4, 8, 16
+		void setTimeStandby(TimeStandby timeStandby);	 							// Set the time standby measurement interval: 0.5, 62, 125, 250, 500ms, 1s, 2s, 4s
+		void setSeaLevelPressure(float pressure = 1013.23f);				// Set the sea level pressure value
+		void getCurrentTemperature(float &temperature);							// Get the current temperature measurement without checking the measuring bit
+		uint8_t getTemperature(float &temperature);									// Get a temperature measurement
+		void getCurrentPressure(float &pressure);										// Get the current pressure without checking the measuring bit
+		uint8_t getPressure(float &pressure);												// Get a pressure measurement
+		void getCurrentTempPres(float &temperature, float &pressure); // Get the current temperature and pressure without checking the measuring bit
+		uint8_t getTempPres(float &temperature, float &pressure);		// Get a temperature and pressure measurement
+		void getCurrentAltitude(float &altitude);										// Get the current altitude without checking the measuring bit
+		uint8_t getAltitude(float &altitude);												// Get an altitude measurement
+		void getCurrentMeasurements(float &temperature, float &pressure, float &altitude); // Get all measurements without checking the measuring bit
+		uint8_t getMeasurements(float &temperature, float &pressure, float &altitude);	// Get temperature, pressure and altitude measurements
+	protected:
+	private:
+		void setMode(Mode mode);																		// Set the barometer mode
+		void setCtrlMeasRegister(Mode mode, Oversampling presOversampling, Oversampling tempOversamping);		// Set the BMP280 control and measurement register
+		void setConfigRegister(IIRFilter iirFilter, TimeStandby timeStandby);		// Set the BMP280 configuration register
+		uint8_t dataReady();																				// Checks if a measurement is ready
+	
+		struct {																										// The BMP280 compensation trim parameters (coefficients)
+			uint16_t dig_T1;
+			int16_t  dig_T2;
+			int16_t  dig_T3;
+			uint16_t dig_P1;
+			int16_t  dig_P2;
+			int16_t  dig_P3;
+			int16_t  dig_P4;
+			int16_t  dig_P5;
+			int16_t  dig_P6;
+			int16_t  dig_P7;
+			int16_t  dig_P8;
+			int16_t  dig_P9;
+		} params;
+			
+		union {																											// Copy of the BMP280's configuration register
+			struct {
+				uint8_t spi3w_en : 1;
+				uint8_t 				 : 1;
+				uint8_t filter 	 : 3;
+				uint8_t t_sb		 : 3;
+			} bit;
+			uint8_t reg;
+		} config = { .reg = 0 };
+		
+		union {																											// Copy of the BMP280's control and measurement register
+			struct {
+				uint8_t mode   : 2;
+				uint8_t osrs_p : 3;
+				uint8_t osrs_t : 3;
+			} bit;
+			uint8_t reg;
+		} ctrl_meas = { .reg = 0 };
+			
+		union {																											// Copy of the BMP280's status register
+			struct {
+				uint8_t im_update : 1;
+				uint8_t						: 2;
+				uint8_t measuring : 1;
+			} bit;
+			uint8_t reg;
+		} status = { .reg = 0 };
+		
+		int32_t t_fine;																							// Bosch t_fine variable
+		int32_t bmp280_compensate_T_int32(int32_t adc_T);						// Bosch temperature compensation function
+		uint32_t bmp280_compensate_P_int64(int32_t adc_P);					// Bosch pressure compensation function
+		bool previous_measuring;																		// Previous measuring state
+		float sea_level_pressure = 1013.23f;												// Sea level pressure
+};
+
+#endif

+ 176 - 0
.pio/libdeps/uno/BMP280_DEV/Device.cpp

@@ -0,0 +1,176 @@
+/*
+  Device is an I2C/SPI compatible base class library.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release
+	V1.0.1 -- Added ESP32 HSPI support	
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Addition of SPI write and read byte masks
+	V1.0.4 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.5 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.6 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#include <Device.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Class Constructors
+////////////////////////////////////////////////////////////////////////////////
+
+Device::Device() : comms(I2C_COMMS) {}															// Initialise constructor for I2C communications
+#ifdef ARDUINO_ARCH_ESP8266
+Device::Device(uint8_t sda, uint8_t scl) : comms(I2C_COMMS_DEFINED_PINS), sda(sda), scl(scl) {}	// Constructor for ESP8266 I2C with user-defined pins
+#endif
+Device::Device(uint8_t cs) : comms(SPI_COMMS), cs(cs), spiClockSpeed(1000000) {}		// Constructor for SPI communications
+#ifdef ARDUINO_ARCH_ESP32																														
+Device::Device(uint8_t sda, uint8_t scl) : comms(I2C_COMMS_DEFINED_PINS), sda(sda), scl(scl) {}	// Constructor for ESP32 I2C with user-defined pins
+Device::Device(uint8_t cs, uint8_t spiPort, SPIClass& spiClass) 										// Constructor for ESP32 HSPI communications
+	: comms(SPI_COMMS), cs(cs), spiPort(spiPort), spi(&spiClass), spiClockSpeed(1000000) {}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Public Member Function
+////////////////////////////////////////////////////////////////////////////////
+
+void Device::setClock(uint32_t clockSpeed)													// Set the I2C or SPI clock speed
+{
+	if (comms == I2C_COMMS)
+	{
+		Wire.setClock(clockSpeed);
+	}
+	else
+	{
+		spiClockSpeed = clockSpeed;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Device I2C & SPI Wrapper (Protected) Member Functions
+////////////////////////////////////////////////////////////////////////////////
+
+void Device::initialise()																						// Initialise device communications
+{
+  if (comms == I2C_COMMS)																						// Check with communications bus has been selected I2C or SPI
+	{
+		Wire.begin();																										// Initialise I2C communication
+		Wire.setClock(400000);																					// Set the SCL clock to default of 400kHz
+	}
+#if defined ARDUINO_ARCH_ESP8266 || defined ARDUINO_ARCH_ESP32
+	else if (comms == I2C_COMMS_DEFINED_PINS)													// Check if the ESP8266 or ESP32 has specified user-defined I2C pins
+	{
+		Wire.begin(sda, scl);																						// Initialise I2C communication with user-defined pins
+		Wire.setClock(400000);																					// Set the SCL clock to default of 400kHz
+		comms = I2C_COMMS;																							// Set the communications to standard I2C
+	}
+#endif
+	else
+	{
+		digitalWrite(cs, HIGH);																					// Pull the chip select (CS) pin high
+		pinMode(cs, OUTPUT);																						// Set-up the SPI chip select pin
+#ifdef ARDUINO_ARCH_ESP32
+		if (spiPort == HSPI)																						// Set-up spi pointer for VSPI or HSPI communications
+		{
+			spi->begin(14, 27, 13, 2);																		// Start HSPI on SCK 14, MOSI 13, MISO 24, SS CS (GPIO2 acts as dummy pin)
+		}
+		else
+		{
+			spi = &SPI;																										// Start VSPI on SCK 5, MOSI 18, MISO 19, SS CS
+			spi->begin();
+		}														
+#else
+		spi = &SPI;																											// Set-up spi pointer for SPI communications
+		spi->begin();
+#endif
+	}
+}
+
+void Device::setI2CAddress(uint8_t addr)														// Set the Device's I2C address
+{	
+	address = addr;
+}
+
+void Device::writeByte(uint8_t subAddress, uint8_t data)
+{
+  if (comms == I2C_COMMS)
+	{
+		Wire.beginTransmission(address);  															// Write a byte to the sub-address using I2C
+		Wire.write(subAddress);          
+		Wire.write(data);                 
+		Wire.endTransmission();          
+	}
+	else // if (comms == SPI_COMMS)
+	{
+		spi->beginTransaction(SPISettings(spiClockSpeed, MSBFIRST, SPI_MODE0));	// Write a byte to the sub-address using SPI
+		digitalWrite(cs, LOW);
+		spi->transfer(subAddress & WRITE_MASK);
+		spi->transfer(data);
+		digitalWrite(cs, HIGH);
+		spi->endTransaction();
+	}
+}
+
+uint8_t Device::readByte(uint8_t subAddress)												// Read a byte from the sub-address using I2C
+{
+  uint8_t data = 0x00;
+	if (comms == I2C_COMMS)																		
+	{
+		Wire.beginTransmission(address);         
+		Wire.write(subAddress);                  
+		Wire.endTransmission(false);             
+		Wire.requestFrom(address, (uint8_t)1);	 
+		data = Wire.read();                      
+	}
+	else // if (comms == SPI_COMMS)
+	{
+		spi->beginTransaction(SPISettings(spiClockSpeed, MSBFIRST, SPI_MODE0));		// Read a byte from the sub-address using SPI
+		digitalWrite(cs, LOW);
+		spi->transfer(subAddress | READ_MASK);
+		data = spi->transfer(data);
+		digitalWrite(cs, HIGH);
+		spi->endTransaction();	
+	}
+  return data;                             													// Return data read from sub-address register
+}
+
+void Device::readBytes(uint8_t subAddress, uint8_t* data, uint16_t count)
+{  
+  if (comms == I2C_COMMS)																						// Read "count" bytes into the "data" buffer using I2C
+	{
+		Wire.beginTransmission(address);          
+		Wire.write(subAddress);                   
+		Wire.endTransmission(false);              
+		uint8_t i = 0;
+		Wire.requestFrom(address, (uint8_t)count);  
+		while (Wire.available()) 
+		{
+			data[i++] = Wire.read();          
+		}
+	}
+	else // if (comms == SPI_COMMS)		
+	{
+		spi->beginTransaction(SPISettings(spiClockSpeed, MSBFIRST, SPI_MODE0));	// Read "count" bytes into the "data" buffer using SPI
+		digitalWrite(cs, LOW);
+		spi->transfer(subAddress | READ_MASK);
+		spi->transfer(data, count);
+		digitalWrite(cs, HIGH);
+		spi->endTransaction();	
+	}
+}

+ 86 - 0
.pio/libdeps/uno/BMP280_DEV/Device.h

@@ -0,0 +1,86 @@
+/*
+  Device is an I2C/SPI compatible base class library.
+	
+	Copyright (C) Martin Lindupp 2019
+	
+	V1.0.0 -- Initial release 
+	V1.0.1 -- Added ESP32 HSPI support	
+	V1.0.2 -- Modification to allow external creation of HSPI object on ESP32
+	V1.0.3 -- Addition of SPI write and read byte masks
+	V1.0.4 -- Modification to allow user-defined pins for I2C operation on the ESP8266
+	V1.0.5 -- Modification to allow user-defined pins for I2C operation on the ESP32
+	V1.0.6 -- Initialise "device" constructor member variables in the same order they are declared
+	
+	The MIT License (MIT)
+	Permission is hereby granted, free of charge, to any person obtaining a copy
+	of this software and associated documentation files (the "Software"), to deal
+	in the Software without restriction, including without limitation the rights
+	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+	copies of the Software, and to permit persons to whom the Software is
+	furnished to do so, subject to the following conditions:
+	The above copyright notice and this permission notice shall be included in all
+	copies or substantial portions of the Software.
+	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+	SOFTWARE.
+*/
+
+#ifndef Device_h
+#define Device_h
+
+#include <Arduino.h>
+#include <Wire.h>
+#include <SPI.h>
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Communications
+////////////////////////////////////////////////////////////////////////////////
+
+#if defined ARDUINO_ARCH_ESP8266 || defined ARDUINO_ARCH_ESP32
+enum Comms { I2C_COMMS, SPI_COMMS, I2C_COMMS_DEFINED_PINS };
+#else						 
+enum Comms { I2C_COMMS, SPI_COMMS };		 
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// Device Class definition
+////////////////////////////////////////////////////////////////////////////////
+
+class Device{
+	public:
+		Device();																										// Device object for I2C operation
+#ifdef ARDUINO_ARCH_ESP8266
+		Device(uint8_t sda, uint8_t scl);														// Device object for ESP8266 I2C operation with user-defined pins
+#endif
+		Device(uint8_t cs);																					// Device object for SPI operation
+#ifdef ARDUINO_ARCH_ESP32
+		Device(uint8_t sda, uint8_t scl);														// Device object for ESP32 I2C operation with user-defined pins
+		Device(uint8_t cs, uint8_t spiPort, SPIClass& spiClass);		// Device object for ESP32 HSPI operation with supplied SPI object
+#endif		
+		void setClock(uint32_t clockSpeed);													// Set the I2C/SPI clock speed
+	protected:
+		void initialise();																					// Initialise communications	
+		void setI2CAddress(uint8_t addr);											  		// Set the Device I2C address
+		void writeByte(uint8_t subAddress, uint8_t data);						// I2C and SPI write byte wrapper function
+		uint8_t readByte(uint8_t subAddress);												// I2C and SPI read byte wrapper function
+		void readBytes(uint8_t subAddress, uint8_t* dest, uint16_t count);		// I2C and SPI read bytes wrapper function
+	private:
+		Comms comms;																								// Communications bus: I2C or SPI
+		uint8_t address;																						// The device I2C address
+		uint8_t cs;																									// The SPI chip select pin
+#ifdef ARDUINO_ARCH_ESP32
+		uint8_t spiPort;																						// SPI port type VSPI or HSPI
+#endif
+		SPIClass* spi;																							// Pointer to the SPI class
+		uint32_t spiClockSpeed;																			// The SPI clock speed
+		const uint8_t WRITE_MASK = 0x7F;														// Sub-address write mask for SPI communications
+		const uint8_t READ_MASK  = 0x80;														// Sub-address read mask for SPI communications
+#if defined ARDUINO_ARCH_ESP8266 || defined ARDUINO_ARCH_ESP32
+		uint8_t sda, scl;																						// Software I2C SDA and SCL pins 
+#endif
+};
+#endif

+ 21 - 0
.pio/libdeps/uno/BMP280_DEV/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 MartinL1
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

File diff suppressed because it is too large
+ 343 - 0
.pio/libdeps/uno/BMP280_DEV/README.md


+ 45 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_HSPI_Normal/BMP280_ESP32_HSPI_Normal.ino

@@ -0,0 +1,45 @@
+//////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - ESP32 HSPI Communications, Default Configuration, Normal Conversion
+//////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                            // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+//BMP280_DEV bmp280(21);                            // Create BMP280_DEV object and set-up for VSPI operation, SCK 5, MOSI 18, MISO 19, SS 21
+SPIClass SPI1(HSPI);                              // Create (instantiate) the SPI1 object for HSPI operation
+BMP280_DEV bmp280(21, HSPI, SPI1);                // Create BMP280_DEV object and set-up for HSPI operation, SCK 14, MOSI 13, MISO 27, SS 21
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_1000MS);     // Set the standby time to 1 second (1000ms)
+  bmp280.startNormalConversion();                 // Start NORMAL continuous conversion
+  
+  xTaskCreatePinnedToCore(                        // Kick-off "TaskOne" pinned to core 1
+    taskOne,
+    "TaskOne",
+    10000,
+    NULL,
+    1,
+    NULL,
+    1);
+}
+
+void taskOne(void* parameter)
+{
+  while(true)
+  {
+    if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+    {
+      Serial.print(temperature);                    // Display the results    
+      Serial.print(F("*C   "));
+      Serial.print(pressure);    
+      Serial.print(F("hPa   "));
+      Serial.print(altitude);
+      Serial.println(F("m"));  
+    }
+  }
+}
+
+void loop() { delay(1000); }                        // Add 1 second delay

+ 29 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP32_I2C_Normal_DefinedPins/BMP280_ESP32_I2C_Normal_DefinedPins.ino

@@ -0,0 +1,29 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - ESP32, I2C Communications, Default Configuration, Normal Conversion, User-Defined Pins
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(A6, A7);                        // Instantiate (create) a BMP280 object and set-up for I2C operation on pins SDA: A6, SCL: A7
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 29 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_ESP8266_I2C_Normal_DefinedPins/BMP280_ESP8266_I2C_Normal_DefinedPins.ino

@@ -0,0 +1,29 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - ESP8266, I2C Communications, Default Configuration, Normal Conversion, User-Defined Pins
+//////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(6, 7);                          // Instantiate (create) a BMP280 object and set-up for I2C operation on pins SDA: 6, SCL: 7
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Alt_Normal/BMP280_I2C_Alt_Normal.ino

@@ -0,0 +1,32 @@
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - I2C Communications (Alternative Address), Default Configuration, Normal Conversion
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin(BMP280_I2C_ALT_ADDR);              // Default initialisation with alternative I2C address (0x76), place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 31 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Forced/BMP280_I2C_Forced.ino

@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - I2C Communications, Default Configuration, Normal Conversion
+/////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4 
+}
+
+void loop() 
+{
+  bmp280.startForcedConversion();                 // Start BMP280 forced conversion (if we're in SLEEP_MODE)
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_I2C_Normal/BMP280_I2C_Normal.ino

@@ -0,0 +1,32 @@
+/////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - I2C Communications, Default Configuration, Normal Conversion
+/////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280;                                // Instantiate (create) a BMP280_DEV object and set-up for I2C operation (address 0x77)
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 31 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Forced/BMP280_SPI_Forced.ino

@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - SPI Communications, Default Configuration, Forced Conversion
+/////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(10);                            // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D10
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4 
+}
+
+void loop() 
+{
+  bmp280.startForcedConversion();                 // Start BMP280 forced conversion (if we're in SLEEP_MODE)
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 32 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal/BMP280_SPI_Normal.ino

@@ -0,0 +1,32 @@
+///////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - SPI Communications, Default Configuration, Normal Conversion
+///////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                           // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;            // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280(10);                            // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D10
+
+void setup() 
+{
+  Serial.begin(115200);                           // Initialise the serial port
+  bmp280.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  //bmp280.setPresOversampling(OVERSAMPLING_X4);    // Set the pressure oversampling to X4
+  //bmp280.setTempOversampling(OVERSAMPLING_X1);    // Set the temperature oversampling to X1
+  //bmp280.setIIRFilter(IIR_FILTER_4);              // Set the IIR filter to setting 4
+  bmp280.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
+}
+
+void loop() 
+{
+  if (bmp280.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(temperature);                    // Display the results    
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 44 - 0
.pio/libdeps/uno/BMP280_DEV/examples/BMP280_SPI_Normal_Multiple/BMP280_SPI_Normal_Multiple.ino

@@ -0,0 +1,44 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// BMP280_DEV - SPI Communications, Default Configuration, Normal Conversion, Mulitple Devices
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include <BMP280_DEV.h>                             // Include the BMP280_DEV.h library
+
+float temperature, pressure, altitude;              // Create the temperature, pressure and altitude variables
+BMP280_DEV bmp280_1(10);                            // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D10
+BMP280_DEV bmp280_2(9);                             // Instantiate (create) a BMP280_DEV object and set-up for SPI operation on digital pin D9
+
+void setup() 
+{
+  Serial.begin(115200);                             // Initialise the serial port
+  bmp280_1.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280_1.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280_1.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE 
+  bmp280_2.begin();                                 // Default initialisation, place the BMP280 into SLEEP_MODE 
+  bmp280_2.setTimeStandby(TIME_STANDBY_2000MS);     // Set the standby time to 2 seconds
+  bmp280_2.startNormalConversion();                 // Start BMP280 continuous conversion in NORMAL_MODE  
+}
+
+void loop() 
+{
+  if (bmp280_1.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(F("BMP280_1 "));                                   // Display the results   
+    Serial.print(temperature);                       
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+  if (bmp280_2.getMeasurements(temperature, pressure, altitude))    // Check if the measurement is complete
+  {
+    Serial.print(F("BMP280_2 "));                                   // Display the results
+    Serial.print(temperature);                          
+    Serial.print(F("*C   "));
+    Serial.print(pressure);    
+    Serial.print(F("hPa   "));
+    Serial.print(altitude);
+    Serial.println(F("m"));  
+  }
+}

+ 64 - 0
.pio/libdeps/uno/BMP280_DEV/keywords.txt

@@ -0,0 +1,64 @@
+#######################################
+# Syntax Coloring Map For The BMP280
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+BMP280	KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+begin	KEYWORD2
+reset	KEYWORD2
+startNormalConversion	KEYWORD2
+startForcedConversion	KEYWORD2
+stopConversion	KEYWORD2
+setPresOversampling	KEYWORD2
+setTempOversampling	KEYWORD2
+setIIRFilter	KEYWORD2
+setTimeStandby	KEYWORD2
+setSeaLevelPressure	KEYWORD2
+getCurrentTemperature	KEYWORD2
+getTemperature	KEYWORD2
+getCurrentPressure	KEYWORD2
+getPressure	KEYWORD2
+getCurrentTempPres	KEYWORD2
+getTempPres	KEYWORD2
+getCurrentAltitude	KEYWORD2
+getAltitude	KEYWORD2
+getCurrentMeasurements	KEYWORD2
+getMeasurements	KEYWORD2
+setClock	KEYWORD2
+
+#######################################
+# Constants (LITERAL1)
+#######################################
+
+BMP280_I2C_ADDR	LITERAL1
+BMP280_I2C_ALT_ADDR	LITERAL1
+
+OVERSAMPLING_SKIP	LITERAL1
+OVERSAMPLING_X1	LITERAL1
+OVERSAMPLING_X2	LITERAL1
+OVERSAMPLING_X4	LITERAL1
+OVERSAMPLING_X8	LITERAL1
+OVERSAMPLING_X16	LITERAL1
+
+IIR_FILTER_OFF	LITERAL1
+IIR_FILTER_2	LITERAL1
+IIR_FILTER_4	LITERAL1
+IIR_FILTER_8	LITERAL1
+IIR_FILTER_16	LITERAL1
+
+TIME_STANDBY_05MS	LITERAL1
+TIME_STANDBY_62MS	LITERAL1
+TIME_STANDBY_125MS	LITERAL1
+TIME_STANDBY_250MS	LITERAL1
+TIME_STANDBY_500MS	LITERAL1
+TIME_STANDBY_1000MS	LITERAL1
+TIME_STANDBY_2000MS	LITERAL1
+TIME_STANDBY_4000MS	LITERAL1

+ 9 - 0
.pio/libdeps/uno/BMP280_DEV/library.properties

@@ -0,0 +1,9 @@
+name=BMP280_DEV
+version=1.0.18
+author=Martin Lindupp
+maintainer=Martin Lindupp
+sentence=An Arduino compatible, non-blocking, I2C/SPI library for the Bosch BMP280 barometer. 
+paragraph=This library can operate the BMP280 in either NORMAL or FORCED modes. NORMAL mode automatically samples at the device sample rate. 
+category=Sensors
+url=https://github.com/MartinL1/BMP280_DEV
+architectures=*

+ 1 - 0
.pio/libdeps/uno/MPU9250/.piopm

@@ -0,0 +1 @@
+{"type": "library", "name": "MPU9250", "version": "0.2.3", "spec": {"owner": "hideakitai", "id": 5639, "name": "MPU9250", "requirements": null, "url": null}}

+ 21 - 0
.pio/libdeps/uno/MPU9250/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Hideaki Tai
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 954 - 0
.pio/libdeps/uno/MPU9250/MPU9250.h

@@ -0,0 +1,954 @@
+#pragma once
+#ifndef MPU9250_H
+#define MPU9250_H
+
+#include <Wire.h>
+
+#include "MPU9250/MPU9250RegisterMap.h"
+#include "MPU9250/QuaternionFilter.h"
+
+enum class AFS { A2G,
+                 A4G,
+                 A8G,
+                 A16G };
+enum class GFS { G250DPS,
+                 G500DPS,
+                 G1000DPS,
+                 G2000DPS };
+enum class MFS { M14BITS,
+                 M16BITS };
+static constexpr uint8_t MPU9250_WHOAMI_DEFAULT_VALUE{0x71};
+static constexpr uint8_t MPU9255_WHOAMI_DEFAULT_VALUE{0x73};
+
+template <typename WireType, uint8_t WHO_AM_I, AFS AFSSEL = AFS::A16G, GFS GFSSEL = GFS::G2000DPS, MFS MFSSEL = MFS::M16BITS>
+class MPU9250_ {
+    static constexpr uint8_t MPU9250_DEFAULT_ADDRESS{0x68};  // Device address when ADO = 0
+    static constexpr uint8_t AK8963_ADDRESS{0x0C};           //  Address of magnetometer
+    uint8_t MPU9250_ADDRESS{MPU9250_DEFAULT_ADDRESS};
+
+    const uint8_t AK8963_WHOAMI_DEFAULT_VALUE{0x48};
+
+    // Set initial input parameters
+    // const uint8_t Mmode {0x02};        // 2 for 8 Hz, 6 for 100 Hz continuous magnetometer data read
+    const uint8_t Mmode{0x06};    // 2 for 8 Hz, 6 for 100 Hz continuous magnetometer data read
+    const float aRes{getAres()};  // scale resolutions per LSB for the sensors
+    const float gRes{getGres()};  // scale resolutions per LSB for the sensors
+    const float mRes{getMres()};  // scale resolutions per LSB for the sensors
+
+    float magCalibration[3] = {0, 0, 0};  // factory mag calibration
+    float magBias[3] = {0, 0, 0};
+    float magScale[3] = {1.0, 1.0, 1.0};  // Bias corrections for gyro and accelerometer
+
+    float gyroBias[3] = {0, 0, 0};   // bias corrections
+    float accelBias[3] = {0, 0, 0};  // bias corrections
+
+    int16_t tempCount;        // temperature raw count output
+    float temperature;        // Stores the real internal chip temperature in degrees Celsius
+    float SelfTestResult[6];  // holds results of gyro and accelerometer self test
+
+    float a[3], g[3], m[3];
+    float q[4] = {1.0f, 0.0f, 0.0f, 0.0f};  // vector to hold quaternion
+    float pitch, yaw, roll;
+    float a12, a22, a31, a32, a33;  // rotation matrix coefficients for Euler angles and gravity components
+    float lin_ax, lin_ay, lin_az;   // linear acceleration (acceleration with gravity component subtracted)
+
+    QuaternionFilter qFilter;
+
+    float magnetic_declination = 4.6;  // Köthen, 26th November
+
+    bool b_verbose{false};
+
+public:
+    MPU9250_() : aRes(getAres()), gRes(getGres()), mRes(getMres()) {}
+
+    bool setup(const uint8_t addr, WireType& w = Wire) {
+        // addr should be valid for MPU
+        if ((addr < MPU9250_DEFAULT_ADDRESS) || (addr > MPU9250_DEFAULT_ADDRESS + 7)) {
+            Serial.print("I2C address 0x");
+            Serial.print(addr, HEX);
+            Serial.println(" is not valid for MPU. Please check your I2C address.");
+            return false;
+        }
+
+        wire = &w;
+        MPU9250_ADDRESS = addr;
+
+        if (isConnectedMPU9250()) {
+            if (b_verbose)
+                Serial.println("MPU9250 is online...");
+
+            initMPU9250();
+
+            if (isConnectedAK8963())
+                initAK8963(magCalibration);
+            else {
+                if (b_verbose)
+                    Serial.println("Could not connect to AK8963");
+                return false;
+            }
+        } else {
+            if (b_verbose)
+                Serial.println("Could not connect to MPU9250");
+            return false;
+        }
+        return true;
+    }
+
+    void verbose(const bool b) { b_verbose = b; }
+
+    void calibrateAccelGyro() {
+        calibrateMPU9250(gyroBias, accelBias);
+    }
+
+    void calibrateMag() {
+        magcalMPU9250(magBias, magScale);
+    }
+
+    bool isConnectedMPU9250() {
+        byte c = readByte(MPU9250_ADDRESS, WHO_AM_I_MPU9250);
+        if (b_verbose) {
+            Serial.print("MPU9250 WHO AM I = ");
+            Serial.println(c, HEX);
+        }
+        return (c == WHO_AM_I);
+    }
+
+    bool isConnectedAK8963() {
+        byte c = readByte(AK8963_ADDRESS, AK8963_WHO_AM_I);
+        if (b_verbose) {
+            Serial.print("AK8963  WHO AM I = ");
+            Serial.println(c, HEX);
+        }
+        return (c == AK8963_WHOAMI_DEFAULT_VALUE);
+    }
+
+    bool available() {
+        return (readByte(MPU9250_ADDRESS, INT_STATUS) & 0x01);
+    }
+
+    bool update() {
+        if (!available()) return false;
+
+        updateAccelGyro();
+        updateMag();
+
+        // Madgwick function needs to be fed North, East, and Down direction like
+        // (AN, AE, AD, GN, GE, GD, MN, ME, MD)
+        // Accel and Gyro direction is Right-Hand, X-Forward, Z-Up
+        // Magneto direction is Right-Hand, Y-Forward, Z-Down
+        // So to adopt to the general Aircraft coordinate system (Right-Hand, X-Forward, Z-Down),
+        // we need to feed (ax, -ay, -az, gx, -gy, -gz, my, -mx, mz)
+        // but we pass (-ax, ay, az, gx, -gy, -gz, my, -mx, mz)
+        // because gravity is by convention positive down, we need to ivnert the accel data
+
+        // get quaternion based on aircraft coordinate (Right-Hand, X-Forward, Z-Down)
+        // acc[mg], gyro[deg/s], mag [mG]
+        // gyro will be convert from [deg/s] to [rad/s] inside of this function
+        qFilter.update(-a[0], a[1], a[2], g[0], -g[1], -g[2], m[1], -m[0], m[2], q);
+
+        if (!b_ahrs) {
+            tempCount = readTempData();                        // Read the adc values
+            temperature = ((float)tempCount) / 333.87 + 21.0;  // Temperature in degrees Centigrade
+        } else {
+            // Define output variables from updated quaternion---these are Tait-Bryan angles, commonly used in aircraft orientation.
+            // In this coordinate system, the positive z-axis is down toward Earth.
+            // Yaw is the angle between Sensor x-axis and Earth magnetic North (or true North if corrected for local declination, looking down on the sensor positive yaw is counterclockwise.
+            // Pitch is angle between sensor x-axis and Earth ground plane, toward the Earth is positive, up toward the sky is negative.
+            // Roll is angle between sensor y-axis and Earth ground plane, y-axis up is positive roll.
+            // These arise from the definition of the homogeneous rotation matrix constructed from quaternions.
+            // Tait-Bryan angles as well as Euler angles are non-commutative; that is, the get the correct orientation the rotations must be
+            // applied in the correct order which for this configuration is yaw, pitch, and then roll.
+            // For more see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles which has additional links.
+            updateRPY();
+        }
+
+        return true;
+    }
+
+    // TODO: more efficient getter, const refrerence of struct??
+    float getRoll() const { return roll; }
+    float getPitch() const { return pitch; }
+    float getYaw() const { return yaw; }
+
+    float getQuaternion(const uint8_t i) const { return (i < 4) ? q[i] : 0.f; }
+
+    float getQuaternionX() const { return q[0]; }
+    float getQuaternionY() const { return q[1]; }
+    float getQuaternionZ() const { return q[2]; }
+    float getQuaternionW() const { return q[3]; }
+
+    float getAcc(const uint8_t i) const { return (i < 3) ? a[i] : 0.f; }
+    float getGyro(const uint8_t i) const { return (i < 3) ? g[i] : 0.f; }
+    float getMag(const uint8_t i) const { return (i < 3) ? m[i] : 0.f; }
+
+    float getAccX() const { return a[0]; }
+    float getAccY() const { return a[1]; }
+    float getAccZ() const { return a[2]; }
+    float getGyroX() const { return g[0]; }
+    float getGyroY() const { return g[1]; }
+    float getGyroZ() const { return g[2]; }
+    float getMagX() const { return m[0]; }
+    float getMagY() const { return m[1]; }
+    float getMagZ() const { return m[2]; }
+
+    float getAccBias(const uint8_t i) const { return (i < 3) ? accelBias[i] : 0.f; }
+    float getGyroBias(const uint8_t i) const { return (i < 3) ? gyroBias[i] : 0.f; }
+    float getMagBias(const uint8_t i) const { return (i < 3) ? magBias[i] : 0.f; }
+    float getMagScale(const uint8_t i) const { return (i < 3) ? magScale[i] : 0.f; }
+
+    float getAccBiasX() const { return accelBias[0]; }
+    float getAccBiasY() const { return accelBias[1]; }
+    float getAccBiasZ() const { return accelBias[2]; }
+    float getGyroBiasX() const { return gyroBias[0]; }
+    float getGyroBiasY() const { return gyroBias[1]; }
+    float getGyroBiasZ() const { return gyroBias[2]; }
+    float getMagBiasX() const { return magBias[0]; }
+    float getMagBiasY() const { return magBias[1]; }
+    float getMagBiasZ() const { return magBias[2]; }
+    float getMagScaleX() const { return magScale[0]; }
+    float getMagScaleY() const { return magScale[1]; }
+    float getMagScaleZ() const { return magScale[2]; }
+
+    float getTemperature() const { return temperature; }
+
+    void setAccBias(const uint8_t i, const float v) {
+        if (i < 3) accelBias[i] = v;
+    }
+    void setGyroBias(const uint8_t i, const float v) {
+        if (i < 3) gyroBias[i] = v;
+    }
+    void setMagBias(const uint8_t i, const float v) {
+        if (i < 3) magBias[i] = v;
+    }
+    void setMagScale(const uint8_t i, const float v) {
+        if (i < 3) magScale[i] = v;
+    }
+
+    void setAccBiasX(const float v) { accelBias[0] = v; }
+    void setAccBiasY(const float v) { accelBias[1] = v; }
+    void setAccBiasZ(const float v) { accelBias[2] = v; }
+    void setGyroBiasX(const float v) { gyroBias[0] = v; }
+    void setGyroBiasY(const float v) { gyroBias[1] = v; }
+    void setGyroBiasZ(const float v) { gyroBias[2] = v; }
+    void setMagBiasX(const float v) { magBias[0] = v; }
+    void setMagBiasY(const float v) { magBias[1] = v; }
+    void setMagBiasZ(const float v) { magBias[2] = v; }
+    void setMagScaleX(const float v) { magScale[0] = v; }
+    void setMagScaleY(const float v) { magScale[1] = v; }
+    void setMagScaleZ(const float v) { magScale[2] = v; }
+
+    void setMagneticDeclination(const float d) { magnetic_declination = d; }
+
+    void print() const {
+        printRawData();
+        printRollPitchYaw();
+        printCalibration();
+    }
+
+    void printRawData() const {
+        // Print acceleration values in milligs!
+        Serial.print("ax = ");
+        Serial.print((int)1000 * a[0]);
+        Serial.print(" ay = ");
+        Serial.print((int)1000 * a[1]);
+        Serial.print(" az = ");
+        Serial.print((int)1000 * a[2]);
+        Serial.println(" mg");
+        // Print gyro values in degree/sec
+        Serial.print("gx = ");
+        Serial.print(g[0], 2);
+        Serial.print(" gy = ");
+        Serial.print(g[1], 2);
+        Serial.print(" gz = ");
+        Serial.print(g[2], 2);
+        Serial.println(" deg/s");
+        // Print mag values in degree/sec
+        Serial.print("mx = ");
+        Serial.print((int)m[0]);
+        Serial.print(" my = ");
+        Serial.print((int)m[1]);
+        Serial.print(" mz = ");
+        Serial.print((int)m[2]);
+        Serial.println(" mG");
+
+        Serial.print("q0 = ");
+        Serial.print(q[0]);
+        Serial.print(" qx = ");
+        Serial.print(q[1]);
+        Serial.print(" qy = ");
+        Serial.print(q[2]);
+        Serial.print(" qz = ");
+        Serial.println(q[3]);
+    }
+
+    void printRollPitchYaw() const {
+        Serial.print("Yaw, Pitch, Roll: ");
+        Serial.print(yaw, 2);
+        Serial.print(", ");
+        Serial.print(pitch, 2);
+        Serial.print(", ");
+        Serial.println(roll, 2);
+    }
+
+    void printCalibration() const {
+        Serial.println("< calibration parameters >");
+        Serial.println("accel bias [g]: ");
+        Serial.print(accelBias[0] * 1000.f);
+        Serial.print(", ");
+        Serial.print(accelBias[1] * 1000.f);
+        Serial.print(", ");
+        Serial.print(accelBias[2] * 1000.f);
+        Serial.println();
+        Serial.println("gyro bias [deg/s]: ");
+        Serial.print(gyroBias[0]);
+        Serial.print(", ");
+        Serial.print(gyroBias[1]);
+        Serial.print(", ");
+        Serial.print(gyroBias[2]);
+        Serial.println();
+        Serial.println("mag bias [mG]: ");
+        Serial.print(magBias[0]);
+        Serial.print(", ");
+        Serial.print(magBias[1]);
+        Serial.print(", ");
+        Serial.print(magBias[2]);
+        Serial.println();
+        Serial.println("mag scale []: ");
+        Serial.print(magScale[0]);
+        Serial.print(", ");
+        Serial.print(magScale[1]);
+        Serial.print(", ");
+        Serial.print(magScale[2]);
+        Serial.println();
+    }
+
+private:
+    float getAres() const {
+        switch (AFSSEL) {
+            // Possible accelerometer scales (and their register bit settings) are:
+            // 2 Gs (00), 4 Gs (01), 8 Gs (10), and 16 Gs  (11).
+            // Here's a bit of an algorith to calculate DPS/(ADC tick) based on that 2-bit value:
+            case AFS::A2G:
+                return 2.0 / 32768.0;
+            case AFS::A4G:
+                return 4.0 / 32768.0;
+            case AFS::A8G:
+                return 8.0 / 32768.0;
+            case AFS::A16G:
+                return 16.0 / 32768.0;
+        }
+    }
+
+    float getGres() const {
+        switch (GFSSEL) {
+            // Possible gyro scales (and their register bit settings) are:
+            // 250 DPS (00), 500 DPS (01), 1000 DPS (10), and 2000 DPS  (11).
+            // Here's a bit of an algorith to calculate DPS/(ADC tick) based on that 2-bit value:
+            case GFS::G250DPS:
+                return 250.0 / 32768.0;
+            case GFS::G500DPS:
+                return 500.0 / 32768.0;
+            case GFS::G1000DPS:
+                return 1000.0 / 32768.0;
+            case GFS::G2000DPS:
+                return 2000.0 / 32768.0;
+        }
+    }
+
+    float getMres() const {
+        switch (MFSSEL) {
+            // Possible magnetometer scales (and their register bit settings) are:
+            // 14 bit resolution (0) and 16 bit resolution (1)
+            // Proper scale to return milliGauss
+            case MFS::M14BITS:
+                return 10. * 4912. / 8190.0;
+            case MFS::M16BITS:
+                return 10. * 4912. / 32760.0;
+        }
+    }
+
+    void updateAccelGyro() {
+        int16_t MPU9250Data[7];        // used to read all 14 bytes at once from the MPU9250 accel/gyro
+        readMPU9250Data(MPU9250Data);  // INT cleared on any read
+
+        // Now we'll calculate the accleration value into actual g's
+        a[0] = (float)MPU9250Data[0] * aRes - accelBias[0];  // get actual g value, this depends on scale being set
+        a[1] = (float)MPU9250Data[1] * aRes - accelBias[1];
+        a[2] = (float)MPU9250Data[2] * aRes - accelBias[2];
+
+        tempCount = MPU9250Data[3];                        // Read the adc values
+        temperature = ((float)tempCount) / 333.87 + 21.0;  // Temperature in degrees Centigrade
+
+        // Calculate the gyro value into actual degrees per second
+        g[0] = (float)MPU9250Data[4] * gRes - gyroBias[0];  // get actual gyro value, this depends on scale being set
+        g[1] = (float)MPU9250Data[5] * gRes - gyroBias[1];
+        g[2] = (float)MPU9250Data[6] * gRes - gyroBias[2];
+    }
+
+    void readMPU9250Data(int16_t* destination) {
+        uint8_t rawData[14];                                        // x/y/z accel register data stored here
+        readBytes(MPU9250_ADDRESS, ACCEL_XOUT_H, 14, &rawData[0]);  // Read the 14 raw data registers into data array
+        destination[0] = ((int16_t)rawData[0] << 8) | rawData[1];   // Turn the MSB and LSB into a signed 16-bit value
+        destination[1] = ((int16_t)rawData[2] << 8) | rawData[3];
+        destination[2] = ((int16_t)rawData[4] << 8) | rawData[5];
+        destination[3] = ((int16_t)rawData[6] << 8) | rawData[7];
+        destination[4] = ((int16_t)rawData[8] << 8) | rawData[9];
+        destination[5] = ((int16_t)rawData[10] << 8) | rawData[11];
+        destination[6] = ((int16_t)rawData[12] << 8) | rawData[13];
+    }
+
+    void updateMag() {
+        int16_t magCount[3] = {0, 0, 0};  // Stores the 16-bit signed magnetometer sensor output
+        readMagData(magCount);            // Read the x/y/z adc values
+        // getMres();
+
+        // Calculate the magnetometer values in milliGauss
+        // Include factory calibration per data sheet and user environmental corrections
+        m[0] = (float)(magCount[0] * mRes * magCalibration[0] - magBias[0]) * magScale[0];  // get actual magnetometer value, this depends on scale being set
+        m[1] = (float)(magCount[1] * mRes * magCalibration[1] - magBias[1]) * magScale[1];
+        m[2] = (float)(magCount[2] * mRes * magCalibration[2] - magBias[2]) * magScale[2];
+    }
+
+    void readMagData(int16_t* destination) {
+        uint8_t rawData[7];                                                // x/y/z gyro register data, ST2 register stored here, must read ST2 at end of data acquisition
+        if (readByte(AK8963_ADDRESS, AK8963_ST1) & 0x01) {                 // wait for magnetometer data ready bit to be set
+            readBytes(AK8963_ADDRESS, AK8963_XOUT_L, 7, &rawData[0]);      // Read the six raw data and ST2 registers sequentially into data array
+            uint8_t c = rawData[6];                                        // End data read by reading ST2 register
+            if (!(c & 0x08)) {                                             // Check if magnetic sensor overflow set, if not then report data
+                destination[0] = ((int16_t)rawData[1] << 8) | rawData[0];  // Turn the MSB and LSB into a signed 16-bit value
+                destination[1] = ((int16_t)rawData[3] << 8) | rawData[2];  // Data stored as little Endian
+                destination[2] = ((int16_t)rawData[5] << 8) | rawData[4];
+            }
+        }
+    }
+
+    void updateRPY() {
+        a12 = 2.0f * (q[1] * q[2] + q[0] * q[3]);
+        a22 = q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3];
+        a31 = 2.0f * (q[0] * q[1] + q[2] * q[3]);
+        a32 = 2.0f * (q[1] * q[3] - q[0] * q[2]);
+        a33 = q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3];
+        pitch = -asinf(a32);
+        roll = atan2f(a31, a33);
+        yaw = atan2f(a12, a22);
+        pitch *= 180.0f / PI;
+        roll *= 180.0f / PI;
+        yaw *= 180.0f / PI;
+        yaw += magnetic_declination;
+        if (yaw >= +180.f)
+            yaw -= 360.f;
+        else if (yaw < -180.f)
+            yaw += 360.f;
+
+        lin_ax = a[0] + a31;
+        lin_ay = a[1] + a32;
+        lin_az = a[2] - a33;
+    }
+
+    int16_t readTempData() {
+        uint8_t rawData[2];                                      // x/y/z gyro register data stored here
+        readBytes(MPU9250_ADDRESS, TEMP_OUT_H, 2, &rawData[0]);  // Read the two raw data registers sequentially into data array
+        return ((int16_t)rawData[0] << 8) | rawData[1];          // Turn the MSB and LSB into a 16-bit value
+    }
+
+    void initAK8963(float* destination) {
+        // First extract the factory calibration for each magnetometer axis
+        uint8_t rawData[3];                            // x/y/z gyro calibration data stored here
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, 0x00);  // Power down magnetometer
+        delay(10);
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, 0x0F);  // Enter Fuse ROM access mode
+        delay(10);
+        readBytes(AK8963_ADDRESS, AK8963_ASAX, 3, &rawData[0]);  // Read the x-, y-, and z-axis calibration values
+        destination[0] = (float)(rawData[0] - 128) / 256. + 1.;  // Return x-axis sensitivity adjustment values, etc.
+        destination[1] = (float)(rawData[1] - 128) / 256. + 1.;
+        destination[2] = (float)(rawData[2] - 128) / 256. + 1.;
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, 0x00);  // Power down magnetometer
+        delay(10);
+        // Configure the magnetometer for continuous read and highest resolution
+        // set Mscale bit 4 to 1 (0) to enable 16 (14) bit resolution in CNTL register,
+        // and enable continuous mode data acquisition Mmode (bits [3:0]), 0010 for 8 Hz and 0110 for 100 Hz sample rates
+        writeByte(AK8963_ADDRESS, AK8963_CNTL, (uint8_t)MFSSEL << 4 | Mmode);  // Set magnetometer data resolution and sample ODR
+        delay(10);
+
+        if (b_verbose) {
+            Serial.println("Calibration values: ");
+            Serial.print("X-Axis sensitivity adjustment value ");
+            Serial.println(destination[0], 2);
+            Serial.print("Y-Axis sensitivity adjustment value ");
+            Serial.println(destination[1], 2);
+            Serial.print("Z-Axis sensitivity adjustment value ");
+            Serial.println(destination[2], 2);
+            Serial.print("X-Axis sensitivity offset value ");
+            Serial.println(magBias[0], 2);
+            Serial.print("Y-Axis sensitivity offset value ");
+            Serial.println(magBias[1], 2);
+            Serial.print("Z-Axis sensitivity offset value ");
+            Serial.println(magBias[2], 2);
+        }
+    }
+
+    void magcalMPU9250(float* dest1, float* dest2) {
+        uint16_t ii = 0, sample_count = 0;
+        int32_t mag_bias[3] = {0, 0, 0}, mag_scale[3] = {0, 0, 0};
+        int16_t mag_max[3] = {-32767, -32767, -32767}, mag_min[3] = {32767, 32767, 32767}, mag_temp[3] = {0, 0, 0};
+
+        if (b_verbose)
+            Serial.println("Mag Calibration: Wave device in a figure eight until done!");
+        delay(4000);
+
+        // shoot for ~fifteen seconds of mag data
+        if (Mmode == 0x02)
+            sample_count = 128;  // at 8 Hz ODR, new mag data is available every 125 ms
+        else if (Mmode == 0x06)
+            sample_count = 1500;  // at 100 Hz ODR, new mag data is available every 10 ms
+
+        for (ii = 0; ii < sample_count; ii++) {
+            readMagData(mag_temp);  // Read the mag data
+            for (int jj = 0; jj < 3; jj++) {
+                if (mag_temp[jj] > mag_max[jj]) mag_max[jj] = mag_temp[jj];
+                if (mag_temp[jj] < mag_min[jj]) mag_min[jj] = mag_temp[jj];
+            }
+            if (Mmode == 0x02) delay(135);  // at 8 Hz ODR, new mag data is available every 125 ms
+            if (Mmode == 0x06) delay(12);   // at 100 Hz ODR, new mag data is available every 10 ms
+        }
+
+        if (b_verbose) {
+            Serial.println("mag x min/max:");
+            Serial.println(mag_max[0]);
+            Serial.println(mag_min[0]);
+            Serial.println("mag y min/max:");
+            Serial.println(mag_max[1]);
+            Serial.println(mag_min[1]);
+            Serial.println("mag z min/max:");
+            Serial.println(mag_max[2]);
+            Serial.println(mag_min[2]);
+        }
+
+        // Get hard iron correction
+        mag_bias[0] = (mag_max[0] + mag_min[0]) / 2;  // get average x mag bias in counts
+        mag_bias[1] = (mag_max[1] + mag_min[1]) / 2;  // get average y mag bias in counts
+        mag_bias[2] = (mag_max[2] + mag_min[2]) / 2;  // get average z mag bias in counts
+
+        dest1[0] = (float)mag_bias[0] * mRes * magCalibration[0];  // save mag biases in G for main program
+        dest1[1] = (float)mag_bias[1] * mRes * magCalibration[1];
+        dest1[2] = (float)mag_bias[2] * mRes * magCalibration[2];
+
+        // Get soft iron correction estimate
+        mag_scale[0] = (mag_max[0] - mag_min[0]) / 2;  // get average x axis max chord length in counts
+        mag_scale[1] = (mag_max[1] - mag_min[1]) / 2;  // get average y axis max chord length in counts
+        mag_scale[2] = (mag_max[2] - mag_min[2]) / 2;  // get average z axis max chord length in counts
+
+        float avg_rad = mag_scale[0] + mag_scale[1] + mag_scale[2];
+        avg_rad /= 3.0;
+
+        dest2[0] = avg_rad / ((float)mag_scale[0]);
+        dest2[1] = avg_rad / ((float)mag_scale[1]);
+        dest2[2] = avg_rad / ((float)mag_scale[2]);
+
+        if (b_verbose) {
+            Serial.println("Mag Calibration done!");
+
+            Serial.println("AK8963 mag biases (mG)");
+            Serial.print(magBias[0]);
+            Serial.print(", ");
+            Serial.print(magBias[1]);
+            Serial.print(", ");
+            Serial.print(magBias[2]);
+            Serial.println();
+            Serial.println("AK8963 mag scale (mG)");
+            Serial.print(magScale[0]);
+            Serial.print(", ");
+            Serial.print(magScale[1]);
+            Serial.print(", ");
+            Serial.print(magScale[2]);
+            Serial.println();
+        }
+    }
+
+    void initMPU9250() {
+        // wake up device
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x00);  // Clear sleep mode bit (6), enable all sensors
+        delay(100);                                    // Wait for all registers to reset
+
+        // get stable time source
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x01);  // Auto select clock source to be PLL gyroscope reference if ready else
+        delay(200);
+
+        // Configure Gyro and Thermometer
+        // Disable FSYNC and set thermometer and gyro bandwidth to 41 and 42 Hz, respectively;
+        // minimum delay time for this setting is 5.9 ms, which means sensor fusion update rates cannot
+        // be higher than 1 / 0.0059 = 170 Hz
+        // DLPF_CFG = bits 2:0 = 011; this limits the sample rate to 1000 Hz for both
+        // With the MPU9250, it is possible to get gyro sample rates of 32 kHz (!), 8 kHz, or 1 kHz
+        writeByte(MPU9250_ADDRESS, MPU_CONFIG, 0x03);
+
+        // Set sample rate = gyroscope output rate/(1 + SMPLRT_DIV)
+        writeByte(MPU9250_ADDRESS, SMPLRT_DIV, 0x04);  // Use a 200 Hz rate; a rate consistent with the filter update rate
+                                                       // determined inset in CONFIG above
+
+        // Set gyroscope full scale range
+        // Range selects FS_SEL and GFS_SEL are 0 - 3, so 2-bit values are left-shifted into positions 4:3
+        uint8_t c = readByte(MPU9250_ADDRESS, GYRO_CONFIG);  // get current GYRO_CONFIG register value
+        // c = c & ~0xE0; // Clear self-test bits [7:5]
+        c = c & ~0x03;                 // Clear Fchoice bits [1:0]
+        c = c & ~0x18;                 // Clear GFS bits [4:3]
+        c = c | (uint8_t)GFSSEL << 3;  // Set full scale range for the gyro
+        // c =| 0x00; // Set Fchoice for the gyro to 11 by writing its inverse to bits 1:0 of GYRO_CONFIG
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, c);  // Write new GYRO_CONFIG value to register
+
+        // Set accelerometer full-scale range configuration
+        c = readByte(MPU9250_ADDRESS, ACCEL_CONFIG);  // get current ACCEL_CONFIG register value
+        // c = c & ~0xE0; // Clear self-test bits [7:5]
+        c = c & ~0x18;                                // Clear AFS bits [4:3]
+        c = c | (uint8_t)AFSSEL << 3;                 // Set full scale range for the accelerometer
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, c);  // Write new ACCEL_CONFIG register value
+
+        // Set accelerometer sample rate configuration
+        // It is possible to get a 4 kHz sample rate from the accelerometer by choosing 1 for
+        // accel_fchoice_b bit [3]; in this case the bandwidth is 1.13 kHz
+        c = readByte(MPU9250_ADDRESS, ACCEL_CONFIG2);  // get current ACCEL_CONFIG2 register value
+        c = c & ~0x0F;                                 // Clear accel_fchoice_b (bit 3) and A_DLPFG (bits [2:0])
+        c = c | 0x03;                                  // Set accelerometer rate to 1 kHz and bandwidth to 41 Hz
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG2, c);  // Write new ACCEL_CONFIG2 register value
+
+        // The accelerometer, gyro, and thermometer are set to 1 kHz sample rates,
+        // but all these rates are further reduced by a factor of 5 to 200 Hz because of the SMPLRT_DIV setting
+
+        // Configure Interrupts and Bypass Enable
+        // Set interrupt pin active high, push-pull, hold interrupt pin level HIGH until interrupt cleared,
+        // clear on read of INT_STATUS, and enable I2C_BYPASS_EN so additional chips
+        // can join the I2C bus and all can be controlled by the Arduino as master
+        writeByte(MPU9250_ADDRESS, INT_PIN_CFG, 0x22);
+        writeByte(MPU9250_ADDRESS, INT_ENABLE, 0x01);  // Enable data ready (bit 0) interrupt
+        delay(100);
+    }
+
+    // Function which accumulates gyro and accelerometer data after device initialization. It calculates the average
+    // of the at-rest readings and then loads the resulting offsets into accelerometer and gyro bias registers.
+    void calibrateMPU9250(float* dest1, float* dest2) {
+        uint8_t data[12];  // data array to hold accelerometer and gyro x, y, z, data
+        uint16_t ii, packet_count, fifo_count;
+        int32_t gyro_bias[3] = {0, 0, 0}, accel_bias[3] = {0, 0, 0};
+
+        // reset device
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x80);  // Write a one to bit 7 reset bit; toggle reset device
+        delay(100);
+
+        // get stable time source; Auto select clock source to be PLL gyroscope reference if ready
+        // else use the internal oscillator, bits 2:0 = 001
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x01);
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_2, 0x00);
+        delay(200);
+
+        // Configure device for bias calculation
+        writeByte(MPU9250_ADDRESS, INT_ENABLE, 0x00);    // Disable all interrupts
+        writeByte(MPU9250_ADDRESS, FIFO_EN, 0x00);       // Disable FIFO
+        writeByte(MPU9250_ADDRESS, PWR_MGMT_1, 0x00);    // Turn on internal clock source
+        writeByte(MPU9250_ADDRESS, I2C_MST_CTRL, 0x00);  // Disable I2C master
+        writeByte(MPU9250_ADDRESS, USER_CTRL, 0x00);     // Disable FIFO and I2C master modes
+        writeByte(MPU9250_ADDRESS, USER_CTRL, 0x0C);     // Reset FIFO and DMP
+        delay(15);
+
+        // Configure MPU6050 gyro and accelerometer for bias calculation
+        writeByte(MPU9250_ADDRESS, MPU_CONFIG, 0x01);    // Set low-pass filter to 188 Hz
+        writeByte(MPU9250_ADDRESS, SMPLRT_DIV, 0x00);    // Set sample rate to 1 kHz
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, 0x00);   // Set gyro full-scale to 250 degrees per second, maximum sensitivity
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, 0x00);  // Set accelerometer full-scale to 2 g, maximum sensitivity
+
+        uint16_t gyrosensitivity = 131;     // = 131 LSB/degrees/sec
+        uint16_t accelsensitivity = 16384;  // = 16384 LSB/g
+
+        // Configure FIFO to capture accelerometer and gyro data for bias calculation
+        writeByte(MPU9250_ADDRESS, USER_CTRL, 0x40);  // Enable FIFO
+        writeByte(MPU9250_ADDRESS, FIFO_EN, 0x78);    // Enable gyro and accelerometer sensors for FIFO  (max size 512 bytes in MPU-9150)
+        delay(40);                                    // accumulate 40 samples in 40 milliseconds = 480 bytes
+
+        // At end of sample accumulation, turn off FIFO sensor read
+        writeByte(MPU9250_ADDRESS, FIFO_EN, 0x00);             // Disable gyro and accelerometer sensors for FIFO
+        readBytes(MPU9250_ADDRESS, FIFO_COUNTH, 2, &data[0]);  // read FIFO sample count
+        fifo_count = ((uint16_t)data[0] << 8) | data[1];
+        packet_count = fifo_count / 12;  // How many sets of full gyro and accelerometer data for averaging
+
+        for (ii = 0; ii < packet_count; ii++) {
+            int16_t accel_temp[3] = {0, 0, 0}, gyro_temp[3] = {0, 0, 0};
+            readBytes(MPU9250_ADDRESS, FIFO_R_W, 12, &data[0]);            // read data for averaging
+            accel_temp[0] = (int16_t)(((int16_t)data[0] << 8) | data[1]);  // Form signed 16-bit integer for each sample in FIFO
+            accel_temp[1] = (int16_t)(((int16_t)data[2] << 8) | data[3]);
+            accel_temp[2] = (int16_t)(((int16_t)data[4] << 8) | data[5]);
+            gyro_temp[0] = (int16_t)(((int16_t)data[6] << 8) | data[7]);
+            gyro_temp[1] = (int16_t)(((int16_t)data[8] << 8) | data[9]);
+            gyro_temp[2] = (int16_t)(((int16_t)data[10] << 8) | data[11]);
+
+            accel_bias[0] += (int32_t)accel_temp[0];  // Sum individual signed 16-bit biases to get accumulated signed 32-bit biases
+            accel_bias[1] += (int32_t)accel_temp[1];
+            accel_bias[2] += (int32_t)accel_temp[2];
+            gyro_bias[0] += (int32_t)gyro_temp[0];
+            gyro_bias[1] += (int32_t)gyro_temp[1];
+            gyro_bias[2] += (int32_t)gyro_temp[2];
+        }
+        accel_bias[0] /= (int32_t)packet_count;  // Normalize sums to get average count biases
+        accel_bias[1] /= (int32_t)packet_count;
+        accel_bias[2] /= (int32_t)packet_count;
+        gyro_bias[0] /= (int32_t)packet_count;
+        gyro_bias[1] /= (int32_t)packet_count;
+        gyro_bias[2] /= (int32_t)packet_count;
+
+        if (accel_bias[2] > 0L) {
+            accel_bias[2] -= (int32_t)accelsensitivity;
+        }  // Remove gravity from the z-axis accelerometer bias calculation
+        else {
+            accel_bias[2] += (int32_t)accelsensitivity;
+        }
+
+        // Construct the gyro biases for push to the hardware gyro bias registers, which are reset to zero upon device startup
+        data[0] = (-gyro_bias[0] / 4 >> 8) & 0xFF;  // Divide by 4 to get 32.9 LSB per deg/s to conform to expected bias input format
+        data[1] = (-gyro_bias[0] / 4) & 0xFF;       // Biases are additive, so change sign on calculated average gyro biases
+        data[2] = (-gyro_bias[1] / 4 >> 8) & 0xFF;
+        data[3] = (-gyro_bias[1] / 4) & 0xFF;
+        data[4] = (-gyro_bias[2] / 4 >> 8) & 0xFF;
+        data[5] = (-gyro_bias[2] / 4) & 0xFF;
+
+        // Push gyro biases to hardware registers
+        writeByte(MPU9250_ADDRESS, XG_OFFSET_H, data[0]);
+        writeByte(MPU9250_ADDRESS, XG_OFFSET_L, data[1]);
+        writeByte(MPU9250_ADDRESS, YG_OFFSET_H, data[2]);
+        writeByte(MPU9250_ADDRESS, YG_OFFSET_L, data[3]);
+        writeByte(MPU9250_ADDRESS, ZG_OFFSET_H, data[4]);
+        writeByte(MPU9250_ADDRESS, ZG_OFFSET_L, data[5]);
+
+        // Output scaled gyro biases for display in the main program
+        dest1[0] = (float)gyro_bias[0] / (float)gyrosensitivity;
+        dest1[1] = (float)gyro_bias[1] / (float)gyrosensitivity;
+        dest1[2] = (float)gyro_bias[2] / (float)gyrosensitivity;
+
+        // Construct the accelerometer biases for push to the hardware accelerometer bias registers. These registers contain
+        // factory trim values which must be added to the calculated accelerometer biases; on boot up these registers will hold
+        // non-zero values. In addition, bit 0 of the lower byte must be preserved since it is used for temperature
+        // compensation calculations. Accelerometer bias registers expect bias input as 2048 LSB per g, so that
+        // the accelerometer biases calculated above must be divided by 8.
+
+        // int32_t accel_bias_reg[3] = {0, 0, 0}; // A place to hold the factory accelerometer trim biases
+        // readBytes(MPU9250_ADDRESS, XA_OFFSET_H, 2, &data[0]); // Read factory accelerometer trim values
+        // accel_bias_reg[0] = (int32_t) (((int16_t)data[0] << 8) | data[1]);
+        // readBytes(MPU9250_ADDRESS, YA_OFFSET_H, 2, &data[0]);
+        // accel_bias_reg[1] = (int32_t) (((int16_t)data[0] << 8) | data[1]);
+        // readBytes(MPU9250_ADDRESS, ZA_OFFSET_H, 2, &data[0]);
+        // accel_bias_reg[2] = (int32_t) (((int16_t)data[0] << 8) | data[1]);
+
+        // uint32_t mask = 1uL; // Define mask for temperature compensation bit 0 of lower byte of accelerometer bias registers
+        // uint8_t mask_bit[3] = {0, 0, 0}; // Define array to hold mask bit for each accelerometer bias axis
+
+        // for(ii = 0; ii < 3; ii++) {
+        //     if((accel_bias_reg[ii] & mask)) mask_bit[ii] = 0x01; // If temperature compensation bit is set, record that fact in mask_bit
+        // }
+
+        // // Construct total accelerometer bias, including calculated average accelerometer bias from above
+        // accel_bias_reg[0] -= (accel_bias[0] / 8); // Subtract calculated averaged accelerometer bias scaled to 2048 LSB/g (16 g full scale)
+        // accel_bias_reg[1] -= (accel_bias[1] / 8);
+        // accel_bias_reg[2] -= (accel_bias[2] / 8);
+
+        // data[0] = (accel_bias_reg[0] >> 8) & 0xFF;
+        // data[1] = (accel_bias_reg[0])      & 0xFF;
+        // data[1] = data[1] | mask_bit[0]; // preserve temperature compensation bit when writing back to accelerometer bias registers
+        // data[2] = (accel_bias_reg[1] >> 8) & 0xFF;
+        // data[3] = (accel_bias_reg[1])      & 0xFF;
+        // data[3] = data[3] | mask_bit[1]; // preserve temperature compensation bit when writing back to accelerometer bias registers
+        // data[4] = (accel_bias_reg[2] >> 8) & 0xFF;
+        // data[5] = (accel_bias_reg[2])      & 0xFF;
+        // data[5] = data[5] | mask_bit[2]; // preserve temperature compensation bit when writing back to accelerometer bias registers
+
+        // Apparently this is not working for the acceleration biases in the MPU-9250
+        // Are we handling the temperature correction bit properly?
+        // Push accelerometer biases to hardware registers
+        // writeByte(MPU9250_ADDRESS, XA_OFFSET_H, data[0]);
+        // writeByte(MPU9250_ADDRESS, XA_OFFSET_L, data[1]);
+        // writeByte(MPU9250_ADDRESS, YA_OFFSET_H, data[2]);
+        // writeByte(MPU9250_ADDRESS, YA_OFFSET_L, data[3]);
+        // writeByte(MPU9250_ADDRESS, ZA_OFFSET_H, data[4]);
+        // writeByte(MPU9250_ADDRESS, ZA_OFFSET_L, data[5]);
+
+        // Output scaled accelerometer biases for display in the main program
+        dest2[0] = (float)accel_bias[0] / (float)accelsensitivity;
+        dest2[1] = (float)accel_bias[1] / (float)accelsensitivity;
+        dest2[2] = (float)accel_bias[2] / (float)accelsensitivity;
+
+        if (b_verbose) {
+            Serial.println("MPU9250 bias");
+            Serial.println(" x   y   z  ");
+            Serial.print((int)(1000 * accelBias[0]));
+            Serial.print(" ");
+            Serial.print((int)(1000 * accelBias[1]));
+            Serial.print(" ");
+            Serial.print((int)(1000 * accelBias[2]));
+            Serial.print(" ");
+            Serial.println("mg");
+            Serial.print(gyroBias[0], 1);
+            Serial.print(" ");
+            Serial.print(gyroBias[1], 1);
+            Serial.print(" ");
+            Serial.print(gyroBias[2], 1);
+            Serial.print(" ");
+            Serial.println("o/s");
+        }
+
+        delay(100);
+
+        initMPU9250();
+
+        delay(1000);
+    }
+
+    // Accelerometer and gyroscope self test; check calibration wrt factory settings
+    void SelfTest()  // Should return percent deviation from factory trim values, +/- 14 or less deviation is a pass
+    {
+        uint8_t rawData[6] = {0, 0, 0, 0, 0, 0};
+        uint8_t selfTest[6];
+        int32_t gAvg[3] = {0}, aAvg[3] = {0}, aSTAvg[3] = {0}, gSTAvg[3] = {0};
+        float factoryTrim[6];
+        uint8_t FS = 0;
+
+        writeByte(MPU9250_ADDRESS, SMPLRT_DIV, 0x00);       // Set gyro sample rate to 1 kHz
+        writeByte(MPU9250_ADDRESS, MPU_CONFIG, 0x02);       // Set gyro sample rate to 1 kHz and DLPF to 92 Hz
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, FS << 3);   // Set full scale range for the gyro to 250 dps
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG2, 0x02);    // Set accelerometer rate to 1 kHz and bandwidth to 92 Hz
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, FS << 3);  // Set full scale range for the accelerometer to 2 g
+
+        for (int ii = 0; ii < 200; ii++) {  // get average current values of gyro and acclerometer
+
+            readBytes(MPU9250_ADDRESS, ACCEL_XOUT_H, 6, &rawData[0]);       // Read the six raw data registers into data array
+            aAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            aAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            aAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+
+            readBytes(MPU9250_ADDRESS, GYRO_XOUT_H, 6, &rawData[0]);        // Read the six raw data registers sequentially into data array
+            gAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            gAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            gAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+        }
+
+        for (int ii = 0; ii < 3; ii++) {  // Get average of 200 values and store as average current readings
+            aAvg[ii] /= 200;
+            gAvg[ii] /= 200;
+        }
+
+        // Configure the accelerometer for self-test
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, 0xE0);  // Enable self test on all three axes and set accelerometer range to +/- 2 g
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, 0xE0);   // Enable self test on all three axes and set gyro range to +/- 250 degrees/s
+        delay(25);                                       // Delay a while to let the device stabilize
+
+        for (int ii = 0; ii < 200; ii++) {  // get average self-test values of gyro and acclerometer
+
+            readBytes(MPU9250_ADDRESS, ACCEL_XOUT_H, 6, &rawData[0]);         // Read the six raw data registers into data array
+            aSTAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            aSTAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            aSTAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+
+            readBytes(MPU9250_ADDRESS, GYRO_XOUT_H, 6, &rawData[0]);          // Read the six raw data registers sequentially into data array
+            gSTAvg[0] += (int16_t)(((int16_t)rawData[0] << 8) | rawData[1]);  // Turn the MSB and LSB into a signed 16-bit value
+            gSTAvg[1] += (int16_t)(((int16_t)rawData[2] << 8) | rawData[3]);
+            gSTAvg[2] += (int16_t)(((int16_t)rawData[4] << 8) | rawData[5]);
+        }
+
+        for (int ii = 0; ii < 3; ii++) {  // Get average of 200 values and store as average self-test readings
+            aSTAvg[ii] /= 200;
+            gSTAvg[ii] /= 200;
+        }
+
+        // Configure the gyro and accelerometer for normal operation
+        writeByte(MPU9250_ADDRESS, ACCEL_CONFIG, 0x00);
+        writeByte(MPU9250_ADDRESS, GYRO_CONFIG, 0x00);
+        delay(25);  // Delay a while to let the device stabilize
+
+        // Retrieve accelerometer and gyro factory Self-Test Code from USR_Reg
+        selfTest[0] = readByte(MPU9250_ADDRESS, SELF_TEST_X_ACCEL);  // X-axis accel self-test results
+        selfTest[1] = readByte(MPU9250_ADDRESS, SELF_TEST_Y_ACCEL);  // Y-axis accel self-test results
+        selfTest[2] = readByte(MPU9250_ADDRESS, SELF_TEST_Z_ACCEL);  // Z-axis accel self-test results
+        selfTest[3] = readByte(MPU9250_ADDRESS, SELF_TEST_X_GYRO);   // X-axis gyro self-test results
+        selfTest[4] = readByte(MPU9250_ADDRESS, SELF_TEST_Y_GYRO);   // Y-axis gyro self-test results
+        selfTest[5] = readByte(MPU9250_ADDRESS, SELF_TEST_Z_GYRO);   // Z-axis gyro self-test results
+
+        // Retrieve factory self-test value from self-test code reads
+        factoryTrim[0] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[0] - 1.0)));  // FT[Xa] factory trim calculation
+        factoryTrim[1] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[1] - 1.0)));  // FT[Ya] factory trim calculation
+        factoryTrim[2] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[2] - 1.0)));  // FT[Za] factory trim calculation
+        factoryTrim[3] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[3] - 1.0)));  // FT[Xg] factory trim calculation
+        factoryTrim[4] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[4] - 1.0)));  // FT[Yg] factory trim calculation
+        factoryTrim[5] = (float)(2620 / 1 << FS) * (pow(1.01, ((float)selfTest[5] - 1.0)));  // FT[Zg] factory trim calculation
+
+        // Report results as a ratio of (STR - FT)/FT; the change from Factory Trim of the Self-Test Response
+        // To get percent, must multiply by 100
+        for (int i = 0; i < 3; i++) {
+            SelfTestResult[i] = 100.0 * ((float)(aSTAvg[i] - aAvg[i])) / factoryTrim[i] - 100.;          // Report percent differences
+            SelfTestResult[i + 3] = 100.0 * ((float)(gSTAvg[i] - gAvg[i])) / factoryTrim[i + 3] - 100.;  // Report percent differences
+        }
+
+        if (b_verbose) {
+            Serial.print("x-axis self test: acceleration trim within : ");
+            Serial.print(SelfTestResult[0], 1);
+            Serial.println("% of factory value");
+            Serial.print("y-axis self test: acceleration trim within : ");
+            Serial.print(SelfTestResult[1], 1);
+            Serial.println("% of factory value");
+            Serial.print("z-axis self test: acceleration trim within : ");
+            Serial.print(SelfTestResult[2], 1);
+            Serial.println("% of factory value");
+            Serial.print("x-axis self test: gyration trim within : ");
+            Serial.print(SelfTestResult[3], 1);
+            Serial.println("% of factory value");
+            Serial.print("y-axis self test: gyration trim within : ");
+            Serial.print(SelfTestResult[4], 1);
+            Serial.println("% of factory value");
+            Serial.print("z-axis self test: gyration trim within : ");
+            Serial.print(SelfTestResult[5], 1);
+            Serial.println("% of factory value");
+        }
+        delay(5000);
+    }
+
+    void writeByte(uint8_t address, uint8_t subAddress, uint8_t data) {
+        wire->beginTransmission(address);    // Initialize the Tx buffer
+        wire->write(subAddress);             // Put slave register address in Tx buffer
+        wire->write(data);                   // Put data in Tx buffer
+        i2c_err_ = wire->endTransmission();  // Send the Tx buffer
+        if (i2c_err_) pirntI2CError();
+    }
+
+    uint8_t readByte(uint8_t address, uint8_t subAddress) {
+        uint8_t data = 0;                         // `data` will store the register data
+        wire->beginTransmission(address);         // Initialize the Tx buffer
+        wire->write(subAddress);                  // Put slave register address in Tx buffer
+        i2c_err_ = wire->endTransmission(false);  // Send the Tx buffer, but send a restart to keep connection alive
+        if (i2c_err_) pirntI2CError();
+        wire->requestFrom(address, (size_t)1);       // Read one byte from slave register address
+        if (wire->available()) data = wire->read();  // Fill Rx buffer with result
+        return data;                                 // Return data read from slave register
+    }
+
+    void readBytes(uint8_t address, uint8_t subAddress, uint8_t count, uint8_t* dest) {
+        wire->beginTransmission(address);         // Initialize the Tx buffer
+        wire->write(subAddress);                  // Put slave register address in Tx buffer
+        i2c_err_ = wire->endTransmission(false);  // Send the Tx buffer, but send a restart to keep connection alive
+        if (i2c_err_) pirntI2CError();
+        uint8_t i = 0;
+        wire->requestFrom(address, count);  // Read bytes from slave register address
+        while (wire->available()) {
+            dest[i++] = wire->read();
+        }  // Put read results in the Rx buffer
+    }
+
+    void pirntI2CError() {
+        if (i2c_err_ == 7) return;  // to avoid stickbreaker-i2c branch's error code
+        Serial.print("I2C ERROR CODE : ");
+        Serial.println(i2c_err_);
+    }
+
+    bool b_ahrs{true};
+
+    WireType* wire;
+    uint8_t i2c_err_;
+};
+
+using MPU9250 = MPU9250_<TwoWire, MPU9250_WHOAMI_DEFAULT_VALUE>;
+using MPU9255 = MPU9250_<TwoWire, MPU9255_WHOAMI_DEFAULT_VALUE>;
+
+#endif  // MPU9250_H

+ 151 - 0
.pio/libdeps/uno/MPU9250/MPU9250/MPU9250RegisterMap.h

@@ -0,0 +1,151 @@
+#pragma once
+#ifndef MPU9250REGISTERMAP_H
+#define MPU9250REGISTERMAP_H
+
+//Magnetometer Registers
+// #define AK8963_ADDRESS   0x0C
+#define AK8963_WHO_AM_I  0x00 // should return 0x48
+#define AK8963_INFO      0x01
+#define AK8963_ST1       0x02  // data ready status bit 0
+#define AK8963_XOUT_L	 0x03  // data
+#define AK8963_XOUT_H	 0x04
+#define AK8963_YOUT_L	 0x05
+#define AK8963_YOUT_H	 0x06
+#define AK8963_ZOUT_L	 0x07
+#define AK8963_ZOUT_H	 0x08
+#define AK8963_ST2       0x09  // Data overflow bit 3 and data read error status bit 2
+#define AK8963_CNTL      0x0A  // Power down (0000), single-measurement (0001), self-test (1000) and Fuse ROM (1111) modes on bits 3:0
+#define AK8963_ASTC      0x0C  // Self test control
+#define AK8963_I2CDIS    0x0F  // I2C disable
+#define AK8963_ASAX      0x10  // Fuse ROM x-axis sensitivity adjustment value
+#define AK8963_ASAY      0x11  // Fuse ROM y-axis sensitivity adjustment value
+#define AK8963_ASAZ      0x12  // Fuse ROM z-axis sensitivity adjustment value
+
+#define SELF_TEST_X_GYRO 0x00
+#define SELF_TEST_Y_GYRO 0x01
+#define SELF_TEST_Z_GYRO 0x02
+
+// #define X_FINE_GAIN      0x03 // [7:0] fine gain
+// #define Y_FINE_GAIN      0x04
+// #define Z_FINE_GAIN      0x05
+// #define XA_OFFSET_H      0x06 // User-defined trim values for accelerometer
+// #define XA_OFFSET_L_TC   0x07
+// #define YA_OFFSET_H      0x08
+// #define YA_OFFSET_L_TC   0x09
+// #define ZA_OFFSET_H      0x0A
+// #define ZA_OFFSET_L_TC   0x0B
+
+#define SELF_TEST_X_ACCEL 0x0D
+#define SELF_TEST_Y_ACCEL 0x0E
+#define SELF_TEST_Z_ACCEL 0x0F
+
+#define SELF_TEST_A      0x10
+
+#define XG_OFFSET_H      0x13  // User-defined trim values for gyroscope
+#define XG_OFFSET_L      0x14
+#define YG_OFFSET_H      0x15
+#define YG_OFFSET_L      0x16
+#define ZG_OFFSET_H      0x17
+#define ZG_OFFSET_L      0x18
+#define SMPLRT_DIV       0x19
+#define MPU_CONFIG           0x1A
+#define GYRO_CONFIG      0x1B
+#define ACCEL_CONFIG     0x1C
+#define ACCEL_CONFIG2    0x1D
+#define LP_ACCEL_ODR     0x1E
+#define WOM_THR          0x1F
+
+#define MOT_DUR          0x20  // Duration counter threshold for motion interrupt generation, 1 kHz rate, LSB = 1 ms
+#define ZMOT_THR         0x21  // Zero-motion detection threshold bits [7:0]
+#define ZRMOT_DUR        0x22  // Duration counter threshold for zero motion interrupt generation, 16 Hz rate, LSB = 64 ms
+
+#define FIFO_EN          0x23
+#define I2C_MST_CTRL     0x24
+#define I2C_SLV0_ADDR    0x25
+#define I2C_SLV0_REG     0x26
+#define I2C_SLV0_CTRL    0x27
+#define I2C_SLV1_ADDR    0x28
+#define I2C_SLV1_REG     0x29
+#define I2C_SLV1_CTRL    0x2A
+#define I2C_SLV2_ADDR    0x2B
+#define I2C_SLV2_REG     0x2C
+#define I2C_SLV2_CTRL    0x2D
+#define I2C_SLV3_ADDR    0x2E
+#define I2C_SLV3_REG     0x2F
+#define I2C_SLV3_CTRL    0x30
+#define I2C_SLV4_ADDR    0x31
+#define I2C_SLV4_REG     0x32
+#define I2C_SLV4_DO      0x33
+#define I2C_SLV4_CTRL    0x34
+#define I2C_SLV4_DI      0x35
+#define I2C_MST_STATUS   0x36
+#define INT_PIN_CFG      0x37
+#define INT_ENABLE       0x38
+#define DMP_INT_STATUS   0x39  // Check DMP interrupt
+#define INT_STATUS       0x3A
+#define ACCEL_XOUT_H     0x3B
+#define ACCEL_XOUT_L     0x3C
+#define ACCEL_YOUT_H     0x3D
+#define ACCEL_YOUT_L     0x3E
+#define ACCEL_ZOUT_H     0x3F
+#define ACCEL_ZOUT_L     0x40
+#define TEMP_OUT_H       0x41
+#define TEMP_OUT_L       0x42
+#define GYRO_XOUT_H      0x43
+#define GYRO_XOUT_L      0x44
+#define GYRO_YOUT_H      0x45
+#define GYRO_YOUT_L      0x46
+#define GYRO_ZOUT_H      0x47
+#define GYRO_ZOUT_L      0x48
+#define EXT_SENS_DATA_00 0x49
+#define EXT_SENS_DATA_01 0x4A
+#define EXT_SENS_DATA_02 0x4B
+#define EXT_SENS_DATA_03 0x4C
+#define EXT_SENS_DATA_04 0x4D
+#define EXT_SENS_DATA_05 0x4E
+#define EXT_SENS_DATA_06 0x4F
+#define EXT_SENS_DATA_07 0x50
+#define EXT_SENS_DATA_08 0x51
+#define EXT_SENS_DATA_09 0x52
+#define EXT_SENS_DATA_10 0x53
+#define EXT_SENS_DATA_11 0x54
+#define EXT_SENS_DATA_12 0x55
+#define EXT_SENS_DATA_13 0x56
+#define EXT_SENS_DATA_14 0x57
+#define EXT_SENS_DATA_15 0x58
+#define EXT_SENS_DATA_16 0x59
+#define EXT_SENS_DATA_17 0x5A
+#define EXT_SENS_DATA_18 0x5B
+#define EXT_SENS_DATA_19 0x5C
+#define EXT_SENS_DATA_20 0x5D
+#define EXT_SENS_DATA_21 0x5E
+#define EXT_SENS_DATA_22 0x5F
+#define EXT_SENS_DATA_23 0x60
+#define MOT_DETECT_STATUS 0x61
+#define I2C_SLV0_DO      0x63
+#define I2C_SLV1_DO      0x64
+#define I2C_SLV2_DO      0x65
+#define I2C_SLV3_DO      0x66
+#define I2C_MST_DELAY_CTRL 0x67
+#define SIGNAL_PATH_RESET  0x68
+#define MOT_DETECT_CTRL  0x69
+#define USER_CTRL        0x6A  // Bit 7 enable DMP, bit 3 reset DMP
+#define PWR_MGMT_1       0x6B // Device defaults to the SLEEP mode
+#define PWR_MGMT_2       0x6C
+#define DMP_BANK         0x6D  // Activates a specific bank in the DMP
+#define DMP_RW_PNT       0x6E  // Set read/write pointer to a specific start address in specified DMP bank
+#define DMP_REG          0x6F  // Register in DMP from which to read or to which to write
+#define DMP_REG_1        0x70
+#define DMP_REG_2        0x71
+#define FIFO_COUNTH      0x72
+#define FIFO_COUNTL      0x73
+#define FIFO_R_W         0x74
+#define WHO_AM_I_MPU9250 0x75 // Should return 0x71
+#define XA_OFFSET_H      0x77
+#define XA_OFFSET_L      0x78
+#define YA_OFFSET_H      0x7A
+#define YA_OFFSET_L      0x7B
+#define ZA_OFFSET_H      0x7D
+#define ZA_OFFSET_L      0x7E
+
+#endif // MPU9250REGISTERMAP_H

+ 223 - 0
.pio/libdeps/uno/MPU9250/MPU9250/QuaternionFilter.h

@@ -0,0 +1,223 @@
+#pragma once
+#ifndef QUATERNIONFILTER_H
+#define QUATERNIONFILTER_H
+
+class QuaternionFilter
+{
+
+    float GyroMeasError = PI * (40.0f / 180.0f);   // gyroscope measurement error in rads/s (start at 40 deg/s)
+    float GyroMeasDrift = PI * (0.0f  / 180.0f);   // gyroscope measurement drift in rad/s/s (start at 0.0 deg/s/s)
+    float beta = sqrt(3.0f / 4.0f) * GyroMeasError;   // compute beta
+    float zeta = sqrt(3.0f / 4.0f) * GyroMeasDrift;   // compute zeta, the other free parameter in the Madgwick scheme usually set to a small or zero value
+    const float Kp = 2.0f * 5.0f; // these are the free parameters in the Mahony filter and fusion scheme, Kp for proportional feedback, Ki for integral
+    const float Ki = 0.0f;
+
+    float deltat = 0.0f, sum = 0.0f;        // integration interval for both filter schemes
+    uint32_t lastUpdate = 0, firstUpdate = 0; // used to calculate integration interval
+    uint32_t Now = 0;        // used to calculate integration interval
+
+    // for mahony only
+    float eInt[3] = {0.0f, 0.0f, 0.0f};       // vector to hold integral error for Mahony method
+
+public:
+
+    void bind() {}
+
+    // MadgwickQuaternionUpdate
+    void update(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float* q)
+    {
+        // updateParams()
+        float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];   // short name local variable for readability
+        float norm;
+        float hx, hy, _2bx, _2bz;
+        float s1, s2, s3, s4;
+        float qDot1, qDot2, qDot3, qDot4;
+
+        // Auxiliary variables to avoid repeated arithmetic
+        float _2q1mx;
+        float _2q1my;
+        float _2q1mz;
+        float _2q2mx;
+        float _4bx;
+        float _4bz;
+        float _2q1 = 2.0f * q1;
+        float _2q2 = 2.0f * q2;
+        float _2q3 = 2.0f * q3;
+        float _2q4 = 2.0f * q4;
+        float _2q1q3 = 2.0f * q1 * q3;
+        float _2q3q4 = 2.0f * q3 * q4;
+        float q1q1 = q1 * q1;
+        float q1q2 = q1 * q2;
+        float q1q3 = q1 * q3;
+        float q1q4 = q1 * q4;
+        float q2q2 = q2 * q2;
+        float q2q3 = q2 * q3;
+        float q2q4 = q2 * q4;
+        float q3q3 = q3 * q3;
+        float q3q4 = q3 * q4;
+        float q4q4 = q4 * q4;
+        gx *= PI / 180.f;
+        gy *= PI / 180.f;
+        gz *= PI / 180.f;
+
+        // updateTime()
+        Now = micros();
+        deltat = ((Now - lastUpdate) / 1000000.0f); // set integration time by time elapsed since last filter update
+        lastUpdate = Now;
+
+        // Normalise accelerometer measurement
+        norm = sqrtf(ax * ax + ay * ay + az * az);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;
+        ax *= norm;
+        ay *= norm;
+        az *= norm;
+
+        // Normalise magnetometer measurement
+        norm = sqrtf(mx * mx + my * my + mz * mz);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;
+        mx *= norm;
+        my *= norm;
+        mz *= norm;
+
+        // Reference direction of Earth's magnetic field
+        _2q1mx = 2.0f * q1 * mx;
+        _2q1my = 2.0f * q1 * my;
+        _2q1mz = 2.0f * q1 * mz;
+        _2q2mx = 2.0f * q2 * mx;
+        hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
+        hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
+        _2bx = sqrtf(hx * hx + hy * hy);
+        _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
+        _4bx = 2.0f * _2bx;
+        _4bz = 2.0f * _2bz;
+
+        // Gradient decent algorithm corrective step
+        s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        s2 = _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        s4 = _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
+        norm = sqrtf(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4);    // normalise step magnitude
+        norm = 1.0f/norm;
+        s1 *= norm;
+        s2 *= norm;
+        s3 *= norm;
+        s4 *= norm;
+
+        // Compute rate of change of quaternion
+        qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1;
+        qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2;
+        qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3;
+        qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4;
+
+        // Integrate to yield quaternion
+        q1 += qDot1 * deltat;
+        q2 += qDot2 * deltat;
+        q3 += qDot3 * deltat;
+        q4 += qDot4 * deltat;
+        norm = sqrtf(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);    // normalise quaternion
+        norm = 1.0f/norm;
+        q[0] = q1 * norm;
+        q[1] = q2 * norm;
+        q[2] = q3 * norm;
+        q[3] = q4 * norm;
+    }
+
+ // Similar to Madgwick scheme but uses proportional and integral filtering on the error between estimated reference vectors and
+ // measured ones.
+    void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float* q)
+    {
+        float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];   // short name local variable for readability
+        float norm;
+        float hx, hy, bx, bz;
+        float vx, vy, vz, wx, wy, wz;
+        float ex, ey, ez;
+        float pa, pb, pc;
+
+        // Auxiliary variables to avoid repeated arithmetic
+        float q1q1 = q1 * q1;
+        float q1q2 = q1 * q2;
+        float q1q3 = q1 * q3;
+        float q1q4 = q1 * q4;
+        float q2q2 = q2 * q2;
+        float q2q3 = q2 * q3;
+        float q2q4 = q2 * q4;
+        float q3q3 = q3 * q3;
+        float q3q4 = q3 * q4;
+        float q4q4 = q4 * q4;
+
+        // Normalise accelerometer measurement
+        norm = sqrtf(ax * ax + ay * ay + az * az);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;        // use reciprocal for division
+        ax *= norm;
+        ay *= norm;
+        az *= norm;
+
+        // Normalise magnetometer measurement
+        norm = sqrtf(mx * mx + my * my + mz * mz);
+        if (norm == 0.0f) return; // handle NaN
+        norm = 1.0f / norm;        // use reciprocal for division
+        mx *= norm;
+        my *= norm;
+        mz *= norm;
+
+        // Reference direction of Earth's magnetic field
+        hx = 2.0f * mx * (0.5f - q3q3 - q4q4) + 2.0f * my * (q2q3 - q1q4) + 2.0f * mz * (q2q4 + q1q3);
+        hy = 2.0f * mx * (q2q3 + q1q4) + 2.0f * my * (0.5f - q2q2 - q4q4) + 2.0f * mz * (q3q4 - q1q2);
+        bx = sqrtf((hx * hx) + (hy * hy));
+        bz = 2.0f * mx * (q2q4 - q1q3) + 2.0f * my * (q3q4 + q1q2) + 2.0f * mz * (0.5f - q2q2 - q3q3);
+
+        // Estimated direction of gravity and magnetic field
+        vx = 2.0f * (q2q4 - q1q3);
+        vy = 2.0f * (q1q2 + q3q4);
+        vz = q1q1 - q2q2 - q3q3 + q4q4;
+        wx = 2.0f * bx * (0.5f - q3q3 - q4q4) + 2.0f * bz * (q2q4 - q1q3);
+        wy = 2.0f * bx * (q2q3 - q1q4) + 2.0f * bz * (q1q2 + q3q4);
+        wz = 2.0f * bx * (q1q3 + q2q4) + 2.0f * bz * (0.5f - q2q2 - q3q3);
+
+        // Error is cross product between estimated direction and measured direction of gravity
+        ex = (ay * vz - az * vy) + (my * wz - mz * wy);
+        ey = (az * vx - ax * vz) + (mz * wx - mx * wz);
+        ez = (ax * vy - ay * vx) + (mx * wy - my * wx);
+        if (Ki > 0.0f)
+        {
+            eInt[0] += ex;      // accumulate integral error
+            eInt[1] += ey;
+            eInt[2] += ez;
+        }
+        else
+        {
+            eInt[0] = 0.0f;     // prevent integral wind up
+            eInt[1] = 0.0f;
+            eInt[2] = 0.0f;
+        }
+
+        // Apply feedback terms
+        gx = gx + Kp * ex + Ki * eInt[0];
+        gy = gy + Kp * ey + Ki * eInt[1];
+        gz = gz + Kp * ez + Ki * eInt[2];
+
+        // Integrate rate of change of quaternion
+        pa = q2;
+        pb = q3;
+        pc = q4;
+        q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * deltat);
+        q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * deltat);
+        q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * deltat);
+        q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * deltat);
+
+        // Normalise quaternion
+        norm = sqrtf(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
+        norm = 1.0f / norm;
+        q[0] = q1 * norm;
+        q[1] = q2 * norm;
+        q[2] = q3 * norm;
+        q[3] = q4 * norm;
+    }
+
+
+};
+
+#endif // QUATERNIONFILTER_H

+ 230 - 0
.pio/libdeps/uno/MPU9250/README.md

@@ -0,0 +1,230 @@
+# MPU9250
+Arduino library for [MPU9250](https://www.invensense.com/products/motion-tracking/9-axis/mpu-9250/) Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device
+
+This library is based on the [great work](https://github.com/kriswiner/MPU9250) by [kriswiner](https://github.com/kriswiner), and re-writen for the simple usage.
+
+## Usage
+
+### Simple Measurement
+
+``` C++
+#include "MPU9250.h"
+
+MPU9250 mpu;
+// MPU9255 mpu; // You can also use MPU9255
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+}
+
+void loop() {
+    if (mpu.update()) {
+        mpu.printRollPitchYaw();
+    }
+}
+```
+
+### Calibration
+
+- device should be stay still during accel/gyro calibration
+- round device around during mag calibration
+
+``` C++
+#include "MPU9250.h"
+
+MPU9250 mpu;
+// MPU9255 mpu; // You can also use MPU9255
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+
+    delay(5000);
+
+    // calibrate anytime you want to
+    mpu.calibrateAccelGyro();
+    mpu.calibrateMag();
+
+    mpu.printCalibration();
+}
+
+void loop() { }
+```
+
+
+## Other Settings
+
+### I2C Address
+
+You must set your own address based on A0, A1, A2 setting as:
+
+``` C++
+mpu.setup(0x70);
+```
+
+### Other I2C library
+
+You can use other I2C library e.g. [SoftWire](https://github.com/stevemarple/SoftWire).
+
+``` C++
+MPU9250_<SoftWire, MPU9250_WHOAMI_DEFAULT_VALUE> mpu;
+SoftWire sw(SDA, SCL);
+
+// in setup()
+mpu.setup(0x70, sw);
+```
+
+### Magnetic Declination
+
+Magnetic declination should be set depending on where you are to get accurate data.
+To set it, use this method.
+
+```C++
+mpu.setMagneticDeclination(value);
+```
+
+You can find magnetic declination in your city [here](http://www.magnetic-declination.com/).
+
+For more details, see [wiki](https://en.wikipedia.org/wiki/Magnetic_declination).
+
+
+### AFS, FGS, MFS
+
+#### AFS
+
+`enum class AFS { A2G, A4G, A8G, A16G };`
+
+#### GFS
+
+`enum class GFS { G250DPS, G500DPS, G1000DPS, G2000DPS };`
+
+#### MFS
+
+`enum class MFS { M14BITS, M16BITS };
+
+#### How to change
+
+MPU9250 class is defined as follows.
+
+```C++
+template <
+	typename WireType,
+	AFS AFSSEL = AFS::A16G,
+	GFS GFSSEL = GFS::G2000DPS,
+	MFS MFSSEL = MFS::M16BITS
+>
+class MPU9250_;
+```
+
+So, please use like
+
+```
+class MPU9250_<TwoWire, AFS::A4G, GFS::G500DPS, MFS::M14BITS> mpu; // most of Arduino
+class MPU9250_<i2c_t3, AFS::A4G, GFS::G500DPS, MFS::M14BITS> mpu; // Teensy
+```
+
+### MPU9255
+
+To use MPU9255 instead of MPU9250, just declare MPU9255.
+
+```C++
+MPU9255 mpu;
+```
+
+## APIs
+
+``` C++
+bool setup(const uint8_t addr, WireType& w = Wire);
+void verbose(const bool b);
+void calibrateAccelGyro();
+void calibrateMag();
+bool isConnectedMPU9250();
+bool isConnectedAK8963();
+bool available();
+bool update();
+
+float getRoll() const;
+float getPitch() const;
+float getYaw() const;
+
+float getQuaternion(uint8_t i) const;
+
+float getQuaternionX() const;
+float getQuaternionY() const;
+float getQuaternionZ() const;
+float getQuaternionW() const;
+
+float getAcc(const uint8_t i) const;
+float getGyro(const uint8_t i) const;
+float getMag(const uint8_t i) const;
+
+float getAccX() const;
+float getAccY() const;
+float getAccZ() const;
+float getGyroX() const;
+float getGyroY() const;
+float getGyroZ() const;
+float getMagX() const;
+float getMagY() const;
+float getMagZ() const;
+
+float getAccBias(const uint8_t i) const;
+float getGyroBias(const uint8_t i) const;
+float getMagBias(const uint8_t i) const;
+float getMagScale(const uint8_t i) const;
+
+float getAccBiasX() const;
+float getAccBiasY() const;
+float getAccBiasZ() const;
+float getGyroBiasX() const;
+float getGyroBiasY() const;
+float getGyroBiasZ() const;
+float getMagBiasX() const;
+float getMagBiasY() const;
+float getMagBiasZ() const;
+float getMagScaleX() const;
+float getMagScaleY() const;
+float getMagScaleZ() const;
+
+float getTemperature() const;
+
+void setAccBias(const uint8_t i, const float v);
+void setGyroBias(const uint8_t i, const float v);
+void setMagBias(const uint8_t i, const float v);
+void setMagScale(const uint8_t i, const float v);
+
+void setAccBiasX(const float v);
+void setAccBiasY(const float v);
+void setAccBiasZ(const float v);
+void setGyroBiasX(const float v);
+void setGyroBiasY(const float v);
+void setGyroBiasZ(const float v);
+void setMagBiasX(const float v);
+void setMagBiasY(const float v);
+void setMagBiasZ(const float v);
+void setMagScaleX(const float v);
+void setMagScaleY(const float v);
+void setMagScaleZ(const float v);
+
+void setMagneticDeclination(const float d);
+
+void print() const;
+void printRawData() const;
+void printRollPitchYaw() const;
+void printCalibration() const;
+```
+
+## License
+
+MIT
+
+## Acknowledgments / Contributor
+
+- [Yuta Asai](https://github.com/asaiyuta)

+ 22 - 0
.pio/libdeps/uno/MPU9250/examples/calibration/calibration.ino

@@ -0,0 +1,22 @@
+#include "MPU9250.h"
+
+MPU9250 mpu;
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+
+    delay(5000);
+
+    // calibrate anytime you want to
+    mpu.calibrateAccelGyro();
+    mpu.calibrateMag();
+
+    mpu.printCalibration();
+}
+
+void loop() {
+}

+ 29 - 0
.pio/libdeps/uno/MPU9250/examples/calibration_eeprom/calibration_eeprom.ino

@@ -0,0 +1,29 @@
+#include "MPU9250.h"
+#include "eeprom_utils.h"
+
+MPU9250 mpu;
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+
+    delay(5000);
+
+    // calibrate when you want to
+    mpu.calibrateAccelGyro();
+    mpu.calibrateMag();
+
+    // save to eeprom
+    saveCalibration();
+
+    // load from eeprom
+    loadCalibration();
+
+    mpu.printCalibration();
+}
+
+void loop() {
+}

+ 150 - 0
.pio/libdeps/uno/MPU9250/examples/calibration_eeprom/eeprom_utils.h

@@ -0,0 +1,150 @@
+#include <EEPROM.h>
+#include "MPU9250.h"
+
+const uint8_t EEPROM_SIZE = 1 + sizeof(float) * 3 * 4;
+extern MPU9250 mpu;
+
+enum EEP_ADDR
+{
+    EEP_CALIB_FLAG = 0x00,
+    EEP_ACC_BIAS = 0x01,
+    EEP_GYRO_BIAS = 0x0D,
+    EEP_MAG_BIAS = 0x19,
+    EEP_MAG_SCALE = 0x25
+};
+
+void writeByte(int address, byte value){
+  EEPROM.put(address, value);
+}
+
+void writeFloat(int address, float value){
+  EEPROM.put(address, value);
+}
+
+byte readByte(int address){
+  byte valueIn;
+  EEPROM.get(address, valueIn);
+  return valueIn;
+}
+
+float readFloat(int address){
+  float valueIn;
+  EEPROM.get(address, valueIn);
+  return valueIn;
+}
+
+void clearCalibration()
+{
+    writeByte(EEP_CALIB_FLAG, 0);
+}
+
+bool isCalibrated()
+{
+    return (readByte(EEP_CALIB_FLAG) == 0x01);
+}
+
+void saveCalibration()
+{
+    writeByte(EEP_CALIB_FLAG, 1);
+    writeFloat(EEP_ACC_BIAS + 0, mpu.getAccBias(0));
+    writeFloat(EEP_ACC_BIAS + 4, mpu.getAccBias(1));
+    writeFloat(EEP_ACC_BIAS + 8, mpu.getAccBias(2));
+    writeFloat(EEP_GYRO_BIAS + 0, mpu.getGyroBias(0));
+    writeFloat(EEP_GYRO_BIAS + 4, mpu.getGyroBias(1));
+    writeFloat(EEP_GYRO_BIAS + 8, mpu.getGyroBias(2));
+    writeFloat(EEP_MAG_BIAS + 0, mpu.getMagBias(0));
+    writeFloat(EEP_MAG_BIAS + 4, mpu.getMagBias(1));
+    writeFloat(EEP_MAG_BIAS + 8, mpu.getMagBias(2));
+    writeFloat(EEP_MAG_SCALE + 0, mpu.getMagScale(0));
+    writeFloat(EEP_MAG_SCALE + 4, mpu.getMagScale(1));
+    writeFloat(EEP_MAG_SCALE + 8, mpu.getMagScale(2));
+}
+
+void loadCalibration()
+{
+    if (isCalibrated())
+    {
+        Serial.println("calibrated? : YES");
+        Serial.println("load calibrated values");
+        mpu.setAccBias(0, readFloat(EEP_ACC_BIAS + 0));
+        mpu.setAccBias(1, readFloat(EEP_ACC_BIAS + 4));
+        mpu.setAccBias(2, readFloat(EEP_ACC_BIAS + 8));
+        mpu.setGyroBias(0, readFloat(EEP_GYRO_BIAS + 0));
+        mpu.setGyroBias(1, readFloat(EEP_GYRO_BIAS + 4));
+        mpu.setGyroBias(2, readFloat(EEP_GYRO_BIAS + 8));
+        mpu.setMagBias(0, readFloat(EEP_MAG_BIAS + 0));
+        mpu.setMagBias(1, readFloat(EEP_MAG_BIAS + 4));
+        mpu.setMagBias(2, readFloat(EEP_MAG_BIAS + 8));
+        mpu.setMagScale(0, readFloat(EEP_MAG_SCALE + 0));
+        mpu.setMagScale(1, readFloat(EEP_MAG_SCALE + 4));
+        mpu.setMagScale(2, readFloat(EEP_MAG_SCALE + 8));
+    }
+    else
+    {
+        Serial.println("calibrated? : NO");
+        Serial.println("load default values");
+        mpu.setAccBias(0, +0.005);
+        mpu.setAccBias(1, -0.008);
+        mpu.setAccBias(2, -0.001);
+        mpu.setGyroBias(0, +1.5);
+        mpu.setGyroBias(1, -0.5);
+        mpu.setGyroBias(2, +0.7);
+        mpu.setMagBias(0, +186.41);
+        mpu.setMagBias(1, -197.91);
+        mpu.setMagBias(2, -425.55);
+        mpu.setMagScale(0, +1.07);
+        mpu.setMagScale(1, +0.95);
+        mpu.setMagScale(2, +0.99);
+    }
+}
+
+void printCalibration()
+{
+    Serial.println("< calibration parameters >");
+    Serial.print("calibrated? : ");
+    Serial.println(readByte(EEP_CALIB_FLAG) ? "YES" : "NO");
+    Serial.print("acc bias x  : ");
+    Serial.println(readFloat(EEP_ACC_BIAS + 0) * 1000.f);
+    Serial.print("acc bias y  : ");
+    Serial.println(readFloat(EEP_ACC_BIAS + 4) * 1000.f);
+    Serial.print("acc bias z  : ");
+    Serial.println(readFloat(EEP_ACC_BIAS + 8) * 1000.f);
+    Serial.print("gyro bias x : ");
+    Serial.println(readFloat(EEP_GYRO_BIAS + 0));
+    Serial.print("gyro bias y : ");
+    Serial.println(readFloat(EEP_GYRO_BIAS + 4));
+    Serial.print("gyro bias z : ");
+    Serial.println(readFloat(EEP_GYRO_BIAS + 8));
+    Serial.print("mag bias x  : ");
+    Serial.println(readFloat(EEP_MAG_BIAS + 0));
+    Serial.print("mag bias y  : ");
+    Serial.println(readFloat(EEP_MAG_BIAS + 4));
+    Serial.print("mag bias z  : ");
+    Serial.println(readFloat(EEP_MAG_BIAS + 8));
+    Serial.print("mag scale x : ");
+    Serial.println(readFloat(EEP_MAG_SCALE + 0));
+    Serial.print("mag scale y : ");
+    Serial.println(readFloat(EEP_MAG_SCALE + 4));
+    Serial.print("mag scale z : ");
+    Serial.println(readFloat(EEP_MAG_SCALE + 8));
+}
+
+void printBytes()
+{
+    for (size_t i = 0; i < EEPROM_SIZE; ++i)
+        Serial.println(readByte(i), HEX);
+}
+
+void setupEEPROM()
+{
+    Serial.println("EEPROM start");
+
+    if (!isCalibrated())
+    {
+        Serial.println("Need Calibration!!");
+    }
+    Serial.println("EEPROM calibration value is : ");
+    printCalibration();
+    Serial.println("Loaded calibration value is : ");
+    loadCalibration();
+}

+ 71 - 0
.pio/libdeps/uno/MPU9250/examples/connection_check/connection_check.ino

@@ -0,0 +1,71 @@
+#include "MPU9250.h"
+
+uint8_t addrs[7] = {0};
+uint8_t device_count = 0;
+
+template <typename WireType = TwoWire>
+void scan_mpu(WireType& wire = Wire) {
+    Serial.println("Searching for i2c devices...");
+    device_count = 0;
+    for (uint8_t i = 0x68; i < 0x70; ++i) {
+        wire.beginTransmission(i);
+        if (wire.endTransmission() == 0) {
+            addrs[device_count++] = i;
+            delay(1);
+        }
+    }
+    Serial.print("Found ");
+    Serial.print(device_count, DEC);
+    Serial.println(" I2C devices");
+
+    Serial.print("I2C addresses are: ");
+    for (uint8_t i = 0; i < device_count; ++i) {
+        Serial.print("0x");
+        Serial.print(addrs[i], HEX);
+        Serial.print(" ");
+    }
+    Serial.println();
+}
+
+template <typename WireType = TwoWire>
+uint8_t readByte(uint8_t address, uint8_t subAddress, WireType& wire = Wire) {
+    uint8_t data = 0;
+    wire.beginTransmission(address);
+    wire.write(subAddress);
+    wire.endTransmission(false);
+    wire.requestFrom(address, (size_t)1);
+    if (wire.available()) data = wire.read();
+    return data;
+}
+
+void setup() {
+    Serial.begin(115200);
+    Serial.flush();
+    Wire.begin();
+    delay(2000);
+
+    scan_mpu();
+
+    if (device_count == 0) {
+        Serial.println("No device found on I2C bus. Please check your hardware connection");
+        while (1)
+            ;
+    }
+
+    // check WHO_AM_I address of MPU
+    for (uint8_t i = 0; i < device_count; ++i) {
+        Serial.print("I2C address 0x");
+        Serial.print(addrs[i], HEX);
+        byte c = readByte(addrs[i], WHO_AM_I_MPU9250);
+        if (c == MPU9250_WHOAMI_DEFAULT_VALUE) {
+            Serial.println(" is MPU9250 and ready to use");
+        } else if (c == MPU9255_WHOAMI_DEFAULT_VALUE) {
+            Serial.println(" is MPU9255 and ready to use");
+        } else {
+            Serial.println(" is not MPU series. Please use correct device");
+        }
+    }
+}
+
+void loop() {
+}

+ 17 - 0
.pio/libdeps/uno/MPU9250/examples/simple/simple.ino

@@ -0,0 +1,17 @@
+#include "MPU9250.h"
+
+MPU9250 mpu;
+
+void setup() {
+    Serial.begin(115200);
+    Wire.begin();
+    delay(2000);
+
+    mpu.setup(0x68);  // change to your own address
+}
+
+void loop() {
+    if (mpu.update()) {
+        mpu.printRollPitchYaw();
+    }
+}

+ 20 - 0
.pio/libdeps/uno/MPU9250/library.json

@@ -0,0 +1,20 @@
+{
+    "name": "MPU9250",
+    "keywords": "i2c,wire,imu",
+    "description": "Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device",
+    "repository":
+    {
+        "type": "git",
+        "url": "https://github.com/hideakitai/MPU9250.git"
+    },
+    "authors":
+    {
+        "name": "Hideaki Tai",
+        "url": "https://github.com/hideakitai",
+        "maintainer": true
+    },
+    "version": "0.2.3",
+    "license": "MIT",
+    "frameworks": "arduino",
+    "platforms": "*"
+}

+ 9 - 0
.pio/libdeps/uno/MPU9250/library.properties

@@ -0,0 +1,9 @@
+name=MPU9250
+version=0.2.3
+author=hideakitai
+maintainer=hideakitai
+sentence=Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device
+paragraph=Arduino library for MPU9250 Nine-Axis (Gyro + Accelerometer + Compass) MEMS MotionTracking™ Device
+category=Device Control
+url=https://github.com/hideakitai/MPU9250
+architectures=*

+ 60 - 0
.vscode/c_cpp_properties.json

@@ -0,0 +1,60 @@
+//
+// !!! WARNING !!! AUTO-GENERATED FILE!
+// PLEASE DO NOT MODIFY IT AND USE "platformio.ini":
+// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags
+//
+{
+    "configurations": [
+        {
+            "name": "PlatformIO",
+            "includePath": [
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/include",
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/src",
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/BMP280_DEV",
+                "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/MPU9250",
+                "/home/user/.platformio/packages/framework-arduino-avr/cores/arduino",
+                "/home/user/.platformio/packages/framework-arduino-avr/variants/standard",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/SPI/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/Wire/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/HID/src",
+                "/home/user/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src",
+                ""
+            ],
+            "browse": {
+                "limitSymbolsToIncludedHeaders": true,
+                "path": [
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/include",
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/src",
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/BMP280_DEV",
+                    "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/libdeps/uno/MPU9250",
+                    "/home/user/.platformio/packages/framework-arduino-avr/cores/arduino",
+                    "/home/user/.platformio/packages/framework-arduino-avr/variants/standard",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/SPI/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/Wire/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/EEPROM/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/HID/src",
+                    "/home/user/.platformio/packages/framework-arduino-avr/libraries/SoftwareSerial/src",
+                    ""
+                ]
+            },
+            "defines": [
+                "PLATFORMIO=50205",
+                "ARDUINO_AVR_UNO",
+                "F_CPU=16000000L",
+                "ARDUINO_ARCH_AVR",
+                "ARDUINO=10808",
+                "__AVR_ATmega328P__",
+                ""
+            ],
+            "cStandard": "c11",
+            "cppStandard": "c++11",
+            "compilerPath": "/home/user/.platformio/packages/toolchain-atmelavr/bin/avr-gcc",
+            "compilerArgs": [
+                "-mmcu=atmega328p",
+                ""
+            ]
+        }
+    ],
+    "version": 4
+}

+ 10 - 0
.vscode/extensions.json

@@ -0,0 +1,10 @@
+{
+    // See http://go.microsoft.com/fwlink/?LinkId=827846
+    // for the documentation about the extensions.json format
+    "recommendations": [
+        "platformio.platformio-ide"
+    ],
+    "unwantedRecommendations": [
+        "ms-vscode.cpptools-extension-pack"
+    ]
+}

+ 44 - 0
.vscode/launch.json

@@ -0,0 +1,44 @@
+// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY
+//
+// PIO Unified Debugger
+//
+// Documentation: https://docs.platformio.org/page/plus/debugging.html
+// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html
+
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "platformio-debug",
+            "request": "launch",
+            "name": "PIO Debug",
+            "executable": "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/build/uno/firmware.elf",
+            "projectEnvName": "uno",
+            "toolchainBinDir": "/home/user/.platformio/packages/toolchain-atmelavr/bin",
+            "internalConsoleOptions": "openOnSessionStart",
+            "preLaunchTask": {
+                "type": "PlatformIO",
+                "task": "Pre-Debug"
+            }
+        },
+        {
+            "type": "platformio-debug",
+            "request": "launch",
+            "name": "PIO Debug (skip Pre-Debug)",
+            "executable": "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/build/uno/firmware.elf",
+            "projectEnvName": "uno",
+            "toolchainBinDir": "/home/user/.platformio/packages/toolchain-atmelavr/bin",
+            "internalConsoleOptions": "openOnSessionStart"
+        },
+        {
+            "type": "platformio-debug",
+            "request": "launch",
+            "name": "PIO Debug (without uploading)",
+            "executable": "/home/user/Schreibtisch/Projektarbeit/lern-tracking-system/.pio/build/uno/firmware.elf",
+            "projectEnvName": "uno",
+            "toolchainBinDir": "/home/user/.platformio/packages/toolchain-atmelavr/bin",
+            "internalConsoleOptions": "openOnSessionStart",
+            "loadMode": "manual"
+        }
+    ]
+}

+ 0 - 0
LICENSE


BIN
Projektarbeit_final_Presentation.odp


+ 0 - 0
README.md


+ 0 - 0
arduino/.gitignore


+ 0 - 0
arduino/.vscode/extensions.json


+ 0 - 0
arduino/include/README


+ 0 - 0
arduino/include/ultrasonic.hpp


+ 0 - 0
arduino/lib/README


+ 0 - 0
arduino/platformio.ini


+ 12 - 3
arduino/src/main.cpp

@@ -27,9 +27,12 @@ void setup() {
   mpu.setup(0x68); 
   delay(1000);
 
-  Serial.println(F("calibrate accel/gyro"));
+  Serial.println(F("Calibrate accel/gyro"));
   mpu.calibrateAccelGyro();
 
+  Serial.println(F("Calibrate Magnetometer"));
+  //mpu.calibrateMag();
+  
   Serial.println(F("FIELDS:\tUS_0\tUS_1\tMAG_X\tMAG_Y\tMAG_Z\tACCEL_X\tACCEL_Y\tACCEL_Z\tGYRO_X\tGYRO_Y\tGYRO_Z\tTEMP\tEXEC_TIME"));
 
 }
@@ -41,6 +44,12 @@ void loop() {
 
   if(micros() >= loopStart + LOOP_INTERVAL_US) {
     loopStart = micros();
+    /*if (Serial.available() > 0){
+      char c = Serial.read(1);
+      if (c == 'c'){
+        mpu.calibrateMag();
+      }
+    }*/
 
     //get mpu values
     mpu.update();
@@ -56,10 +65,10 @@ void loop() {
       (long)(mpu.getAccX()*1000),
       (long)(mpu.getAccY()*1000),
       (long)(mpu.getAccZ()*1000),
-      //gyroscope
+      /*gyroscope (values not needed)
       (long)(mpu.getGyroX()*1000),
       (long)(mpu.getGyroY()*1000),
-      (long)(mpu.getGyroZ()*1000),
+      (long)(mpu.getGyroZ()*1000),*/
       //temperature
       (long)(mpu.getTemperature()*1000),
       //execution time in microseconds

+ 0 - 0
arduino/src/ultrasonic.cpp


+ 0 - 0
arduino/test/README


BIN
hntrgrnd.jpg


+ 0 - 0
images/Aufgabenstellung.jpg


+ 0 - 0
images/BOM.png


+ 0 - 0
images/Projektarbeit_final_Presentation.odp


+ 0 - 0
images/acustic tracking.jpg


+ 0 - 0
images/cave.jpg


+ 0 - 0
images/full_system.jpg


+ 0 - 0
images/item2.png


+ 0 - 0
images/screenrecord_thumb.jpg


+ 0 - 0
images/tracking.png


BIN
markers.png


+ 19 - 0
platformio.ini

@@ -0,0 +1,19 @@
+; PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:uno]
+platform = atmelavr
+board = uno
+framework = arduino
+monitor_speed = 1000000
+board_build.f_cpu = 16000000L
+lib_deps = 
+	hideakitai/MPU9250@^0.2.3
+	martinl1/BMP280_DEV@^1.0.18

+ 0 - 0
raspberry-pi/.vscode/settings.json


BIN
raspberry-pi/SourceSansPro-Semibold.otf


+ 11 - 3
raspberry-pi/config.ini

@@ -36,8 +36,6 @@
   overhead_left  = 20
   overhead_right = 20
 
-[mag_sensor]
-
 [opt_sensor]
   capture_device = -1
   debug_image = yes
@@ -52,6 +50,16 @@
   # RaspiCam datasheet: https://www.raspberrypi.org/documentation/hardware/camera/
   fov = 53.50
 
+[mag_sensor]
+max_x = 800
+max_y = 800
+off_x = 0
+off_y = 0
+off_z = 0
+scale_x = 0
+scale_y = 0
+scale_z = 0
+
 [gui]
-  fullscreen = yes
+  fullscreen = no
   log_lines  = 100

+ 0 - 0
raspberry-pi/gui/SourceSansPro-Semibold.otf


+ 0 - 0
raspberry-pi/gui/graph.py


+ 0 - 0
raspberry-pi/gui/logScreen.py


+ 132 - 21
raspberry-pi/gui/mainWindow.py

@@ -7,18 +7,21 @@ import numpy as np
 from gui.popup import CalibrationPopUp
 from gui.graph import Graph
 from gui.logScreen import LogScreen
+from sensors.opticalSensor import OpticalSensor
 import logHandler
 
 
 class MainWindow(tk.Frame):
-  def __init__(self, root, conf, ac_sensor, opt_sensor):
-    self.root        = root
-    self.conf        = conf
-    self.ac_sensor   = ac_sensor
-    self.opt_sensor  = opt_sensor
-    self.log_handler = logHandler.get_log_handler()
-    self.popup_window       = None
-    self.log_window  = None
+  def __init__(self, root, conf, ac_sensor, opt_sensor, mag_sensor):
+    self.root         = root
+    self.conf         = conf
+    self.ac_sensor    = ac_sensor
+    self.opt_sensor   = opt_sensor
+    self.mag_sensor   = mag_sensor
+    self.log_handler  = logHandler.get_log_handler()
+    self.popup_window = None
+    self.log_window   = None
+    self.mainWindow   = None
 
     tk.Frame.__init__(self, root)
     # data plot at left side
@@ -55,17 +58,44 @@ class MainWindow(tk.Frame):
     tk.Label(self.controls, textvariable=self.opt_dro_offset, anchor="nw").pack(side="top", fill="both", expand=False)
     tk.Label(self.controls, textvariable=self.opt_dro_size, anchor="nw").pack(side="top", fill="both", expand=False)
 
-    quit_button = tk.Button(self.controls, text="Quit", command=self.root.destroy, height=2, foreground="red")
-    quit_button.pack(side="bottom", fill="both")
+    self.mag_dro_val_sums = np.ndarray((4), dtype=np.float)
+    self.mag_dro_val_count = 0
+    self.mag_dro_x = tk.StringVar()
+    self.mag_dro_y = tk.StringVar()
+    self.mag_label = tk.Label(self.controls, text="Magnetic Sensor", anchor="c", font=("Helvatica", 10, 'bold'))
+    self.mag_label.pack(side="top", fill="both", expand=False)
+    tk.Label(self.controls, textvariable=self.mag_dro_x, anchor = "nw").pack(side = "top", fill = "both", expand = False)
+    tk.Label(self.controls, textvariable=self.mag_dro_y, anchor = "nw").pack(side = "top", fill = "both", expand = False)
 
-    calibrate_button = tk.Button(self.controls, text="Calibrate AC", command=self.calibrate_ac, height=4)
-    calibrate_button.pack(side="bottom", fill="both")
+    self.quit_button = tk.Button(self.controls, text="Quit", command=self.root.destroy, height=2, foreground="red")
+    self.quit_button.pack(side="bottom", fill="both")
 
-    clear_button = tk.Button(self.controls, text="Clear graph", command=self.graph.clear, height=4)
-    clear_button.pack(side="bottom", fill="both")
+    self.calibrate_submenu_button = tk.Button(self.controls, text="Calibrate", command=self.calibrate_submenu, height=2)
+    self.calibrate_submenu_button.pack_forget()
+    
+    self.calibrate_all_button = tk.Button(self.controls, text="Calibrate All", command=self.calibrate_all, height=2)
+    self.calibrate_all_button.pack_forget()
+    
+    self.calibrateac_button = tk.Button(self.controls, text="Calibrate AC", command=self.calibrate_ac, height=2)
+    self.calibrateac_button.pack_forget()
+
+    self.calibrateopt_button = tk.Button(self.controls, text = "Calibrate OPT", command = self.calibrate_opt,height = 2)
+    self.calibrateopt_button.pack_forget()
+    
+    self.calibratemag_button = tk.Button(self.controls, text = "Calibrate MAG", command = self.calibrate_mag,height = 2)
+    self.calibratemag_button.pack_forget()
 
-    logscreen_button = tk.Button(self.controls, text="Log", command=self.open_log, height=4)
-    logscreen_button.pack(side="bottom", fill="both")
+    self.clear_button = tk.Button(self.controls, text="Clear graph", command=self.graph.clear, height=2)
+    self.clear_button.pack_forget()
+
+    self.logscreen_button = tk.Button(self.controls, text="Log", command=self.open_log, height=2)
+    self.logscreen_button.pack_forget()
+
+    self.menu_button = tk.Button(self.controls, text="Menu", command=self.menu, height=2)
+    self.menu_button.pack(side="bottom", fill="both")
+    
+    self.menu_back_button = tk.Button(self.controls, text="Back", command=self.back, height=2)
+    self.menu_back_button.pack_forget()
 
   def update(self):
     if not self.root.winfo_exists():
@@ -87,8 +117,17 @@ class MainWindow(tk.Frame):
         opt_positions.append(data[0:2])
         self.opt_dro_val_sums += data
         self.opt_dro_val_count += 1
+    
+    mag_positions = []
+    while self.mag_sensor.queue.qsize() > 0:
+      name, data = self.mag_sensor.queue.get()
+      if name == "data":
+        mag_positions.append(data[0:2])
+        self.mag_dro_val_sums += data
+        self.mag_dro_val_count += 1
+    
     # graph shows all values as a line
-    self.graph.update([ac_positions, opt_positions])
+    self.graph.update([ac_positions, opt_positions, mag_positions])
 
     # update status color
     if self.ac_sensor.dummyActive:
@@ -106,6 +145,14 @@ class MainWindow(tk.Frame):
     else:
       self.opt_label.config(fg="black", bg="yellow")
 
+    if not self.mag_sensor.success:
+     self.mag_label.config(fg="white", bg="red")
+    elif len(mag_positions) > 0:
+     self.mag_label.config(fg="white", bg="green")
+    else:
+     self.mag_label.config(fg="black", bg="yellow")
+
+
     # readouts will only be updated so often
     if self.controlsUpdateTime + 0.4 < time.time():
       self.controlsUpdateTime = time.time()
@@ -120,7 +167,7 @@ class MainWindow(tk.Frame):
       self.ac_dro_t1.set("t1: {:.3f} ms".format(self.ac_dro_val_sums[2]/1000))
       self.ac_dro_t2.set("t2: {:.3f} ms".format(self.ac_dro_val_sums[3]/1000))
 
-      self.ac_dro_val_sums.fill(0)
+      self.ac_dro_val_sums.fill(0)    
       self.ac_dro_val_count = 0
 
       if self.opt_dro_val_count > 0:
@@ -130,13 +177,23 @@ class MainWindow(tk.Frame):
 
       self.opt_dro_x.set("X: {:.1f} mm".format(self.opt_dro_val_sums[0]))
       self.opt_dro_y.set("Y: {:.1f} mm".format(self.opt_dro_val_sums[1]))
-      self.opt_dro_offset.set("offset: {:.1f} %".format(self.opt_dro_val_sums[2]*100))
-      self.opt_dro_size.set("size: {:.1f} %".format(self.opt_dro_val_sums[3]*100))
+      self.opt_dro_offset.set("X Offset: {:.1f} %".format(self.opt_dro_val_sums[2]*100))
+      self.opt_dro_size.set("Y Offset: {:.1f} %".format(self.opt_dro_val_sums[3]*100))
 
       self.opt_dro_val_sums.fill(0)
       self.opt_dro_val_count = 0
 
-    if self.popup_window:
+      if self.mag_dro_val_count > 0:
+        self.mag_dro_val_sums /= self.mag_dro_val_count
+      else:
+        self.mag_dro_val_sums.fill(0)
+
+      self.mag_dro_x.set("X: {:.1f} mm".format(self.mag_dro_val_sums[0]))
+      self.mag_dro_y.set("Y: {:.1f} mm".format(self.mag_dro_val_sums[1]))
+      #self.mag_dro_offset_x.set("X Offset: {:.1f} %".format(self.mag_dro_val_sums[2]*100))
+      #self.mag_dro_offset_y.set("Y Offset: {:.1f} %".format(self.mag_dro_val_sums[3]*100))
+
+    if self.popup_window: # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!~~~~~~~+++*
       self.popup_window.update()
     
     if self.log_window:
@@ -144,6 +201,21 @@ class MainWindow(tk.Frame):
 
     self.root.after(30, self.update)
 
+  def calibrate_submenu(self):
+    
+    self.calibrate_submenu_button.pack_forget()
+    self.menu_button.pack_forget()
+    self.clear_button.pack_forget()
+    self.logscreen_button.pack_forget()
+    self.menu_back_button.pack(side="bottom", fill="both")
+    self.calibratemag_button.pack(side="bottom", fill="both")
+    self.calibrateopt_button.pack(side="bottom", fill="both")
+    self.calibrateac_button.pack(side="bottom", fill="both")
+    self.calibrate_all_button.pack(side="bottom", fill="both")
+  
+  def calibrate_all(self):
+    pass
+  
   def calibrate_ac(self):
     self.ac_sensor.start_calibration()
     if not self.popup_window or not self.pu_root.winfo_exists():
@@ -160,6 +232,25 @@ class MainWindow(tk.Frame):
       self.popup_window = CalibrationPopUp(self.pu_root, self.ac_sensor.calibration_state, self.conf)
       self.popup_window.pack(side="top", fill="both", expand=True)
 
+  def calibrate_opt(self):
+    pass
+  
+  def calibrate_mag(self): ###
+    self.mag_sensor.start_calibration()
+    if not self.popup_window or not self.pu_root.winfo_exists():
+      # create new window
+      self.pu_root = tk.Toplevel(self.root)
+      self.pu_root.wm_transient(self.root)
+      self.pu_root.wm_title("Magnetic Sensor Calibration")
+      # make it centered
+      x = (self.pu_root.winfo_screenwidth()  - 500) / 2
+      y = (self.pu_root.winfo_screenheight() - 200) / 2
+      self.pu_root.geometry(f'500x200+{int(x)}+{int(y)}')
+      # deactivate mainWindow
+      self.pu_root.grab_set()
+      self.popup_window = CalibrationPopUp(self.pu_root, self.mag_sensor.calibration_state, self.conf)
+      self.popup_window.pack(side="top", fill="both", expand=True)
+
   def open_log(self):
     #create new window
     self.log_root = tk.Toplevel(self.root)
@@ -174,3 +265,23 @@ class MainWindow(tk.Frame):
     self.log_window = LogScreen(self.log_root)
     self.log_window.pack(side="top", fill="both", expand=True)
 
+  
+  # Menu Button
+  def menu(self):
+    self.menu_back_button.pack(side="bottom", fill="both")
+    self.calibrate_submenu_button.pack(side="bottom", fill="both")
+    self.clear_button.pack(side="bottom", fill="both")
+    self.logscreen_button.pack(side="bottom", fill="both")
+    self.menu_button.pack_forget()
+
+  #Back Button
+  def back(self):
+    self.calibratemag_button.pack_forget()
+    self.calibrateopt_button.pack_forget()
+    self.calibrateac_button.pack_forget()
+    self.calibrate_all_button.pack_forget()
+    self.clear_button.pack_forget()
+    self.logscreen_button.pack_forget()
+    self.calibrate_submenu_button.pack_forget()
+    self.menu_button.pack(side="bottom", fill="both")
+    self.menu_back_button.pack_forget()

+ 0 - 0
raspberry-pi/gui/popup.py


+ 0 - 0
raspberry-pi/logHandler.py


+ 7 - 5
raspberry-pi/main.py

@@ -2,6 +2,7 @@
 
 from sensors.acousticSensor import AcousticSensor
 from sensors.opticalSensor import OpticalSensor
+from sensors.magneticSensor import MagneticSensor
 from gui.mainWindow import MainWindow
 
 import configparser
@@ -11,19 +12,22 @@ import logHandler
 
 conf = configparser.ConfigParser()
 conf.read('config.ini')
+print(conf.sections())
 
 def main():
   log_handler = logHandler.get_log_handler(int(conf['gui']['log_lines']))
   ac_sensor = AcousticSensor(conf)
   opt_sensor = OpticalSensor(conf)
+  mag_sensor = MagneticSensor(conf)
 
   try:
     ac_sensor.start()
     opt_sensor.start()
+    mag_sensor.start()
     root = tk.Tk()
     root.title("Tracking System")
     root.attributes('-fullscreen', conf['gui']['fullscreen'] == "yes")
-    view = MainWindow(root, conf, ac_sensor, opt_sensor)
+    view = MainWindow(root, conf, ac_sensor, opt_sensor, mag_sensor)
     view.pack(side="top", fill="both", expand=True)
     view.update()
     root.mainloop()
@@ -36,8 +40,6 @@ def main():
   finally:
     ac_sensor.stop()
     opt_sensor.stop()
+    mag_sensor.stop()
 
-main()
-
-
-
+main()

+ 0 - 0
raspberry-pi/markers.png


+ 0 - 0
raspberry-pi/requirements.txt


+ 1 - 0
raspberry-pi/sensors/acousticSensor.py

@@ -30,6 +30,7 @@ class AcousticSensor:
     self.sonic_speed            = float(conf["ac_sensor"]["sonicspeed"])
     self.overhead_left          = float(conf["ac_sensor"]["overhead_left"])
     self.overhead_right         = float(conf["ac_sensor"]["overhead_right"])
+    self.check = 1
 
     self.log_handler = logHandler.get_log_handler()
 

+ 0 - 0
raspberry-pi/sensors/calibration.py


+ 21 - 0
raspberry-pi/sensors/camera_calibration-master/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Abhishek Padalkar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 18 - 0
raspberry-pi/sensors/camera_calibration-master/README.md

@@ -0,0 +1,18 @@
+# Calibrate camera with arUco markers using opencv and python. 
+(Code developed and tested on opencv 3.3.1)
+
+
+# camera_calibration
+Code and resources for camera calibration using arUco markers and opencv 
+
+1. Print the aruco marker board provided. (You can generate your own board, see the code "camera_calibration.py")
+2. Take around 50 images of the printed board pasted on a flat card-board, from different angles. Use Use data_generation node for this.
+3. Set path to store images first.
+(See sample images of arUco board in aruco_data folder)
+
+## Calibrating camera
+1. Measure length of the side of individual marker and spacing between two marker.
+2. Input above data (length and spacing) in "camera_calibration.py".
+3. Set path to stored images of aruco marker.
+4. Set calibrate_camera flag to true.
+

BIN
raspberry-pi/sensors/camera_calibration-master/aruco_marker_board.pdf


+ 115 - 0
raspberry-pi/sensors/camera_calibration-master/camera_calibration.py

@@ -0,0 +1,115 @@
+"""
+This code assumes that images used for calibration are of the same arUco marker board provided with code
+
+"""
+
+import cv2
+from cv2 import aruco
+import yaml
+import numpy as np
+from pathlib import Path
+from tqdm import tqdm
+
+# root directory of repo for relative path specification.
+root = Path(__file__).parent.absolute()
+
+# Set this flsg True for calibrating camera and False for validating results real time
+calibrate_camera = True
+
+# Set path to the images
+calib_imgs_path = root.joinpath("aruco_data")
+
+# For validating results, show aruco board to camera.
+aruco_dict = aruco.getPredefinedDictionary( aruco.DICT_6X6_1000 ) # !! Hier Format unserer Marker eingeben
+
+#Provide length of the marker's side
+markerLength = 3.75  # Here, measurement unit is centimetre.
+
+# Provide separation between markers
+markerSeparation = 0.5   # Here, measurement unit is centimetre.
+
+# create arUco board
+board = aruco.GridBoard_create(4, 5, markerLength, markerSeparation, aruco_dict) # !! Das GridBoard könnte man aus opticalSensor.py imortieren? oder einfach nochmal kreieren
+
+'''uncomment following block to draw and show the board'''
+#img = board.draw((864,1080))
+#cv2.imshow("aruco", img)
+
+arucoParams = aruco.DetectorParameters_create()
+
+if calibrate_camera == True:
+    img_list = []
+    calib_fnms = calib_imgs_path.glob('*.jpg') # auf png bzgl. unserer Marker
+    print('Using ...', end='')
+    for idx, fn in enumerate(calib_fnms):
+        print(idx, '', end='')
+        img = cv2.imread( str(root.joinpath(fn) ))
+        img_list.append( img )
+        h, w, c = img.shape
+    print('Calibration images')
+
+    counter, corners_list, id_list = [], [], []
+    first = True
+    for im in tqdm(img_list):
+        img_gray = cv2.cvtColor(im,cv2.COLOR_RGB2GRAY)
+        corners, ids, rejectedImgPoints = aruco.detectMarkers(img_gray, aruco_dict, parameters=arucoParams)
+        if first == True:
+            corners_list = corners
+            id_list = ids
+            first = False
+        else:
+            corners_list = np.vstack((corners_list, corners))
+            id_list = np.vstack((id_list,ids))
+        counter.append(len(ids))
+    print('Found {} unique markers'.format(np.unique(ids)))
+
+    counter = np.array(counter)
+    print ("Calibrating camera .... Please wait...")
+    #mat = np.zeros((3,3), float)
+    ret, mtx, dist, rvecs, tvecs = aruco.calibrateCameraAruco(corners_list, id_list, counter, board, img_gray.shape, None, None )
+
+    print("Camera matrix is \n", mtx, "\n And is stored in calibration.yaml file along with distortion coefficients : \n", dist)
+    data = {'camera_matrix': np.asarray(mtx).tolist(), 'dist_coeff': np.asarray(dist).tolist()}
+    with open("calibration.yaml", "w") as f: # !!!
+        yaml.dump(data, f)
+
+else:
+    camera = cv2.VideoCapture(0)
+    ret, img = camera.read()
+
+    with open('calibration.yaml') as f: # !!!
+        loadeddict = yaml.load(f)
+    mtx = loadeddict.get('camera_matrix')
+    dist = loadeddict.get('dist_coeff')
+    mtx = np.array(mtx)
+    dist = np.array(dist)
+
+    ret, img = camera.read()
+    img_gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
+    h,  w = img_gray.shape[:2]
+    newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),1,(w,h))
+
+    pose_r, pose_t = [], []
+    while True:
+        ret, img = camera.read()
+        img_aruco = img
+        im_gray = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY)
+        h,  w = im_gray.shape[:2]
+        dst = cv2.undistort(im_gray, mtx, dist, None, newcameramtx)
+        corners, ids, rejectedImgPoints = aruco.detectMarkers(dst, aruco_dict, parameters=arucoParams)
+        #cv2.imshow("original", img_gray)
+        if corners == None:
+            print ("pass")
+        else:
+
+            ret, rvec, tvec = aruco.estimatePoseBoard(corners, ids, board, newcameramtx, dist) # For a board
+            print ("Rotation ", rvec, "Translation", tvec)
+            if ret != 0:
+                img_aruco = aruco.drawDetectedMarkers(img, corners, ids, (0,255,0))
+                img_aruco = aruco.drawAxis(img_aruco, newcameramtx, dist, rvec, tvec, 10)    # axis length 100 can be changed according to your requirement
+
+            if cv2.waitKey(0) & 0xFF == ord('q'):
+                break;
+        cv2.imshow("World co-ordinate frame axes", img_aruco)
+
+cv2.destroyAllWindows()

+ 28 - 0
raspberry-pi/sensors/camera_calibration-master/data_generation.py

@@ -0,0 +1,28 @@
+'''This script is for generating data
+1. Provide desired path to store images.
+2. Press 'c' to capture image and display it.
+3. Press any button to continue.
+4. Press 'q' to quit.
+'''
+
+import cv2
+
+camera = cv2.VideoCapture(0)
+ret, img = camera.read()
+
+
+#path = "/home/abhishek/stuff/object_detection/explore/aruco_data/" # !!
+count = 0
+while True:
+    name = path + str(count)+".jpg"
+    ret, img = camera.read()
+    cv2.imshow("img", img)
+
+
+    if cv2.waitKey(20) & 0xFF == ord('c'):
+        cv2.imwrite(name, img)
+        cv2.imshow("img", img)
+        count += 1
+        if cv2.waitKey(0) & 0xFF == ord('q'):
+
+            break;

+ 11 - 0
raspberry-pi/sensors/camera_calibration-master/importante.txt

@@ -0,0 +1,11 @@
+### bzgl.Optische Kalibrierung ###
+
+-   da es schon funktioniert hat, müssen (nur) einige Parameter angepasst werden bzw. der Code so umgeschrieben werden, sodass die distortion coefficients, sowie
+    die camera matrix, nicht in einer .yaml, sondern in der config.ini gespeichert werden
+-   dafür den ConfigParser verwenden
+    Links:  https://docs.python.org/3/library/configparser.html
+            https://wiki.python.org/moin/ConfigParserExamples
+            https://learntutorials.net/de/python/topic/9186/configparser
+-   Wie es genau funktioniert steht in den Comments beider .py-Dateien
+-   Wie man es benutzt, steht in der README.md
+-   (Wenn möglich) data_generation.py & camera_calibration.py als eine datei (nicht pflicht)

BIN
raspberry-pi/sensors/camera_calibration-master/markers.png


+ 9 - 9
raspberry-pi/sensors/connection.py

@@ -124,17 +124,17 @@ class ArduinoSlave(SerialConnection):
   
   def getAccelValues(self):
     return (
-      int(self.sensorData[5]) / 1000,
-      int(self.sensorData[6]) / 1000,
-      int(self.sensorData[7]) / 1000
+      int(self.sensorData[8]) / 1000,
+      int(self.sensorData[9]) / 1000,
+      int(self.sensorData[10]) / 1000,
     )
   
-  def getGyroValues(self):
-    return (
-      int(self.sensorData[8])  / 1000,
-      int(self.sensorData[9])  / 1000,
-      int(self.sensorData[10]) / 1000
-    )
+  #def getGyroValues(self):
+    #return (
+    #  int(self.sensorData[11])  / 1000, #
+    #  int(self.sensorData[12])  / 1000, #
+    #  int(self.sensorData[13]) / 1000  #
+    #)
   
   def getTemperature(self): # in °C
     return int(self.sensorData[11]) / 1000

+ 97 - 8
raspberry-pi/sensors/magneticSensor.py

@@ -1,24 +1,113 @@
+from distutils.command.config import config
+import queue
+import statistics
+from struct import calcsize
+import numpy as np
 import time
-
+from configparser import ConfigParser
+from sensors.calibration import CalibrationStateMashine
 from sensors.connection import globalArduinoSlave
+import logHandler
 
 conn = globalArduinoSlave()
 
 
 class MagneticSensor:
-  def __init__(self):
-    pass
-
+  def __init__(self, conf):
+    self.conf = conf
+    self.queue = queue.Queue()
+    self.calibration_state = CalibrationStateMashine()
+    self.success = False
+    self.offset_x = float(conf["mag_sensor"]["off_x"])
+    self.offset_y = float(conf["mag_sensor"]["off_y"])
+    self.scale_x = float(conf["mag_sensor"]["scale_x"])
+    self.scale_y = float(conf["mag_sensor"]["scale_y"])
+    self.max_x = float(conf["mag_sensor"]["max_x"])
+    self.max_y = float(conf["mag_sensor"]["max_y"])
+    self.log_handler = logHandler.get_log_handler()
+  
   def start(self):
     if not conn.isConnected():
       conn.open()
+    self.success = True
     conn.addRecvCallback(self._readCb)
 
   def _readCb(self, raw):
-    print("mag: ", conn.getMagneticField())
+    value = conn.getMagneticField()
+    if value[0] >= 0 and value[1] >= 0:
+      self.calibrate(value)
+      position = self.calculate_position(value)
+      if position != None:
+        self.pass_to_gui(position + value)
+  
+  def start_calibration(self): ###
+    self.calibration_state.reset_state()
+    self.time_vals = [[],[],[]]
+    self.calibration_state.next_state()
+  
+  def calibrate(self, value): ### öffnet erstmal popup :) # how?
+     if self.calibration_state.get_state() == self.calibration_state.ACCUMULATING_1:
+      self.time_vals[0].append(value[0])
+      self.time_vals[1].append(value[1])
+      self.time_vals[2].append(value[2])
+      self.calibration_state.progress = len(self.time_vals[0]) / 2
+      if len(self.time_vals[0]) >= 100:
+        self.cal_values["front"][0]  = statistics.mean(self.time_vals[0])
+        self.cal_values["front"][1] = statistics.mean(self.time_vals[1])
+        self.cal_values["front"][2] = statistics.mean(self.time_vals[2])
+        self.time_vals = [[],[],[]]
+        self.calibration_state.next_state() # signal gui to get next position
+
+      elif self.calibration_state.get_state() == self.calibration_state.ACCUMULATING_2:
+        self.time_vals[0].append(value[0])
+        self.time_vals[1].append(value[1])
+        self.time_vals[2].append(value[2])
+        self.calibration_state.progress = 50 + len(self.time_vals[0]) / 2
+        if len(self.time_vals[0]) >= 100:
+          self.cal_values["back"][0] = statistics.mean(self.time_vals[0])
+          self.cal_values["back"][1] = statistics.mean(self.time_vals[1])
+          self.cal_values["back"][2] = statistics.mean(self.time_vals[2])
+        
+          # all values have been captured
+          
+          # Hard iron distortion & Soft iron distortion 
+          self.offset_x = (self.cal_values["back"][0] - self.cal_values["front"][0]) / 2
+          self.offset_y = (self.cal_values["back"][1] - self.cal_values["front"][1]) / 2
+
+          avg_delta_x = (self.cal_values["back"][0] - self.cal_values["front"][0]) / 2
+          avg_delta_y = (self.cal_values["back"][1] - self.cal_values["front"][1]) / 2
+          avg_delta_z = (self.cal_values["back"][2] - self.cal_values["front"][2]) / 2
+
+          avg_delta = (avg_delta_x + avg_delta_y + avg_delta_z) / 3
 
-  def calibrate(self, x, y):
-    pass
+          self.scale_x = avg_delta / avg_delta_x
+          self.scale_y = avg_delta / avg_delta_y
+          self.scale_z = avg_delta / avg_delta_z
+
+          # max values for placeholder algorithm
+          self.max_x = (self.cal_values["back"][0] - self.offset_x) * self.scale_x
+          self.max_y = (self.cal_values["back"][1] - self.offset_y) * self.scale_y
+        
+          self.calibration_state.next_state()
+
+          return self.offset_x, self.offset_y, self.scale_x, self.scale_y, self.max_x, self.max_y
 
   def read(self):
-    return conn.getMagneticField()
+    value = conn.getMagneticField()
+    return value
+  
+  def calculate_position(self): ###
+    corrected_x = (conn.getMagneticField[0] - self.offset_x) * self.scale_x
+    corrected_y = (conn.getMagneticField[1] - self.offset_y) * self.scale_y
+    # placeholder algorithm (to see if the sensor even works)
+    x = (corrected_x * 400) / self.max_x
+    y = (corrected_y * 400) / self.max_y
+    return (x, y)
+
+  def stop(self):
+    self.log_handler.log_and_print("stop magnetic sensor")
+    self.success = False
+    conn.close()
+
+  def pass_to_gui(self, data):
+    self.queue.put(("data", data))

+ 0 - 0
raspberry-pi/sensors/opticalSensor.py


+ 12 - 0
raspberry-pi/sensors/workspace.code-workspace

@@ -0,0 +1,12 @@
+{
+	"folders": [
+		{
+			"path": "../.."
+		},
+		{
+			"name": "arduino",
+			"path": "../../arduino"
+		}
+	],
+	"settings": {}
+}

+ 3 - 0
start.sh

@@ -0,0 +1,3 @@
+#!/usr/bin/bash
+cd ./raspberry-pi
+python3 main.py

+ 0 - 0
ultrasound-tests.ods