G919-GAS軟件 JSON格式數據通訊協議-陣列數據解析
版本記錄
Date | Author | Version | Note |
---|---|---|---|
2024.04.07 | Dog Tao | V1.0 | 發布通訊協議。 |
2025.05.06 | Dog Tao | V1.1 | 1. 增加了【高速采樣】模式下的通訊協議。2. 增加了“軟件開發建議”小節。 |
文章目錄
- G919-GAS軟件 JSON格式數據通訊協議-陣列數據解析
- 普通采樣模式
- 精確行列更新
- 批量行列更新
- 高速采樣模式
- 軟件開發建議
- 采集板固件
- 上位機軟件
本協議遵從 《G919-GAS軟件 JSON格式數據通訊協議》所制定的規范。以下針對陣列結構化數據進行通訊協議制定。
普通采樣模式
普通采樣模式適合數據采樣頻率不高(≤2Hz)、數據傳輸速率較慢(例如藍牙BLE傳輸)的場景。一般具備較低響應速度的氣體傳感器、電化學傳感器、阻敏型壓力傳感器等構成的陣列適合采用此模式進行數據傳輸。
精確行列更新
精確行列更新的上報格式適用于在一個大型陣列中僅更新單個單元的值。
設備(下位機)的JSON鍵值定義參考("ID"
與"NT"
的鍵值必須實現,其他可根據需要增刪改):
名稱 | ID | NT | CT | ROW | COL | VAL |
---|---|---|---|---|---|---|
功能 | 設備標識符 | 設備網絡地址 | 計數 | 行號 | 列號 | 單值 |
值 | MAC地址(后32位) | 網絡地址(16位) | 時間戳或數據序號(uint ) | 行(uint ) | 列(uint ) | 對應行列的讀出(float ) |
JSON數據類型 | 字符串 | 字符串 | 數字 | 數字 | 數字 | 數字 |
設備(下位機)的串口(含藍牙等透傳)發送的數據示例:
// Set new value of coordinate (0, 0) to 28.3
{"ID":"25382B57","NT":"785F","ROW":0,"COL":0,"VAL":28.3}
// Set new value of coordinate (0, 1) to 29.9
{"ID":"25382B57","NT":"785F","ROW":0,"COL":1,"VAL":29.9}
// Set new value of coordinate (0, 2) to 32.5
{"ID":"25382B57","NT":"785F","ROW":0,"COL":2,"VAL":32.5}
批量行列更新
批量行列更新的上報格式適用于在一個小型陣列中實時刷新全部或部分單元值。
設備(下位機)的JSON鍵值定義參考("ID"
與"NT"
的鍵值必須實現,其他可根據需要增刪改):
名稱 | ID | NT | CT | ROW | COL | VAL |
---|---|---|---|---|---|---|
功能 | 設備標識符 | 設備網絡地址 | 計數 | 行號 | 列號 | 多值 |
值 | MAC地址(后32位) | 網絡地址(16位) | 時間戳或數據序號(uint ) | 起始行(uint ) | 起始列(uint ) | 指定行列作為起點的批量讀出(float 數組) |
JSON數據類型 | 字符串 | 字符串 | 數字 | 數字 | 數字 | 數組 |
設備(下位機)的串口發送的數據示例:
// Set the continuous values starting from coordinate (0, 0)
// e.g., for a 4x4 array, the values are updated in the whole row 0 and row 1, respectively.
{"ID":"25382B57","NT":"785F","ROW":0,"COL":0,"VALS":[28.3, 29.9, 82.1, 46.8, 45.2, 54.6, 31.8, 25.6]}
注意,批量行列更新的順序是默認按行更新,即沿當前坐標所在的行開始更新到當前行末,隨后在下一行從頭開始繼續更新數據。
高速采樣模式
高速采樣模式適合數據采樣頻率高(≥5Hz)、數據傳輸速率較快(例如串口直連、WiFi傳輸)的場景。一般具備較高響應速度的壓電、熱電、光電傳感器等構成的陣列適合采用此模式進行數據傳輸。
高速采樣模式采用類似普通采樣模式下的“批量行列更新”,直接刷新一個陣列中全部或部分單元值。
設備(下位機)的JSON鍵值定義參考("ID"
與"NT"
的鍵值必須實現,其他可根據需要增刪改):
名稱 | ID | NT | CT | ROW | COL | VALS1-0 | VALS1-1 | VALS1-N | VALS2-0 | VALS2-1 | VALS2-N |
---|---|---|---|---|---|---|---|---|---|---|---|
功能 | 設備標識符 | 設備網絡地址 | 計數 | 行號 | 列號 | 陣列1第0個時間步的值 | 陣列1第1個時間步的值 | 陣列1第N個時間步的值 | 陣列2第0個時間步的值 | 陣列2第1個時間步的值 | 陣列2第N個時間步的值 |
值 | MAC地址(后32位) | 網絡地址(16位) | 時間戳或數據序號(uint ) | 起始行(uint ) | 起始列(uint ) | 指定行列作為起點的批量讀出(float 數組) | 同前 | 同前 | 同前 | 同前 | 同前 |
JSON數據類型 | 字符串 | 字符串 | 數字 | 數字 | 數字 | 數組 | 數組 | 數組 | 數組 | 數組 | 數組 |
需要特別說明的是,高速采樣模式采用多個時間步數據累積同時發送的形式,減少了數據傳輸的頻率、增大了單次數據傳輸的量。這樣可以保證高速采樣數據在上位機軟件的高效記錄與繪制(每次繪制多個數據點)。例如,在10 Hz采樣速率下,可以將每秒10個時間步采樣的數據累積同時發送,上位機軟件每秒繪圖1次,每次繪制10個數據點。
設備(下位機)的串口發送的數據示例:
// Set the continuous values starting from coordinate (0, 0){ "ID": "ESP32_402FA8","CT": 101,"ROW": 0,"COL": 0,"VALS1-0": [2010.0, 1995.8, 2009.4, 2009.9, 2012.3, 2012.0, 2012.4, 2012.6, 1998.8, 1998.4, 1998.8, 1998.6, 2000.6, 2000.6, 2001.0, 2000.6],"VALS1-1": [2009.6, 1995.9, 2009.5, 2009.5, 2012.6, 2012.4, 2012.1, 2012.4, 1998.9, 1999.0, 1998.6, 1999.0, 2000.9, 2000.6, 2001.0, 2001.1],"VALS1-2": [2009.9, 1993.4, 2009.5, 2007.9, 2012.8, 2013.9, 2012.3, 2012.6, 1998.9, 1998.6, 1998.6, 1998.5, 2000.8, 2001.0, 2000.9, 1971.3],"VALS1-3": [1999.1, 1999.3, 1999.5, 2013.5, 2001.8, 2003.3, 2015.9, 2016.0, 1987.1, 1987.6, 2002.6, 2003.0, 1989.6, 1987.6, 2004.4, 2005.3],"VALS1-4": [2013.6, 2013.4, 1999.5, 1999.1, 2016.1, 2002.1, 2001.6, 1998.1, 2002.6, 1981.5, 1987.3, 1987.4, 2005.0, 1989.8, 1989.0, 1989.5],"VALS1-5": [1999.3, 1997.3, 1999.4, 1993.4, 2002.3, 2001.6, 2001.9, 2018.9, 1987.6, 1986.6, 1987.6, 2002.6, 1989.9, 1989.8, 1989.5, 2005.1],"VALS1-6": [2013.4, 2013.3, 2013.4, 2013.8, 2016.1, 2016.1, 2016.4, 2016.5, 2002.9, 1996.3, 2003.0, 2002.9, 2005.1, 2004.5, 2005.3, 2004.9],"VALS1-7": [2013.5, 2013.4, 2013.0, 2013.0, 2016.4, 2016.4, 2015.4, 2016.3, 2002.6, 1987.5, 2003.0, 2002.6, 2004.9, 2004.6, 2005.3, 2005.0],"VALS1-8": [2013.8, 2013.6, 2013.5, 2013.5, 2016.5, 2016.0, 2016.1, 2016.1, 2002.8, 1987.4, 2002.9, 2002.6, 2004.9, 2008.6, 2004.5, 2005.0],"VALS1-9": [2013.8, 2013.9, 2013.3, 2013.4, 2016.6, 2016.1, 2016.3, 2016.3, 2002.8, 2002.8, 2002.6, 2002.4, 2005.1, 1991.1, 2005.1, 2004.4],"VALS2-0": [754.5, 750.2, 774.0, 785.4, 767.0, 761.5, 773.9, 756, 772.5, 761.0, 755.5, 759.9, 776, 745.7, 760.5, 782.9],"VALS2-1": [763.9, 789.4, 774.2, 777.0, 755.7, 767.5, 772.7, 752.4, 776.5, 765.2, 754.7, 753.5, 764.5, 747.0, 759.5, 777.5],"VALS2-2": [756.4, 783, 770.9, 771.4, 751.9, 763.5, 767.5, 750.5, 771.5, 763.7, 752.5, 754.0, 753.5, 745.4, 756, 798.5],"VALS2-3": [800.7, 817.7, 814.5, 754.5, 834.7, 825, 790.0, 682.5, 803.4, 806.5, 762.0, 737, 791.0, 791.0, 758.5, 753.7],"VALS2-4": [723.7, 693, 807, 839.7, 701.9, 754.5, 806.2, 836.7, 749.5, 761.7, 783, 807.5, 732.7, 754.2, 785.5, 816.5],"VALS2-5": [810.5, 831.9, 845.4, 818.7, 806.2, 824.9, 820.5, 649.2, 806.4, 802.5, 790.9, 757.7, 777.0, 784.9, 803.0, 755.7],"VALS2-6": [713.5, 697.9, 698.9, 688.5, 679.4, 752.0, 693.0, 697.5, 740.2, 725.5, 703.7, 715.7, 709, 711.5, 722.4, 731.2],"VALS2-7": [693, 678.7, 706.2, 720.2, 715.2, 712.4, 703.5, 722.7, 738.5, 729.4, 712.9, 738.5, 712.9, 713.7, 730.0, 753.2],"VALS2-8": [717.9, 699.4, 733.2, 751.5, 734, 721.5, 729.9, 736.9, 761.7, 752.4, 731.5, 765.5, 738.5, 731.0, 753.0, 776.4],"VALS2-9": [734.0, 715, 734.9, 745.5, 743.2, 728.9, 725.0, 745.5, 767.5, 747.5, 722.2, 744.5, 729.7, 723.5, 737.5, 753.0]
}
軟件開發建議
采集板固件
在【普通采樣】模式下,數據的采樣頻率與傳輸頻率不高且一致,因為可以遵循簡單的“通道切換->ADC采樣->JSON封裝->數據傳輸”的單任務執行序列。
FreeRTOS系統中的任務執行時序圖如下所示:
在【高速采樣】模式下,數據的采樣頻率較高,數據傳輸頻率較低,需要采用隊列(FreeRTOS Queue)用于“生產-消費”緩沖。例如,設置兩個任務,一個任務用來做數據采集,每0.1秒采集一次數據送到隊列中,另外一個任務用來做數據傳輸,每1秒鐘把隊列中的10個數據全部發出去。
FreeRTOS系統中的任務執行時序圖如下所示:
以下是ESP32平臺基于FreeRTOS系統開發的代碼示例:
- Data Acquisition Task(Task1) 以 100 ms 間隔讀取 16 通道 ADC 并調用
xQueueSend
將adc_data_t
推入隊列。- FreeRTOS Queue(Queue) 用于在兩任務之間做生產-消費緩沖。
- Data Transmission Task(Task2) 每秒取出 10 次
xQueueReceive
拉取一秒鐘的數據樣本,隨后使用cJSON
等庫將其封裝成含有 “VALS1-0” 到 “VALS1-9” 的JSON
,并通過串口或網絡接口發送出去。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/adc.h"
#include "esp_log.h"
#include "cJSON.h" // Include a JSON library (e.g., cJSON)#define ADC_CHANNEL_NUM 16 // Number of ADC channels
#define DATA_QUEUE_LENGTH 10 // 10 samples per second (from 10 Hz)
#define TASK1_DELAY_MS 100 // Task1 delay: 100 ms (10 Hz)
#define TASK2_DELAY_MS 1000 // Task2 delay: 1000 ms (1 Hz)typedef struct {float adc_values[ADC_CHANNEL_NUM]; // Array holds one sample (16 ADC values)
} adc_data_t;static QueueHandle_t adcQueue = NULL;
static const char *TAG = "ESP32_APP";/** Task1: Data Acquisition** - Reads 16 channels using ADC (here simulated by random data).* - Pushes the resulting adc_data_t structure into the queue.* - Runs every 100 ms (10 Hz).*/
void adc_task(void *pvParameters)
{adc_data_t sample;TickType_t ticks = xTaskGetTickCount();while (1) {// Acquire ADC data for 16 channels.// Replace the following loop with real ADC API calls, e.g., adc1_get_raw() if needed.for (int i = 0; i < ADC_CHANNEL_NUM; i++) {// For demonstration, generate a pseudo ADC reading.sample.adc_values[i] = (float)(rand() % 4096) * (3.3f / 4096);}// Push the sample into the queue (non-blocking).if (xQueueSend(adcQueue, &sample, 0) != pdPASS) {ESP_LOGW(TAG, "Queue full! Data sample dropped.");}// Wait 100 ms to maintain a 10 Hz sampling rate.vTaskDelayUntil(&ticks, pdMS_TO_TICKS(TASK1_DELAY_MS));}
}/** Task2: Data Transmission** - Runs at 1 Hz (every second).* - Retrieves 10 samples (one second’s worth of ADC data) from the queue.* - Encapsulates the 10 samples into a JSON object with keys "VALS1-0" to "VALS1-9".* - The JSON object also includes a device ID.* - After building the JSON string, the data is logged (or could be sent over a network interface).*/
void tx_task(void *pvParameters)
{adc_data_t samples[DATA_QUEUE_LENGTH]; // Buffer to hold one-second of datachar keyStr[16]; // Buffer to format key names (e.g., "VALS1-0")TickType_t ticks = xTaskGetTickCount();while (1) {// Retrieve 10 samples from the queue.for (int i = 0; i < DATA_QUEUE_LENGTH; i++) {if (xQueueReceive(adcQueue, &samples[i], pdMS_TO_TICKS(TASK2_DELAY_MS)) != pdPASS) {ESP_LOGW(TAG, "Failed to retrieve sample %d from queue.", i);// Optionally, you might zero-fill or skip this sample.memset(&samples[i], 0, sizeof(adc_data_t));}}// Create a JSON object.cJSON *root = cJSON_CreateObject();cJSON_AddStringToObject(root, "ID", "ESP32_402FA8");// For each sample, add a key (e.g., "VALS1-0") with its corresponding ADC data array.for (int i = 0; i < DATA_QUEUE_LENGTH; i++) {sprintf(keyStr, "VALS1-%d", i);cJSON *arr = cJSON_CreateArray();for (int j = 0; j < ADC_CHANNEL_NUM; j++) {cJSON_AddItemToArray(arr, cJSON_CreateNumber(samples[i].adc_values[j]));}cJSON_AddItemToObject(root, keyStr, arr);}// Generate the JSON string.char *jsonStr = cJSON_Print(root);ESP_LOGI(TAG, "Transmitted JSON Message:\n%s", jsonStr);// Here you can add code to send the JSON message over a network interface, UART, etc.// Free allocated memory and delete the JSON object.free(jsonStr);cJSON_Delete(root);// Wait 1 second (transmission period).vTaskDelayUntil(&ticks, pdMS_TO_TICKS(TASK2_DELAY_MS));}
}/** app_main: Entry point of the ESP32 application.** - Creates a queue for the ADC data.* - Launches both the ADC acquisition and the transmission tasks.*/
void app_main(void)
{// Create a queue to hold ADC data samples (each sample is adc_data_t).adcQueue = xQueueCreate(DATA_QUEUE_LENGTH, sizeof(adc_data_t));if (adcQueue == NULL) {ESP_LOGE(TAG, "Failed to create ADC data queue");return;}// Create and start the ADC acquisition task.xTaskCreate(adc_task, "adc_task", 2048, NULL, 5, NULL);// Create and start the transmission task.xTaskCreate(tx_task, "tx_task", 4096, NULL, 5, NULL);
}
注意,需要修改"cJSON.c"文件(print_number
),使打印JSON字符串(cJSON_Print
)的浮點數類型(float
)保留一位小數。
/* Render the number nicely from the given item into a string. */
static cJSON_bool print_number(const cJSON *const item, printbuffer *const output_buffer)
{// 省略無關內容/* copy the printed number to the output and replace locale* dependent decimal point with '.' */for (i = 0; i < ((size_t)length); i++){if (number_buffer[i] == decimal_point){output_pointer[i] = '.';i++;output_pointer[i] = number_buffer[i];i++;break;}else{output_pointer[i] = number_buffer[i];}}output_pointer[i] = '\0';output_buffer->offset += (size_t)(i);return true;
}
上位機軟件
上位機軟件根據應用目標,應當分別實現或者同時實現對普通采樣模式、高速采樣模式的協議解析支持。同時實現對兩種采樣模式的協議解析支持時,應當設定一個協議切換功能(普通/高速采樣模式選擇)。
在【普通采樣】模式下,上位機每次接收的是單次刷新數據,考慮到較低的采樣頻率和對時間同步的較低敏感度,可以直接設置一個中間緩存數據,每次接收到數據后就刷新中間緩存數據。同時,直接采用定時器定時讀取中間緩存數據,刷新曲線繪制與數據保存。
在【高速采樣】模式下,上位機每次接收到的是上一個時間段中的一批數據,應當將它們依次在歷史曲線中繪制和保存。考慮到一些數據傳輸方式受環境的影響可能存在一定的延時(例如網絡傳輸),可以通過"CT"鍵的值(時間戳)在上位機軟件中進行時間的同步與對齊,即上位機軟件在此模式下不應采用定時器刷新的方式繪圖和保存數據,而是應該采用“數據驅動”的方式:收到數據后,根據時間戳計算每個時間步的精確時間(X軸值),然后將這些數據點依次繪制和保存。