MagicWand:基于星闪SLE 1.0技术的智能家居控制项目

星幻魔控-海思智棒

分支1Tags0
文件最后提交记录最后更新时间
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前
1 年前

星幻魔控·海思智棒

MIT License 唤醒那个相信光的男人!TIGA!

[TOC]

1 作品介绍

本作品代码仓库: https://github.com/Maker-Hisilicon-Club/OH-Smart-Stick

SDK仓库: https://gitee.com/hihopeorg_group/near-link

功能介绍: 你是否曾经梦想成为光之巨人,与迪迦奥特曼一起守护地球的和平?现在,有了这款神奇的智能家居遥控器,你的梦想即将成真!

星幻魔控·海思智棒是一款集成了星闪SLE 1.0技术的智能家居遥控器,其独特的设计灵感来源于迪迦奥特曼的变身棒,旨在为用户带来一种前所未有的智能控制体验。通过变身棒式的外形设计与直观的交互方式,用户可以轻松控制家中的各种智能家居设备,享受科技带来的便捷生活。

虽然我们不能真的成为奥特曼,但有了这款 “海思智棒” ,家居控制将变得简单有趣,仿佛我们也能拥有一种神奇的力量!

作品完成情况:

核心功能介绍: 1.星闪客户端控制: 海思智棒作为客户端,采用星闪1对多技术实现与3种智能家居设备的无线连接。用户可以通过模仿迪迦奥特曼变身棒的动作,轻松操作家中的智能设备。

2.MPU6050动作识别: 内置MPU6050传感器实现动作识别功能,目前已经支持8种不同的手势动作,用户可以通过特定的手势来触发相应的控制指令。

3.智能插座控制: 基于继电器的智能插座控制系统,允许用户通过海思智棒远程开启或关闭家中的电器设备,实现智能化管理。

4.智能灯光控制: 利用SG90舵机实现智能灯光的控制功能,用户可以通过挥动海思智棒来调整室内灯光开关,减少日常开关灯的带来的麻烦。

5.智能空调控制: 集成红外学习模块的智能空调控制系统,用户可以通过海思智棒发送预设的红外信号来调节空调的开关、温度等设置。

注:动作支持录制,智能设备可扩展,上方仅本次预设动作

1.1 文件目录说明

OH-Smart-Stick
├── LICENSE.txt
├── README.md
├── sle_ac_server // 服务端-智能空调控制
│   ├── BUILD.gn
│   ├── inc
│   │   ├── infrared.h
│   │   ├── sle_ac_server.h
│   │   └── sle_ac_server_adv.h
|   |   └── iot_gpio_ex.h
│   └── src
│       ├── infrared.c
│       ├── sle_ac_server.c
│       └── sle_ac_server_adv.c
│       └── hal_iot_gpio_ex.c
|
├── sle_socket_server // 服务端-智能插座控制
│   ├── BUILD.gn
│   ├── inc
│   │   ├── relay.h
│   │   ├── SLE_SOCKET_Server.h
│   │   └── SLE_SOCKET_Server_adv.h
|   |   └── iot_gpio_ex.h
│   └── src
│       ├── relay.c
│       ├── SLE_SOCKET_Server.c
│       └── SLE_SOCKET_Server_adv.c
│       └── hal_iot_gpio_ex.c
├── sle_light_server // 服务端-智能灯光控制
│   ├── BUILD.gn
│   ├── inc
│   │   ├── Servo.h
│   │   ├── SLE_light_Server.h
│   │   └── SLE_light_Server_adv.h
|   |   └── iot_gpio_ex.h
│   └── src
│       ├── Servo.c
│       ├── SLE_light_Server.c
│       └── SLE_light_Server_adv.c
│       └── hal_iot_gpio_ex.c
│
│── sle_stick_client // 客户端-海思智棒
│    ├── BUILD.gn
│   ├── inc
│   │   ├── mpu6050.h
│   │   └── sle_client.h
|   |   └── iot_gpio_ex.h
│   └── src
│       ├── mpu6050.c
│       └── hal_iot_gpio_ex.c
│       └── sle_client.c

1.2 约束与限制

1.2.1 硬件要求

  • 安装有Linux系统的PC机
  • NearLink_DK_WS63星闪开发板
  • USB Type-C数据线

1.2.2 软件环境

  • OpenHarmony SDK (v1022)
  • Python开发环境 (3.8)
  • Git版本控制工具 (≥2.34.1)
  • NBU (梅科尔工作室自研星闪开发板构建利器, 开发效率提升60%)

1.3 作品实现效果

整体展示效果视频链接: Smart-Stick 演示视频 链接:https://pan.baidu.com/s/15etGvrEBp8vhBYI1PYRBCw?pwd=1234 提取码:1234

2 作品开发指南

2.1 Client端-海思智棒

2.1.1 海思智棒-硬件实现

海思智棒硬件组成: 1.HiHope_NearLink_DK3863E_V03核心板x1

2.MPU6050传感器x1

3.智棒整体接线演示

注:整个Client端程序里我也有用到两个按钮,主要作用是通过按钮触发GPIO中断回调,以此实现控制手势动作数据采集功能开启的控制效果,这里不再赘述,开发者朋友们可以根据实际情况来实现按钮触发功能。

2.1.2 海思智棒-软件实现

1.Client端程序的整体设计思想参考星闪1连多,即Client端扫描到不同Server端的广播,并依次建立连接,通过提前预设好不同Server端的address,来实现对不同Server端发送控制信号。 下方是核心部分代码

// 该部分程序展示如何对对不同Server端发送控制信号

if (flag == 1) {
    // 分配内存空间,用于存放发送的数据
    shuju.data = malloc(8);
    // 夂制字符串 "LED_ON" 到分配的内存空间中
    memcpy(shuju.data, "LED_ON", 7);
    // 设置 handle 值
    shuju.handle = 0;
    // 设置 type 值
    shuju.type = 0;
    // 设置 data 的长度
    shuju.data_len = 7;
    // 创建一个临时地址对象
    sle_addr_t temp_addr;
    // 复制目标服务器地址到临时地址对象中
    memcpy(temp_addr.addr, addr_servo, SLE_ADDR_LEN);

    // 检查当前连接的设备是否为目标设备
    if (compare_addr(&temp_addr, &g_connected_devices[g_connected_device_count].addr)) {
        PRINT("当前连接设备即目标设备\r\n");
        // 打印当前连接设备的地址信息
        print_addr(&g_connected_devices[g_connected_device_count].addr, "Connected device");
        // 打印临时地址对象的信息
        print_addr(&temp_addr, "Temp device");
        // 发送通知给客户端
        example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t));
        // 释放内存
        free(shuju.data);
    } else {
        // 查找目标设备的索引值
        uint8_t device_index = find_device_index_by_addr(&temp_addr);
        // 更新连接 ID 为找到的目标设备的连接 ID
        g_conn_id = g_connected_devices[device_index].conn_id;
        // 打印当前连接设备的地址信息
        print_addr(&g_connected_devices[g_connected_device_count].addr, "Connected device");
        // 打印临时地址对象的信息
        print_addr(&temp_addr, "Temp device");
        // 输出提示信息,表示当前连接设备不是目标设备,并更新了连接 ID
        PRINT("当前连接设备非目标设备,g_conn_id已切换:%d 找到的索引值为%d\r\n", g_conn_id, device_index);
        // 发送通知给客户端
        example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t));
        // 释放内存
        free(shuju.data);
    }
} else if (flag == 2) {
    // 分配内存空间,用于存放发送的数据
    shuju.data = malloc(9);
    // 复制字符串 "LED_OFF" 到分配的内存空间中
    memcpy(shuju.data, "LED_OFF", 8);
    // 设置 handle 值
    shuju.handle = 0;
    // 设置 type 值
    shuju.type = 0;
    // 设置 data 的长度
    shuju.data_len = 8;
    // 创建一个临时地址对象
    sle_addr_t temp_addr;
    // 复制目标服务器地址到临时地址对象中
    memcpy(temp_addr.addr, addr_servo, SLE_ADDR_LEN);

    // 检查当前连接的设备是否为目标设备
    if (compare_addr(&temp_addr, &g_connected_devices[g_connected_device_count].addr)) {
        // 发送通知给客户端
        example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t));
        // 释放内存
        free(shuju.data);
    } else {
        // 查找目标设备的索引值
        uint8_t device_index = find_device_index_by_addr(&temp_addr);
        // 更新连接 ID 为找到的目标设备的连接 ID
        g_conn_id = g_connected_devices[device_index].conn_id;
        // 输出提示信息,表示当前连接设备不是目标设备,并更新了连接 ID
        PRINT("当前连接设备非目标设备,g_conn_id已更改:%d 索引值为%d\r\n", g_conn_id, device_index);
        // 发送通知给客户端
        example_sle_Client_send_notify_by_handle(shuju.data, sizeof(ssapc_write_param_t));
        // 释放内存
        free(shuju.data);
    }
}

2.MPU6050驱动,参考sle_smart_client\src\mpu6050.c文件,主要通过IIC通信实现驱动,并加入卡尔曼滤波和姿态角解算,方便后续进行动作识别。 核心部分代码

void MPU6050_GetData(SensorData *data)
{
    uint8_t DataH, DataL; // 定义数据高8位和低8位的变量

    // 初始化Kalman滤波器
    kalman_filter_t kf_acc_x, kf_acc_y, kf_acc_z;
    kalman_filter_t kf_gyro_x, kf_gyro_y, kf_gyro_z;

    // 例如,Q = 0.001, R = 0.03, X = 初始估计值, P = 初始协方差估计值
    kalman_filter_init(&kf_acc_x, 0.001, 0.03, 0.0, 1.0);
    kalman_filter_init(&kf_acc_y, 0.001, 0.03, 0.0, 1.0);
    kalman_filter_init(&kf_acc_z, 0.001, 0.03, 0.0, 1.0);
    kalman_filter_init(&kf_gyro_x, 0.001, 0.03, 0.0, 1.0);
    kalman_filter_init(&kf_gyro_y, 0.001, 0.03, 0.0, 1.0);
    kalman_filter_init(&kf_gyro_z, 0.001, 0.03, 0.0, 1.0);

    // 读取加速度计和陀螺仪数据
    DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);                  // 读取加速度计X轴的高8位数据
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);                  // 读取加速度计X轴的低8位数据
    AccX = (DataH << 8) | DataL;                                    // 数据拼接
    data->acc_x = kalman_filter_update(&kf_acc_x, AccX / 16384.0f); // 应用Kalman滤波器并转换单位

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);                  // 读取加速度计Y轴的高8位数据
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);                  // 读取加速度计Y轴的低8位数据
    AccY = (DataH << 8) | DataL;                                    // 数据拼接
    data->acc_y = kalman_filter_update(&kf_acc_y, AccY / 16384.0f); // 应用Kalman滤波器并转换单位

    DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);                  // 读取加速度计Z轴的高8位数据
    DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);                  // 读取加速度计Z轴的低8位数据
    AccZ = (DataH << 8) | DataL;                                    // 数据拼接
    data->acc_z = kalman_filter_update(&kf_acc_z, AccZ / 16384.0f); // 应用Kalman滤波器并转换单位

    DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);                    // 读取陀螺仪X轴的高8位数据
    DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);                    // 读取陀螺仪X轴的低8位数据
    GyroX = (DataH << 8) | DataL;                                    // 数据拼接
    data->gyro_x = kalman_filter_update(&kf_gyro_x, GyroX / 131.0f); // 应用Kalman滤波器并转换单位

    DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);                    // 读取陀螺仪Y轴的高8位数据
    DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);                    // 读取陀螺仪Y轴的低8位数据
    GyroY = (DataH << 8) | DataL;                                    // 数据拼接
    data->gyro_y = kalman_filter_update(&kf_gyro_y, GyroY / 131.0f); // 应用Kalman滤波器并转换单位

    DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);                    // 读取陀螺仪Z轴的高8位数据
    DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);                    // 读取陀螺仪Z轴的低8位数据
    GyroZ = (DataH << 8) | DataL;                                    // 数据拼接
    data->gyro_z = kalman_filter_update(&kf_gyro_z, GyroZ / 131.0f); // 应用Kalman滤波器并转换单位

    char kalman_strings[6][20];
    snprintf(kalman_strings[0], sizeof(kalman_strings[0]), "%.2f", data->acc_x);
    snprintf(kalman_strings[1], sizeof(kalman_strings[1]), "%.2f", data->acc_y);
    snprintf(kalman_strings[2], sizeof(kalman_strings[2]), "%.2f", data->acc_z);
    snprintf(kalman_strings[3], sizeof(kalman_strings[3]), "%.2f", data->gyro_y);
    snprintf(kalman_strings[4], sizeof(kalman_strings[4]), "%.2f", data->gyro_x);
    snprintf(kalman_strings[5], sizeof(kalman_strings[5]), "%.2f", data->gyro_z);

    // 输出格式化后的字符串
    printf("MPU6050_Data=kalman_acc_x:%s,kalman_acc_y:%s,kalman_acc_z:%s,kalman_gyro_x:%s,kalman_gyro_y:%s,kalman_gyro_z:%s\r\n",
           kalman_strings[0], kalman_strings[1], kalman_strings[2],
           kalman_strings[3], kalman_strings[4], kalman_strings[5]);
}

2.1.3 海思智棒-识别8种不同动作

基础动作识别 基础动作识别有四种,通过换算姿态角的方式实现,即 “上挥”、“下挥”、“左旋”、“右旋” 姿态角解算代码如下

void calculate_accel_angle(int16_t AccX, int16_t AccY, int16_t AccZ)
{
    // 计算角度
    current_angle.roll = atan2(AccX, sqrt(AccY * AccY + AccZ * AccZ)) * 180.0f / M_PI;
    current_angle.pitch = atan2(-AccY, AccZ) * 180.0f / M_PI;
}

// 从陀螺仪数据更新角度
void update_gyro_angle(float GyroX, float GyroY, float GyroZ, float dt)
{

    // 积分得到角度
    current_angle.pitch += GyroX * dt*100;
    current_angle.roll += GyroY * dt*100;
    current_angle.yaw += GyroZ * dt*100;
}

// 上方两个函数具体调用用法如下
readSensorData(motion1Samples);
// 转换加速度计数据为角度
calculate_accel_angle(motion1Samples->acc_x, motion1Samples->acc_y, motion1Samples->acc_z);

// 时间间隔
float dt = I2C_TASK_DURATION_MS / 1000.0f;

// 使用陀螺仪数据更新角度
update_gyro_angle(motion1Samples->gyro_x, motion1Samples->gyro_y, motion1Samples->gyro_z, dt);

高级动作识别 基础动作识别一样预设四种,通过欧几里得距离对比的方式实现,即 “迪迦变身”、“L”、“I”、“C”

在做高级动作识别的时候需要先测算出预期动作的MPU6050传感器6轴具体数值,可手动做相同动作重复30组(根据实际情况),再求出平均值即得出该动作的预设值,方便欧几里得距离测算。 高级动作识别代码如下

// 计算两个 n 维空间点之间的欧几里得距离
float calculateEuclideanDistance(SensorData current, SensorData reference)
{
    // 初始化平方差的总和
    float sum = pow((current.acc_x - reference.acc_x), 2) +
                pow((current.acc_y - reference.acc_y), 2) +
                pow((current.acc_z - reference.acc_z), 2) +
                pow((current.gyro_x - reference.gyro_x), 2) +
                pow((current.gyro_y - reference.gyro_y), 2) +
                pow((current.gyro_z - reference.gyro_z), 2);

    // 计算总和的平方根,得到欧几里得距离
    return sqrt(sum);
}

// 检查当前传感器读数是否匹配已知的手势
int isGestureMatched(SensorData current, SensorData reference, float threshold)
{
    // 当前函数未使用阈值参数
    unused(threshold);

    // 计算当前传感器数据与参考传感器数据之间的欧几里得距离
    float distance = calculateEuclideanDistance(current, reference);

    // 将距离转换为整数(根据应用场景,这一步可能不需要)
    int dis = (int)distance;

    // 返回距离的整数值
    return dis;
    // 以检查距离是否落在特定范围内,然后再返回匹配结果。
}

// 对多个传感器读数取平均值,得到手势的平均表示
void averageGestureSamples(SensorData samples[], int numSamples, SensorData *average)
{
    // 使用零初始化平均传感器数据结构
    memset(average, 0, sizeof(SensorData));

    // 汇总所有传感器读数
    for (int i = 0; i < numSamples; i++)
    {
        *average = (SensorData){
            .acc_x = average->acc_x + samples[i].acc_x,
            .acc_y = average->acc_y + samples[i].acc_y,
            .acc_z = average->acc_z + samples[i].acc_z,
            .gyro_x = average->gyro_x + samples[i].gyro_x,
            .gyro_y = average->gyro_y + samples[i].gyro_y,
            .gyro_z = average->gyro_z + samples[i].gyro_z};
    }

    // 将每个分量除以样本数量,得到平均值
    average->acc_x /= numSamples;
    average->acc_y /= numSamples;
    average->acc_z /= numSamples;
    average->gyro_x /= numSamples;
    average->gyro_y /= numSamples;
    average->gyro_z /= numSamples;
}

如果开发者朋友们需要新增手势动作的话,可以按照这个办法,先求出目标动作的初始值,再通过这种欧几里得距离对比法,实现特定动作的识别,实测准确率有70%左右哦!

2.2 Server端1-智能空调控制

2.2.1 智能空调控制-硬件实现

海思智棒硬件组成: 1.HiHope_NearLink_DK3863E_V03核心板x1

2.红外学习模块x1

3.整体接线图(其实是UART原理)

2.2.2 智能空调控制-软件实现

该Server端的核心是UART通信,接收到来自Client端的星闪信号后,WS63E会通过UART向红外学习模块发送数据,进而该红外学习模块会对我们需要遥控的设备(空调等)发出红外信号进行控制,我这边预设了几种已经学习好的控制案例,开发者朋友们可以在sle_ac_server\src\infrared.c文件中找到。

核心代码如下

void send_ac_power_on(void)
{
    printf("准备发送空调开机信号\n");

    len = IrSend(buf, 0);
    printf("IrSend 返回长度: %d\n", len);

    int write_result = uapi_uart_write(CONFIG_UART_BUS_ID, buf, len, 1000);
    printf("uapi_uart_write 返回结果: %d\n", write_result);

    printf("空调开机信号发送完成\n");
    PRINT("空调打开\n", 0);
}

void send_ac_power_off(void)
{
    len = IrSend(buf, 1);
    PRINT("空调关闭\n", 1);
    uapi_uart_write(CONFIG_UART_BUS_ID, buf, len, 1000);

}

2.3 Server端2-智能插座控制

2.3.1 智能插座控制-硬件实现

1.BearPi-Pico_H3863核心板x1

2.继电器模块x1

3.整体接线图

2.3.2 智能插座控制-软件实现

该Server端的核心是GPIO,接收到来自Client端的星闪信号后,H3863会通过控制GPIO的电平高低对继电器进行控制,通过继电器实现外接插座的通电与断电。 核心部分代码如下

// 打开继电器
void relay_on(void)
{
    // 设置继电器引脚为高电平
    uapi_gpio_set_val(RELAY_PIN, GPIO_LEVEL_HIGH);
    relay_state = true;
}

// 关闭继电器
void relay_off(void)
{
    // 设置继电器引脚为低电平
    uapi_gpio_set_val(RELAY_PIN, GPIO_LEVEL_LOW);
    relay_state = false;
}

// 切换继电器状态
void relay_toggle(void)
{
    if (relay_state)
    {
        relay_off();
    }
    else
    {
        relay_on();
    }
}

// 获取继电器状态
bool relay_get_state(void)
{
    return relay_state;
}

2.4 Server端3-智能灯光控制

2.4.1 智能灯光控制-硬件实现

1.HiHope_NearLink_DK3863E_V03核心板x1

2.SG90舵机x1

3.整体接线图

2.4.2 智能灯光控制-软件实现

该Server端的核心是舵机驱动,接收到来自Client端的星闪信号后,WS63E会通过控制GPIO的电平高低模拟PWM波对舵机进行控制,通过舵机旋转实现灯光开关的控制。 核心部分代码如下

// 舵机驱动
void S92RInit(void)
{
    uapi_pin_set_mode(BSP_SG92R, HAL_PIO_FUNC_GPIO);
    uapi_gpio_set_dir(BSP_SG92R, GPIO_DIRECTION_OUTPUT);
    uapi_gpio_set_val(BSP_SG92R, GPIO_LEVEL_LOW);
}

void SetAngle(unsigned int duty)
{
    unsigned int time = FREQ_TIME;

    uapi_gpio_set_val(BSP_SG92R, GPIO_LEVEL_HIGH);
    uapi_systick_delay_us(duty);
    uapi_gpio_set_val(BSP_SG92R, GPIO_LEVEL_LOW);
    uapi_systick_delay_us(time - duty);
}

/* The steering gear is centered
 * 1、依据角度与脉冲的关系,设置高电平时间为1500微秒
 * 2、不断地发送信号,控制舵机居中
 */
void RegressMiddle(void)
{
    unsigned int angle = 1500;
    for (int i = 0; i < COUNT; i++) {
        SetAngle(angle);
    }
}

/* Turn 90 degrees to the right of the steering gear
 * 1、依据角度与脉冲的关系,设置高电平时间为500微秒
 * 2、不断地发送信号,控制舵机向右旋转90度
 */
/*  Steering gear turn right */
void EngineTurnRight(void)
{
    unsigned int angle = 500;
    for (int i = 0; i < COUNT; i++) {
        SetAngle(angle);
    }
}

/* Turn 90 degrees to the left of the steering gear
 * 1、依据角度与脉冲的关系,设置高电平时间为2500微秒
 * 2、不断地发送信号,控制舵机向左旋转90度
 */
/* Steering gear turn left */
void EngineTurnLeft(void)
{
    unsigned int angle = 2500;
    for (int i = 0; i < COUNT; i++) {
        SetAngle(angle);
    }
}

注:所有Server端address要提前预设好,并且不能重复

3 作品部署指南

  • 请参见 本作品采用 SDK 仓库中的 README 文件。

作者

杨 阳 email: yangdev89@foxmail.com

版权说明

该项目签署了MIT 授权许可,详情请参阅 LICENSE.txt

鸣谢

梅科尔工作室 MakerStudio

项目介绍

星幻魔控-海思智棒

定制我的领域