4

MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA

 1 year ago
source link: https://os.51cto.com/article/713273.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
984507b04efcfa1e991478e5ad33d88ee3bf65.png

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

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

一、项目介绍

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

#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA-开源基础软件社区

系统分为三个部分:

视频演示地址:​​https://ost.51cto.com/show/14366​​。

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

#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA-开源基础软件社区

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

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

#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA-开源基础软件社区

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

#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云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一键配网。
  2. 获取设备端输入输出电流电压。
#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA-开源基础软件社区
#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA-开源基础软件社区
#DAYU200体验官#MPPT光伏发电项目 DAYU200、Hi3861、华为云IotDA-开源基础软件社区

原理图中,在太阳能输入端,锂电池端接分压电阻。分别接入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 = 得到当前电流输入值。输出功率(电池或充电电压)

3.温度控制。

当系统温度过高时,自动关闭系统。使用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;
}

4.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();
}

5.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​​。

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

配置流程如下:

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​​。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK