G919-GAS软件 JSON格式数据通讯协议-阵列数据解析

G919-GAS软件 JSON格式数据通讯协议-阵列数据解析

版本记录

DateAuthorVersionNote
2024.04.07Dog TaoV1.0发布通讯协议。
2025.05.06Dog TaoV1.11. 增加了【高速采样】模式下的通讯协议。2. 增加了“软件开发建议”小节。


本协议遵从 《G919-GAS软件 JSON格式数据通讯协议》所制定的规范。以下针对阵列结构化数据进行通讯协议制定。

普通采样模式

普通采样模式适合数据采样频率不高(≤2Hz)、数据传输速率较慢(例如蓝牙BLE传输)的场景。一般具备较低响应速度的气体传感器、电化学传感器、阻敏型压力传感器等构成的阵列适合采用此模式进行数据传输。

精确行列更新

精确行列更新的上报格式适用于在一个大型阵列中仅更新单个单元的值。

设备(下位机)的JSON键值定义参考("ID""NT"的键值必须实现,其他可根据需要增删改):

名称IDNTCTROWCOLVAL
功能设备标识符设备网络地址计数行号列号单值
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"的键值必须实现,其他可根据需要增删改):

名称IDNTCTROWCOLVAL
功能设备标识符设备网络地址计数行号列号多值
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"的键值必须实现,其他可根据需要增删改):

名称IDNTCTROWCOLVALS1-0VALS1-1VALS1-NVALS2-0VALS2-1VALS2-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系统中的任务执行时序图如下所示:

Data Acqu.&Trans. Task ADC Hardware Transmission Hardware 开始一次采样与传输 切换到通道 n 通道 n 就绪 读取通道 n 电压值 返回采样值 loop [通道 0 到 15] 封装 JSON 消息 cJSON_Print() 发送 JSON 数据 发送 JSON 字符串 loop [每次采样周期(如 1 秒)] Data Acqu.&Trans. Task ADC Hardware Transmission Hardware

在【高速采样】模式下,数据的采样频率较高,数据传输频率较低,需要采用队列(FreeRTOS Queue)用于“生产-消费”缓冲。例如,设置两个任务,一个任务用来做数据采集,每0.1秒采集一次数据送到队列中,另外一个任务用来做数据传输,每1秒钟把队列中的10个数据全部发出去。

FreeRTOS系统中的任务执行时序图如下所示:

Data Acquisition Task ADC Hardware FreeRTOS Queue Data Transmission Task Transmission Hardware 开始一次 16 通道数据采集 切换到通道 n 通道 n 准备就绪 读取通道 n 电压值 返回采样值 loop [channels 0 to 15] 将 16 通道采样结果封装至 sample 结构 xQueueSend(sample) loop [every 100 ms (10 Hz)] 开始组装 10 条样本 xQueueReceive(sample[0]) xQueueReceive(sample[1]) xQueueReceive(sample[2]) xQueueReceive(sample[3]) xQueueReceive(sample[4]) xQueueReceive(sample[5]) xQueueReceive(sample[6]) xQueueReceive(sample[7]) xQueueReceive(sample[8]) xQueueReceive(sample[9]) 封装 JSON 消息 cJSON_Print() 发送 JSON 数据 发送 JSON 字符串 loop [every 1 s (1 Hz)] Data Acquisition Task ADC Hardware FreeRTOS Queue Data Transmission Task Transmission Hardware

以下是ESP32平台基于FreeRTOS系统开发的代码示例:

  • Data Acquisition Task(Task1) 以 100 ms 间隔读取 16 通道 ADC 并调用xQueueSendadc_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 data
    char 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轴值),然后将这些数据点依次绘制和保存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全能骑士涛锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值