4. 消息格式

消息格式使用 Google's Protocol Buffers 来编解码。 手机端可以选择任意语言版本,比如kotlin。 因为编解码规则是开源的,所以有的语言即使不在官方支持范围内也可以找到第三方的开源实现。

4.1. 数据结构定义文件

数据格式定义可以从这里下载:proto.zip.

4.2. 数据封包

因为每个数据包长度不会超过默认MTU(23字节),所以经BLE发送的包不做封包处理,直接发送编好码的数据。

备注

即使以后消息可能会超过23字节,也可以调整MTU。所以暂时不会考虑拆包的情况。

4.3. 最外层格式

最外层的数据结构见 message Lynx {}。里面包含了 RequestResponseIndication 三选一的数据。 具体结构参考如下源文件。

syntax = "proto3";

import "handshake.proto";
import "copilot.proto";
import "measure.proto";
import "latest_result.proto";

message Request {
  int32 index = 1; // Assigned by Phone side.
  oneof request {
    Handshake.Request handshake = 2;
    Copilot.Request copilot = 3;
    Measure.Request measure = 4;
    LatestResult.Request latestResult = 5;
  }
}

message Response {
  int32 index = 1; // Same as the corresponding Request
  oneof response {
    Handshake.Response handshake = 2;
    Copilot.Response copilot = 3;
    Measure.Response measure = 4;
    LatestResult.Response  latestResult = 5;
  }
}

message Indication {
  int32 index = 1; // Same as the corresponding Request
  oneof indication {
    Copilot.Indication copilot = 2;
    Measure.Indication measure = 3;
  }
}

message Lynx {
  oneof  message {
    Request request = 1;
    Response response = 2;
    Indication indication = 3;
  }
}

4.4. 握手格式(handshake)

握手数据可以用来获取硬件的信息。

握手数据分为 RequestResponse 两种,定义在 message Handshake {} 中, 在 message Lynx {} 中被引用。

syntax = "proto3";

import "error.proto";

enum HardwareType {
  UNKNOWN = 0;
  TIMING_CUSHION = 1;
  STRETCH_DETECTOR = 2;
  JUMP_DETECTOR = 3;
}

message Handshake {
  message Request {

  }

  message Response {
    // Software version is "Major.Minor.Patch+Tweak"
    // It is stored in a uint32 value with below format.
    // Major << 24 | Minor << 16 | Patch << 8 | Tweak
    uint32 softVersion = 1;
    HardwareType hardwareType = 2;
    // Always LynxError.SUCCESS
    LynxError error = 3;
  }
}

4.5. 副驾格式(copilot)

副驾数据用来分享另一个作为结束计时的压力垫的信息。手机需要扫描另一个压力垫的NFC,获取序列号,然后通过此消息分享副压力垫的信息。

副驾数据分为 RequestResponseIndication 三种,定义在 message Copilot {} 中, 在 message Lynx {} 中被引用。

syntax = "proto3";

import "error.proto";

message Copilot {
  message Request {
    // Serial number of the copilot device. The string length is 10 bytes.
    string sn = 1;
    // Timeout value for connecting to the copilot device.
    int32 timeoutInSecond = 2;
  }

  message Response {
    // LynxError.SUCCESS                              - Success
    // LynxError.COMMAND_IN_PROCESS                   - The device is still processing a previous command.
    // LynxError.COMMAND_NOT_SUPPORT                  - The device does not support this command. Only timing cushion device support this command.
    // LynxError.TIME_SYNC_SERVER_START_FAIL          - Time sync server start failed.
    // LynxError.COPILOT_ALREADY_CONNECTED            - The device is already connected to the copilot.
    // LynxError.COPILOT_ALREADY_CONNECTED_DIFFERENT  - The device is connected to another copilot. You need to restart the device to connect to a new copilot.
    LynxError error = 1;
  }

  message Indication {
    // LynxError.SUCCESS                      - Success
    // LynxError.COPILOT_CONNECT_FAIL         - Failed to connect the copilot with specific sn.
    // LynxError.COPILOT_INVALID_HARDWARE     - The copilot device is not timing cushion.
    // LynxError.COPILOT_INDEX_MISMATCH       - Internal error occurred during communication with copilot device.
    // LynxError.TIME_SYNC_CLIENT_START_FAIL  - Copilot device failed to start time sync client.
    // LynxError.COPILOT_SYNC_CONNECT_FAIL    - Connecting to copilot time sync client failed.
    LynxError error = 1;
  }
}

4.6. 测量格式(measure)

测量数据用来传输测量相关的数据。

测量数据分为 RequestResponseIndication 三种,定义在 message Measure {} 中, 在 message Lynx {} 中被引用。

syntax = "proto3";

import "error.proto";

message Measure {
  message Request {
    // true - start
    // false - stop
    bool start = 1;
    // Timeout value for a measure. If not successful measure is detected, an error will be sent via Indication.
    int32 timeoutInSecond = 2;
    // 仅对跳远有效。此设置是检测到位置并保持的时间。单位是0.5秒。范围从1到6,即0.5秒到3秒。不指定的话就用默认值1秒。
    optional int32 jump_detector_stable_time = 3;
  }

  message Response {
    // LynxError.SUCCESS                      - Success
    // LynxError.COMMAND_IN_PROCESS           - The device is still processing a previous command.
    // LynxError.HARDWARE_TYPE_UNKNOWN        - The device does not detect any sensor. It does not support measure command.
    // LynxError.COPILOT_COMMUNICATE_FAIL     - For timing cushion device only. Failed to notify the copilot device.
    LynxError error = 1;
  }

  message Indication {
    // Valid result, if error is LynxError.SUCCESS.
    // TIMING_CUSHION     - Unit in millisecond
    // STRETCH_DETECTOR   - Unit in millimeter (The final distance between sensor and shiled)
    // JUMP_DETECTOR      - Unit in millimeter (highest position << 16 + lowest position)
    uint32 result = 1;
    // LynxError.SUCCESS                      - Success
    // LynxError.MEASURE_TIMEOUT              - Measure timeout.
    // LynxError.INTERNAL_ERROR               - Internal error.
    // LynxError.COPILOT_COMMUNICATE_FAIL     - For timing cushion device only. Failed to receive response from the copilot device.
    // LynxError.COPILOT_INDEX_MISMATCH       - For timing cushion device only. Internal error occurred during communication with copilot device.
    // LynxError.TIME_SYNC_NOT_FINISHED       - For timing cushion device only. Time is not synced with copilot device.
    // LynxError.MEASURE_WRONG_ORDER          - For timing cushion device only. Copilot's timestamp is early than this device. The time diff is negative.
    LynxError error = 2;
  }
}

4.7. 最后结果格式(latest result)

如果手机没有收到设备发来的测试结果,那么可以通过此命令来查询最后一次的测量结果。

最后结果数据分为 RequestResponse 两种,定义在 message LastResult {} 中, 在 message Lynx {} 中被引用。

syntax = "proto3";

import "error.proto";


message LatestResult {
  message Request {}

  message Response {
    // Last valid result in "Measure.Indication" if error is LynxError.SUCCESS.
    uint32 result = 1;
    // LynxError.LAST_RESULT_EMPTY                 - There is no last result. It occurs under two conditions. 1. No measure occur after power up. 2. You have started a measure and then stop before a successful measure.
    // Other values are the same as the error returned by "Measure.Response" or "Measure.Indication" last time.
    LynxError error = 2;
  }
}

4.8. 错误码

错误码的定义如下:

syntax = "proto3";

enum LynxError {
  SUCCESS = 0;
  INTERNAL_ERROR = 1;
  HARDWARE_TYPE_UNKNOWN = 2;

  COMMAND_NOT_SUPPORT = 5;
  COMMAND_IN_PROCESS = 6;
  TIME_SYNC_SERVER_START_FAIL = 10;
  TIME_SYNC_CLIENT_START_FAIL = 11;
  TIME_SYNC_NOT_FINISHED = 12;
  COPILOT_CONNECT_FAIL = 13;
  COPILOT_INDEX_MISMATCH = 14;
  COPILOT_SYNC_CONNECT_FAIL = 15;
  COPILOT_INVALID_HARDWARE = 16;
  COPILOT_COMMUNICATE_FAIL = 17;
  COPILOT_ALREADY_CONNECTED = 18;
  COPILOT_ALREADY_CONNECTED_DIFFERENT = 19;

  MEASURE_TIMEOUT = 20;
  MEASURE_WRONG_ORDER = 21;

  LAST_RESULT_EMPTY = 30;
}