天天看點

我用鴻蒙開發出一台機器人【萌萌的那種】

我用鴻蒙開發一台機器人【萌萌的那種】

0. 說明

正式接觸鴻蒙也快9個月了,一直想做一個有意思的極客項目:既能用到鴻蒙特性【ArkUI、原子化服務、碰一碰等】、涉及南北向開發,又能融入實際生活。斷斷續續,從南到北,粗略過了一遍,終于,近期實作了這一想法。今天,向大家展示個人極客項目--智慧終端機器人MRobot,如下圖: 

我用鴻蒙開發出一台機器人【萌萌的那種】
我用鴻蒙開發出一台機器人【萌萌的那種】
我用鴻蒙開發出一台機器人【萌萌的那種】
我用鴻蒙開發出一台機器人【萌萌的那種】
我用鴻蒙開發出一台機器人【萌萌的那種】

1. 方案設計

1.1 功能需求分析

MRobot = 快捷啟動 + 自由移動 + APP互動 + 多裝置互聯 + 雲端控制 + 其他拓展(語音、趣味功能) 

我用鴻蒙開發出一台機器人【萌萌的那種】
  • 快捷啟動:碰一碰拉起配網頁面 | 在“我的服務”中使用卡片啟動APP | 桌面卡片 ;
  • 自由移動:具備轉向、前進、後退等基本運動能力;
  • APP互動: 控制機器人移動;
  • 多裝置互聯:通過機器人控制其他裝置,如門鎖、台燈、風扇、澆水機等;
  • 雲端控制:基于mqtt協定控制互聯裝置,無需與機器人配網即可控制,一是解決機器人不在身邊的情形(如歸家開門),二是提供web端供其他人使用(如舍友手機不是HarmonyOS系統);
  • 其他拓展:離線語音互動,對機器人喊話就能控制一切; 可控磁吸配件(給小朋友準備的。),如發光環、交通燈、迷你風扇

1.2 結構設計與硬體選型

參考貓、飛船傳回艙等元素,花一天設計出了MRobot,萌不萌仁者見仁啦。硬體選型方面,機器人控制核心毫無疑問hi3861模組;N20小功率電機以及驅動;語音子產品使用SU03T;一塊800mAh锂電池以及TypeC充放管理子產品;藍牙用于與互聯裝置通信;幾塊磁鐵預備做配件;左耳有觸摸傳感,提供oled顯示子產品或MPU6050選擇(不同時使用)。 

我用鴻蒙開發出一台機器人【萌萌的那種】

1.3 控制資料流

  • APP:北向APP
  • Web: 雲端iot服務
  • MRobot:智慧終端機器人 小萌
  • Union Devices:互聯裝置,門鎖台燈等
  • Speaker:語音子產品
  • Human: 萬惡之源
    我用鴻蒙開發出一台機器人【萌萌的那種】

2. 北向App開發

  • APP基于ArkUI的類web開發範式,大部分使用JS開發。APP将完成各功能頁面展示、消息下發功能。整個工程主要參考​​HarmonyOS Connect裝置開發​​實作,去除掉模闆JAVA實作闆塊,使用JS接口替代。

2.1 建立原子化服務APP

  • 使用OneHop模闆建立原子化服務工程,命名MRobot。可參考分享貼碰一碰分享總貼
    我用鴻蒙開發出一台機器人【萌萌的那種】
     删除不需要的control子產品(基于JAVA),建立一個jscontrol page,這是将是控制首頁。參考1.抛除束縛,自定義裝置
    我用鴻蒙開發出一台機器人【萌萌的那種】

2.2 UI設計

  • 根據需求分析設計UI,因為第一次寫APP,渲染樣式沒有概念,是以在圖檔上做文章,對設計的MRobot模型進行渲染後導出圖檔置于APP中,所有的UI主要用到button、switch、swiper元件,先基于可視化低代碼開發,後轉為hml(jsctronl page是)。ui總覽如下; 
  • 碰一碰拉起,進入配網頁面,提供兩個選項,一是正常配網,二是直接進入雲端web頁面秒控裝置。
  • 首頁作為主要頁面(jscontrol page),從上往下依次是:MRobot機器人運動控制、秒控裝置(發送消息給機器人,實作互聯裝置控制)、表情聊天(點選表情發送消息給機器人,機器人做出運動、語音反應);
  • 點選更多,将會打開一些隐藏頁面,如随機計算、認識宇宙等,主要是面對小朋友,未來增加通關解鎖相應技能。 下面總結常用的一些UI能力,友善調試與開發:
//彈窗
import prompt from '@system.prompt'; 
    { prompt.showToast({
            message: "Hello Kun",
            duration: 1000, });
//頁面路由 push back replace 
import router from '@system.router';
router.push({
          uri: 'pages/netconfig/netconfig'
        });

// switch 狀态檢測
    switchChange(e){
        console.log(e.checked);
        if( e.checked){
            prompt.showToast({
                message: "連接配接"
            });
        }
        else{
            this.talk_msg = "reset MRobot";
            prompt.showToast({
                message: "斷開"
            });
        };
    }
複制      

2.3 NAN-JS通信

在建立的jsctronl 中需要調用JS通信接口在建立的NAN通道中發送資料,詳細參考:​​2.擁抱JS通信接口​​,因為是調用JS接口,擷取NAN通道deviceInfo十分容易。 例如,發送機器人前進核心代碼如下:

import {getApp} from '../../common.js';
    //運動控制
    Forward(){
        this.front_img='/common/images/up0.png';
        this.work_status ="萌萌退下";
        this.talk_msg = "F";
        this.sendMessage();
    },
  //發送消息
    sendMessage(){
        var ret =1;
        var message = this.talk_msg;
        let commonInfo = {
            sessionId: getApp(this).ConfigParams.deviceInfo.sessionId
        };
        getApp(this).NetConfig.sendMessage(commonInfo, message, (result)=>{
                if(result.code==0)
                {
                    prompt.showToast({
                        message: "發送成功",
                        duration: 1000,
                    });
                ret =0;}
                else
                { prompt.showToast({
                        message: "發送失敗",
                        duration: 1000,
                    });
               ret= -1;};
            });
        if(ret==0)
        {
            return 0;
        }
        else return -1;
    },      

在南向開發中對消息進行比對即可進行相應動作。

2.4 真機測試

APP真機調試算基本技能了,為什麼我要單獨拿出來呢,因為我入門北向時,遇到一個坑。裝雙系統的深有體會,Windows時間會慢,于是我給直接調到了UTC+14:00 聖誕島,現在我已經在過520了(doge); 那麼問題來了:AGC平台簽名會校驗時間,時間不對簽名會失敗,是以一直用另一台電腦做應用開發,折騰好久才發現自己筆記本時間不對導緻簽名失敗。。。

3. 南向開發

南向完成MRobot開發、互聯裝置開發。基于OpenHarmony 1.0.1 LTS對 hi3861 進行資源配置設定、各子產品調試、整機調試、功能驗證。 南向難免要面向硬體,MRobot與互聯裝置電氣原理圖如下: 

3.1 擷取OpenHarmony源碼

以下是我擷取源碼的操作以及過程遇到的問題以及解決方法。

/***************** openharmony 1.0.1源碼 ******************/
repo下載下傳【安裝git、repo】,#特别注意:請下載下傳OpenHarmony 1.0.1 版本,後續會更新支援OpenHarmony其他版本
mkdir ~/OpenHarmony1.01
cd ~/OpenHarmony1.01
repo init -u [email protected]:openharmony/manifest.git -b OpenHarmony_1.0.1_release --no-repo-verify
repo sync -c
repo forall -c 'git lfs pull'
來自 <https://gitee.com/openharmony-sig/knowledge_demo_smart_home/tree/master/dev/docs/smart_cleaner> 

或者https://repo.huaweicloud.com/harmonyos/os/  下載下傳解壓

/**********  編譯1.0.1源碼時,遇到問題以及解決方法: ******************* /
-使用hb 、python build.py 指令編譯
(1) hb -h執行之後遇到 please run  in source root,因為1.0.1的ohos-build是0.2.1 ,  與3.0的不通用,
【解決方法】解除安裝掉 pip uninstall ohos-build 
               再在1.0源碼根目錄安裝hb 執行 pip install build/lite  
(2)在源碼處可以hb set,但hb build -f編譯時遇到 clang not  install
    【解決方法】 ~/.bashrc中添加python環境變量即可

-使用DevEcoTools編譯
(1)找不到gn、ninja
    【解決方法】所有解壓的工具鍊,如gcc、ninja、gn等,需要在 ~/.bashrc中添加環境變量,示例 export -----
(2)找不到clang 
【解決方法】 ~/.bashrc中添加python、gcc、ninja、gn等環境變量即可      

順便提一下,我并沒有使用ubuntu環境進行裝置開發,因為我發現 DevEco Tools Beta3.1 可以直接編譯1.0.1源碼。大家可以嘗試下windows一站式開發。源碼需要修改,配置build選項開啟I2C、UART等(MRobot使用了兩個序列槽)。具體操作可參考:​​0.1.2​​。

3.2 MRobot碰一碰配網互動

結合winder demo很快就能找出配網接口,配網之後即可接收資料進行裝置控制。整體控制流程如下: 

核心代碼如下(詳情可參考:​​3.南向開發-裝置配網​​):

include:
│   ├── netcfg.h   // 無感配網注冊相關接口
│   ├── network_config_service.h //無感配網相關頭檔案。
libs:
├── libs
│   ├── libhilinkadapter_3861.a // 無感配網相關庫檔案。
│   └── libnetcfgdevicesdk.a // 無感配網相關庫檔案。
src:
    ├── netcfg.c // NAN相關操作和聯網動作      
//注冊回調
static void *MRobotTask(const char *arg)
{
    (void)arg;
    ····
    NetCfgRegister(MRobotNetEventHandler); // 進入配網狀态并注冊網絡監聽事件
    ····
}

//回調任務
static int MRobotNetEventHandler(NET_EVENT_TYPE event, void *data)
{
    switch (event)
    {
    case NET_EVENT_CONNECTTED: // 網絡連接配接成功
        m_netstatus = true;
        printf("m_netstatus:%d\n\n", m_netstatus); // 顯示網絡已連接配接
        break;
    case NET_EVENT_RECV_DATA:                                      // 接收到網絡資訊(FA發送的消息)
        MRobotProcessAppMessage((const char *)data, strlen(data)); // 處理對應的資訊
        break;
    default:
        break;
    }
    return 0;
}

//接收資料
static void MRobotProcessAppMessage(const char *data, int data_len)
{
    if (data_len != MESSAGE_LEN)
    {
        strcpy(app_msg, data);
        // app_msg=data;
        printf("------app_msg:%s \r\n", app_msg);
        printf("----- data:%s\r\n", data);
        WINERR("data len invalid! \n");
        return;
    }
}

//消息處理
// app運動控制
static void AppCtrMRobot()
{
    switch (*app_msg)
    {
    case 'F':
    {
        printf("退下\r\n");
        // OledFillScreen(0x00);
        // OledShowString(25, 2, "Forward", 2);
        Backward(circle_time);
        Stop_motor();
        return; //每次僅有一種情況,滿足直接return回main
    }
   ···
}      

3.3 互聯裝置開發

 互聯裝置包括門鎖、風扇、台燈、澆水機均使用hi3861模組作為核心闆,若有更多裝置可以快捷添加。每台裝置支援使用藍牙序列槽控制、基于mqtt的雲端控制。增加web端選擇也讓非HarmonyOS移動裝置控制互聯裝置,讓更多的人享受便利。 下面以門鎖為例,給出開發方案:

/**
 * 
 *  V2 版本  所有union裝置都融合在一起,共用一套代碼
 * 缺點是浪費時間作其他裝置的事;
 * 也提供了各裝置單獨的版本 對應fan_mqtt.c door_mqtt.c  lamb_mqtt.c  water_mqtt.c 編譯記得修改 BUILD.gn
 * */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "ohos_init.h"
#include "cmsis_os2.h"

#include "wifi_connect.h"
#include "MQTTClient.h"

#include "wifiiot_errno.h"
#include "wifiiot_gpio.h"
#include "wifiiot_gpio_ex.h"
#include "wifiiot_adc.h"
#include "wifiiot_uart.h"
#include "wifiiot_pwm.h"
#include "hi_uart.h"


#define UART_BUFF_SIZE 1000
#define MQTT_BUFF_SIZE 1000

static const char *data = "MRobot_door\r\n";
uint8_t uart_buff[UART_BUFF_SIZE] = {0};
uint8_t *uart_buff_ptr = uart_buff;
char mqtt_buff[MQTT_BUFF_SIZE] = {0};

static unsigned char sendBuf[1000];
static unsigned char readBuf[1000];

Network network;

void messageArrived(MessageData *data)
{
    printf("Message arrived on topic %.*s: %.*s\n", data->topicName->lenstring.len, data->topicName->lenstring.data,
           data->message->payloadlen, data->message->payload);
    strcpy(mqtt_buff,data->message->payload);
    printf("mqtt_buff%s \n",mqtt_buff);
}

/************* MRobotUnionDevices Control ******************/
// GPIO 接口與原理圖對應  使用哪個就在主函數加入Init、 Ctr函數。

static void MyUartInit(void)
{
    uint32_t ret;
    WifiIotUartAttribute uart_attr = {
        .baudRate = 115200,

        // data_bits: 8bits
        .dataBits = 8,
        .stopBits = 1,
        .parity = 0,
    };

    // Initialize uart driver
    ret = UartInit(WIFI_IOT_UART_IDX_1, &uart_attr, NULL);
    if (ret != WIFI_IOT_SUCCESS)
    {
        printf("Failed to init uart! Err code = %d\n", ret);
        return;
    }
}



/*****************Door*************************/
static void DoorInit(void)
{
    //初始化GPIO
    GpioInit();
    IoSetFunc(WIFI_IOT_IO_NAME_GPIO_10, WIFI_IOT_IO_FUNC_GPIO_10_GPIO);
    GpioSetDir(WIFI_IOT_GPIO_IDX_10, WIFI_IOT_GPIO_DIR_OUT);
    GpioSetOutputVal(WIFI_IOT_GPIO_IDX_10, 1);
}
/* @brief  Servo  control *
 @param angle  input value: 0-200 *
*/
void My_servo(int angle)
{
    int j = 0;
    int k = 20000 / 200; //實際應該是20000/180
    angle = k * angle;
    for (j = 0; j < 5; j++)
    {
        GpioSetOutputVal(WIFI_IOT_GPIO_IDX_10, 1);
        //hi_udelay(angle); // angle ms
        usleep(angle);
        GpioSetOutputVal(WIFI_IOT_GPIO_IDX_10, 0);
        usleep(20000-angle);
    }  // 20ms 控制舵機
}
static void DoorCtr(void)
{
    //UartRead(WIFI_IOT_UART_IDX_1, uart_buff_ptr, UART_BUFF_SIZE);
    hi_uart_read_timeout(WIFI_IOT_UART_IDX_1, uart_buff_ptr, UART_BUFF_SIZE,10);
    printf("Uart1 read door data:%s \n", uart_buff_ptr);
    if (uart_buff[0] == '2' && uart_buff[1] == '1')
    {
        //模拟20ms周期 PWM  控制舵機開門
        printf("******* 開 門 *****\n");
        My_servo(100); //開門
        uart_buff[0] ='5';
    }

    if (mqtt_buff[0] == '2' && mqtt_buff[1] == '1')
    {
        //模拟20ms周期 PWM  控制舵機開門

        printf("******* 開 門 *****\n");
        My_servo(100); //開門
        mqtt_buff[0] ='5';
    }
    
    My_servo(180); //自動複位 模拟舵機有點迷。值是測試出來的,上面的延時<0 居然可以實作舵機控制
    printf("******* 門 機 械 複 位 *****\n");
}

static void MQTT_DoorTask(void)
{
    WifiConnect("r1", "88888889");
    printf("Starting ...\n");
    int rc, count = 0;
    MQTTClient client;

    NetworkInit(&network);
    printf("NetworkConnect  ...\n");

    MyUartInit();
    DoorInit();
begin:
    NetworkConnect(&network, "我的伺服器位址iot頁面", 1883); // hellokun
    printf("MQTTClientInit  ...\n");
    MQTTClientInit(&client, &network, 2000, sendBuf, sizeof(sendBuf), readBuf, sizeof(readBuf));

    MQTTString clientId = MQTTString_initializer;
    clientId.cstring = "MRobot";

    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
    data.clientID = clientId;
    data.willFlag = 0;
    data.MQTTVersion = 3;
    data.keepAliveInterval = 0;
    data.cleansession = 1;

    printf("MQTTConnect  ...\n");
    rc = MQTTConnect(&client, &data);
    if (rc != 0)
    {
        printf("MQTTConnect: %d\n", rc);
        NetworkDisconnect(&network);
        MQTTDisconnect(&client);
        osDelay(200);
        goto begin;
    }

    printf("MQTTSubscribe  ...\n");
    //其他裝置 web_fan_btn web_lamb_btn  web_water_btn
        rc = MQTTSubscribe(&client, "web_door_btn", 2, messageArrived); //回調
        if (rc != 0)
        {
            printf("MQTTSubscribe: %d\n", rc);
            osDelay(200);
            goto begin;
        }

    while (++count)
    {
        //使用哪個裝置就在加入對應Init、 Ctr。
        DoorCtr();  //序列槽、mqtt資料控制門
                                                      
        MQTTMessage message;
        char payload[30];

        message.qos = 2;
        message.retained = 0;
        message.payload = payload;
        sprintf(payload, "message number %d", count);
        message.payloadlen = strlen(payload);

        //其他裝置 釋出fan lamb water
        if ((rc = MQTTPublish(&client, "door", &message)) != 0)
        {
            printf("Return code from MQTT publish is %d\n", rc);
            NetworkDisconnect(&network);
            MQTTDisconnect(&client);
            goto begin;
        }
        osDelay(50); 
        printf("----- count = %d ------\r\n",count);
    }

    //無需釋出
    // while (++count)
    // {
    //     MQTTMessage message;
    //     char payload[30];

    //     message.qos = 2;
    //     message.retained = 0;
    //     message.payload = payload;
    //     sprintf(payload, "message number %d", count);
    //     message.payloadlen = strlen(payload);

    //     //其他裝置 釋出fan lamb water
    //     if ((rc = MQTTPublish(&client, "door", &message)) != 0){
    //         printf("Return code from MQTT publish is %d\n", rc);
    //         NetworkDisconnect(&network);
    //         MQTTDisconnect(&client);
    //         goto begin;
    //     }
    //     osDelay(50);
    // }
}
static void MQTT_Door(void)
{
    osThreadAttr_t attr;

    attr.name = "MQTT_DoorTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew((osThreadFunc_t)MQTT_DoorTask, NULL, &attr) == NULL)
    {
        printf("[MQTT_Door] Falied to create MQTT_DoorTask!\n");
    }
}
APP_FEATURE_INIT(MQTT_Door);      

3.4 南向開發架構總結

MRobot檔案架構:  互聯裝置檔案架構: 

3.5 趣味配件

4. 機器人 demo

個人總結