commit 24b2764199c7c1a0ff66695523d6c5b45d90baaf Author: crosstyan Date: Mon Jul 7 15:23:17 2025 +0800 init diff --git a/example.jsonc b/example.jsonc new file mode 100644 index 0000000..9f1c7c8 --- /dev/null +++ b/example.jsonc @@ -0,0 +1,225 @@ +{ + "version": 1, // version of the protocol, major number only + "msg_type": "telemetry", // telemetry only for now + // optional, defaults to "descriptive" + // + // for strict mode, no `metadata` in fields is allowed + // all fields must be well defined in separate documentation (e.g. JSON schema) + // + // for descriptive mode, everything should self contained (like unit, sample interval, etc.) + "mode": "descriptive", + "dev_id": "148413b4-c352-49a9-9c48-9d15276a99e7", // uuid-v4, unique for each device + "dev_type": "device_type_name", + // "dev_type field supports: + // string: designated by a unique string + // {"type_id": "148413b4-c352-49a9-9c48-9d15276a99e7"} // uuid-v4, unique for each device type + // {"type_name": "device_type_name"} // unique string, same as the string above + "timestamp": 1715145600, // UNIX timestamp in seconds + // timestamp fields supports: + // number: UNIX timestamp in seconds (default) + // {"unix_ns": 1715145600000000000} + // {"unix_us": 1715145600000000} + // {"unix_ms": 1715145600000} + // {"unix_s": 1715145600} // same as the number above + // {"iso8601": "2025-01-01T00:00:00Z"} + "fields": [ + // note the name of the field must be unique in the array + // discrete fields + { + // `id` MUST be unique in the `fields` array + // should be ASCII string, no whitespace, no special characters, only underscore is allowed + "id": "field_1", + "value": "field_value", // string, number, boolean or enum value + // field specific metadata + "metadata": { + // required + // primitive types are + // + // type Primitive = number | string | boolean | enum + // where `enum` is new type alias of number with well defined values + // type DataType = Primitive | + // [Primitive] | // array + // {v: [Primitive], t: [Timestamp]} // irregular + // + // - `null` is not allowed + // - nesting (array of array or irregular of array) is not allowed + "data_type": "string", + // optional + // timestamp of the time of sampling + // which might differ from the timestamp of uplink message + // if not provided, the timestamp of uplink message is used + "timestamp": 1715145600, + // optional + // recommended to use UCUM (unified code for units of measure) + // but arbitrary string is supported + "unit": "unit_name", + // optional + // confidence of sampling, 0-1, 1 means 100% confidence + "confidence": 0.95, + // optional + // user defined fields (arbitrary key-value pairs) + "user": { + "user_defined_field_1": "user_defined_value_1", + "user_defined_field_2": "user_defined_value_2" + } + } + }, + // batch fields, for continuous data + { + "id": "field_2", // unique string + "value": [ + 1, + 2, + 3 + ], // array of numbers, strings, booleans or enum values + "metadata": { + // required + // "array", "array", "array", or "array" + // no nesting is allowed + "data_type": "array", + "timestamp": 1715145600, // timestamp of the time of sampling + "sample_interval": 0.5, // sample interval in seconds, 0.5 means 2 samples per second + // sample_interval fields also supports: + // {"ms": 0, "s": 0, "m": 0, "h": 0, "d": 0, "w": 0} + // the total sample interval is the sum of each field's sample interval in each unit + // optional + // recommended to use UCUM (unified code for units of measure) + // but arbitrary string is supported + "unit": "unit_name", + // optional + // array of confidence, or single scalar confidence + // confidence of each sample, 0-1, 1 means 100% confidence + // if it's array, its length must be same as the length of the value array + "confidence": [ + 0.95, + 0.95, + 0.95 + ] + } + }, + // batch fields, with enum + { + "id": "air_quality", + // interpreted as: + // good, good, moderate, good, unhealthy, very_unhealthy, hazardous + // for each hour past from `timestamp`, the value is sampled + "value": [ + 1, + 1, + 2, + 1, + 3, + 4, + 5 + ], + "metadata": { + // refer to global enum definitions in `root.metadata.enums` + "data_type": "array", + "timestamp": 1715145600, + // indicate sample interval in 1 hour + "sample_interval": { + "h": 1 + } + } + }, + // irregular batch fields, with inline enum + { + "id": "water_quality", + // the length of v and t must be the same + "value": { + "v": [ + 1, + 2, + 2, + 1, + 1, + 3 + ], + // any legal timestamp format is supported + "t": [ + 1715145600, + 1715145600, + 1715145600, + 1715145600, + 1715145600, + 1715145600 + ] + }, + "metadata": { + // refer to local enum definitions in current `metadata.enum` + "data_type": "irregular", + "enum": { + "good": 1, + "moderate": 2, + "bad": 3 + }, + "timestamp": 1715145600 + // `sample_interval` would be ignored for irregular field + } + }, + // an example of sensor with invalid reading + { + "id": "temperature", + "value": 0, + "metadata": { + // optional + // any UTF-8 string is allowed + // data consumer would decide whether or how to display this label + "label": "温度", + "data_type": "number", + // required when error occurs, SHOULD NOT appear in non-error cases + // defaults to 0 for non-error cases + // + // well-defined error codes are in separate documentation + "error_code": 1001, + // optional + // a custom error message + "error_msg": "sensor disconnected", + "confidence": 0, + "unit": "Cel" + } + } + ], + // message level metadata + "metadata": { + // optional + // global enum definitions + "enums": [ + { + // unique string in enums array + "id": "air_quality", + "value": { + "good": 1, + "moderate": 2, + "unhealthy": 3, + "very_unhealthy": 4, + "hazardous": 5 + } + } + ], + // optional + "location": { + "coordinates": [ + 24.123456, + 120.123456 + ], // latitude and longitude in degrees + // optional, defaults to "gcj02" + "coordinate_system": "wgs84", // "wgs84", "gcj02", "bd09" + // optional + "accuracy": 10, // accuracy in meters + // optional + "altitude": 100 // altitude in meters + }, + // optional + // battery in percentage, 0-100 + "battery": 99, + // optional, + // for each sample, increase by 1 + // defaults to uint32 cyclic counter + "seq": 12345, + "user": { + "user_defined_global_field_1": "user_defined_global_value_1", + "user_defined_global_field_2": "user_defined_global_value_2" + } + } +} \ No newline at end of file diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..05086f5 --- /dev/null +++ b/schema.json @@ -0,0 +1,338 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/telemetry.schema.json", + "title": "Telemetry Message", + "description": "Schema describing a telemetry message exchanged by devices.", + "type": "object", + "additionalProperties": false, + "properties": { + "version": { + "type": "integer", + "minimum": 1, + "description": "Protocol semantic version (integer)." + }, + "msg_type": { + "type": "string", + "enum": [ + "telemetry" + ], + "description": "Currently only the literal string 'telemetry' is supported." + }, + "dev_id": { + "$ref": "#/$defs/uuid" + }, + "dev_type": { + "description": "Device type identifier (string or one-of helper objects).", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "required": [ + "type_id" + ], + "additionalProperties": false, + "properties": { + "type_id": { + "$ref": "#/$defs/uuid" + } + } + }, + { + "type": "object", + "required": [ + "type_name" + ], + "additionalProperties": false, + "properties": { + "type_name": { + "type": "string" + } + } + } + ] + }, + "timestamp": { + "$ref": "#/$defs/timestamp" + }, + "fields": { + "type": "array", + "description": "Array of discrete or batch field objects.", + "items": { + "oneOf": [ + { + "$ref": "#/$defs/discrete_field" + }, + { + "$ref": "#/$defs/batch_field" + } + ] + } + }, + "enum": { + "type": "array", + "items": { + "$ref": "#/$defs/enum_definition" + } + }, + "location": { + "$ref": "#/$defs/location" + }, + "battery": { + "type": "integer", + "minimum": 0, + "maximum": 100, + "description": "Battery percentage (0-100)." + }, + "sqe": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295, + "description": "Sample sequence counter (uint32)." + } + }, + "required": [ + "version", + "msg_type", + "dev_id", + "dev_type", + "timestamp", + "fields" + ], + "$defs": { + "uuid": { + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + "description": "UUID v4 string." + }, + "timestamp": { + "description": "UNIX epoch (seconds) or an object with a single alternative timestamp representation.", + "oneOf": [ + { + "type": "number" + }, + { + "type": "object", + "minProperties": 1, + "maxProperties": 1, + "additionalProperties": false, + "properties": { + "unix_ns": { + "type": "number" + }, + "unix_us": { + "type": "number" + }, + "unix_ms": { + "type": "number" + }, + "unix_s": { + "type": "number" + }, + "iso8601": { + "type": "string", + "format": "date-time" + } + } + } + ] + }, + "sample_interval": { + "oneOf": [ + { + "type": "number", + "description": "Interval in seconds." + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "ms": { + "type": "number" + }, + "s": { + "type": "number" + }, + "m": { + "type": "number" + }, + "h": { + "type": "number" + }, + "d": { + "type": "number" + }, + "w": { + "type": "number" + } + } + } + ] + }, + "confidence": { + "oneOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + { + "type": "array", + "items": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + } + ] + }, + "discrete_field": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "data_type", + "value", + "timestamp" + ], + "properties": { + "name": { + "type": "string" + }, + "data_type": { + "type": "string" + }, + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "timestamp": { + "$ref": "#/$defs/timestamp" + }, + "unit": { + "type": "string" + }, + "confidence": { + "$ref": "#/$defs/confidence" + } + } + }, + "batch_field": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "array_type", + "value", + "timestamp" + ], + "properties": { + "name": { + "type": "string" + }, + "array_type": { + "type": "string" + }, + "value": { + "type": "array", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + } + }, + "timestamp": { + "$ref": "#/$defs/timestamp" + }, + "sample_interval": { + "$ref": "#/$defs/sample_interval" + }, + "unit": { + "type": "string" + }, + "confidence": { + "$ref": "#/$defs/confidence" + } + } + }, + "enum_definition": { + "type": "object", + "additionalProperties": false, + "required": [ + "identifier", + "value" + ], + "properties": { + "identifier": { + "type": "string" + }, + "value": { + "type": "object", + "additionalProperties": { + "type": "number" + } + } + } + }, + "location": { + "type": "object", + "additionalProperties": false, + "required": [ + "coordinates" + ], + "properties": { + "coordinates": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "items": [ + { + "type": "number", + "minimum": -90, + "maximum": 90 + }, + { + "type": "number", + "minimum": -180, + "maximum": 180 + } + ] + }, + "coordinate_system": { + "type": "string", + "enum": [ + "wgs84", + "gcj02", + "bd09" + ], + "default": "gcj02" + }, + "accuracy": { + "type": "number", + "minimum": 0 + }, + "altitude": { + "type": "number" + } + } + } + } +} \ No newline at end of file