天天看點

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

一、項目介紹

​ 能源危機日益嚴重,發展新能源勢在必行。光伏發電就是不錯的選擇,但是光電轉換效率一直是困擾行業發展的一大難題。本項目通過MPPT全稱“最大功率點跟蹤”(Maximum Power Point Tracking)實時偵測太陽能闆的發電電壓,并追蹤最高電壓電流值(VI),使系統以最大功率輸出電力。 下圖使用300W的光伏太陽能闆為4串12V的磷酸鐵锂電池進行充電。基本功能已經實作,項目中裝置代碼、應用端代碼、原理圖等将全部開源,PCB電路還在調試中。

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

系統分為三個部分:

視訊示範位址:https://ost.51cto.com/show/14366

應用端:

OpenHarmony應用端:使用潤和DAYU200開發闆,基于ArkUI/eTS開發架構,實作光伏發電控制器應用端,可實時監控光伏控制器裝置狀态。并将裝置資料同步到華為雲IotDA,可實作廣域網裝置狀态檢測和控制。

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

HarmonyOS應用端:使用HarmonyOS原子化服務能力,應用免安裝。支援NFC碰一碰配網(NAN+SoftAP),配網成功拉起裝置控制頁面。裝置控制子產品同OpenHarmony應用端。同時提供服務卡片,可将重要的裝置資訊添加到桌面,友善随時随地進行檢視。

裝置端:

裝置端為太陽能充放電控制器,輸入端接太陽能光伏闆,輸出端接锂電池等儲能裝置。主要晶片采用Hi3861,核心算法采用MPPT“最大功率點跟蹤”(Maximum Power Point Tracking),可顯著提升太陽能光伏闆的發電效率。原理圖如下:

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

雲端:

雲端接入華為雲IotDA,負責裝置資料采集,下發指令給裝置。

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

二、項目目錄

項目gitee位址:https://gitee.com/liangzili/oh-solar-control

├─1.OpenHarmony_Firmware				// 裝置端代碼
├─2.OpenHarmony_APP				 		// dayu200 應用端代碼
├─3.HarmonyOS_APP				 		// 鴻蒙手機 應用端代碼
├─4.Schematic_PCB						// 原理圖
└─HuaweiYunCloud						// 華為雲模型檔案
           

三、裝置端代碼

裝置端實作的功能:

1.NFC一鍵配網

  1. 擷取裝置端輸入輸出電流電壓。
    #DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼
    #DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼
    #DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼
    原理圖中,在太陽能輸入端,锂電池端接分壓電阻。分别接入ADS1115的AIN0和AIN3接口。

    1.OpenHarmony_Firmware\OH_SolarControl\ADS1X15

    檔案夾下移植了ADS1X15 Arduino端驅動代碼到OpenHarmony。電流檢測使用ACS712子產品,接入ADS1115的AIN1和AIN2接口,ADS1115通過I2C子產品與Hi3861通訊。接入主要代碼如下:
    #include "ADS1X15.h"
    
    hi_gpio_init();                                                     //GPIO子產品初始化
    
    // 端口複用I2C
    hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2C0_SDA);
    hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_I2C0_SCL);
    
    // ADS1X15初始化
    ADS1X15_begin();
    
    // 采集電壓:
    int SamplingCount = 4;  //采樣數
    for(int i = 0; i<SamplingCount; i++){                            // 電壓傳感器平均采樣計數 (推薦: 3)
        //TODO:增加ADS1115檢測
        operatingData->involtage = operatingData->involtage + ADS1X15_computeVolts(ADS1X15_readADC_SingleEnded(3));   //
        operatingData->outvoltage = operatingData->outvoltage + ADS1X15_computeVolts(ADS1X15_readADC_SingleEnded(1));   //
        operatingData->incurrent = operatingData->incurrent + ADS1X15_computeVolts(ADS1X15_readADC_SingleEnded(2));   // 
        operatingData->outcurrent = operatingData->outcurrent + ADS1X15_computeVolts(ADS1X15_readADC_SingleEnded(0));   // 
    }
    operatingData->involtage  = operatingData->involtage/SamplingCount*40.2857;   //分壓系數        
    operatingData->outvoltage  = operatingData->outvoltage/SamplingCount*25;  //分壓系數   
    // 采集電流:
    operatingData->incurrent  = operatingData->incurrent/SamplingCount;  //
    operatingData->incurrent  = (operatingData->incurrent-2.45)/0.066; //  ACS712供電:4.96V,無電流時,電壓為VCC/2.靈敏度0.066A/V
    operatingData->outcurrent  = operatingData->outcurrent/SamplingCount;  //
    operatingData->outcurrent  = (operatingData->outcurrent-2.45)/0.066; // (檢測電流v - 電流傳感器中點2.525v)*-1 / 電流傳感器靈敏度0.066A/V = 得到目前電流輸入值。輸出功率(電池或充電電壓)
               
  2. 溫度控制

    當系統溫度過高時,自動關閉系統。使用NTC100K的溫度傳感器,由于Hi3861系統資源比較有限,是以使用二分查表法計算溫度值,關鍵代碼如下:

    /**
     * @brief AD值對應溫度值表(升序表)
     * NTC溫度傳感器R25=100K,分壓電阻51K,NTC參考電壓3.3V,ADC分辨率12位,ADC參考電壓4*1.8
     * 計算方法參考 NTC計算表.excel
     */
    const uint16_t NTC100K[100] = {
        0x220, 0x232, 0x243, 0x255, 0x268, 0x27A, 0x28D, 0x29F, 0x2B2, 0x2C5, // 20~39 ℃
        0x2D8, 0x2EB, 0x2FE, 0x311, 0x324, 0x338, 0x34B, 0x35E, 0x371, 0x384, // 30~39 ℃
        0x397, 0x3AA, 0x3BD, 0x3D0, 0x3E2, 0x3F4, 0x407, 0x419, 0x42B, 0x43C, // 40~49 ℃
        0x44E, 0x45F, 0x470, 0x481, 0x492, 0x4A2, 0x4B2, 0x4C2, 0x4D2, 0x4E1, // 50~59 ℃
        0x4F0, 0x4FF, 0x50E, 0x51C, 0x52A, 0x538, 0x546, 0x553, 0x560, 0x56C, // 60~69 ℃
        0x579, 0x585, 0x591, 0x59D, 0x5A8, 0x5B3, 0x5BE, 0x5C8, 0x5D3, 0x5DD, // 70~79 ℃
        0x5E7, 0x5F0, 0x5FA, 0x603, 0x60C, 0x614, 0x61D, 0x625, 0x62D, 0x635, // 80~89 ℃
        0x63C, 0x644, 0x64B, 0x652, 0x659, 0x65F, 0x666, 0x66C, 0x672, 0x678, // 90~99 ℃
        0x67E, 0x684, 0x689, 0x68E, 0x694, 0x699, 0x69D, 0x6A2, 0x6A7, 0x6AB, // 100~109 ℃
        0x6B0, 0x6B4, 0x6B8, 0x6BC, 0x6C0, 0x6C4, 0x6C8, 0x6CB, 0x6CF, 0x6D2, // 110~119 ℃
    };
    
    
    // 采集溫度: 使用Hi3861自帶的ADC擷取熱敏電阻
    hi_adc_channel_index channel3 = HI_ADC_CHANNEL_3;       // ADC通道編号
    hi_u16 *data;                                           // 讀取到的資料儲存位址
    hi_adc_equ_model_sel equ_model = HI_ADC_EQU_MODEL_8;    // 平均算法模式:使用8次平均算法模式
    hi_adc_cur_bais cur_bais = HI_ADC_CUR_BAIS_DEFAULT;     // 模拟電源控制:使用預設識别模式,可修改1.8V/3.3V
    hi_u16 delay_cnt = 0;                                   // 從配置采樣到啟動采樣的延時時間計數,一次計數是334ns,其值需在0~0xFF0之間
    hi_adc_read(channel3, &data, equ_model, cur_bais, delay_cnt);       // 從一個ADC通道讀一個資料
    hi_float voltage = hi_adc_convert_to_voltage(data);                 // 将ADC讀取到的碼字轉換為電壓,(data * 1.8 * 4 / 4096)
    
    voltage = 3.3*51/voltage-51;                                        // 實際電壓,供電3.3V,分壓電阻51KΩ
    // operatingData->temp  = 1/((ln(voltage/100)/3950)+1/298.15)-273.15;  // 使用公式計算溫度值,不支援ln函數
    operatingData->temp = AdcConvertTemp(NTC100K,100,20,data);      // 使用二分查表法計算溫度值
    
    if (operatingData->temp > 60 )                                  // 溫度超過100℃≈6.6f,80℃≈12.38f
    {
        systemState.overTemperture = true;
        systemState.errCount++;
    }else
    {
        systemState.overTemperture = false;
    }
               
  3. OLED顯示

    将系統實時運作狀态顯示出來,相關代碼包含在

    1.OpenHarmony_Firmware\OH_SolarControl\ssd1306

    檔案夾下
    InitGpio();
    ssd1306_Init();
    ssd1306_Fill(Black);
    ScreenPrint(0, 0,"Hello");
    
    void ScreenPrint(int x,int y,char* message){
        ssd1306_SetCursor(x, y);
        ssd1306_DrawString(message, Font_7x10, White);
        ssd1306_UpdateScreen();
    }
               
  4. mqtt接入華為雲

四、OpenHarmony應用端代碼

  1. 界面實作

    頁面使用ets進行編寫,主要代碼如下:

    DeviceInfo()                        // 裝置資訊
          Devicestate(this.DeviceStateData)   // 裝置狀态
    
          // 電流電壓
          Flex({ justifyContent: FlexAlign.SpaceBetween,alignItems:ItemAlign.Center }){
            Column() {
              Text(this.InVoltage+' V').fontSize(30)
              Text('輸入電壓').fontSize(30)
            }
            .width('33%')
    
            Column() {
              Text(this.OutVoltage+' V').fontSize(30)
              Text('輸出電壓').fontSize(30)
            }
            .width('34%')
    
            Column() {
              Text(this.InCurrent+' A').fontSize(30)
              Text('輸入電流').fontSize(30)
            }
            .width('33%')
    
          }.align(Alignment.Center).borderRadius(15).backgroundColor(0xE5E5E5).width('90%').height(180).margin({top:10})
               
  2. Http通路

    連接配接華為雲IotDA需要使用get、post請求雲端資料,發送請求配置代碼:

    export class HttpRequestOptions {
        method: string
        extraData: Object
        header: Object
        readTimeout: number
        connectTimeout: number
    
        constructor() {
            this.method = 'POST'
            this.header = {
                'Content-Type': 'application/json'
            }
            this.readTimeout = 5000
            this.connectTimeout = 5000
        }
    
        setMethod(method: string) {
            this.method = method
            Logger.info(TAG, `setMethod method is ${this.method}`)
        }
    
        setExtraData(extraData: Object) {
            this.extraData = extraData
            Logger.info(TAG, `setExtraData extraData is ${JSON.stringify(this.extraData)}`)
        }
    
        setHeader(header: Object) {
            this.header = header
            Logger.info(TAG, `setHeader header is ${JSON.stringify(this.header)}`)
        }
    }
    
    /*********************** 網絡資料請求 *********************************/
    async request(uri: string, op: Object) {
        let httpRequest = http.createHttp()
        Logger.info(TAG, `createHttp uri = ${uri}`)
        try {
            let result = await httpRequest.request(uri, op)
            Logger.info(TAG, `HttpResponse's result is ${JSON.stringify(result.result)}`)
            Logger.info(TAG, `responseCode is ${result.responseCode} header is ${JSON.stringify(result.header)}
            cookies is ${JSON.stringify(result.cookies)}}`)
            return result
        } catch (err) {
            Logger.info(TAG, `This err is ${JSON.stringify(err)}`)
            httpRequest.destroy()
            return err
        }
    }
               
  3. 華為雲API接口

    擷取IAM使用者Token接口,該接口可以用于通過使用者名和密ma的方式進行認證來擷取IAM使用者Token。

    async getIAMUserToken(){
            let PostHeader = {
                'Content-Type': 'application/json'
            }
            let PostBody = {
                "auth": {
                    "identity": {
                        "methods": [
                            "password"
                        ],
                        "password": {
                            "user": {
                                "name": this.IAMUserName,
                                "password": this.IAMPassword,
                                "domain": {
                                    "name": this.IAMDoaminId
                                }
                            }
                        }
                    },
                    "scope": {
                        "project": {
                            "name": this.region
                        }
                    }
                }
            }
            let requestData = await this.request('https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens', { //發起網絡資料請求,url/請求頭
                method: 'POST',
                extraData: PostBody,  // 請求體
                header: PostHeader,
                readTimeout: 5000,
                connectTimeout: 5000,
            })
            Logger.info(TAG, `getIAMUserToken header is ${JSON.stringify(requestData.header)}`)//響應頭.Object類型
            Logger.info(TAG, `getIAMUserToken result is ${JSON.stringify(requestData.result)}`)//相應體.string類型
            return requestData.header['X-Subject-Token']
               
    查詢裝置影子資料接口,通過調用此接口查詢指定裝置的裝置影子資訊,相關代碼如下
    async showDeviceShadow(){
            let PostHeader = {
                'Content-Type': 'application/json',
                'X-Auth-Token': this.X_Auth_Token
            }
            let PostBody = {}
            let requestData = await this.request('https://iotda.cn-north-4.myhuaweicloud.com/v5/iot/'+this.project_id+'/devices/'+this.device_id+'/shadow', { //發起網絡資料請求,url/請求頭
                method: 'GET',
                extraData: PostBody,  // 請求體
                header: PostHeader,
                readTimeout: 5000,
                connectTimeout: 5000,
            })
            Logger.info(TAG, `showDeviceShadow header is ${JSON.stringify(requestData.header)}`)//響應頭.Object類型
            Logger.info(TAG, `showDeviceShadow result is ${JSON.stringify(requestData.result)}`)//相應體.string類型
            return JSON.parse(requestData.result).shadow[0].reported.properties
        }
               

五、HarmonyOS應用端代碼

HarmonyOS應用端可以直接使用DevEco Studio自帶的OneHop模闆,需要安裝DevEco Studio 3.0.0.800 Beta2 for HarmonyOS

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

這部分的内容我在之前的文章已經寫過,這裡就不再贅述了,原貼連結 碰一碰實作-開源基礎軟體社群-51CTO.COM

應用端代碼分為兩個子產品,entry和control,entry子產品負責裝置配網,control子產品負責裝置資料采集和裝置控制。

entry配網子產品

模闆中配網預設使用的是NAN配網模式,配網成功率比較差,可以增加SoftAP配網模式,兩種模式配網,增加裝置配網成功率。首先修改

getWifiInfo()

函數。

getWifiInfo() {
        getApp(this).NetConfig.getWifiList((result) => {						// 擷取wifi清單
            if (result.code == 0 && result.data && result.data.length > 0) {	// 如果擷取清單成功
                this.wifiApInfo = result.data[0]
                for (let i = 0;i < result.data.length; i++) {
                    if (result.data[i].hasDefaultPassword) {
                        this.wifiApInfo = result.data[i];
                        break;
                    }
                }
                if (Object.keys(this.wifiApInfo).length == 0) {
                    this.desc = "沒有已連上的wifi"
                    return;
                }
                if (this.isNAN) {
                    this.discoverDeviceByNAN()
                } else {
                    this.startSoftAp()
                }
            } else {															// 否則擷取清單失敗
                this.isFail = true
            }
        });
    },
           

discoverDevice()

函數分解為NAN、SoftAP兩種方式

/************************ NAN配網 *********************************/
discoverDeviceByNAN() {
        this.desc = "開始發現裝置"
        let scanInfo = {
            duration: 5,
            lockTime: 60,
            sessionId: getApp(this).ConfigParams.sessionId
        };
        // Step1 discover the device through the NaN broadcast service.
        getApp(this).NetConfig.discoveryByNAN(scanInfo, (result) => {
            if (result.code == 0) {
                this.desc = "NAN發現裝置成功"
                getApp(this).ConfigParams.deviceInfo = result.data;
                this.registerDisconnectCallback(getApp(this).ConfigParams.deviceInfo.sessionId);
                let connectInfo = {
                    targetDeviceId: getApp(this).ConfigParams.deviceInfo.productId,
                    type: 0,
                    pin: '11111111',
                    password: getApp(this).ConfigParams.deviceInfo.sn,
                    sessionId: getApp(this).ConfigParams.deviceInfo.sessionId
                };
                console.info("netconfig connectInfo" + JSON.stringify(connectInfo))
                this.connectDevice(connectInfo);
            } else {
                this.desc = "NAN發現裝置失敗"
                this.startSoftAp()
            }
        });
    },
/************************ SoftAP配網 *********************************/
    startSoftAp() {
        this.isNAN = false
        this.desc = "softAP配網"
        this.disconnectDevice()
        getApp(this).ConfigParams.deviceInfo.sessionId = ''
        this.discoverDeviceBySoftAp()
    },
    discoverDeviceBySoftAp() {
        if (!this.targetDeviceId) {
            this.desc = "apName為空: " + this.targetDeviceId  //TODO
            return;
        }
        getApp(this).NetConfig.discoveryBySoftAp((result) => {
            console.info("NetConfig# discoveryBySoftAp" + JSON.stringify(result))
            if (result.code == 0) {
                this.desc = "softAP發現成功"
                getApp(this).ConfigParams.deviceInfo = result.data;
                getApp(this).ConfigParams.deviceInfo.sessionId = ''
                let connectInfo = {
                    targetDeviceId: "teamX-Lamp01",
                    // targetDeviceId: this.targetDeviceId, // 裝置ap熱點名,從NFC中tag=5的值擷取
                    type: 1,
                    pin: '11111111',
                    password: '',
                    sessionId: ''
                };
                this.connectDevice(connectInfo);
            } else {
                this.isFail = true
            }
        })
    },
           

連接配接裝置也分為兩種方式:

connectDevice(connectInfo) {
        if (this.isNAN) {
            this.desc = "連接配接裝置中(NAN)"
        } else {
            this.desc = "連接配接裝置中(SoftAp)"
        }
        console.info("Netconfig connectDevice argument" + JSON.stringify(connectInfo))
        // Step2 connect the device.
        getApp(this).NetConfig.connectDevice(connectInfo, (result) => {
            if (result.code === 0) {
                this.desc = "連接配接裝置成功"
                this.configDevice();
            } else {
                console.error("netconfig connectDevice fail" + JSON.stringify(result))
                if (this.isNAN) {
                    this.desc = "連接配接裝置失敗(NAN)"
                    this.startSoftAp()
                } else {
                    this.desc = "連接配接裝置失敗(softAp)"
                    this.isFail = true
                    this.disconnectDevice();
                }
            }
        });
    },
           

配網函數需要做同樣的修改,其他配網方式基本不變。

async configDevice() {
        this.desc = "開始配網"
        let netConfigInfo = {
            ssid: this.wifiApInfo.ssid,
            ssidPassword: '',
            isDefaultPassword: true,
            channel: this.wifiApInfo.channel,
            sessionId: getApp(this).ConfigParams.deviceInfo.sessionId,
            type: this.isNAN ? 0 : 1,
            wifiApId: this.wifiApInfo.wifiApId,
            vendorData: '',
            timeout: 30,
            paramValid: true
        };
        console.info("netconfig configDevice" + JSON.stringify(netConfigInfo))
        // Step4 config the device net.
        getApp(this).NetConfig.configDeviceNet('deviceInfo', 'accountInfo', netConfigInfo, (result) => {
            if (result.code == 0) {
                this.desc = "配網成功"
                // Step5 config the device net success, go to the control.
                this.goToControl();
            } else if (this.isNAN) {
                this.startSoftAp()
            } else {
                this.desc = "配網失敗"
                this.isFail = true
                this.disconnectDevice();
            }
        });
    },
           

兩種方式配網,配網的成功率會增加很多,這種方式參考了OpenHarmony-SIG/knowledge 智慧家居開發樣例。這個倉提供了很多OpenHarmony物聯網裝置的樣例,感興趣的小夥伴,可以仔細研究下。

control控制子產品

新裝置的定義在

3.HarmonyOS_APP/SolarControl/entry/src/main/java/com/zml/solarcontrol/MainAbility.java

。當entry子產品配網成功時,會拉起control子產品界面并将

productName

參數一并傳遞過來。

public class MainAbility extends AceAbility {
    private static final String DEFAULT_MODULE = "default";
    private static final String LOGIN_MODULE = "login";
    private static final String JS_MODULE = DEFAULT_MODULE;
    private static String productId;
    private String productName = "SOLAR";		// 指定裝置名
           

控制子產品下添加一個新的裝置

SOLAR

,其中資源包含在

3.HarmonyOS_APP/SolarControl/control/src/main/js/default/common/SOLAR

檔案夾下,配置檔案包含在

3.HarmonyOS_APP/SolarControl/control/src/main/resources/rawfile/SOLAR

檔案夾下。

#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼

配置流程如下:

1.産品配置檔案
src/main/resources/rawfile/XXXX/XXXX_zh.json

2.UX資源圖
src/main/js/default/common/XXXX/XXXX.png

3.如果使用網絡圖檔
src/main/java/com/liangzili/myonehop/DataHandlerAbility.java
//将網絡字首指派給iconUrl即可
result.put("iconUrl", SampleDeviceDataHandler.EXAMPLE_RESOURCE_DIR + "/" + productName);

4.修改網絡裝置模式
src/main/java/com/liangzili/myonehop/DataHandlerAbility.java
private static final int DEVICE_DATA_MODE = DEVICE_DATA_MODE_NETWORK_DEVICE;

5.添加XXXX裝置的資料處理邏輯
參考NetworkDeviceDataHandler.java中的fanDataModel,模闆中已經實作了一個智能電風扇的資料處理邏輯
           

目前項目基本架構已經實作,還有部分功能在完善中,近期會繼續更新文檔。

繼續閱讀