#DAYU200体验官#MPPT光伏发电项目 DAYU200Hi3861华为云IotDA

Posted 开源基础软件社区官方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#DAYU200体验官#MPPT光伏发电项目 DAYU200Hi3861华为云IotDA相关的知识,希望对你有一定的参考价值。

一、项目介绍

​ 能源危机日益严重,发展新能源势在必行。光伏发电就是不错的选择,但是光电转换效率一直是困扰行业发展的一大难题。本项目通过MPPT全称“最大功率点跟踪”(Maximum Power Point Tracking)实时侦测太阳能板的发电电压,并追踪最高电压电流值(VI),使系统以最大功率输出电力。 下图使用300W的光伏太阳能板为4串12V的磷酸铁锂电池进行充电。基本功能已经实现,项目中设备代码、应用端代码、原理图等将全部开源,PCB电路还在调试中。

系统分为三个部分:
视频演示地址:https://ost.51cto.com/show/14366
应用端:

OpenHarmony应用端:使用润和DAYU200开发板,基于ArkUI/eTS开发框架,实现光伏发电控制器应用端,可实时监控光伏控制器设备状态。并将设备数据同步到华为云IotDA,可实现广域网设备状态检测和控制。

HarmonyOS应用端:使用HarmonyOS原子化服务能力,应用免安装。支持NFC碰一碰配网(NAN+SoftAP),配网成功拉起设备控制页面。设备控制模块同OpenHarmony应用端。同时提供服务卡片,可将重要的设备信息添加到桌面,方便随时随地进行查看。

设备端:

设备端为太阳能充放电控制器,输入端接太阳能光伏板,输出端接锂电池等储能设备。主控芯片采用Hi3861,核心算法采用MPPT“最大功率点跟踪”(Maximum Power Point Tracking),可显著提升太阳能光伏板的发电效率。原理图如下:

云端:

云端接入华为云IotDA,负责设备数据采集,下发命令给设备。

二、项目目录

项目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. 获取设备端输入输出电流电压。

    原理图中,在太阳能输入端,锂电池端接分压电阻。分别接入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, `HttpResponses 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

这部分的内容我在之前的文章已经写过,这里就不再赘述了,原贴链接 碰一碰实现-开源基础软件社区-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文件夹下。

配置流程如下:

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,模板中已经实现了一个智能电风扇的数据处理逻辑

目前项目基本框架已经实现,还有部分功能在完善中,近期会继续更新文档。

想了解更多关于开源的内容,请访问:

51CTO 开源基础软件社区

https://ost.51cto.com/#bkwz

以上是关于#DAYU200体验官#MPPT光伏发电项目 DAYU200Hi3861华为云IotDA的主要内容,如果未能解决你的问题,请参考以下文章

matlab simulink光伏发电系统MPPT算法

光伏发电系统及其MPPT控制

基于simulink的PV光伏发电MPPT仿真

#DAYU200体验官# ArkUI eTS实践开发一个管家服务系统

#DAYU200体验官# OpenHarmony3.2的编译烧录

#DAYU200体验官# OpenHarmony标准系统运行docker软件