0.21.0 • Published 2 days ago

@deconz-community/ddf-bundler v0.21.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 days 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.

{
  "source": "https://deconz-community.github.io/ddf-store/XXXX/XXXX",
  "last_modified": "2023-01-08T17:24:24z",
  "version_deconz": ">2.19.3",
  "product": "acme 2000",
  "links": [
    "url-to-forum-entry",
    "url-to-github-entry"
  ],
  "device_identifiers": [
    ["Philips", "acme 2000"],
    ["Signify", "acme 200"]
  ]
}

source (optional)

The URL of the DDF page on the store, could be used to update DDF.

Example
"https://deconz-community.github.io/ddf-store/XXXX/XXXX"

last_modified (required)

The last modified date of the bundle in a complete date plus hours ISO 8601 format.

Example
"2023-01-08T17:24:24z"

version_deconz (required)

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

Example
">2.20.1"

product (required)

The english device commercial name of the device.

Example
"acme 2000"

product_localised (optional)

The device commercial name of the device localised indexed by the ISO 639-1 code.

Note that the "en" name it's on product property.

Example
{
  "de": "german product name",
  "fr": "french product name"
}

links (optional)

Any link usefull about this bundle, can be issue link, forum link, device official web page

Example
[
  "url-to-forum-entry",
  "url-to-github-entry"
]

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.DDFC - DDF JSON (compressed) - unique

U32 'DDFC'
U32 Chunk Size
Data[Size]

Holds the base DDF compressed with zlib.

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]

FileType

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

For Text file they are all compressed using zlib.

TagDescriptionDataFormat
SCJSJavascript file for read, write or parseText filejavascript
JSONGeneric files for items / constantsText filejson
BTNMButton maps* WIP NOT USEDText filejson
CHLGChangelogText filemarkdown
NOTIInformational noteText filemarkdown
NOTWWarning noteText filemarkdown
KWISKnow issueText filemarkdown
IMGPImage in PNG can be used in UIBinarypng

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 { decode } from '@deconz-community/ddf-bundler'
import { readFile } from 'fs/promises'

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

const bundle = await decode(blob)

Encoding a bundle

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

const bundle = Bundle()

bundle.data.name = 'sample.ddf'
bundle.data.desc.product = 'Sample product'
bundle.data.ddfc = '{"schema": "devcap1.schema.json"}'
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.21.0

2 days ago

0.20.0

4 days ago

0.19.0

7 days ago

0.18.1

7 days ago

0.17.0

8 days ago

0.18.0

7 days ago

0.16.0

20 days ago

0.15.1

5 months ago

0.15.0

5 months ago

0.14.0

5 months ago

0.13.0

5 months ago

0.10.0

10 months ago

0.11.0

8 months ago

0.9.0

10 months ago

0.12.0

5 months ago

0.8.0

10 months ago

0.7.0

10 months ago

0.5.2

10 months ago

0.5.0

11 months ago

0.2.1

1 year ago

0.2.0

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago

0.0.1

1 year ago