我們的手機有顯示屏有觸摸屏,可以使用界面的的方式選擇連接那個wifi,然后輸入密碼用于連接網絡。對于iot設備來說沒有觸摸屏也沒有顯示屏,如何把wifi名字和wifi密碼傳輸到iot設備呢?為了解決這個問題所以就有了smartconfig。
ESP smartconfig為ESP8266提供的兩種智能配網使用demo,分別有ESPTouch和AirKiss,或者ESPTouch和AirKiss同時共存。ESPTouch為樂鑫研發的一種配網方式,AirKiss為TI研發的中配網方式,兩種方式原理差不多。
配網方式1:
第一種是AP模式的方式配網,AP模式就是lot設備像路由器那樣生成一個wifi,手機連接lot設備生成的wifi。iot設備運行一個tcp server,手機為tcp client,然后手機通過一個tcp方式將家庭路由器wifi的SSID和Password發送到iot設備。lot設備接收到wifi賬號和密碼之后從AP模式切換回Station模式,然后用過手機發送下來的SSID和Password連接家庭的路由器,lot連接上家庭路由器wifi之后,廣播UDP數據包,數據包里面有lot設備名字和設備id和ip地址。手機在發送完家庭路由的wifi SSID和Password之后斷開lot的wifi去連接家庭路由器wifi,然后監聽等lot設備連上路由wifi后的發送的UDP數據包,然后通過UDP數據包中lot設備名字和設備id和ip地址知道那個設備已經連接上家庭路由,然后顯示出來通知用戶。
第二種配網方式就是我們要說的smartconfig方式,上面的AP模式的成功率為100%,但是有一點不好的是非常繁瑣需要用戶連接lot設備的wifi,smartconfig的方式優點是配網方式十分簡單,但是不好的地方為由可能會配網失敗或者有的家庭路由器不支持。smartconfig的原理是需要配網的iot設備配置為混雜模式,監聽網絡中的所有報文,然后手機發送UDP報文,在UDP報文中含有家庭路由的wifi密碼和wifi ssid名字,因為在2.4g這個頻段中有非常多的UDP報文和且有十幾信道,所以iot需要解析很多報文,而且還需要不斷的切換信道去抓取,所以有可能抓不到手機發送出來的UDP報文,從而配置失敗。
ESP8266_RTOS_SDK\examples\wifi\smart_config\main\smartconfig_main.c
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "tcpip_adapter.h"
#include "esp_smartconfig.h"
#include "smartconfig_ack.h"
/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t wifi_event_group;
/* The event group allows multiple bits for each event,
but we only care about one event - are we connected
to the AP with an IP? */
static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "sc";
void smartconfig_example_task(void * parm);
static esp_err_t event_handler(void *ctx, system_event_t *event)
{
/* For accessing reason codes in case of disconnection */
system_event_info_t *info = &event->event_info;
switch(event->event_id) {
case SYSTEM_EVENT_STA_START:
//step 4 wifi初始化化成功建立一個任務smartconfig_example_task
xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
break;
case SYSTEM_EVENT_STA_GOT_IP:
//step 8 lot設備連接上wifi
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
ESP_LOGE(TAG, "Disconnect reason : %d", info->disconnected.reason);
if (info->disconnected.reason == WIFI_REASON_BASIC_RATE_NOT_SUPPORT) {
/*Switch to 802.11 bgn mode */
esp_wifi_set_protocol(ESP_IF_WIFI_STA, WIFI_PROTOCAL_11B | WIFI_PROTOCAL_11G | WIFI_PROTOCAL_11N);
}
esp_wifi_connect();
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
break;
default:
break;
}
return ESP_OK;
}
//step 3
static void initialise_wifi(void)
{
//初始化tcpip 適配器
tcpip_adapter_init();
//初始化事件標準組用來事件同步
wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK( esp_event_loop_init(event_handler, NULL) );
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
//設置為station模式啟動wifi
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
//step 6
static void sc_callback(smartconfig_status_t status, void *pdata)
{
switch (status) {
case SC_STATUS_WAIT:
ESP_LOGI(TAG, "SC_STATUS_WAIT");
break;
case SC_STATUS_FIND_CHANNEL:
ESP_LOGI(TAG, "SC_STATUS_FINDING_CHANNEL");
break;
case SC_STATUS_GETTING_SSID_PSWD:
ESP_LOGI(TAG, "SC_STATUS_GETTING_SSID_PSWD");
break;
//step 7
case SC_STATUS_LINK:
//解析UPD包獲取到路由器的wifi和密碼
ESP_LOGI(TAG, "SC_STATUS_LINK");
wifi_config_t *wifi_config = pdata;
ESP_LOGI(TAG, "SSID:%s", wifi_config->sta.ssid);
ESP_LOGI(TAG, "PASSWORD:%s", wifi_config->sta.password);
ESP_ERROR_CHECK( esp_wifi_disconnect() );
//配置wifi賬號和wifi密碼
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, wifi_config) );
//開始連接wifi
ESP_ERROR_CHECK( esp_wifi_connect() );
break;
case SC_STATUS_LINK_OVER:
ESP_LOGI(TAG, "SC_STATUS_LINK_OVER");
if (pdata != NULL) {
sc_callback_data_t *sc_callback_data = (sc_callback_data_t *)pdata;
switch (sc_callback_data->type) {
case SC_ACK_TYPE_ESPTOUCH:
ESP_LOGI(TAG, "Phone ip: %d.%d.%d.%d", sc_callback_data->ip[0], sc_callback_data->ip[1], sc_callback_data->ip[2], sc_callback_data->ip[3]);
ESP_LOGI(TAG, "TYPE: ESPTOUCH");
break;
case SC_ACK_TYPE_AIRKISS:
ESP_LOGI(TAG, "TYPE: AIRKISS");
break;
default:
ESP_LOGE(TAG, "TYPE: ERROR");
break;
}
}
xEventGroupSetBits(wifi_event_group, ESPTOUCH_DONE_BIT);
break;
default:
break;
}
}
//step 5
void smartconfig_example_task(void * parm)
{
EventBits_t uxBits;
//設置smartconfig為ESPTOUCH和AIRKISS兩種方式共存
ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS) );
//啟動esp_smartconfig_start
ESP_ERROR_CHECK( esp_smartconfig_start(sc_callback) );
while (1) {
uxBits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
if(uxBits & CONNECTED_BIT) {
ESP_LOGI(TAG, "WiFi Connected to ap");
}
if(uxBits & ESPTOUCH_DONE_BIT) {
ESP_LOGI(TAG, "smartconfig over");
esp_smartconfig_stop();
vTaskDelete(NULL);
}
}
}
void app_main()
{
//step 1初始化nvs flash
ESP_ERROR_CHECK( nvs_flash_init() );
//step 2初始化wifi
initialise_wifi();
}
1.make flash -j8
2.在蘋果商店或者安卓商店搜索ESPTOUCH,下載手機端軟件。或者自己下載源代碼編譯
安卓:(https://github.com/EspressifApp/EsptouchForAndroid)
蘋果:(https://github.com/EspressifApp/EsptouchForIOS)
下面是esp8266燒錄后的打印輸出。
I (162) boot: Loaded app from partition at offset 0x10000
mode : sta(ec:fa:bc:1d:33:e0)
add if0
SC version: V2.5.4
scandone
scandone
I (5) sc: SC_STATUS_FINDING_CHANNEL
TYPE: ESPTOUCH
T|PHONE MAC: 90 f0 52 0c 16 2d
T|AP MAC : bc 5f f6 1b e8 1c
I (8) sc: SC_STATUS_GETTING_SSID_PSWD
T|pswd: 1234567890
T|ssid: TEST001
I (17) sc: SC_STATUS_LINK
I (17) sc: SSID:TEST001
I (17) sc: PASSWORD:1234567890
scandone
state: 0 -> 2 (b0)
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 2
pm open phy_2,type:2 0 0
cnt
connected with TEST001, channel 3
I (22) event: sta ip: 192.168.0.108, mask: 255.255.255.0, gw: 192.168.0.1
I (22) sc: WiFi Connected to ap
I (25) sc: SC_STATUS_LINK_OVER
I (25) sc: Phone ip: 192.168.0.108
I (25) sc: smartconfig over
通過上面的代碼smartconfig_main.c,上面的step1到step8就是smartconfig配置的步驟,不理解的同學可以仔細看看步驟加深理解
本文分享自華為云社區《華為云IoT+OpenHarmony的智能家居開發【華為云IoT+鴻蒙】華為云IoT+鴻蒙-云社區-華為云》,作者:袁睿。
1. 選題為基于OpenHarmony的智能家居,應用場景為戶用,受益人群為住戶。
2. 開發的軟件設備為智能門鎖,儲物精靈,軟硬件開發都有的是光伏逆變器。
3. 解決的問題:
傳統的智能家居:智能單品,需要手動加入場景,無網不能智能控制
創新的智能家居:空間智能,自發現后融入場景,分布式軟總線控制
4. 關鍵功能點:
智能門鎖:密碼解鎖,NFC解鎖,數字管家控制,服務卡片控制
儲物精靈:密碼解鎖,NFC解鎖,防火簾控制,分布式軟總線控制
逆變器:單相逆變,隔離拓撲,組件小型化,高轉換率與低總諧波失真
1. 操作系統:OpenHarmony 3.0
2. 開發軟件:VS code(Deveco studio tool),DevEco Studio
3. 開發板:深開鴻KHDVK-3861B,潤和DAYU200,潤和hispark AI Camera
3. 關于環境:
操作系統:Ubuntu 編譯構建:Python
包管理工具:HPM NPM 環境:Node.js
燒錄軟件:Hiburn USB串口驅動:CH341SER.exe
本地ssh:finalshell ssh文件對比:Beyond Conpare
4. 虛擬機
(1) 虛擬機環境
Ubuntu(華為的硬件開發一般都在此linux環境下進行)
虛擬機Vmware:下載之后使用上述提到的華為云中國鏡像。
下載VS code的Linux版與OpenHarmony3.0源碼。
(2)虛擬機環境:
將Ubuntu Shell環境修改為bash:ls -l /bin/sh
在下載VS code后下載華為硬件編制插件(Device tool)
(3) HB編譯插件:
安裝:python3 -m pip install --user ohos-build
變量:vim ~/.bashrc
復制粘貼到.bashrc的最后一行:export PATH=~/.local/bin:$PATH
更新變量:source ~/.bashrc
檢查:pip install ohos-build
5. 逆變器的主要硬件選材:
(1)選材方案(選型依據)
Diode(二極管):高頻檢波電路,小型化,配對二極管不混組
Inductor(感應器):標稱電感值,自共振頻率(與互感值成正比)直流電阻(盡可能小),額定電流,直流重疊允許電流,溫度上升允許電流。
Resistor(電阻器):貼片電阻,根據穩定性需求選擇薄膜或厚膜
SPICE NMOS:封裝尺寸,基本上封裝越大越好,能承受的電流就越大;導通電壓Vgs,盡量選擇比實際電路中可提供的控制電壓小的;導通電阻Rds,越小越好相應的導通電阻越小、分壓越小、發熱也越小。寄生電容Cgs,會影響mos的打開速度。寄生電容太大,方波會失真Rds越小,Cgs越大。
(2)主要選材
半導體選材:國產半導體RX65T125PS2A
電源IC選材:國產IC芯片ID7s625
DSP處理器:洞察到STM32系列軟合了DSP處理器的功能。
(一)儲物精靈
(1)用戶角度:先從用戶角度考慮需求與如何使用,再從技術層面解析
(具體用戶使用方法這里不多贅述,詳細內容直接看下午開發內容)
(2)實現原理:(下文的步驟會詳細介紹,在這里先介紹初期設想)
① 關于Mqtt協議在華為云的打通(設備在線激活):使用mqttX或mqttfx。
② 華為云:根據提示創建并獲取密鑰等信息,獲取ClientID等身份識別信息,然后在云端的Topic(事件主題)中自定義訂閱與發布,對產品進行定義。
③ AppGallery Connect網站:創建并注冊HarmonyOS產品,根據提示流程。
④ 設備開發具體解析:每個設備都是一個子節點,實現了基于OpenHarmony設備的L0、L1、L2設備之間的互聯互通。主控程序基于 OpenHarmony JS應用程序框架設計實現,并使用MQTT物聯網通信協議接入華為云IOT平臺,同時可將控制指令發送至華為云IOT平臺,供云端處理。DAYU開發板(軟件+硬件)具體實現為中控MQTT通信過程處于內核態驅動程序,JS應用通過發起接口調用后,進入用戶態調用內核態接口的流程,并且JS應用會將所需要向云端發送的MQTT協議主題內容直接傳入內核態,內核態不作數據處理和解析,直接將數據發布至云端,這樣設計的目的是為了在添加設備的時候,僅需改變JS應用的數據結構,并不需要修改設備的代碼,完成了解耦合。
NFC錄入與記錄:使用NFC擴展板錄入,詳細請見下方軟總線設備通訊鏈接。
⑤ 智能識別對比:識別對象的數據庫,這里的識別作為單一的照片識別。vuforia 的服務器制作該 target 的識別數據庫,該數據庫要下載并綁定工程到target。圖片由攝像頭獲取視頻逐幀比對。
(3)設備側
第一步:網絡連接 使設備接電后自動聯網
我們會在代碼中預置熱點名稱與密碼
在創建包后新建mqtt_entry.c用于存放熱點自連代碼的地址:
/home/open/Downloads/code-v3.0-LTS/OpenHarmony/applications/sample/wifi-iot/app/mqtt_demo
{
int ret;
errno_t rc;
hi_wifi_assoc_request assoc_req = {0};
/* 拷貝SSID到assoc的req */
/* copy SSID to assoc_req */
rc = memcpy_s(assoc_req.ssid, HI_WIFI_MAX_SSID_LEN + 1, "rui666", 8); //熱點名
/* WPA-PSK. CNcomment: 認證類型:WPA2-PSK.CNend */
if (rc != EOK) {
return -1;
}
//熱點加密方式
assoc_req.auth = HI_WIFI_SECURITY_WPA2PSK;
memcpy(assoc_req.key, "88888888", 11); //熱點的密碼
ret = hi_wifi_sta_connect(&assoc_req);
if (ret != HISI_OK) {
return -1;
}
return 0;
} //預置熱點名和密碼 在設備通電后會自連
這里把原有的ability等量代換成了自發現熱點。
*OpenHarmony_ability的碰一碰自發現與自配網見下述。
第二步:上報訂閱與下發,在此包內創建main函數
/home/open/Downloads/code-v3.0-LTS/OpenHarmony/applications/sample/wifi-iot/app/mqtt_demo
void mqtt_callback(MessageData *msg_data)
{
size_t res_len = 0;
uint8_t *response_buf = NULL;
char topicname[45] = { "$crsp/" };
LOS_ASSERT(msg_data);
printf("topic %.*s receive a message\r\n", msg_data->topicName->lenstring.len, msg_data->topicName->lenstring.data);
printf("message is %.*s\r\n",
msg_data->message->payloadlen,
msg_data->message->payload);
}
int mqtt_connect(void)
{
int rc = 0;
NetworkInit(&n);
NetworkConnect(&n, "a161fa3144.iot-mqtts.cn-north-4.myhuaweicloud.com", 1883);
buf_size = 4096+1024;
onenet_mqtt_buf = (unsigned char *) malloc(buf_size);
onenet_mqtt_readbuf = (unsigned char *) malloc(buf_size);
if (!(onenet_mqtt_buf && onenet_mqtt_readbuf))
{
printf("No memory for MQTT client buffer!");
return -2;
}
MQTTClientInit(&mq_client, &n, 1000, onenet_mqtt_buf, buf_size, onenet_mqtt_readbuf, buf_size);
MQTTStartTask(&mq_client);
data.keepAliveInterval = 30;
data.cleansession = 1;
data.clientID.cstring = "61f6e729de9933029be57672_88888888_0_0_2022020905";
data.username.cstring = "61f6e729de9933029be57672_88888888";
data.password.cstring = "43872acc0b1e6aa7bf9e6a69f12aa9b1ebc07daffb67e18cf905c847a594f813";
data.cleansession = 1;
mq_client.defaultMessageHandler = mqtt_callback;
//連接服務器
rc = MQTTConnect(&mq_client, &data);
//訂閱消息,設置回調函數
MQTTSubscribe(&mq_client, "porsche", 0, mqtt_callback);
while(1)
{
MQTTMessage message;
message.qos = QOS1;
message.retained = 0;
message.payload = (void *)"openharmony";
message.payloadlen = strlen("openharmony");
//上報
if (MQTTPublish(&mq_client, "hi3861", &message) < 0)
{
printf("MQTTPublish faild !\r\n");
}
IoTGpioSetOutputVal(9, 0); //9gpio 0 light on
usleep(1000000);
}
return 0;
}
第三步:儲物精靈保險模式&舵機開門
舵機開鎖:
int servoID =0;
void My_servo(uint8_t servoID,int angle)
{
int j=0;
int k=2000/200;
angle = k*angle;
for (j=0;j<5;j++){
IoTGpioSetOutputVal(servoID, 1);
hi_udelay(angle);
IoTGpioSetOutputVal(servoID, 1);
hi_udelay(20000-angle);
}
}
保險模式:
static int DealSetPassword(cJSON *objCmd)
{
int ret = -1;
char *pstr = NULL;
cJSON *objParas = NULL;
cJSON *objPara = NULL;
CommandParamSetPsk setLockPskParam;
memset(&setLockPskParam, 0x00, sizeof(CommandParamSetPsk));
if ((objParas = cJSON_GetObjectItem(objCmd, "paras")) == NULL) {
RaiseLog(LOG_LEVEL_ERR, "Paras not exist");
return ret;
}
if ((objPara = cJSON_GetObjectItem(objParas, "PskId")) != NULL) {
char *id = cJSON_GetStringValue(objPara); //密碼標識(string型)
if (id == NULL || strlen(id) > LOCK_ID_LENGTH) {
RaiseLog(LOG_LEVEL_ERR, "check lock id failed!");
return -1;
}
strncpy(setLockPskParam.id, id, strlen(id));
} else {
return ret;
}
if ((objPara = cJSON_GetObjectItem(objParas, "Option")) != NULL) {
char *option = cJSON_GetStringValue(objPara);
printf("option = %c \n", *option); //三個命令(string型)
if (*option == 'A') {
setLockPskParam.option = OPTION_ADD; //新增密碼
} else if (*option == 'U') {
setLockPskParam.option = OPTION_UPDATE; //更新密碼
} else if (*option == 'D') {
setLockPskParam.option = OPTION_DELETE; //刪除密碼
} else {
RaiseLog(LOG_LEVEL_ERR, "no such option(%c)!", *option);
return -1;
}
} else {
return ret;
}
if ((objPara = cJSON_GetObjectItem(objParas, "LockPsk")) != NULL) {
char *psk = cJSON_GetStringValue(objPara);
if (psk == NULL || strlen(psk) > LOCK_PSK_LENGTH) {
RaiseLog(LOG_LEVEL_ERR, "check psk failed!");
return -1;
}
strncpy(setLockPskParam.password, psk, strlen(psk));
} else {
return ret;
}
ret = IotProfile_CommandCallback(CLOUD_COMMAND_SETPSK, &setLockPskParam);
return ret;
}
第四步:標注GPIO口
識別GPIO口與接入(這里要注意一個接的是正極一個是接地還有一個為信號傳輸口)
void mqtt_test(void)
{
IoTGpioInit(9);
IoTGpioSetDir(9, IOT_GPIO_DIR_OUT);
mqtt_connect();
}
第五步:吊起mqtt協議(build.gn版)
與主函數平行的Build.gn,吊起函數與第三方庫的內容:
sources = [
"mqtt_test.c",
"mqtt_entry.c"
]
include_dirs = [
"//utils/native/lite/include",
"//kernel/liteos_m/components/cmsis/2.0",
"//base/iot_hardware/interfaces/kits/wifiiot_lite",
"//vendor/hisi/hi3861/hi3861/third_party/lwip_sack/include",
"//foundation/communication/interfaces/kits/wifi_lite/wifiservice",
"//third_party/pahomqtt/MQTTPacket/src",
"//third_party/pahomqtt/MQTTClient-C/src",
"//third_party/pahomqtt/MQTTClient-C/src/liteOS",
"//kernel/liteos_m/kal/cmsis",
"//base/iot_hardware/peripheral/interfaces/kits",
]
deps = [
"//third_party/pahomqtt:pahomqtt_static", //吊起MQTT協議
]
}
Build.gn:與APP并列的build.gn用于指出要編譯的主函數,可以使用startup后面跟要編譯的主包也可以直接features進行選中,在這里可以把不需要參與編譯的項目通過#給注釋掉。
在start_up里的builld.gn:
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"mqtt_demo:mqtt_test", //標注主函數,指定位置編譯
]
儲物精靈Pro版(識別功能版):(使用第三方平臺:Vuforia)
我們的原理就是上傳畫面到云端,然后逐幀分解比對(此功能目前還在完善)
(4)軟件側(偏向軟件,但是還是嵌入式開發)
步驟一:接收服務器的存儲代碼
exports.i***ta2=function(req,res){
console.log("iot_data:",req)
const url = new URL("Get the URL provided by HUAWEI CLOUD"+req.url) //The address configured inside the forwarding destination
let properties = JSON.stringify(req.body.notify_data.body.services)
console.log("Store data:",properties)
let addArr = [properties]
var addSql = 'INSERT INTO sesnor_Record(properties) VALUES (?)'
var callBack = function(err,data){
console.log("error:"+err)
console.log("Property insertion result:"+JSON.stringify(data))
res.send(data)
}
sqlUtil.sqlContent(addSql,addArr,callBack)
}
步驟二:射頻貼紙&復旦卡拉取本地方案
寫入復旦卡請用第三方的軟件,NFC射頻貼紙使用應用調試助手(華為應用市場可下載)。
void RC522_Config ( void )
{
uint8_t ucStatusReturn; //Returns the status
uint8_t flag_station = 1; //Leave the flag bit of the function
while ( flag_station )
{
/* Seek cards (method: all in the range), the first search fails again, and when the search is successful, the card sequence is passed into the array ucArray_ID*/
if ( ( ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID ) ) != MI_OK )
ucStatusReturn = PcdRequest ( PICC_REQALL, ucArray_ID );
if ( ucStatusReturn == MI_OK )
{
/* An anti-collision operation in which the selected sequence of cards is passed into an array ucArray_ID */
if ( PcdAnticoll ( ucArray_ID ) == MI_OK )
{
if ( PcdSelect ( ucArray_ID ) == MI_OK )
{
printf ("\nRC522 is Ready!\n");
flag_station = 0;
}
}
}
}
}
步驟三:智能窗簾軌解決方案
因為馬達可以無限前后方向的對窗簾軌進行拉動所以選擇馬達來進行拉動。另外要注意馬達的功率來判斷是否加入繼電器,詳情請移步源碼倉(購買馬達時要向供應商索要相關數據,同時向開發板供應商索要已公開的腳位置圖)
static void RtcTimeUpdate(void)
{
extern int SntpGetRtcTime(int localTimeZone, struct tm *rtcTime);
struct tm rtcTime;
SntpGetRtcTime(CONFIG_LOCAL_TIMEZONE,&rtcTime);
RaiseLog(LOG_LEVEL_INFO, "Year:%d Month:%d Mday:%d Wday:%d Hour:%d Min:%d Sec:%d", \
rtcTime.tm_year + BASE_YEAR_OF_TIME_CALC, rtcTime.tm_mon + 1, rtcTime.tm_mday,\
rtcTime.tm_wday, rtcTime.tm_hour, rtcTime.tm_min, rtcTime.tm_sec);
if (rtcTime.tm_wday > 0) {
g_appController.curDay = rtcTime.tm_wday - 1;
} else {
g_appController.curDay = EN_SUNDAY;
}
g_appController.curSecondsInDay = rtcTime.tm_hour * CN_SECONDS_IN_HOUR + \
rtcTime.tm_min * CN_SECONDS_IN_MINUTE + rtcTime.tm_sec + 8; // add 8 ms offset
}
static uint32_t Time2Tick(uint32_t ms)
{
uint64_t ret;
ret = ((uint64_t)ms * osKernelGetTickFreq()) / CN_MINISECONDS_IN_SECOND;
return (uint32_t)ret;
}
#define CN_REACTION_TIME_SECONDS 1
static void BoardLedButtonCallbackF1(char *arg)
{
static uint32_t lastSec = 0;
uint32_t curSec = 0;
RaiseLog(LOG_LEVEL_INFO, "BUTTON PRESSED");
curSec = g_appController.curSecondsInDay;
if((curSec) < (lastSec + CN_REACTION_TIME_SECONDS)) {
RaiseLog(LOG_LEVEL_WARN, "Frequecy Pressed Button");
return;
}
lastSec = curSec;
g_appController.curtainStatus = CN_BOARD_SWITCH_ON;
g_appController.pwmLedDutyCycle = \
g_appController.pwmLedDutyCycle > 0 ? g_appController.pwmLedDutyCycle:CONFIG_LED_DUTYCYCLEDEFAULT;
osEventFlagsSet(g_appController.curtainEvent, CN_LAMP_EVENT_SETSTATUS);
return;
}
static void BoardLedButtonCallbackF2(char *arg)
{
uint32_t lastSec = 0;
uint32_t curSec = 0;
RaiseLog(LOG_LEVEL_INFO, "BUTTON PRESSED");
curSec = g_appController.curSecondsInDay;
if ((curSec) < (lastSec + CN_REACTION_TIME_SECONDS)) {
RaiseLog(LOG_LEVEL_WARN, "Frequecy Pressed Button");
return;
}
lastSec = curSec;
g_appController.curtainStatus = CN_BOARD_SWITCH_OFF;
osEventFlagsSet(g_appController.curtainEvent, CN_LAMP_EVENT_SETSTATUS);
return;
}
#define CURTAIN_MOTOR_GPIO_IDX 8
#define WIFI_IOT_IO_FUNC_GPIO_8_GPIO 0
#define WIFI_IOT_IO_FUNC_GPIO_14_GPIO 4
#define MOTOR_WORK_SECOND 6
static void E53SC1_MotorInit(void)
{
IoTGpioInit(CURTAIN_MOTOR_GPIO_IDX);
IoTGpioSetFunc(CURTAIN_MOTOR_GPIO_IDX, WIFI_IOT_IO_FUNC_GPIO_8_GPIO);
IoTGpioSetDir(CURTAIN_MOTOR_GPIO_IDX, IOT_GPIO_DIR_OUT); //設置GPIO_8為輸出模式
return;
}
static void E53SC1_SetCurtainStatus(int curtainStatus)
{
if ((curtainStatus == CN_BOARD_CURTAIN_OPEN) || (curtainStatus == CN_BOARD_CURTAIN_CLOSE)) {
IoTGpioSetOutputVal(CURTAIN_MOTOR_GPIO_IDX, 1); //設置GPIO_8輸出高電平打開電機
sleep(MOTOR_WORK_SECOND);
IoTGpioSetOutputVal(CURTAIN_MOTOR_GPIO_IDX, 0); //設置GPIO_8輸出低電平關閉電機
}
return;
}
static void DataCollectAndReport(const void *arg)
{
(void)arg;
uint32_t curtainEvent;
uint32_t waitTicks;
waitTicks = Time2Tick(CONFIG_SENSOR_SAMPLE_CYCLE);
while (1) {
curtainEvent = osEventFlagsWait(g_appController.curtainEvent, CN_CURTAIN_EVENT_SETSTATUS, \
osFlagsWaitAny, waitTicks);
if (curtainEvent & CN_CURTAIN_EVENT_SETSTATUS) {
RaiseLog(LOG_LEVEL_INFO, "GetEvent:%08x", curtainEvent);
E53SC1_SetCurtainStatus(g_appController.curtainStatus);
}
(void) IotProfile_Report(g_appController.curtainStatus);
}
return;
}
static int UpdateShedule(CommandParamSetShedule *shedule)
{
if (shedule->num == 1 && shedule->day[0] == 0) { // set the one time schedule to current weekday
shedule->day[0] = (g_appController.curDay + 1);
}
switch (shedule->option) {
case 'A':
IOT_ScheduleAdd(shedule->scheduleID, shedule->day, shedule->num, shedule->startHour * CN_SECONDS_IN_HOUR +\
shedule->startMinute * CN_SECONDS_IN_MINUTE, shedule->duration, shedule->shedulecmd.cmd, 0);
break;
case 'U':
IOT_ScheduleUpdate(shedule->scheduleID, shedule->day, shedule->num, shedule->startHour * CN_SECONDS_IN_HOUR +\
shedule->startMinute * CN_SECONDS_IN_MINUTE, shedule->duration, shedule->shedulecmd.cmd, 0);
break;
case 'D':
IOT_ScheduleDelete(shedule->scheduleID, shedule->day, shedule->num, shedule->startHour * CN_SECONDS_IN_HOUR +\
shedule->startMinute * CN_SECONDS_IN_MINUTE, shedule->duration, shedule->shedulecmd.cmd, 0);
break;
default:
RaiseLog(LOG_LEVEL_ERR, "the schedule has no such option!\n");
break;
}
return 0;
}
void CurrentTimeCalcTimerHander(){
g_appController.curSecondsInDay ++;
}
#define TimeCalcTicks_NUMBER 100
#define CN_MINISECONDS_IN_1000MS 1000
static void CurtainShedule(void)
{
int startSecondInDay = 0;
int endSecondInDay = 0;
int settingCmd = 0;
int executeTaskTime = 0; // indicate the do something busy
osTimerId_t CurrentTimeCalc_id;
CurrentTimeCalc_id = osTimerNew(CurrentTimeCalcTimerHander, osTimerPeriodic, NULL, NULL);
osTimerStart(CurrentTimeCalc_id, TimeCalcTicks_NUMBER);
while (1) {
osDelay(Time2Tick(CN_MINISECONDS_IN_1000MS));
if (g_appController.curSecondsInDay >= CN_SECONS_IN_DAY) {
g_appController.curSecondsInDay = 0;
g_appController.curDay++;
if (g_appController.curDay >= EN_DAYALL) {
g_appController.curDay = EN_MONDAY;
}
IOT_ScheduleSetUpdate(1);
}
// check if we need do some task here
if (IOT_ScheduleIsUpdate(g_appController.curDay, g_appController.curSecondsInDay)) {
if (executeTaskTime > 0) {
executeTaskTime = 0;
if (g_appController.curtainStatus == CN_BOARD_CURTAIN_OPEN) {
g_appController.curtainStatus = CN_BOARD_CURTAIN_CLOSE;
osEventFlagsSet(g_appController.curtainEvent, CN_CURTAIN_EVENT_SETSTATUS);
}
}
startSecondInDay = IOT_ScheduleGetStartTime();
endSecondInDay = startSecondInDay + IOT_ScheduleGetDurationTime();
IOT_ScheduleGetCommand(&settingCmd, NULL);
}
RaiseLog(LOG_LEVEL_INFO, "start:%d end:%d cur:%d",startSecondInDay, endSecondInDay, g_appController.curSecondsInDay);
if ((endSecondInDay == startSecondInDay) && (g_appController.curSecondsInDay == endSecondInDay)) {
if (g_appController.curtainStatus != settingCmd) {
RaiseLog(LOG_LEVEL_INFO, "Triggering");
g_appController.curtainStatus = settingCmd;
osEventFlagsSet(g_appController.curtainEvent, CN_CURTAIN_EVENT_SETSTATUS);
}
IOT_ScheduleSetUpdate(1);
}
}
return;
}
int IotProfile_CommandCallback(int command, void *buf)
{
CommandParamSetShedule setSheduleParam;
CommandParamSetCurtain setCurtainParam;
//CommandParamSetDutyCycle setDutyCycleParam;
CLOUD_CommandType cmd = (CLOUD_CommandType)command;
if (cmd == CLOUD_COMMAND_SETCURTAIN_STATUS) {
setCurtainParam = *(CommandParamSetCurtain *)buf;
g_appController.curtainStatus = setCurtainParam.status;
RaiseLog(LOG_LEVEL_INFO, "setCurtainParam.status:%d\r\n", setCurtainParam.status);
osEventFlagsSet(g_appController.curtainEvent, CN_LAMP_EVENT_SETSTATUS);
return 0;
} else if (cmd == CLOUD_COMMAND_SETSHEDULE) {
setSheduleParam = *(CommandParamSetShedule *)buf;
RaiseLog(LOG_LEVEL_INFO, "setshedule:day:%d hour:%d minute:%d duration:%d \r\n", \
setSheduleParam.day,setSheduleParam.startHour,setSheduleParam.startMinute, setSheduleParam.duration);
return UpdateShedule(&setSheduleParam);
}
return -1;
}
static int IotWifiInfo_get(char *ssid, int id_size, char *pwd, int pd_size)
{
int retval = UtilsGetValue(SID_KEY, ssid, id_size);
if (retval <= 0) {
RaiseLog(LOG_LEVEL_ERR, "no such ssid stored! \n");
return 0;
}
if ( UtilsGetValue(PWD_KEY, pwd, pd_size) < 0) {
RaiseLog(LOG_LEVEL_INFO, "ssid(%s) no password stored! \n", ssid);
} else {
RaiseLog(LOG_LEVEL_INFO, "ssid : %s, pwd : %s! \n", ssid, pwd);
}
return 1;
}
static void IotWifiInfo_set(char *ssid, char *pwd)
{
if (UtilsSetValue(SID_KEY, ssid) != 0) {
RaiseLog(LOG_LEVEL_ERR, "store ssid failed! \n");
return;
}
if (UtilsSetValue(PWD_KEY, pwd) != 0) {
RaiseLog(LOG_LEVEL_ERR, "store password failed! \n");
UtilsDeleteValue(SID_KEY);
return;
}
RaiseLog(LOG_LEVEL_INFO, "store password success! \n");
}
static void IotMainTaskEntry(const void *arg)
{
osThreadAttr_t attr;
NfcInfo nfcInfo;
(void)arg;
char ssid[BUFF_SIZE] = {0};
char pwd[BUFF_SIZE] = {0};
int ret = 0;
g_appController.pwmLedDutyCycle = CONFIG_LED_DUTYCYCLEDEFAULT;
BOARD_InitPwmLed();
BOARD_InitWifi();
E53SC1_MotorInit();
IOT_ScheduleInit();
ret = Board_IsButtonPressedF2();
osDelay(MAIN_TASK_DELAY_TICKS);
LedFlashFrequencySet(CONFIG_FLASHLED_FRENETCONFIG);
nfcInfo.deviceID = "6136ceba0ad1ed02866fa3b2_Curtain01";
nfcInfo.devicePWD = "12345678";
if (ret) {
RaiseLog(LOG_LEVEL_INFO, "Netconfig Button has pressed! \n");
if (BOARD_NAN_NetCfgStartConfig(SOFTAP_NAME, ssid, sizeof(ssid), pwd, sizeof(pwd)) < 0) {
RaiseLog(LOG_LEVEL_ERR, "BOARD_NetCfgStartConfig failed! \n");
return;
} else {
ret = AFTER_NETCFG_ACTION;
}
} else {
ret = IotWifiInfo_get(ssid, sizeof(ssid), pwd, sizeof(pwd));
if (ret == 0) {
if (BOARD_NAN_NetCfgStartConfig(SOFTAP_NAME, ssid, sizeof(ssid), pwd, sizeof(pwd)) < 0) {
RaiseLog(LOG_LEVEL_ERR, "BOARD_NetCfgStartConfig failed! \n");
return;
} else {
ret = AFTER_NETCFG_ACTION;
}
}
}
LedFlashFrequencySet(CONFIG_FLASHLED_FREWIFI);
if (BOARD_ConnectWifi(ssid, pwd) != 0) {
RaiseLog(LOG_LEVEL_ERR, "BOARD_ConnectWifi failed! \n");
if (ret == AFTER_NETCFG_ACTION) {
NotifyNetCfgResult(NETCFG_DEV_INFO_INVALID);
}
hi_hard_reboot(HI_SYS_REBOOT_CAUSE_CMD);
return;
}
if (ret == AFTER_NETCFG_ACTION) {
RaiseLog(LOG_LEVEL_DEBUG, "Connect wifi success ! \n");
NotifyNetCfgResult(NETCFG_OK);
osDelay(MAIN_TASK_DELAY_TICKS);
RaiseLog(LOG_LEVEL_DEBUG, "StopNetCfg wifi success ! \n");
StopNetCfg();
IotWifiInfo_set(ssid, pwd);
}
LedFlashFrequencySet(CONFIG_FLASHLED_FRECLOUD);
RtcTimeUpdate();
if (CLOUD_Init() != 0) {
return;
}
if (CLOUD_Connect(nfcInfo.deviceID, nfcInfo.devicePWD, \
CONFIG_CLOUD_DEFAULT_SERVERIP, CONFIG_CLOUD_DEFAULT_SERVERPORT) != 0) {
return;
}
LedFlashFrequencySet(CONFIG_FLASHLED_WORKSWELL);
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = CONFIG_TASK_DEFAULT_STACKSIZE;
attr.priority = CONFIG_TASK_DEFAULT_PRIOR;
attr.name = "DataCollectAndReport";
if (osThreadNew((osThreadFunc_t)DataCollectAndReport, NULL, (const osThreadAttr_t *)&attr) == NULL) {
return;
}
attr.name = "CurtainShedule";
if (osThreadNew((osThreadFunc_t)CurtainShedule, NULL, (const osThreadAttr_t *)&attr) == NULL) {
return;
}
return;
}
static void IotMainEntry(void)
{
osThreadAttr_t attr;
RaiseLog(LOG_LEVEL_INFO, "DATA:%s Time:%s \r\n",__FUNCTION__, __DATE__, __TIME__);
g_appController.curtainEvent = osEventFlagsNew(NULL);
if ( g_appController.curtainEvent == NULL) {
return;
}
// Create the IoT Main task
attr.attr_bits = 0U;
attr.cb_mem = NULL;
attr.cb_size = 0U;
attr.stack_mem = NULL;
attr.stack_size = CONFIG_TASK_MAIN_STACKSIZE;
attr.priority = CONFIG_TASK_MAIN_PRIOR;
attr.name = "IoTMain";
(void) osThreadNew((osThreadFunc_t)IotMainTaskEntry, NULL, (const osThreadAttr_t *)&attr);
return;
}
步驟四:無感配網解決方案(重要ability)
OpenHarmony設備之間的信息傳遞利用特有的NAN協議實現。利用手機和智能設備之間的WiFi 感知訂閱、發布能力,實現了數字管家與設備之間的互聯。在完成設備間的認證和響應后,即可發送相關配網數據。同時還支持與常規SoftAP配網方式共存。
相關代碼:
teamX/common/iot_wifi/libs/libhilinkadapter_3861.a // 無感配網相關庫文件
teamX/common/iot_wifi/libs/libnetcfgdevicesdk.a // 無感配網相關庫文件
teamX/common/inc/iot_netcfg_nan.h
teamX/common/inc/network_config_service.h // 無感配網相關頭文件
teamX/common/iot_wifi/iot_wifi.c // 相關聯網接口
teamX/common/iot_wifi/iot_netcfg_nan.c // 無感配網相關實現
數字管家可以在gitee下載源碼,在下載的team_X中查看詳細介紹
步驟五:第三方平臺接入
儲物精靈Pro版(識別功能版):(使用第三方平臺:Vuforia)
我們的原理就是上傳畫面到云端,然后逐幀分解比對(此功能目前還在完善)
第六步:實時攝像功能與智能檢測光照值功能(正在實驗中)
int GetLightAverageVal(unsigned char cnt)
{
unsigned short readVal = 0;
unsigned int totalVal = 0, totalCnt = 0;
for(unsigned char i=0; i<cnt; i++)
{
if(LightSensorVal(&readVal) == IOT_SUCCESS)
{
totalVal += readVal;
totalCnt++;
}
usleep(50000);
}
return (totalVal/totalCnt);
}
enum ENV_LIGHT_STATE GetEnvLightState(void)
{
enum ENV_LIGHT_STATE lightState = LIGHT_DAY;
int lightVal = GetLightAverageVal(5);
if(lightVal > ENV_LIGHT_LEVEL_LOWEST)
{
lightState = LIGHT_NIGHT;
}
else if(lightVal > ENV_LIGHT_LEVEL_LOW)
{
lightState = LIGHT_DUSK;
}
else
{
lightState = LIGHT_DAY;
}
return lightState;
}
第七步:分布式檢索功能(實驗中)
傳統的分布式使用的是Elasticsearch進行,鑒于OpenHarmony能力所以需要開發出對口的ability。
isCreateIfMissing() //分布式數據庫創建、打開、關閉和刪除
setCreateIfMissing(boolean isCreateIfMissing) //數據庫不存在時是否創建
isEncrypt() //獲取數據庫是否加密
setEncrypt(boolean isEncrypt) //設置數據庫是否加密
getStoreType() //獲取分布式數據庫的類型
setStoreType(KvStoreType storeType) //設置分布式數據庫的類型
KvStoreType.DEVICE_COLLABORATION //設備協同分布式數據庫類型
KvStoreType.SINGLE_VERSION //單版本分布式數據庫類型
getKvStore(Options options, String storeId) //根據Options配置創建和打開標識符為 storeId 的分布式數據庫
closeKvStore(KvStore kvStore) //關閉分布式數據庫
getStoreId() //分布式數據增、刪、改、查。
subscribe(SubscribeType subscribeType, KvStoreObserver observer) //訂閱
sync(List<String> deviceIdList, SyncMode mode) 數據同步
開發說明:(包括OH分布式文件)
1. 構造分布式數據庫管理類(創建 KvManagerConfig 對象)
Context context;
...
KvManagerConfig config = new KvManagerConfig(context);
KvManager kvManager =
KvManagerFactory.getInstance().createKvManager(config);
2. 獲取/創建單版本分布式數據庫(聲明需要創建的單版本分布式數據庫ID說明)
Options CREATE = new Options();
CREATE.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
String storeID = "testApp";
SingleKvStore singleKvStore = kvManager.getKvStore(CREATE, storeID);
3. 訂閱分布式數據更改(客戶端需要實現KvStoreObserver接口&結構并注冊KvStoreObserver實例)
class KvStoreObserverClient implements KvStoreObserver() {
public void onChange(ChangeNotification notification) {
List<Entry> insertEntries = notification.getInsertEntries();
List<Entry> updateEntries = notification.getUpdateEntries();
List<Entry> deleteEntries = notification.getDeleteEntries();
}
}
KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient);
4. 構造需要寫入單版本分布式數據庫的Key和Value(將鍵值數據寫入單版本分布式數據庫)
String key = "todayWeather";
String value = "Sunny";
singleKvStore.putString(key, value);
5. 構造需要從單版本分布式數據庫快照中查詢的Key(數據取自單版本分布式數據庫快照)
String key = "todayWeather";String value = singleKvStore.getString(key);
6. 獲取設備列表與同步數據(PUSH_ONLY)
List<DeviceInfo> deviceInfoList = kvManager.getConnectedDevicesInfo(DeviceFilterStrategy.NO_FILTER);
List<String> deviceIdList = new ArrayList<>();
for (DeviceInfo deviceInfo : deviceInfoList) {
deviceIdList.add(deviceInfo.getId());
}
singleKvStore.sync(deviceIdList, SyncMode.PUSH_ONLY);
7. 首先get到設備數據交換權限
ohos.permission.DISTRIBUTED_DATASYNC
requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
//然后在AbilitySlice中聲明數據庫并使用即可,這里不多贅述
9. 關于API的開放能力請詳見官方文檔,這里不再贅述。
10. 懟相關接口(正在實驗的內容)
SearchAbility searchAbility = new SearchAbility(context);
CountDownLatch lock = new CountDownLatch(1);
searchAbility.connect(new ServiceConnectCallback() {
@Override
public void onConnect() {
lock.countDown();
}
@Override
public void onDisconnect() {
}
});
lock.await(3000, TimeUnit.MILLISECONDS);
11. 設置搜索屬性與插入索引和重構查詢等將會在下一次提交中進行補充。
(二)智能門鎖
(上面的儲物精靈源碼也包括智能門鎖的功能實現,這里補充介紹開發)
1. 環境搭建:
(1) 需要手動配置在deveco tool里的用戶組件
(2) 接舵機的Gpio口
這里要注意一個接的是正極一個是接地還有一個為信號傳輸口
(3) 云端配置
首先在華為云官方獲取Client ID等身份識別信息,然后在云端的Topic中自定義訂閱與發布。
在初次開發時可以使用MQTTX軟件進行了命令的訂閱與下發實驗,顯示在線成功接收到上報和訂閱的消息。
這樣華為云的配置就成功了
如有需要還要進行產品定義與多功能的增加與實驗
2.關于編譯:
(1) 在VS code編譯
點擊build就可以生成json文件啦,編譯成功后upload就可以直接燒錄進去。(注意:在編譯之后如果要再編譯必須點擊clean以刪除build產生的json文件避免報錯)
(2)在Ubuntu通過命令行編譯
hb set //這是用于產生json文件的
hb build //這是用于編譯的,編譯后則會在源碼的out文件夾中產生bin文件
hb clean //在build一次以后如果如果要再build那就必須進行此命令來刪除json文件
在build成功后開發者就會發現在源碼中的out文件夾中看到allinone.bin,然后發送到windows下使用Hiburn進行燒錄即可(波特蘭最大3000000,否則會燒壞板子)下圖為HiBurn的配置方法,點擊Connect即可燒錄。
3.碰一碰卡片(原子化服務)
數字管家需要通過在APPGallery Connect中創建項目后添加應用從而獲取Json文件,然后放在碼云中下在的DistSchedule\netconfig\src\main\resources中。然后按照文檔開發UI界面,點擊構建的Generate Key and CSR創建用戶名與密鑰進行簽名。
用戶操作界面:在slice目錄下新建 xxxSlice.java文件,通過addActionRoute方法為此AbilitySlice配置一條路由規則,并且在在應用配置文件(config.json)中注冊。在resources/base/layout下新建對應xml布局文件,在上述兩個文件中編寫相應的UI。
數字管家數據處理:從slice獲取deviceId:在onStart中通過調用DeviceID等,獲取設備的名稱等方便數字管家識別設備。從slice頁面獲取狀態:開關鎖可以直接調用intent.getBooleanParam來確定是進行開關鎖還是對門鎖的日程進行編排。
編寫設備控制命令的解析:在CommandUtil中根據具體設備定義profile,來新增獲取命令和解析命令的方法,用于設備在本地調用sendCommand來發送命令和解析。
配置設備端信息:在DeviceData的initData中,根據設備ProductID添加設備圖片ID、跳轉的action參數和解析方法,配置完成后設備列表頁、用戶頁面等都能通過該配置進行圖片加載、路由跳轉和解析。
最后進行接口對接與NFC寫入就可以了(通過應用調試助手寫入NFC識別詳細用于快速讓手機識別到設備從而吊起數字管家實現鴻蒙的Ability)
(三)逆變器
1. 拓撲圖
設計的單相逆變器,擁有隔離拓撲,通過控制GaN(HEMT)的高頻開關實現逆變
關于橋臂:兩個半橋產生中性點電壓,另外兩個半橋產生線電壓,最后一個半橋作為有源濾波器。
2.現在STM32f407兼容了OpenHarmony 3.2 bata版,因為f4系列軟合了dsp處理所以無需另外使用dsp從處理器。考慮到盡量減少直流側輸入電流紋波,輸出的正弦波盡可能的平滑與減小總諧波失真,設計了一種并聯有源濾波器,它比在輸入端使用批量電容更有效地補償紋波。
3.考慮到大部分EDA的元件庫原件都不全,我在kicad按照廠家提供的數據手冊畫了個原件,并按照例出的參數進行了標注。
4. 關于電流與電壓的總諧波失真等:有源濾波器工作在更高的電壓變化下將相應的能量存儲在陶瓷電容器中,陶瓷電容器的電容隨著電壓的降低而增加。通過算法保持Vin穩定同時允許有源濾波器產生大的波紋。輸出電流結合電磁屏蔽的開環霍爾傳感器形成非常緊湊的測量裝置提供電流解耦并降低對共模和寄生感應噪聲的敏感性。特定的GaN控制調制降低了濾波器電感中的電流可以在不達到飽和水平的情況下降低其核心尺寸。
5. 關于硬件選材:在上文的 二.競賽開發平臺 的逆變器中有介紹
6.通訊部分
(1)分布式軟總線
基于UDP的coap協議,OpenHarmony特有分布式軟總線。
編程步驟: 1.創建socket;
2.設置socket屬性,用函數setsockopt();
3.綁定IP地址、端口等信息到socket上,用函數bind();
4.循環接收/發送數據,用函數recvfrom&sendto;
5.關閉網絡連接。
創建一個socket,無論是客戶端還是服務器端都需要創建一個socket。該函數返回socket文件描述符,類似于文件描述符。socket是一個結構體,被創建在內核中。
class UdpClient {
private DatagramSocket client;
public String sendAndReceive(String ip, int port, String msg) {
String responseMsg = "";
try {
//Create a client-side DatagramSocket object without having to pass in addresses and objects
client = new DatagramSocket();
byte[] sendBytes = msg.getBytes();
//Encapsulates the address of the destination to be sent
InetAddress address = InetAddress.getByName(ip);
//Encapsulates the object to send the DatagramPacket
DatagramPacket sendPacket = new DatagramPacket(sendBytes,sendBytes.length,address,port);
try {
//sent Data
client.send(sendPacket);
}catch (Exception e){
// e.printStackTrace();
}
byte[] responseBytes = new byte[2048];
//Create a DatagramPacket object for the response information
DatagramPacket responsePacket = new DatagramPacket(responseBytes,responseBytes.length);
try {
//Waiting for the response information, as on the server side, the client blocks at this step until it receives a packet
client.receive(responsePacket);
}catch (Exception e){
// e.printStackTrace();
}
//Parse the packet contents
responseMsg = new String(responsePacket.getData(),0,responsePacket.getLength());
}catch (Exception e){
// e.printStackTrace();
}finally {
//Close the client
if(client != null){
client.close();
client = null;
}
}
return responseMsg;
}
}
DatagramSocket類代表一個發送和接收數據包的插座該類是遵循 UDP協議 實現的一個Socket類
#define _PROT_ 8800 //UDP server port number
#define _SERVER_IP_ "666.66.66.666"
#define TCP_BACKLOG 5
#define IP_LEN 16
#define WIFI_SSID "rui666" //WiFi name
#define WIFI_PASSWORD "1145141919810" //WIFI oassword
開發板的IP與端口號
public void performClassification() {
int res = classifier.getResult(accelMeasurements, gyroMeasurements);
TaskDispatcher uiTaskDispatcher = this.getContext().getUITaskDispatcher();
String lab = classes[res];
result = lab;
TaskDispatcher globalTaskDispatcher = getContext().getGlobalTaskDispatcher(TaskPriority.DEFAULT);
globalTaskDispatcher.asyncDispatch(new Runnable() {
public void run() {
HiLog.warn(label, udpClient.sendAndReceive("666.66.66.666", 8800, result));
}
});
相關參數意義(注意要手搓的定義內容):
sin_family //Refers to protocol families, which can only be AF_INET in socket programming
sin_port //Storage port number (using network byte order)
sin_addr //Store the IP address and use the in_addr this data structure
sin_zero //Empty bytes are reserved in order to keep sockaddr and sockaddr_in two data structures the same size
fd //socket
buf //UDP datagram buffer (contains data to be sent)
len //The length of the UDP datagram
flags //Invocation operation mode (typically set to 0)
addr //A struct that points to the host address information that receives the data (type conversion required sockaddr_in)
alen //The length of the structure referred to by addr
nfds //Represents the range of all file descriptors in a collection
readfds //Select monitors a collection of readable file handles、
writefds //A collection of writable file handles that select monitors
exceptfds //A collection of exception file handles that select monitors
timeout //The timeout end time of select() this time, NULL means permanent wait
測試客戶端的成功方法:通過UDP軟件進行相關的發送與接收,并查看打印信息。因為與下文介紹的MQTTX軟件使用原理差不多所以這里不多贅述。
(2) MQTT
Mqtt是用于設備與服務器通訊的一種協議,使設備可以上報訂閱下發信息。需要下載此協議并存放在thirdparty(第三方庫),并在頭文件中吊起。
從開發板廠商官網下載實驗demo進行實驗。因為目前大多數廠商使用的都是OpenHarmony 1.0代碼作為演示,不同的源碼版本在編譯規則和文件名上都會不同,所以在下載的源碼中的頭文件吊起等也要修改才能接入mqtt協議。
Mqtt最重要要吊起的功能文件在 /home/open/Downloads/code_v3.0LTS/OpenHarmony/third_party/pahomqtt/MQTTClient-C/src里,特別是liteOS中。
7.服務卡片
(1)服務卡片原理
(2)APPGallery Connect
①數字管家:
數字管家需要通過在APPGallery Connect中創建項目后添加應用從而獲取Json文件,在完成下述的2后把此文件放在碼云中下載的FA源碼的:
DistSchedule\netconfig\src\main\resources中。然后按照文檔開發UI界面,點擊構建的Generate Key and CSR創建用戶名與密鑰進行簽名。
官網在我的項目中創建項目,選擇harmonyOS平臺等完成填寫
https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/
②邏輯處理:
(i)用戶操作界面:在slice目錄下新建 xxxSlice.java文件,通過addActionRoute方法為此AbilitySlice配置一條路由規則,并且在在應用配置文件(config.json)中注冊。在resources/base/layout下新建對應xml布局文件,在上述兩個文件中編寫相應的UI。
(ii)數字管家數據處理:從slice獲取deviceId:在onStart中通過調用DeviceID等,獲取設備的名稱等方便數字管家識別設備。從slice頁面獲取狀態:開關鎖可以直接調用intent.getBooleanParam來確定是進行開關鎖還是對門鎖的日程進行編排。
(iii)編寫設備控制命令的解析:在CommandUtil中根據具體設備定義profile,來新增獲取命令和解析命令的方法,用于設備在本地調用sendCommand來發送命令和解析。
(iv)配置設備端信息:在DeviceData的initData中,根據設備ProductID添加設備圖片ID、跳轉的action參數和解析方法,配置完成后設備列表頁、用戶頁面等都能通過該配置進行圖片加載、路由跳轉和解析。
(v) NFC寫入:最后進行接口對接與NFC寫入就可以了(通過應用調試助手寫入NFC識別詳細用于快速讓手機識別到設備從而吊起數字管家實現鴻蒙的Ability)可以寫到開發板的NFC預存區,也可以寫在huawei share的碰一碰卡片上。(目前這兩種寫法都可以寫無數次,在下一次寫入時會自動清除上一次所寫的)
③開發方式:
(i) 用戶操作界面:通過桌面可以在卡片中點擊相關服務,卡片中可以呈現一個或多個服務。
(ii)工作原理:通過嵌入到UI界面拉起那款應用的服務(可以通過緩存實現快速打開)從而起到交互功能的原子化服務。
(iii)生命周期管理:對設備使用方的 RPC 對象進行管理,請求進行校驗以及對更新后的進行回調處理。
(iv)卡片尺寸:目前官方有四種尺寸,可以在new中自己選中喜歡的尺寸。
(v)上手開發:新建一個服務卡片
選擇自己所需的卡片框架
(vi)開發環節:創建完之后然后就可以看到在原有的subject中生成了config.json文件。js默認配置了卡片大小等信息,froms下的是ability中生命周期管理的核心部分(用于回調),會在主函數中實現調用。
要在這里把false改成true。
上圖的文件包為主要的開發位置,開發者動的是index下的三個包。
完成簽名之后在在線調試的實驗機器上運行后就會產生一張純的FA卡片了,此時環境已經搭建完畢。
本地緩存調取:src在main下的resources中建rawfile用于存放緩存,在編譯時候打包進hap中懟到鴻蒙設備中即可get到。
下面以開發1*2的mini卡片為例,在本地預置了緩存文件后我們目光轉向卡片,繼續把播放按鈕與卡片解耦開,通過hml塞入顯示信息等。isWidget當true時,card_containerdiv就會變為div布局。Ispause為true時,按鈕呈現播放;為false時,顯示暫停按鈕。
在 css 文件采用原子布局的display-index。display-index 的值越大,則越優先顯示。在 main中的onCreateForm 里isMiniWidget 的data設置為 true。
在.json和main中相對應的地方添加點擊事件,到此為止就可以通過點擊卡片就可以得到start與stop的互動了。做完顯示界面以后,接入界面與預置的本地緩存,然后封裝即可。
上圖上中下分別是更新(onUpdateForm),刪除(onDeleteForm),事件消息(message),
更新(onUpdateForm): 卡片更新與持久化儲存卡片,定時更新與請求更新時進行調用。
刪除(onDeleteForm):用于刪除卡片時調用。 圖三:formid&massage,接收通知。一張Fa卡片創建時需要滿足的基本功能:布局加載~請求數據(ohos&intent)~產生卡片(long&生成ID用于調用){通過枚舉值得到}。
這樣一張服務卡片就開發好了。
1. 關于智能門鎖:
基于OpenHarmony開發,使用原子化服務,擁有密碼解鎖,NFC解鎖,數字管家控制等功能。
2. 關于儲物精靈
基于OpenHarmony開發,使用原子化服務,密碼解鎖,NFC解鎖,防火簾控制,分布式軟總線控制等。
3. 關于逆變器
基于OpenHarmony開發,拓撲架構大幅度縮小轉換器橋臂和EMI濾波器的尺寸,在算法使用CEC加權效率設計與峰值電壓追蹤,通過品質因數公式FOM算出使用合適的GaN半導體選型結合五個橋臂的設計可以最小化逆變器的能量傳遞。
1. 編譯成功
2. 動圖演示(導入到word中是動圖,word可能無法顯示出動圖效果所以把相關圖片動圖在上傳文件夾中備份了一份)
以下動圖分別是門鎖的舵機驅動,NFC打卡,智能門軌的演示動圖。
關注#華為云開發者聯盟# 點擊下方,第一時間了解華為云新鮮技術~
華為云博客_大數據博客_AI博客_云計算博客_開發者中心-華為云