diff --git a/usermods/mpu6050_imu/readme.md b/usermods/mpu6050_imu/readme.md
new file mode 100644
index 0000000..adb19ef
--- /dev/null
+++ b/usermods/mpu6050_imu/readme.md
@@ -0,0 +1,94 @@
+# MPU-6050 Six-Axis (Gyro + Accelerometer) Driver
+
+This usermod-v2 modification allows the connection of a MPU-6050 IMU sensor to
+allow for effects that are controlled by the orientation or motion of the WLED Device.
+
+The MPU6050 has a built in "Digital Motion Processor" which does a lot of the heavy
+lifting in integrating the gyro and accel measurements to get potentially more
+useful gravity vector and orientation output.
+
+It is pretty straightforward to comment out some of the variables being read off the device if they're not needed to save CPU/Mem/Bandwidth.
+
+_Story:_
+
+As a memento to a long trip I was on, I built an icosahedron globe. I put lights inside to indicate cities I travelled to.
+
+I wanted to integrate an IMU to allow either on-board, or off-board effects that would
+react to the globes orientation. See the blog post on building it or a video demo .
+
+## Adding Dependencies
+
+I2Cdev and MPU6050 must be installed.
+
+To install them, add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file.
+
+You also need to change lib_compat_mode from strict to soft in platformio.ini (This ignores that I2Cdevlib-MPU6050 doesn't list platform compatibility)
+
+For example:
+
+```
+lib_compat_mode = soft
+lib_deps =
+ FastLED@3.3.2
+ NeoPixelBus@2.5.7
+ ESPAsyncTCP@1.2.0
+ ESPAsyncUDP@697c75a025
+ AsyncTCP@1.0.3
+ Esp Async WebServer@1.2.0
+ IRremoteESP8266@2.7.3
+ I2Cdevlib-MPU6050@fbde122cc5
+```
+
+## Wiring
+
+The connections needed to the MPU6050 are as follows:
+```
+ VCC VU (5V USB) Not available on all boards so use 3.3V if needed.
+ GND G Ground
+ SCL D1 (GPIO05) I2C clock
+ SDA D2 (GPIO04) I2C data
+ XDA not connected
+ XCL not connected
+ AD0 not connected
+ INT D8 (GPIO15) Interrupt pin
+```
+
+You could probably modify the code not to need an interrupt, but I used the
+setup directly from the example.
+
+## JSON API
+
+This code adds:
+```json
+"u":{
+ "IMU":{
+ "Quat": [w, x, y, z],
+ "Euler": [psi, theta, phi],
+ "Gyro": [x, y, z],
+ "Accel": [x, y, z],
+ "RealAccel": [x, y, z],
+ "WorldAccel": [x, y, z],
+ "Gravity": [x, y, z],
+ "Orientation": [yaw, pitch, roll]
+ }
+}
+```
+to the info object
+
+## Usermod installation
+
+1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory.
+2. Register the usermod by adding `#include "usermod_mpu6050_imu.h.h"` in the top and `registerUsermod(new MPU6050Driver());` in the bottom of `usermods_list.cpp`.
+
+Example **usermods_list.cpp**:
+
+```cpp
+#include "wled.h"
+
+#include "usermod_mpu6050_imu.h"
+
+void registerUsermods()
+{
+ usermods.add(new MPU6050Driver());
+}
+```
diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h
new file mode 100644
index 0000000..965ab41
--- /dev/null
+++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h
@@ -0,0 +1,274 @@
+#pragma once
+
+#include "wled.h"
+
+/* This driver reads quaternion data from the MPU6060 and adds it to the JSON
+ This example is adapted from:
+ https://github.com/jrowberg/i2cdevlib/tree/master/Arduino/MPU6050/examples/MPU6050_DMP6_ESPWiFi
+
+ Tested with a d1 mini esp-12f
+
+ GY-521 NodeMCU
+ MPU6050 devkit 1.0
+ board Lolin Description
+ ======= ========== ====================================================
+ VCC VU (5V USB) Not available on all boards so use 3.3V if needed.
+ GND G Ground
+ SCL D1 (GPIO05) I2C clock
+ SDA D2 (GPIO04) I2C data
+ XDA not connected
+ XCL not connected
+ AD0 not connected
+ INT D8 (GPIO15) Interrupt pin
+
+ Using usermod:
+ 1. Copy the usermod into the sketch folder (same folder as wled00.ino)
+ 2. Register the usermod by adding #include "usermod_filename.h" in the top and registerUsermod(new MyUsermodClass()) in the bottom of usermods_list.cpp
+ 3. I2Cdev and MPU6050 must be installed as libraries, or else the .cpp/.h file
+ for both classes must be in the include path of your project. To install the
+ libraries add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file.
+ 4. You also need to change lib_compat_mode from strict to soft in platformio.ini (This ignores that I2Cdevlib-MPU6050 doesn't list platform compatibility)
+ 5. Wire up the MPU6050 as detailed above.
+*/
+
+#include "I2Cdev.h"
+
+#include "MPU6050_6Axis_MotionApps20.h"
+//#include "MPU6050.h" // not necessary if using MotionApps include file
+
+// Arduino Wire library is required if I2Cdev I2CDEV_ARDUINO_WIRE implementation
+// is used in I2Cdev.h
+#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
+ #include "Wire.h"
+#endif
+
+// ================================================================
+// === INTERRUPT DETECTION ROUTINE ===
+// ================================================================
+
+volatile bool mpuInterrupt = false; // indicates whether MPU interrupt pin has gone high
+void IRAM_ATTR dmpDataReady() {
+ mpuInterrupt = true;
+}
+
+
+class MPU6050Driver : public Usermod {
+ private:
+ MPU6050 mpu;
+
+ // MPU control/status vars
+ bool dmpReady = false; // set true if DMP init was successful
+ uint8_t mpuIntStatus; // holds actual interrupt status byte from MPU
+ uint8_t devStatus; // return status after each device operation (0 = success, !0 = error)
+ uint16_t packetSize; // expected DMP packet size (default is 42 bytes)
+ uint16_t fifoCount; // count of all bytes currently in FIFO
+ uint8_t fifoBuffer[64]; // FIFO storage buffer
+
+ //NOTE: some of these can be removed to save memory, processing time
+ // if the measurement isn't needed
+ Quaternion qat; // [w, x, y, z] quaternion container
+ float euler[3]; // [psi, theta, phi] Euler angle container
+ float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container
+ VectorInt16 aa; // [x, y, z] accel sensor measurements
+ VectorInt16 gy; // [x, y, z] gyro sensor measurements
+ VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements
+ VectorInt16 aaWorld; // [x, y, z] world-frame accel sensor measurements
+ VectorFloat gravity; // [x, y, z] gravity vector
+
+ static const int INTERRUPT_PIN = 15; // use pin 15 on ESP8266
+
+ public:
+ //Functions called by WLED
+
+ /*
+ * setup() is called once at boot. WiFi is not yet connected at this point.
+ */
+ void setup() {
+ // join I2C bus (I2Cdev library doesn't do this automatically)
+ #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE
+ Wire.begin();
+ Wire.setClock(400000); // 400kHz I2C clock. Comment this line if having compilation difficulties
+ #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE
+ Fastwire::setup(400, true);
+ #endif
+
+ // initialize device
+ Serial.println(F("Initializing I2C devices..."));
+ mpu.initialize();
+ pinMode(INTERRUPT_PIN, INPUT);
+
+ // verify connection
+ Serial.println(F("Testing device connections..."));
+ Serial.println(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
+
+ // load and configure the DMP
+ Serial.println(F("Initializing DMP..."));
+ devStatus = mpu.dmpInitialize();
+
+ // supply your own gyro offsets here, scaled for min sensitivity
+ mpu.setXGyroOffset(220);
+ mpu.setYGyroOffset(76);
+ mpu.setZGyroOffset(-85);
+ mpu.setZAccelOffset(1788); // 1688 factory default for my test chip
+
+ // make sure it worked (returns 0 if so)
+ if (devStatus == 0) {
+ // turn on the DMP, now that it's ready
+ Serial.println(F("Enabling DMP..."));
+ mpu.setDMPEnabled(true);
+
+ // enable Arduino interrupt detection
+ Serial.println(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
+ attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
+ mpuIntStatus = mpu.getIntStatus();
+
+ // set our DMP Ready flag so the main loop() function knows it's okay to use it
+ Serial.println(F("DMP ready! Waiting for first interrupt..."));
+ dmpReady = true;
+
+ // get expected DMP packet size for later comparison
+ packetSize = mpu.dmpGetFIFOPacketSize();
+ } else {
+ // ERROR!
+ // 1 = initial memory load failed
+ // 2 = DMP configuration updates failed
+ // (if it's going to break, usually the code will be 1)
+ Serial.print(F("DMP Initialization failed (code "));
+ Serial.print(devStatus);
+ Serial.println(F(")"));
+ }
+ }
+
+ /*
+ * connected() is called every time the WiFi is (re)connected
+ * Use it to initialize network interfaces
+ */
+ void connected() {
+ //Serial.println("Connected to WiFi!");
+ }
+
+
+ /*
+ * loop() is called continuously. Here you can check for events, read sensors, etc.
+ */
+ void loop() {
+ // if programming failed, don't try to do anything
+ if (!dmpReady) return;
+
+ // wait for MPU interrupt or extra packet(s) available
+ if (!mpuInterrupt && fifoCount < packetSize) return;
+
+ // reset interrupt flag and get INT_STATUS byte
+ mpuInterrupt = false;
+ mpuIntStatus = mpu.getIntStatus();
+
+ // get current FIFO count
+ fifoCount = mpu.getFIFOCount();
+
+ // check for overflow (this should never happen unless our code is too inefficient)
+ if ((mpuIntStatus & 0x10) || fifoCount == 1024) {
+ // reset so we can continue cleanly
+ mpu.resetFIFO();
+ Serial.println(F("FIFO overflow!"));
+
+ // otherwise, check for DMP data ready interrupt (this should happen frequently)
+ } else if (mpuIntStatus & 0x02) {
+ // wait for correct available data length, should be a VERY short wait
+ while (fifoCount < packetSize) fifoCount = mpu.getFIFOCount();
+
+ // read a packet from FIFO
+ mpu.getFIFOBytes(fifoBuffer, packetSize);
+
+ // track FIFO count here in case there is > 1 packet available
+ // (this lets us immediately read more without waiting for an interrupt)
+ fifoCount -= packetSize;
+
+
+ //NOTE: some of these can be removed to save memory, processing time
+ // if the measurement isn't needed
+ mpu.dmpGetQuaternion(&qat, fifoBuffer);
+ mpu.dmpGetEuler(euler, &qat);
+ mpu.dmpGetGravity(&gravity, &qat);
+ mpu.dmpGetGyro(&gy, fifoBuffer);
+ mpu.dmpGetAccel(&aa, fifoBuffer);
+ mpu.dmpGetLinearAccel(&aaReal, &aa, &gravity);
+ mpu.dmpGetLinearAccelInWorld(&aaWorld, &aaReal, &qat);
+ mpu.dmpGetYawPitchRoll(ypr, &qat, &gravity);
+ }
+ }
+
+
+
+ void addToJsonInfo(JsonObject& root)
+ {
+ int reading = 20;
+ //this code adds "u":{"Light":[20," lux"]} to the info object
+ JsonObject user = root["u"];
+ if (user.isNull()) user = root.createNestedObject("u");
+
+ JsonArray imu_meas = user.createNestedObject("IMU");
+ JsonArray quat_json = imu_meas.createNestedArray("Quat");
+ quat_json.add(qat.w);
+ quat_json.add(qat.x);
+ quat_json.add(qat.y);
+ quat_json.add(qat.z);
+ JsonArray euler_json = imu_meas.createNestedArray("Euler");
+ euler_json.add(euler[0]);
+ euler_json.add(euler[1]);
+ euler_json.add(euler[2]);
+ JsonArray accel_json = imu_meas.createNestedArray("Accel");
+ accel_json.add(aa.x);
+ accel_json.add(aa.y);
+ accel_json.add(aa.z);
+ JsonArray gyro_json = imu_meas.createNestedArray("Gyro");
+ gyro_json.add(gy.x);
+ gyro_json.add(gy.y);
+ gyro_json.add(gy.z);
+ JsonArray world_json = imu_meas.createNestedArray("WorldAccel");
+ world_json.add(aaWorld.x);
+ world_json.add(aaWorld.y);
+ world_json.add(aaWorld.z);
+ JsonArray real_json = imu_meas.createNestedArray("RealAccel");
+ real_json.add(aaReal.x);
+ real_json.add(aaReal.y);
+ real_json.add(aaReal.z);
+ JsonArray grav_json = imu_meas.createNestedArray("Gravity");
+ grav_json.add(gravity.x);
+ grav_json.add(gravity.y);
+ grav_json.add(gravity.z);
+ JsonArray orient_json = imu_meas.createNestedArray("Orientation");
+ orient_json.add(ypr[0]);
+ orient_json.add(ypr[1]);
+ orient_json.add(ypr[2]);
+ }
+
+
+ /*
+ * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
+ * Values in the state object may be modified by connected clients
+ */
+ void addToJsonState(JsonObject& root)
+ {
+ //root["user0"] = userVar0;
+ }
+
+
+ /*
+ * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
+ * Values in the state object may be modified by connected clients
+ */
+ void readFromJsonState(JsonObject& root)
+ {
+ //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!"));
+ }
+
+
+ /*
+ * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
+ */
+ uint16_t getId()
+ {
+ return USERMOD_ID_IMU;
+ }
+
+};
\ No newline at end of file
diff --git a/wled00/const.h b/wled00/const.h
index 2c2ad78..1ec4caf 100644
--- a/wled00/const.h
+++ b/wled00/const.h
@@ -20,6 +20,7 @@
#define USERMOD_ID_TEMPERATURE 3 //Usermod "usermod_temperature.h"
#define USERMOD_ID_FIXNETSERVICES 4 //Usermod "usermod_Fix_unreachable_netservices.h"
#define USERMOD_ID_PIRSWITCH 5 //Usermod "usermod_PIR_sensor_switch.h"
+#define USERMOD_ID_IMU 6 //Usermod "usermod_mpu6050_imu.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot