npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@deconz-community/ddf-bundler

v0.31.0

Published

Creating and editing DDF bundles for deconz

Downloads

81

Readme

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.

| Tag | Description | Data | Format | |------|------------------------------------------|-----------|------------| | DDFC | DDF JSON file (devcap1.schema.json) | Text file | json | | JSON | Generic files for items / constants | Text file | json | | SCJS | Javascript file for read, write or parse | Text file | javascript | | CHLG | Changelog | Text file | markdown | | INFO | Informational note | Text file | markdown | | WARN | Warning note | Text file | markdown | | KWIS | Know issue | Text file | markdown |

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

| Tag | Path format | Example | |------|--------------|-------------------------------------------------------| | DDFC | Repository | tuya/ZY-M100_human_breathing_presence.json | | JSON | Repository | generic/items/attr_id_item.json | | SCJS | DDF Relative | ../generic/illuminance_cluster/lux_to_lightlevel.js | | CHLG | DDF Relative | ZY-M100_changelog.md | | INFO | DDF Relative | ZY-M100_info.md | | WARN | DDF Relative | ZY-M100_warning.md | | KWIS | DDF Relative | ZY-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
  },
)