一、項目介紹
能源危機日益嚴重,發展新能源勢在必行。光伏發電就是不錯的選擇,但是光電轉換效率一直是困擾行業發展的一大難題。本項目通過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一鍵配網
- 擷取裝置端輸入輸出電流電壓。
#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼 #DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼 原理圖中,在太陽能輸入端,锂電池端接分壓電阻。分别接入ADS1115的AIN0和AIN3接口。#DAYU200體驗官#MPPT光伏發電項目 DAYU200、Hi3861、華為雲IotDA一、項目介紹二、項目目錄三、裝置端代碼四、OpenHarmony應用端代碼五、HarmonyOS應用端代碼
檔案夾下移植了ADS1X15 Arduino端驅動代碼到OpenHarmony。電流檢測使用ACS712子產品,接入ADS1115的AIN1和AIN2接口,ADS1115通過I2C子產品與Hi3861通訊。接入主要代碼如下:1.OpenHarmony_Firmware\OH_SolarControl\ADS1X15
#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 = 得到目前電流輸入值。輸出功率(電池或充電電壓)
-
溫度控制
當系統溫度過高時,自動關閉系統。使用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; }
-
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(); }
- mqtt接入華為雲
四、OpenHarmony應用端代碼
-
界面實作
頁面使用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})
-
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 } }
-
華為雲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,模闆中已經實作了一個智能電風扇的資料處理邏輯
目前項目基本架構已經實作,還有部分功能在完善中,近期會繼續更新文檔。