Merge branch 'games-usermod' into mdev

This commit is contained in:
Ewowi
2022-09-27 15:31:11 +02:00
5 changed files with 299 additions and 218 deletions

View File

@@ -33,7 +33,7 @@
; default_envs = esp8285_H801
; default_envs = d1_mini_5CH_Shojo_PCB
; default_envs = esp32mdev
default_envs = esp32mdevums
default_envs = esp32mdevmax
; default_envs = esp32mdev_PSRAM
; default_envs = esp32s3-mdev
; default_envs = esp8266mdev
@@ -546,7 +546,7 @@ board_build.partitions = ${esp32.default_partitions}
board_build.f_flash = 80000000L
board_build.flash_mode = dio
[env:esp32mdevums]
[env:esp32mdevmax]
board = esp32dev
platform = ${esp32.platform}
upload_speed = 460800 ; or 921600
@@ -561,20 +561,22 @@ build_flags = ${common.build_flags_esp32}
-D WLED_USE_MY_CONFIG ; include custom my_config.h
-D USERMOD_AUDIOREACTIVE
-D UM_AUDIOREACTIVE_USE_NEW_FFT ; use latest (upstream) FFTLib, instead of older library midified by blazoncek. Slightly faster, more accurate, needs 2KB RAM extra
-D USERMOD_DALLASTEMPERATURE
-D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI
-D USERMOD_FOUR_LINE_DISPLAY
-D USERMOD_ROTARY_ENCODER_UI
-D USERMOD_AUTO_SAVE
; -D USERMOD_DALLASTEMPERATURE
; -D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI
; -D USERMOD_FOUR_LINE_DISPLAY
; -D USERMOD_ROTARY_ENCODER_UI
; -D USERMOD_AUTO_SAVE
-D USERMOD_CUSTOMEFFECTS ; WLEDSR usermod
-D USERMOD_WEATHER ; WLEDSR usermod
-D USERMOD_MPU6050_IMU ; gyro/accelero for USERMOD_GAMES (ONLY WORKS IF USERMOD_FOUR_LINE_DISPLAY NOT INCLUDED - I2C SHARING BUG)
-D USERMOD_GAMES ; WLEDSR usermod
; -D WLED_DEBUG ; lots of generic debug messages
; -D SR_DEBUG ; some extra debug messages from audioreactive
lib_deps = ${esp32.lib_deps}
OneWire@~2.3.5 ; used for USERMOD_FOUR_LINE_DISPLAY and USERMOD_GAMES?
olikraus/U8g2 @ ^2.28.8 ; used for USERMOD_FOUR_LINE_DISPLAY
; OneWire@~2.3.5 ; used for USERMOD_FOUR_LINE_DISPLAY and USERMOD_DALLASTEMPERATURE
; olikraus/U8g2 @ ^2.28.8 ; used for USERMOD_FOUR_LINE_DISPLAY
https://github.com/kosme/arduinoFFT#develop @ 1.9.2 ; used for USERMOD_AUDIOREACTIVE
ElectronicCats/MPU6050 @ 0.6.0 ; used for USERMOD_MPU6050_IMU
; monitor_filters = esp32_exception_decoder ; used to show crash details
board_build.partitions = ${esp32.default_partitions}
; board_build.partitions = tools/WLED_ESP32_16MB.csv

View File

@@ -20,9 +20,9 @@ react to the globes orientation. See the blog post on building it <https://www.r
I2Cdev and MPU6050 must be installed.
To install them, add I2Cdevlib-MPU6050@fbde122cc5 to lib_deps in the platformio.ini file.
To install them, add ElectronicCats/MPU6050 @ 0.6.0 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)
<!-- 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:
@@ -36,7 +36,7 @@ lib_deps =
AsyncTCP@1.0.3
Esp Async WebServer@1.2.0
IRremoteESP8266@2.7.3
I2Cdevlib-MPU6050@fbde122cc5
ElectronicCats/MPU6050 @ 0.6.0
```
## Wiring
@@ -77,18 +77,19 @@ 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`.
<!-- 1. Copy the file `usermod_mpu6050_imu.h` to the `wled00` directory. -->
2. Register the usermod by adding `#include "usermod_mpu6050_imu.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"
#ifdef USERMOD_MPU6050_IMU
#include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h"
#endif
void registerUsermods()
{
#ifdef USERMOD_MPU6050_IMU
usermods.add(new MPU6050Driver());
}
#endif
```

View File

@@ -2,6 +2,17 @@
#include "wled.h"
#ifdef WLED_DEBUG
#define DEBUG_PRINT_IMU(x) Serial.print(x)
#define DEBUG_PRINT_IMULN(x) Serial.println(x)
#define DEBUG_PRINT_IMUF(x...) Serial.printf(x)
#else
#define DEBUG_PRINT_IMU(x)
#define DEBUG_PRINT_IMULN(x)
#define DEBUG_PRINT_IMUF(x...)
#endif
/* 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
@@ -26,15 +37,14 @@
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)
libraries add ElectronicCats/MPU6050 @ 0.6.0 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
@@ -56,6 +66,7 @@ class MPU6050Driver : public Usermod {
private:
MPU6050 mpu;
bool enabled = true;
unsigned long lastUMRun = millis();
// MPU control/status vars
bool dmpReady = false; // set true if DMP init was successful
@@ -65,47 +76,61 @@ class MPU6050Driver : public Usermod {
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
public:
// orientation/motion vars
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
float euler[3]; // [psi, theta, phi] Euler angle container
float ypr[3]; // [yaw, pitch, roll] yaw/pitch/roll container and 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() {
DEBUG_PRINT_IMULN("mpu setup");
PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
// 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
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);
Fastwire::setup(400, true);
#endif
// // initialize serial communication
// // (115200 chosen because it is required for Teapot Demo output, but it's
// // really up to you depending on your project)
// Serial.begin(115200);
// while (!Serial); // wait for Leonardo enumeration, others continue immediately
// NOTE: 8MHz or slower host processors, like the Teensy @ 3.3V or Arduino
// Pro Mini running at 3.3V, cannot handle this baud rate reliably due to
// the baud timing being too misaligned with processor ticks. You must use
// 38400 or slower in these cases, or use some kind of external separate
// crystal solution for the UART timer.
// initialize device
DEBUG_PRINTLN(F("Initializing I2C devices..."));
DEBUG_PRINT_IMULN(F("Initializing I2C devices..."));
mpu.initialize();
pinMode(INTERRUPT_PIN, INPUT);
// verify connection
DEBUG_PRINTLN(F("Testing device connections..."));
DEBUG_PRINTLN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
DEBUG_PRINT_IMULN(F("Testing device connections..."));
DEBUG_PRINT_IMULN(mpu.testConnection() ? F("MPU6050 connection successful") : F("MPU6050 connection failed"));
// // wait for ready
// DEBUG_PRINT_IMULN(F("\nSend any character to begin DMP programming and demo: "));
// while (Serial.available() && Serial.read()); // empty buffer
// while (!Serial.available()); // wait for data
// while (Serial.available() && Serial.read()); // empty buffer again
// load and configure the DMP
DEBUG_PRINTLN(F("Initializing DMP..."));
DEBUG_PRINT_IMULN(F("Initializing DMP..."));
devStatus = mpu.dmpInitialize();
// supply your own gyro offsets here, scaled for min sensitivity
@@ -116,77 +141,52 @@ class MPU6050Driver : public Usermod {
// make sure it worked (returns 0 if so)
if (devStatus == 0) {
// turn on the DMP, now that it's ready
DEBUG_PRINTLN(F("Enabling DMP..."));
mpu.setDMPEnabled(true);
// Calibration Time: generate offsets and calibrate our MPU6050
mpu.CalibrateAccel(6);
mpu.CalibrateGyro(6);
#ifdef WLED_DEBUG
mpu.PrintActiveOffsets();
#endif
// turn on the DMP, now that it's ready
DEBUG_PRINT_IMULN(F("Enabling DMP..."));
mpu.setDMPEnabled(true);
// enable Arduino interrupt detection
DEBUG_PRINTLN(F("Enabling interrupt detection (Arduino external interrupt 0)..."));
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), dmpDataReady, RISING);
mpuIntStatus = mpu.getIntStatus();
// enable Arduino interrupt detection
DEBUG_PRINT_IMU(F("Enabling interrupt detection (Arduino external interrupt "));
DEBUG_PRINT_IMU(digitalPinToInterrupt(INTERRUPT_PIN));
DEBUG_PRINT_IMULN(F(")..."));
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
DEBUG_PRINTLN(F("DMP ready! Waiting for first interrupt..."));
dmpReady = true;
// set our DMP Ready flag so the main loop() function knows it's okay to use it
DEBUG_PRINT_IMULN(F("DMP ready! Waiting for first interrupt..."));
dmpReady = true;
// get expected DMP packet size for later comparison
packetSize = mpu.dmpGetFIFOPacketSize();
// 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)
DEBUG_PRINT(F("DMP Initialization failed (code "));
DEBUG_PRINT(devStatus);
DEBUG_PRINTLN(F(")"));
// ERROR!
// 1 = initial memory load failed
// 2 = DMP configuration updates failed
// (if it's going to break, usually the code will be 1)
DEBUG_PRINT_IMU(F("DMP Initialization failed (code "));
DEBUG_PRINT_IMU(devStatus);
DEBUG_PRINT_IMULN(F(")"));
}
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected() {
//DEBUG_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 (!enabled || !dmpReady || strip.isUpdating()) 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();
DEBUG_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;
if (!enabled || (strip.isUpdating() && (millis() - lastUMRun < 2))) return; // be nice, but not too nice
lastUMRun = millis(); // update time keeping
if (!dmpReady) return;
// read a packet from FIFO
if (mpu.dmpGetCurrentFIFOPacket(fifoBuffer)) { // Get the Latest packet
//NOTE: some of these can be removed to save memory, processing time
// if the measurement isn't needed
mpu.dmpGetQuaternion(&qat, fifoBuffer);
@@ -200,25 +200,24 @@ class MPU6050Driver : public Usermod {
}
}
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");
StaticJsonDocument<600> doc; //measured 528
JsonObject imu_meas = doc.createNestedObject("IMU");
#ifdef WLED_DEBUG
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]);
euler_json.add(euler[0] * 180/M_PI);
euler_json.add(euler[1] * 180/M_PI);
euler_json.add(euler[2] * 180/M_PI);
JsonArray accel_json = imu_meas.createNestedArray("Accel");
accel_json.add(aa.x);
accel_json.add(aa.y);
@@ -227,10 +226,6 @@ class MPU6050Driver : public Usermod {
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);
@@ -239,49 +234,54 @@ class MPU6050Driver : public Usermod {
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]);
#endif
JsonArray world_json = imu_meas.createNestedArray("WorldAccel");
world_json.add(aaWorld.x);
world_json.add(aaWorld.y);
world_json.add(aaWorld.z);
JsonArray orient_json = imu_meas.createNestedArray("YPR");
orient_json.add(ypr[0] * 180/M_PI);
orient_json.add(ypr[1] * 180/M_PI);
orient_json.add(ypr[2] * 180/M_PI);
char stringBuffer[300]; // measured 266
serializeJson(imu_meas, stringBuffer);
JsonArray mainObject = user.createNestedArray("IMU");
mainObject.add(stringBuffer);
// Serial.printf("imu_meas %u (%u %u) stringBuffer %u\n", (unsigned int)imu_meas.memoryUsage(), (unsigned int)imu_meas.size(), (unsigned int)imu_meas.nesting(), strlen(stringBuffer));
}
/*
* 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) DEBUG_PRINTLN(F("Don't burn down your garage!"));
//}
// void addToConfig(JsonObject& root)
// {
// JsonObject top = root.createNestedObject("MPU6050");
// top[FPSTR("enabled")] = enabled;
/*
* addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object.
* It will be called by WLED when settings are actually saved (for example, LED settings are saved)
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
*/
// void addToConfig(JsonObject& root)
// {
// JsonObject top = root.createNestedObject("MPU6050_IMU");
// JsonArray pins = top.createNestedArray("pin");
// pins.add(HW_PIN_SCL);
// pins.add(HW_PIN_SDA);
// }
// JsonObject interruptPin = top.createNestedObject(FPSTR("interruptPin"));
// interruptPin["pin"] = interruptPin;
// }
// bool readFromConfig(JsonObject& root)
// {
// JsonObject top = root[FPSTR("MPU6050")];
// bool configComplete = !top.isNull();
// configComplete &= getJsonValue(top[FPSTR("enabled")], enabled);
// configComplete &= getJsonValue(top[FPSTR("interruptPin")]["pin"], interruptPin);
// return configComplete;
// }
/*
* 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;

View File

@@ -1,25 +1,19 @@
/*
Games usermod by ewowi, september 2022
Contains:
- mode_pongGame
- Depending on USERMOD_MPU6050_IMU
- mode_IMUTest (shows IMU values only if WLED_DEBUG)
- class Frame3D and struct Voxel
- mode_3DIMUCube (uses class Frame3D to show a rotating cube, if USERMOD_MPU6050_IMU then IMU used for rotation)
- class GamesUsermod (Add the modes/effects and initiates IMU)
*/
#pragma once
#include "wled.h"
/*
* Usermods allow you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
*
* This is an example for a v2 usermod.
* v2 usermods are class inheritance based and can (but don't have to) implement more functions, each of them is shown in this example.
* Multiple v2 usermods can be added to one compilation easily.
*
* Creating a usermod:
* This file serves as an example. If you want to create a usermod, it is recommended to use usermod_v2_empty.h from the usermods folder as a template.
* Please remember to rename the class and file to a descriptive name.
* You may also use multiple .h and .cpp files.
*
* Using a 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
*/
//inspired by https://noobtuts.com/cpp/2d-pong-game
typedef struct PongBall {
float x;// = SEGMENT.virtualWidth() / 2;
@@ -181,94 +175,170 @@ uint16_t mode_pongGame(void) {
static const char _data_FX_MODE_PONGGAME[] PROGMEM = "🎮 Pong@!;!;!;2d";
//https://howtomechatronics.com/tutorials/arduino/arduino-and-mpu6050-accelerometer-and-gyroscope-tutorial/
#define MPU_ADDR 0x68 // I2C address of the MPU-6050. If AD0 pin is set to HIGH, the I2C address will be 0x69.
int16_t accelerometer_x, accelerometer_y, accelerometer_z; // variables for accelerometer raw data
int16_t gyro_x, gyro_y, gyro_z; // variables for gyro raw data
int16_t temperature; // variables for temperature data
uint16_t mode_gyro(void) {
#ifdef USERMOD_MPU6050_IMU
MPU6050Driver *IMU = nullptr;
uint16_t mode_IMUTest(void) {
SEGMENT.fill(BLACK);
uint8_t y = 0;
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_x+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_y+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (accelerometer_z+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_x+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_y+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (gyro_z+INT16_MAX)/(2*INT16_MAX), y+=2, BLUE);
if (IMU != nullptr) {
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aa.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaReal.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaReal.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaReal.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->gy.x+1024)/(2*1024), y+=1, RED);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->gy.y+1024)/(2*1024), y+=1, RED);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->gy.z+1024)/(2*1024), y+=1, RED);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaWorld.x+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaWorld.y+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->aaWorld.z+INT16_MAX)/(2*INT16_MAX), y+=1, BLUE);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->ypr[0]* 180/M_PI+180)/(2*180), y+=1, RED);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->ypr[1]* 180/M_PI+180)/(2*180), y+=1, RED);
SEGMENT.setPixelColorXY(SEGMENT.virtualWidth() * (IMU->ypr[2]* 180/M_PI+180)/(2*180), y+=1, RED);
}
return FRAMETIME;
}
static const char _data_FX_MODE_GYRO[] PROGMEM = "🎮 Gyro@!;!;!;2d";
static const char _data_FX_MODE_IMUTest[] PROGMEM = "🎮 IMUTest@;;;2d";
#ifndef FLD_PIN_SCL
#define FLD_PIN_SCL i2c_scl
#endif
#ifndef FLD_PIN_SDA
#define FLD_PIN_SDA i2c_sda
#endif
//WLEDSR 3D to 2D mapping
struct Voxel {
float x;
float y;
float z;
uint32_t col;
};
class Frame3D {
private:
std::vector<Voxel> points;
void rotate(float a, float b, float angle, float &x, float &y) {
x = cosf(angle) * a - sinf(angle) * b;
y = sinf(angle) * a + cosf(angle) * b;
}
float yaw;
float pitch;
float roll;
public:
Frame3D(float yaw1, float pitch1, float roll1) {
points.clear();
yaw = yaw1;
pitch = pitch1;
roll = roll1;
}
~Frame3D() {
std::sort(points.begin(), points.end(), [](const Voxel &lhs, const Voxel &rhs) {return lhs.z > rhs.z;});
for(size_t i = 0; i < points.size(); ++i) {
float w = 0.5;//SEGMENT.virtualWidth()/2;
float h = 0.5;//SEGMENT.virtualHeight()/2;
float perspective = SEGMENT.intensity / 64.0;
if(points[i].z > 0) {
float projX, projY;
projX = w+points[i].x/points[i].z*perspective;
projY = h+points[i].y/points[i].z*perspective;
SEGMENT.setPixelColorXY(projX, projY, points[i].col, false); //no aa
}
}
}
void setPixelColorXYZ(Voxel voxel) {
float camx = 0;
float camy = 0;
float camz = -6;
rotate(voxel.x,voxel.z,yaw, voxel.x, voxel.z); // Camera yaw
rotate(voxel.y,voxel.z,pitch, voxel.y, voxel.z); // Camera pitch
rotate(voxel.x,voxel.y,roll, voxel.x, voxel.y); // Camera roll
voxel.x -= camx;
voxel.y -= camy;
voxel.z -= camz;
points.push_back(voxel);
}
void drawLineXYZ(Voxel from, Voxel to, uint32_t col) {
for (float x=MIN(from.x, to.x); x<=MAX(from.x, to.x); x+=.05)
for (float y=MIN(from.y, to.y); y<=MAX(from.y, to.y); y+=.05)
for (float z=MIN(from.z, to.z); z<=MAX(from.z, to.z); z+=.05)
setPixelColorXYZ({x, y, z, col});
}
};
uint16_t mode_3DIMUCube(void) {
SEGMENT.fill(BLACK);
float yaw = 0;
float pitch = 0;
float roll = 0;
#ifdef USERMOD_MPU6050_IMU
if (IMU != nullptr) {
yaw = -IMU->ypr[0];
pitch = IMU->ypr[1];
roll = IMU->ypr[2];
}
#else
//simulate rotation
yaw = (fmod(SEGENV.call, 360)-180) / (180/M_PI); //-180 .. 180
pitch = yaw;
roll = yaw;
#endif
Frame3D frame3D = Frame3D(yaw, pitch, roll);
Voxel leftbottomback = {-1,-1,-1};
Voxel rightbottomback = {1,-1,-1};
Voxel lefttopback = {-1,1,-1};
Voxel righttopback = {1,1,-1};
Voxel leftbottomfront = {-1,-1,1};
Voxel rightbottomfront = {1,-1,1};
Voxel lefttopfront = {-1,1,1};
Voxel righttopfront = {1,1,1};
frame3D.drawLineXYZ(leftbottomback, rightbottomback, SEGMENT.color_from_palette(255/12, false, true, 0));
frame3D.drawLineXYZ(leftbottomback, lefttopback, SEGMENT.color_from_palette(255/12*2, false, true, 0));
frame3D.drawLineXYZ(rightbottomback, righttopback, SEGMENT.color_from_palette(255/12*3, false, true, 0));
frame3D.drawLineXYZ(lefttopback, righttopback, SEGMENT.color_from_palette(255/12*4, false, true, 0));
frame3D.drawLineXYZ(leftbottomfront, leftbottomback, SEGMENT.color_from_palette(255/12*9, false, true, 0));
frame3D.drawLineXYZ(rightbottomfront, rightbottomback, SEGMENT.color_from_palette(255/12*10, false, true, 0));
frame3D.drawLineXYZ(lefttopfront, lefttopback, SEGMENT.color_from_palette(255/12*11, false, true, 0));
frame3D.drawLineXYZ(righttopfront, righttopback, SEGMENT.color_from_palette(255/12*12, false, true, 0));
frame3D.drawLineXYZ(leftbottomfront, rightbottomfront, SEGMENT.color_from_palette(255/12*5, false, true, 0));
frame3D.drawLineXYZ(leftbottomfront, lefttopfront, SEGMENT.color_from_palette(255/12*6, false, true, 0));
frame3D.drawLineXYZ(rightbottomfront, righttopfront, SEGMENT.color_from_palette(255/12*7, false, true, 0));
frame3D.drawLineXYZ(lefttopfront, righttopfront, SEGMENT.color_from_palette(255/12*8, false, true, 0));
return FRAMETIME;
}
static const char _data_FX_MODE_3DIMUCube[] PROGMEM = "🎮 3DIMUCube@,Perspective;!;!;,pal=1,2d"; //random cycle
class GamesUsermod : public Usermod {
private:
bool enabled = true;
int8_t ioPin[5] = {FLD_PIN_SCL, FLD_PIN_SDA, -1, -1, -1}; // I2C pins: SCL, SDA
unsigned long lastUMRun = millis();
public:
//Functions called by WLED
void setup() {
bool isHW;
PinOwner po = PinOwner::UM_Unspecified;
uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl;
uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda;
if (ioPin[0] < 0 || ioPin[1] < 0) {
ioPin[0] = hw_scl;
ioPin[1] = hw_sda;
}
isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda);
if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins
PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } };
if (!pinManager.allocateMultiplePins(pins, 2, po)) { enabled = false; return; }
// PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } };
// if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; }
Wire.begin();
Wire.beginTransmission(MPU_ADDR); // Begins a transmission to the I2C slave (GY-521 board)
Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // set to zero (wakes up the MPU-6050)
Wire.endTransmission(true);
strip.addEffect(255, &mode_pongGame, _data_FX_MODE_PONGGAME);
strip.addEffect(255, &mode_gyro, _data_FX_MODE_GYRO);
#ifdef USERMOD_MPU6050_IMU
IMU = (MPU6050Driver *)usermods.lookup(USERMOD_ID_IMU);
#ifdef WLED_DEBUG
strip.addEffect(255, &mode_IMUTest, _data_FX_MODE_IMUTest);
#endif
#endif
strip.addEffect(255, &mode_3DIMUCube, _data_FX_MODE_3DIMUCube); //works also without IMU
}
/*
* connected() is called every time the WiFi is (re)connected
* Use it to initialize network interfaces
*/
void connected() {
//Serial.println("Connected to WiFi!");
}
void loop() {
if (!enabled || (strip.isUpdating() && (millis() - lastUMRun < 2))) return; // be nice, but not too nice
lastUMRun = millis(); // update time keeping
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40]
Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active.
Wire.requestFrom(MPU_ADDR, 7*2); // request a total of 7*2=14 registers
// "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable
accelerometer_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L)
accelerometer_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L)
accelerometer_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L)
temperature = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L)
gyro_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L)
gyro_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L)
gyro_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L)
}
void addToJsonState(JsonObject& root)

View File

@@ -145,6 +145,9 @@
#include "../usermods/usermod_v2_weather/usermod_v2_weather.h"
#endif
#ifdef USERMOD_MPU6050_IMU
#include "../usermods/mpu6050_imu/usermod_mpu6050_imu.h"
#endif
#ifdef USERMOD_GAMES
#include "../usermods/usermod_v2_games/usermod_v2_games.h"
#endif
@@ -282,6 +285,11 @@ void registerUsermods()
usermods.add(new WeatherUsermod());
#endif
#ifdef USERMOD_MPU6050_IMU
usermods.add(new MPU6050Driver());
#endif
#ifdef USERMOD_GAMES
usermods.add(new GamesUsermod());
#endif