mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-29 03:44:24 +00:00
add remote source code (#12)
Co-authored-by: jyy <jiangyeying@m5stack.com>
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
#include "ui_imu_screen.h"
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "../lvgl_port.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static lv_obj_t *edge_lines[12];
|
||||
static lv_obj_t *cross_lines[2]; // 标识用的对角线
|
||||
|
||||
static lv_point_t edge_points[12][2]; // 12条边,每条2个点
|
||||
static lv_point_t cross_points[2][2]; // 2条对角线,每条2个点
|
||||
|
||||
lv_obj_t *imu_screen;
|
||||
lv_obj_t *cube_container = NULL;
|
||||
lv_obj_t *imu_battery_label;
|
||||
lv_obj_t *imu_channel_info_label;
|
||||
lv_obj_t *imu_id_info_label;
|
||||
lv_obj_t *imu_data_label;
|
||||
|
||||
// 3D cube parameters
|
||||
typedef struct {
|
||||
float x, y, z;
|
||||
} Point3D;
|
||||
|
||||
typedef struct {
|
||||
float x, y;
|
||||
} Point2D;
|
||||
|
||||
// Define the 8 vertices of a cube
|
||||
static Point3D vertices[8] = {{-1, -1, -1}, {1, -1, -1}, {1, 1, -1}, {-1, 1, -1},
|
||||
{-1, -1, 1}, {1, -1, 1}, {1, 1, 1}, {-1, 1, 1}};
|
||||
|
||||
// Define the 12 edges of a cube (indices of vertices connected)
|
||||
static int edges[12][2] = {
|
||||
{0, 1}, {1, 2}, {2, 3}, {3, 0}, // fornt
|
||||
{4, 5}, {5, 6}, {6, 7}, {7, 4}, // behind
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7} // middle connecting line
|
||||
};
|
||||
|
||||
IMU_Angle_t g_imu_angle = {0.0f, 0.0f};
|
||||
|
||||
/**
|
||||
* @brief Creates the IMU screen with all UI elements including a 3D cube visualization
|
||||
*
|
||||
* This function initializes and creates the main IMU screen interface with:
|
||||
* - Title label showing "StackChan :)"
|
||||
* - A cube container for 3D visualization
|
||||
* - 12 cube edge lines forming a 3D cube
|
||||
* - 2 diagonal cross lines for orientation reference
|
||||
* - Battery status label
|
||||
* - Channel information label
|
||||
* - Receiver ID label
|
||||
*
|
||||
* The function handles LVGL locking to ensure thread-safe operations.
|
||||
*/
|
||||
void create_imu_screen()
|
||||
{
|
||||
while (!lvgl_port_lock()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
if (imu_screen == NULL) {
|
||||
imu_screen = lv_obj_create(NULL);
|
||||
}
|
||||
lv_obj_clear_flag(imu_screen, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
// Create title
|
||||
lv_obj_t *label = lv_label_create(imu_screen);
|
||||
lv_label_set_text(label, "StackChan :)");
|
||||
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 10);
|
||||
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
|
||||
|
||||
// create container
|
||||
cube_container = lv_obj_create(imu_screen);
|
||||
lv_obj_set_size(cube_container, 115, 115);
|
||||
lv_obj_align(cube_container, LV_ALIGN_TOP_MID, 0, 35);
|
||||
lv_obj_set_style_bg_opa(cube_container, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(cube_container, 0, 0);
|
||||
lv_obj_set_style_pad_all(cube_container, 0, 0);
|
||||
lv_obj_clear_flag(cube_container, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
// Create 12 cube edges
|
||||
for (int i = 0; i < 12; i++) {
|
||||
edge_lines[i] = lv_line_create(cube_container);
|
||||
lv_obj_set_style_line_width(edge_lines[i], 2, 0);
|
||||
lv_obj_set_style_line_color(edge_lines[i], lv_color_black(), 0);
|
||||
lv_obj_add_flag(edge_lines[i], LV_OBJ_FLAG_FLOATING);
|
||||
|
||||
// Add default coordinate points to avoid empty line segments
|
||||
lv_point_t default_points[2] = {{0, 0}, {0, 0}};
|
||||
lv_line_set_points(edge_lines[i], default_points, 2);
|
||||
}
|
||||
|
||||
// Create 2 lines identifying the diagonals
|
||||
for (int i = 0; i < 2; i++) {
|
||||
cross_lines[i] = lv_line_create(cube_container);
|
||||
lv_obj_set_style_line_width(cross_lines[i], 2, 0);
|
||||
lv_obj_set_style_line_color(cross_lines[i], lv_color_make(0, 255, 255), 0);
|
||||
lv_obj_add_flag(cross_lines[i], LV_OBJ_FLAG_FLOATING);
|
||||
|
||||
// Add default coordinate
|
||||
lv_point_t default_cross_points[2] = {{0, 0}, {0, 0}};
|
||||
lv_line_set_points(cross_lines[i], default_cross_points, 2);
|
||||
}
|
||||
|
||||
// Create other UI elements
|
||||
imu_battery_label = lv_label_create(imu_screen);
|
||||
lv_label_set_text(imu_battery_label, "Bat: 100%");
|
||||
lv_obj_align(imu_battery_label, LV_ALIGN_TOP_LEFT, 10, 160);
|
||||
lv_obj_set_style_text_font(imu_battery_label, &lv_font_montserrat_14, 0);
|
||||
|
||||
imu_channel_info_label = lv_label_create(imu_screen);
|
||||
lv_label_set_text(imu_channel_info_label, "Channel: 1");
|
||||
lv_obj_align(imu_channel_info_label, LV_ALIGN_TOP_LEFT, 10, 180);
|
||||
lv_obj_set_style_text_font(imu_channel_info_label, &lv_font_montserrat_14, 0);
|
||||
|
||||
imu_id_info_label = lv_label_create(imu_screen);
|
||||
lv_label_set_text(imu_id_info_label, "Receiver ID:\n0(broadcast)");
|
||||
lv_obj_align(imu_id_info_label, LV_ALIGN_TOP_LEFT, 10, 200);
|
||||
lv_obj_set_style_text_font(imu_id_info_label, &lv_font_montserrat_14, 0);
|
||||
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the 3D cube visualization based on IMU accelerometer data
|
||||
*
|
||||
* This function takes accelerometer readings (ax, ay, az) and calculates
|
||||
* the pitch and roll angles to rotate a 3D cube representation. It performs:
|
||||
* - Input validation to check for NaN or infinite values
|
||||
* - Calculation of pitch and roll angles using trigonometric functions
|
||||
* - 3D to 2D projection of cube vertices with rotation transformations
|
||||
* - Updates all 12 cube edge lines and 2 diagonal marker lines
|
||||
*
|
||||
* @param ax Accelerometer X-axis reading
|
||||
* @param ay Accelerometer Y-axis reading
|
||||
* @param az Accelerometer Z-axis reading
|
||||
*/
|
||||
void update_imu_cube(float ax, float ay, float az)
|
||||
{
|
||||
// Check if the input value is valid
|
||||
if (isnan(ax) || isnan(ay) || isnan(az) || isinf(ax) || isinf(ay) || isinf(az)) {
|
||||
printf("Invalid IMU data received!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate tilt angle (based on gravitational acceleration)
|
||||
float pitch = atan2(ay, sqrt(ax * ax + az * az));
|
||||
float roll = atan2(ax, sqrt(ay * ay + az * az));
|
||||
|
||||
if (az < 0) {
|
||||
pitch = M_PI - pitch;
|
||||
}
|
||||
|
||||
g_imu_angle.pitch = pitch;
|
||||
g_imu_angle.roll = roll;
|
||||
|
||||
// 3D projection calculation
|
||||
Point2D projected[8]; // Store the 2D points after projection
|
||||
int centerX = lv_obj_get_width(cube_container) / 2;
|
||||
int centerY = lv_obj_get_height(cube_container) / 2;
|
||||
|
||||
float scale = 30.0f;
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
Point3D p = vertices[i];
|
||||
|
||||
// Pitch
|
||||
float y1 = p.y * cos(pitch) - p.z * sin(pitch);
|
||||
float z1 = p.y * sin(pitch) + p.z * cos(pitch);
|
||||
float x1 = p.x;
|
||||
|
||||
// Roll
|
||||
float x2 = x1 * cos(roll) + z1 * sin(roll);
|
||||
float z2 = -x1 * sin(roll) + z1 * cos(roll);
|
||||
float y2 = y1;
|
||||
|
||||
// Orthographic projection
|
||||
projected[i].x = centerX + x2 * scale;
|
||||
projected[i].y = centerY + y2 * scale;
|
||||
}
|
||||
|
||||
// Update 12 cube edges
|
||||
for (int i = 0; i < 12; i++) {
|
||||
edge_points[i][0].x = (int16_t)projected[edges[i][0]].x;
|
||||
edge_points[i][0].y = (int16_t)projected[edges[i][0]].y;
|
||||
edge_points[i][1].x = (int16_t)projected[edges[i][1]].x;
|
||||
edge_points[i][1].y = (int16_t)projected[edges[i][1]].y;
|
||||
lv_line_set_points(edge_lines[i], edge_points[i], 2);
|
||||
}
|
||||
|
||||
// Update 2 diagonal markers
|
||||
cross_points[0][0] = (lv_point_t){(int16_t)projected[0].x, (int16_t)projected[0].y};
|
||||
cross_points[0][1] = (lv_point_t){(int16_t)projected[2].x, (int16_t)projected[2].y};
|
||||
lv_line_set_points(cross_lines[0], cross_points[0], 2);
|
||||
|
||||
cross_points[1][0] = (lv_point_t){(int16_t)projected[1].x, (int16_t)projected[1].y};
|
||||
cross_points[1][1] = (lv_point_t){(int16_t)projected[3].x, (int16_t)projected[3].y};
|
||||
lv_line_set_points(cross_lines[1], cross_points[1], 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the complete IMU screen with sensor data and system information
|
||||
*
|
||||
* This function updates the entire IMU screen with real-time data including:
|
||||
* - Updates the 3D cube visualization via update_imu_cube()
|
||||
* - Battery percentage display
|
||||
* - Communication channel information
|
||||
* - Receiver ID display (handles broadcast case)
|
||||
*
|
||||
* Optimized to only update labels when values have changed using static tracking variables.
|
||||
*
|
||||
* @param ax Accelerometer X-axis reading
|
||||
* @param ay Accelerometer Y-axis reading
|
||||
* @param az Accelerometer Z-axis reading
|
||||
* @param bat Battery level percentage (0-100)
|
||||
* @param id Receiver ID (0 for broadcast)
|
||||
* @param channel ESP-NOW communication channel
|
||||
* @return IMU_Angle_t Current pitch and roll angles calculated from IMU data
|
||||
*/
|
||||
IMU_Angle_t update_imu_screen(float ax, float ay, float az, uint8_t bat, uint8_t id, uint8_t channel)
|
||||
{
|
||||
static uint8_t last_bat = 0xFF;
|
||||
static uint8_t last_id = 0xFF;
|
||||
static uint8_t last_channel = 0xFF;
|
||||
|
||||
while (!lvgl_port_lock()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
|
||||
update_imu_cube(ax, ay, az);
|
||||
|
||||
if (imu_battery_label && bat != last_bat) {
|
||||
lv_label_set_text_fmt(imu_battery_label, "Bat: %d%%", bat);
|
||||
last_bat = bat;
|
||||
}
|
||||
|
||||
if (imu_channel_info_label && channel != last_channel) {
|
||||
lv_label_set_text_fmt(imu_channel_info_label, "Channel: %u", channel);
|
||||
last_channel = channel;
|
||||
}
|
||||
|
||||
if (imu_id_info_label && id != last_id) {
|
||||
if (id == 0) {
|
||||
lv_label_set_text(imu_id_info_label, "Receiver ID:\n0(broadcast)");
|
||||
} else {
|
||||
lv_label_set_text_fmt(imu_id_info_label, "Receiver ID: %u", id);
|
||||
}
|
||||
last_id = id;
|
||||
}
|
||||
lvgl_port_unlock();
|
||||
return g_imu_angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destroys and cleans up the IMU screen resources
|
||||
*
|
||||
* This function safely removes the IMU screen from memory by:
|
||||
* - Acquiring LVGL lock for thread safety
|
||||
* - Deleting the main screen object if it exists
|
||||
* - Setting all UI element pointers to NULL to prevent dangling references
|
||||
*
|
||||
* After execution, the screen will need to be recreated before use again.
|
||||
*/
|
||||
void ui_imu_screen_destory()
|
||||
{
|
||||
while (!lvgl_port_lock()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
if (imu_screen != NULL) {
|
||||
lv_obj_del(imu_screen);
|
||||
imu_screen = NULL;
|
||||
}
|
||||
lvgl_port_unlock();
|
||||
|
||||
imu_battery_label = NULL;
|
||||
imu_channel_info_label = NULL;
|
||||
imu_id_info_label = NULL;
|
||||
imu_data_label = NULL;
|
||||
}
|
||||
Reference in New Issue
Block a user