3.2 自定義協議驅動開發指導
概述
本文檔指導自定義協議驅動開發人員,了解物聯網設備接入相關知識,并且可以根據設備接入SDK開發驅動,實現設備物模型定義的功能。
1.了解物聯網設備接入
1.1 設備接入概念
設備接入是Link IoT Edge提供的基礎能力,設備接入模塊在Link IoT Edge中稱為驅動(driver)或設備接入驅動。所有連接到Link IoT Edge的設備都需要通過驅動實現接入。
設備接入驅動在Link IoT Edge框架位置如圖所示。
1.2 設備接入驅動
1.2.1 設備接入驅動的功能組成
一個完整的驅動(設備接入模塊)由設備的連接管理、設備的數據(協議)轉換和設備的數據與命令處理三個模塊組成。
1.2.1.1連接管理
指設備與網關建立通信連接。Link IoT Edge不限制建立通信連接的協議,可根據業務需求靈活選擇。
1.2.1.2數據轉換
指設備接入驅動將獲取到的終端設備數據轉換為符合阿里云IoT物模型規范的數據格式,并上報到阿里云IoT Cloud。阿里云物聯網平臺物模型規范請參考物模型。
1.2.1.3數據與命令處理
指驅動可以處理云端對于設備的操作請求,并完成對設備的服務調用和處理調用結果,最終將結果返回到阿里云物聯網平臺。
1.2.2 設備接入驅動的開發工作
設備接入驅動是Link IoT Edge中獨立的服務模塊,您可以根據業務協議需求開發自定義設備接入驅動。下圖展示了自定義驅動的功能和數據流向,并指出了開發一個自定義驅動需要做的開發工作。
2.驅動開發
Link IoT Edge提供設備接入SDK(Link Edge Device Access,簡稱LEDA)方便您開發自己的驅動,SDK目前支持C、Node.js、Java和Python四種版本的語言。
2.1 LEDA接口介紹
LEDA支持的四種開發語言詳細內容,請參見C、Node.js、Java和Python。
雖然SDK版本不同,但提供的功能是一樣的,LEDA接口調用關系如下圖所示:
2.2 驅動編碼示例
在驅動開發過程中進行驅動編碼時,需遵循物聯網邊緣計算的驅動編碼規范和步驟。
2.2.1 驅動和設備配置
進行驅動編碼前,需要了解Link IoT Edge的設備信息配置和驅動信息配置相關內容。
2.2.1.1 驅動配置
驅動信息配置在阿里云物聯網平臺進行配置。部署邊緣實例時,驅動配置信息會被部署到邊緣網關,其內容以JSON格式存儲在Link IoT Edge配置中心,可以通過leda_get_driver_info接口獲取。
驅動信息配置為JSON格式:
{
"json":{
"ip":"127.0.0.1",
"port":54321
}
}
格式參數說明如下:
參數名稱 | 說明 |
json | 驅動配置的格式為JSON格式配置。配置內容為自定義內容。 |
驅動配置示例:
2.2.1.2 設備配置
設備信息配置在阿里云物聯網平臺控制臺配置。部署邊緣實例時,設備信息配置會被部署到邊緣網關,其內容以JSON格式存儲,可以通過leda_get_device_info接口獲取。
設備信息配置格式定義:
{
"deviceList": [{
"custom": {
"ip":"127.0.0.1",
"port":22322
}, // 設備自定義配置
"productKey": "xxxxxxxxxxx", // 產品ProductKey,在創建產品時生成
"deviceName": "demo_led", // 設備DeviceName,在創建設備時設置
}]
}
設備信息配置參數說明:
配置名稱 | 配置解釋 |
deviceList | 當前驅動下所有已進行設備配置的設備列表。 |
custom | 設備自定義配置。 |
productKey | 設備所在產品唯一標識符。 |
deviceName | 設備名稱。 |
實際設備配置示例:
2.2.2 驅動代碼示例
完成驅動編碼,可參考以下4個示例。本示例的完整工程源碼請參見Github源碼庫LED設備驅動。
2.2.2.1 初始化驅動資源
調用leda_init接口完成資源初始化。
int main(int argc, char** argv)
{
...
/* init driver */
if (LE_SUCCESS != (ret = leda_init(WORKER_THREAD_NUMS)))
{
log_e(TAG_NAME_LED_DRIVER, "leda_init failed\n");
return ret;
}
...
return LE_SUCCESS;
}
2.2.2.2 解析驅動配置,完成設備上線
通過調用leda_get_driver_info接口獲取驅動配置,解析設備的連接信息,并根據解析結果連接設備。設備連接成功后,調用leda_get_device_info接口獲取設備配置并解析,根據解析結果驗證設備功能。功能驗證通過后,調用leda_register_and_online_by_device_name接口完成設備注冊并上線到阿里云物聯網平臺。
static int online_devices()
{
...
/* 獲取驅動和設備配置 */
size = leda_get_device_info_size();
if (size >0)
{
device_config = (char*)malloc(size);
if (NULL == device_config)
{
log_e(TAG_DEMO_LED, "allocate memory failed\n");
return LE_ERROR_INVAILD_PARAM;
}
if (LE_SUCCESS != (ret = leda_get_device_info(device_config, size)))
{
log_e(TAG_DEMO_LED, "get device config failed\n");
return ret;
}
}
/* 解析驅動和設備配置 */
devices = cJSON_Parse(device_config);
if (NULL == devices)
{
log_e(TAG_DEMO_LED, "device config parser failed\n");
return LE_ERROR_INVAILD_PARAM;
}
cJSON_ArrayForEach(item, devices)
{
if (cJSON_Object == item->type)
{
/* 解析配置內容 */
result = cJSON_GetObjectItem(item, "productKey");
productKey = result->valuestring;
result = cJSON_GetObjectItem(item, "deviceName");
deviceName = result->valuestring;
result = cJSON_GetObjectItem(item, "custom");
if (NULL != result)
{
log_i(TAG_DEMO_LED, "custom content: %s\n", cJSON_Print(result));
}
/* 注冊并上線設備 */
device_cb.get_properties_cb = get_properties_callback_cb;
device_cb.set_properties_cb = set_properties_callback_cb;
device_cb.call_service_cb = call_service_callback_cb;
device_cb.service_output_max_count = 5;
dev_handle = leda_register_and_online_by_device_name(productKey, deviceName, &device_cb, NULL);
if (dev_handle < 0)
{
log_e(TAG_DEMO_LED, "product:%s device:%s register failed\n", productKey, deviceName);
continue;
}
g_dev_handle = dev_handle;
log_i(TAG_DEMO_LED, "product:%s device:%s register success\n", productKey, deviceName);
}
}
...
return LE_SUCCESS;
}
2.2.2.3 上報數據
將收到的設備數據轉換為阿里云IoT物模型格式并上報到物聯網平臺,調用leda_report_properties接口上報設備屬性數據,調用leda_report_event接口上報設備事件。
/* 上報數據 */
while (1)
{
/* 上報屬性 */
leda_device_data_t dev_proper_data[1] =
{
{
.type = LEDA_TYPE_INT,
.key = {"temperature"},
.value = {0}
}
};
sprintf(dev_proper_data[0].value, "%d", g_dev_temperature);
leda_report_properties(g_dev_handle, dev_proper_data, 1);
/* 上報事件 */
if (g_dev_temperature > 50)
{
leda_device_data_t dev_event_data[1] =
{
{
.type = LEDA_TYPE_INT,
.key = {"temperature"},
.value = {0}
}
};
sprintf(dev_event_data[0].value, "%d", g_dev_temperature);
leda_report_event(g_dev_handle, "high_temperature", dev_event_data, 1);
}
sleep(5);
}
2.2.2.4 處理云端服務請求
實現服務請求的三個回調函數如下所示。
get接口:處理獲取設備屬性的請求。
set接口:處理設置設備屬性的請求。
service接口:處理調用設備自定義方法的請求。
static int get_properties_callback_cb(device_handle_t device_handle,
leda_device_data_t properties[],
int properties_count,
void *usr_data)
{
...
return ret;
}
static int set_properties_callback_cb(device_handle_t device_handle,
const leda_device_data_t properties[],
int properties_count,
void *usr_data)
{
...
return ret;
}
static int call_service_callback_cb(device_handle_t device_handle,
const char *service_name,
const leda_device_data_t data[],
int data_count,
leda_device_data_t output_data[],
void *usr_data)
{
...
return ret;
}
2.3 驅動產出方式
2.3.1 驅動依賴注意事項
設備接入驅動根據協議和業務場景的不同,可能會涉及第三方庫依賴。Link IoT Edge針對開發設備接入驅動所用不同開發語言,分別制定了第三方庫依賴規則。
C版本SDK:C語言屬于編譯型語言,如果編譯目標環境和運行環境不一致,則很可能導致不可運行。所以對于使用設備接入C版本SDK開發驅動,需要保證開發編譯目標環境和運行環境相同。 驅動包中包含驅動程序和依賴動態庫。如果該驅動依賴于第三方庫,則需要將動態庫和驅動程序一起打包生成最終驅動程序包。
Node.js版本SDK:使用設備接入SDK Node.js版本開發驅動時,若依賴第三方庫,需要到Link IoT Edge運行環境上開發驅動,并在驅動目錄中使用如下命令安裝依賴。
npm install 第三方庫名
Python版本SDK:使用設備接入SDK Python版本開發驅動時,若依賴第三方庫,需要到Link IoT Edge運行環境上開發驅動,并在驅動目錄中使用如下命令安裝依賴。
pip3 install -t . 第三方庫名
2.3.2 驅動打包方式
基于Link IoT Edge提供的SDK開發驅動并完成調試后,需將產物打包為.zip包,并確保驅動Binary或index源文件在.zip包第一級目錄。
每個版本SDK開發的驅動在打包時,有不同的打包規則。
基于C SDK開發的驅動對于C語言開發的驅動,驅動包中包含驅動程序和驅動依賴的動態庫。如果驅動程序包含依賴庫,則需要將依賴庫放置指定的位置,即在驅動程序當前路徑下的lib文件夾下。具體操作步驟如下:
規定驅動程序需命名為main。
在main當前路徑下創建lib文件夾。
將main依賴的動態庫全部拷貝到lib文件夾下。
使用zip命令對當前路徑下的main和lib進行壓縮處理生成zip包。
zip -r your_driver_name.zip main lib
基于Python SDK開發的驅動包文件中須包含index.py,并且在該文件中定義handler函數。驅動是一個在函數計算應用引擎中持續運行的函數,所以在驅動包中須包含index.py文件,并且在該文件中定義handler函數。 驅動運行時,會加載index.py文件。而該文件中,函數計算應用定義的handler函數是不會被調用,因此驅動代碼須放在handler函數外,保證加載index.py文件時能直接執行。詳情請參考Python版本SDK
基于Node.js SDK開發的驅動包文件中須包含index.js,并且在該文件中定義handler函數。 驅動運行時,會加載index.js文件。而該文件中,函數計算應用定義的handler函數是不會被調用,因此驅動代碼須放在handler函數外,保證加載index.js文件時能直接執行。詳情請參考Nodejs版本SDK
2.4 驅動使用示例
2.4.1 上傳及發布驅動
在SI集成工作臺si.iot.aliyun.com上,選擇邊緣接入->驅動管理,選擇新增驅動
根據提示設置參數,需要注意的是通信協議類型要選擇【自定義】
上傳已完成編譯并打包的led_driver.zip文件。
完成參數的設置并上傳成功驅動文件后,單擊確定。您可以在自研驅動列表中看到剛剛創建的驅動
2.4.2分配驅動和設備到邊緣實例
2.4.2.1 添加驅動到邊緣實例
登錄SI集成工作臺si.iot.aliyun.com,在網關管理設備集成頁面,點擊網關右側的設置圖標,首先,在彈出菜單中選擇添加協議:
然后,在下拉選擇的協議列表中選擇“DemoDriver”,選擇“確定”。
2.4.2.2 新建設備
如下圖所示,通過點擊通道右側的“設置”圖標,選擇“新建設備”:
在彈出的新建設備頁面中按照如下方式選擇刷卡門禁
點擊確定按鈕即可完成設備創建,創建好的設備如下如所示:
2.4.3 部署邊緣實例
在實例詳情頁面,右上角單擊部署,部署邊緣實例。部署成功后邊緣實例名稱后顯示部署成功。
2.4.4 查看驅動日志
在邊緣網關宿主機命令行中通過docker命令進入LE網關運行環境,命令如下:
sudo docker exec -it $(sudo docker ps | grep k8s_linkedge | awk '{print $1}') bash
進入網關容器內之后可以在本地查看驅動日志。
a. 通過./fctl show
命令,可以查看到部署驅動的具體位置。
cd /linkedge/gateway/build/bin/
./fctl show
系統顯示類似如下圖所示:
參數說明:
字段名稱 | 字段解釋 |
DriverName | 驅動名稱,該名稱為在上傳及發布驅動步驟中設置的驅動名稱。 |
CodePath | 驅動部署到邊緣網關中的位置路徑。 |
Process PID | 驅動啟動后的進程ID。 |
b. 驅動在運行過程中會產生運行日志,通過查看運行日志可以了解驅動運行狀態,每個自定義驅動生成的日志文件統一放在/linkedge/run/logger/fc-base路徑下。每個驅動日志文件路徑為/linkedge/run/logger/fc-base/xxxx/log_xxxx.txt。
說明驅動日志文件路徑中的xxxx為在上傳驅動時填寫的名稱。
cd /linkedge/run/logger/fc-base/demo_driver && ls -l
詳情如下圖所示:
驅動運行日志可以查看大部分驅動的運行狀態,但是有時還需要查看其它日志配合了解當前驅動的運行情況,例如設備上線異常時可以查看dimu
日志,日志文件路徑為/linkedge/run/logger/dimu/log_xxxxx.txt。
cd /linkedge/run/logger/dimu
ls -l
詳情如下圖所示:
設備上線成功后,如果設備數據上報有異常,則可以查看cloud_proxy
日志。cloud_proxy產生的運行日志文件路徑格式為/linkedge/run/logger/cloud-proxy/log_xxxxx.txt。
cd /linkedge/run/logger/cloud-proxy
ls -l
詳情如下圖所示:
2.5 驅動調試方式
2.5.1 云端在線調試
如果設備上線成功,則可以使用阿里云物聯網平臺在線調試功能調試驅動和設備,該功能頁面還可以實時查看設備上報的數據信息,也可以觸發對設備服務的調用請求。
a.在物聯網平臺控制臺,選擇監控運維 > 在線調試,在在線調試頁面,選擇調試產品和調試設備。
b.選擇調試設備待調功能和服務,進行調試,并查看設備實時運行日志
2.5.2 邊緣端替換驅動
如果在驅動調試過程中發現問題,需要修改驅動代碼重新生成新的驅動進行調試,這時只需要在本地替換修改編譯后的驅動即可。
a. 找到驅動的位置。可以通過fctl命令進行查找,具體字段解釋查看調試信息。
cd /linkedge/gateway/build/bin/
./fctl show
b. 根據CodePath找到驅動所在位置路徑,完成替換。
c. 根據Process PID得到驅動進程ID,使用kill -9 Pid命令重新啟動驅動。
kill -9 Pid #Pid為驅動進程ID,即通過fctl命令查看得到的Process PID