0.31.0 • Published 9 months ago

@deconz-community/ddf-bundler v0.31.0

Weekly downloads
-
License
MIT
Repository
github
Last release
9 months ago

DDF bundler

General

The format is a Resource Interchange File Format (RIFF), see: https://en.wikipedia.org/wiki/Resource_Interchange_File_Format Which can store arbitrary content and more importantly can by extended easily while being backward compatible.

All multi byte values are little-endian encoded.

DDF_BUNDLE_MAGIC is ASCII "DDFB"

Each chunk is prefixed by it's tag, then the size and finnaly the data.

U32 'RIFF'
U32 RIFF Size
U32 DDF_BUNDLE_MAGIC
U32 DDF_Bundle Size
U32 Chunk Tag
U32 Chunk Size
Data[Size]
....
U32 Chunk Tag
U32 Chunk Size
Data[Size]

The file can be see as a tree structure.

Visual representation of the DDF split by chunks

DDF_bundle_format.png

Chunks

DDFB.DESC - Descriptor - unique

U32 'DESC'
U32 Chunk Size
Data[Size]

This is always the first chunk and allows fast indexing and matching without parsing the whole DDF. It's a JSON file.

{
  "uuid": "331012bd-ce22-4a1b-9f4a-d092aa2cca92",
  "product": "Hue white and color ambiance gradient light",
  "version_deconz": ">2.27.0",
  "last_modified": "2024-05-05T14:07:12.000Z",
  "device_identifiers": [
    [
      "Signify Netherlands B.V.",
      "LCX001"
    ]
  ]
}

uuid (required)

The UUID of the DDF, it's a unique identifier for the DDF.

Example
"331012bd-ce22-4a1b-9f4a-d092aa2cca92"

vendor (required)

The english device commercial name of the vendor.

Example
"Hue white and color ambiance gradient light"

product (required)

The english device commercial name of the device.

Example
"Hue white and color ambiance gradient light"

version_deconz (required)

The minimum version for Deconz. It's using Semantic Versioning with version comparaison. See Semver Calculator for example.

Example
">2.27.0"

last_modified (required)

The lastest modified date of any file of the bundle in a complete date plus hours ISO 8601 format.

Example
"2024-05-05T14:07:12.000Z"

device_identifiers (required)

The list of device identifier, it's generated from each combinaison of manufacturername and modelid from the DDF.

Example
[
  ["Philips", "acme 2000"],
  ["Signify", "acme 200"]
]

DDFB.EXTF - External file - multiple

U32 'EXTF'
U32 FileType (see below)
U16 PathLength
U8 [PathLength] Filepath
U16 ModificationTimeLength
U8 [ModificationTimeLength] ModificationTime in ISO 8601 format
U32 FileSize
U8 Data[FileSize]

See ISO 8601 for the date format.

FileType

FileType is a tag to know what kind of file the chunk contain.

TagDescriptionDataFormat
DDFCDDF JSON file (devcap1.schema.json)Text filejson
JSONGeneric files for items / constantsText filejson
SCJSJavascript file for read, write or parseText filejavascript
CHLGChangelogText filemarkdown
INFOInformational noteText filemarkdown
WARNWarning noteText filemarkdown
KWISKnow issueText filemarkdown

FilePath

The file name of the path must be unique in the bundle. You can use the same file name in different directories.

The path of the file in the bundle. There is 2 types of path, Repository and DDF Relative.

The Repository path is the absolute path of the file from the GitHub repository from the (devices directory)https://github.com/dresden-elektronik/deconz-rest-plugin/tree/master/devices. For example deconz-rest-plugin/devices/tuya/ZY-M100_human_breathing_presence.json became tuya/ZY-M100_human_breathing_presence.json once bundled.

The DDF Relative path is the path exactly how they are written in the DDF json file. It's always relative to the path of the DDF json file. For example if the DDF json file contain :

{
  "parse": {
    "dpid": 104,
    "script": "../generic/illuminance_cluster/lux_to_lightlevel.js",
    "fn": "tuya"
  }
}

The path of the file on the disk is deconz-rest-plugin/devices/generic/illuminance_cluster/lux_to_lightlevel.js and became ../generic/illuminance_cluster/lux_to_lightlevel.js once bundled.

Path format by FileType
TagPath formatExample
DDFCRepositorytuya/ZY-M100_human_breathing_presence.json
JSONRepositorygeneric/items/attr_id_item.json
SCJSDDF Relative../generic/illuminance_cluster/lux_to_lightlevel.js
CHLGDDF RelativeZY-M100_changelog.md
INFODDF RelativeZY-M100_info.md
WARNDDF RelativeZY-M100_warning.md
KWISDDF RelativeZY-M100_known_issues.md

DDFB.VALI - DDF Validation result - unique - optional

The result of the validation using the ddf-validator. It's a JSON object. If there is no errors the errors property is omitted.

result can be success, error or skipped.

Example success
{
  "result": "success",
  "version": "2.20.0"
}
Example error
{
  "result": "error",
  "version": "2.20.0",
  "errors": [
    {
      "type": "simple",
      "message": "Missing file 'warning.md'."
    },
    {
      "type": "validation",
      "message": "Unrecognized key(s) in object: 'cl'",
      "path": ["subdevices", 0, "items", 6, "parse"],
      "file": "generic/items/state_airquality_item.json",
      "line": 40,
      "column": 5
    },
    {
      "type": "validation",
      "message": "Unrecognized key(s) in object: 'cl'",
      "file": "ddf.json",
      "path": ["subdevices", 0, "items", 9, "parse"],
      "line": 40,
      "column": 5
    }
  ]
}
Example skipped

Used when the flag ddfvalidate is set to false in the DDFC.

{
  "result": "skipped",
  "version": "2.20.0"
}

SIGN - Signature - multiple

Signatures are very easy to handle with a few lines of code and make sure the DDF is not messed with. A DDF bundle which is submitted for testing can be promoted to stable / official by simply adding another signature in this chunk nothing else needs to be modified.

Holds one signature over the DDF_BUNDLE_MAGIC chunk. The signature and public key use the secp256k1 ECDSA format. https://paulmillr.com/noble/

U32 'SIGN'
U32 Chunk Size
U16 PublicKey Length
U8 [PublicKey Length] PublicKey
U16 Signature Length
U8 [Signature Length] Signature

Thoses chunk are always at the end of the bundle and not inside the DDFB chunk.

Example usage

Decoding a bundle

import { readFile } from 'node:fs/promises'
import { decode } from '@deconz-community/ddf-bundler'

const data = await readFile(path.join(__dirname, 'ddf/aq1_vibration_sensor.ddb'))
const blob = new Blob([data])
blob.name = 'aq1_vibration_sensor'

const bundle = await decode(blob)

Encoding a bundle

import { Bundle, encode } from '@deconz-community/ddf-bundler'

const bundle = Bundle()

bundle.data.name = 'sample'
bundle.data.desc.product = 'Sample product'
bundle.data.files.push({
  type: 'DDFC',
  data: '{"schema": "devcap1.schema.json"}',
  path: 'ddf.json',
  last_modified: new Date(),
})
bundle.data.files.push({
  type: 'JSON',
  data: JSON.stringify({ foo: 'bar' }),
  path: 'foo.json',
  last_modified: new Date(),
})

const encoded = encode(bundle)

Building a bundle from files

import { buildFromFiles, createSource } from '@deconz-community/ddf-bundler'

const bundle = await buildFromFiles(
  `file://${genericDirectoryPath}`,
  `file://${inputFilePath}`,
  async (path) => {
    if (sources.has(path))
      return sources.get(path)!
    const filePath = path.replace('file://', '')
    const data = await fs.readFile(filePath)
    const source = createSource(new Blob([data]), {
      path,
      last_modified: (await fs.stat(filePath)).mtime,
    })
    sources.set(path, source)
    return source
  },
)
0.31.0

9 months ago

0.30.0

9 months ago

0.29.1

10 months ago

0.29.0

1 year ago

0.28.1

1 year ago

0.28.0

1 year ago

0.27.1

1 year ago

0.27.0

1 year ago

0.26.0

1 year ago

0.25.0

1 year ago

0.24.0

1 year ago

0.23.0

1 year ago

0.22.0

1 year ago

0.21.0

1 year ago

0.20.0

1 year ago

0.19.0

1 year ago

0.18.1

1 year ago

0.17.0

1 year ago

0.18.0

1 year ago

0.16.0

1 year ago

0.15.1

2 years ago

0.15.0

2 years ago

0.14.0

2 years ago

0.13.0

2 years ago

0.10.0

2 years ago

0.11.0

2 years ago

0.9.0

2 years ago

0.12.0

2 years ago

0.8.0

2 years ago

0.7.0

2 years ago

0.5.2

2 years ago

0.5.0

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago

0.0.1

2 years ago