[
  {
    "path": ".ci/scripts/do_sysroot.sh",
    "content": "#!/bin/bash\n\nset -e\n\nlibusb_ver=1.0.29\nrtlsdr_ver=2.0.2\npothos_ver=2021.07.25-vc16\n\n[ \"$(uname)\" = \"Darwin\" ] && export tools=/opt/local\n\n# prefer GNU commands\nrealpath=$(command -v grealpath || :)\nrealpath=\"${realpath:-realpath}\"\nsed=$(command -v gsed || :)\nsed=\"${sed:-sed}\"\n\n# from https://libusb.info/\nif [ ! -e libusb/include/libusb.h ]\nthen\n    [ -e libusb-${libusb_ver}.7z ] || curl -L -O https://github.com/libusb/libusb/releases/download/v${libusb_ver}/libusb-${libusb_ver}.7z\n    mkdir -p libusb\n    7z x -olibusb -y libusb-${libusb_ver}.7z\nfi\n\n# remove this script name and two dir levels to get the source root\nsource_dir=$(dirname $(dirname $(dirname $($realpath -s $0))))\nsysroot32=$(pwd)/sysroot32\nsysroot64=$(pwd)/sysroot64\nsysroot32static=$(pwd)/sysroot32static\nsysroot64static=$(pwd)/sysroot64static\n\nmkdir -p sysroot{32,64}{,static}/usr/{include,lib,bin}\n\ncp libusb/include/libusb.h $sysroot32/usr/include\ncp libusb/include/libusb.h $sysroot64/usr/include\ncp libusb/include/libusb.h $sysroot32static/usr/include\ncp libusb/include/libusb.h $sysroot64static/usr/include\n\ncp libusb/MinGW32/static/libusb-1.0.a $sysroot32static/usr/lib\ncp libusb/MinGW64/static/libusb-1.0.a $sysroot64static/usr/lib\n\ncp libusb/MinGW32/dll/libusb-1.0.dll $sysroot32/usr/bin\ncp libusb/MinGW32/static/libusb-1.0.dll.a $sysroot32/usr/lib\ncp libusb/MinGW64/dll/libusb-1.0.dll $sysroot64/usr/bin\ncp libusb/MinGW64/static/libusb-1.0.dll.a $sysroot64/usr/lib\n\nif [ ! -d rtl-sdr-${rtlsdr_ver} ]\nthen\n    # or git clone https://github.com/osmocom/rtl-sdr.git\n    [ -e rtl-sdr-${rtlsdr_ver}.tar.gz ] || curl -L -o rtl-sdr-${rtlsdr_ver}.tar.gz https://github.com/osmocom/rtl-sdr/archive/v${rtlsdr_ver}.tar.gz\n    tar xzf rtl-sdr-${rtlsdr_ver}.tar.gz\nfi\n\ncd rtl-sdr-${rtlsdr_ver}\n\nif [ ! -e $sysroot32/usr/lib/librtlsdr.a ]\nthen\n    export CMAKE_SYSROOT=$sysroot32 ; echo $CMAKE_SYSROOT\n    cmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-i686.cmake \\\n        -DLIBUSB_FOUND=1 \\\n        -DLIBUSB_LIBRARIES=$CMAKE_SYSROOT/usr/lib/libusb-1.0.dll.a \\\n        -DLIBUSB_INCLUDE_DIRS=$CMAKE_SYSROOT/usr/include \\\n        -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\n    rm -rf build-tmp\n    mv $CMAKE_SYSROOT/usr/lib/librtlsdr_static.a $CMAKE_SYSROOT/usr/lib/librtlsdr.a\nfi\n\nif [ ! -e $sysroot32static/usr/lib/librtlsdr.a ]\nthen\n    export CMAKE_SYSROOT=$sysroot32static ; echo $CMAKE_SYSROOT\n    cmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-i686.cmake \\\n        -DLIBUSB_FOUND=1 \\\n        -DLIBUSB_LIBRARIES=$CMAKE_SYSROOT/usr/lib/libusb-1.0.a \\\n        -DLIBUSB_INCLUDE_DIRS=$CMAKE_SYSROOT/usr/include \\\n        -DBUILD_SHARED_LIBS:BOOL=OFF \\\n        -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\n    rm -rf build-tmp\n    mv $CMAKE_SYSROOT/usr/lib/librtlsdr_static.a $CMAKE_SYSROOT/usr/lib/librtlsdr.a\n    rm $CMAKE_SYSROOT/usr/lib/librtlsdr.dll.a\n    rm $CMAKE_SYSROOT/usr/bin/librtlsdr.dll\nfi\n\nif [ ! -e $sysroot64/usr/lib/librtlsdr.a ]\nthen\n    export CMAKE_SYSROOT=$sysroot64 ; echo $CMAKE_SYSROOT\n    cmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-x86-64.cmake \\\n        -DLIBUSB_FOUND=1 \\\n        -DLIBUSB_LIBRARIES=$CMAKE_SYSROOT/usr/lib/libusb-1.0.dll.a \\\n        -DLIBUSB_INCLUDE_DIRS=$CMAKE_SYSROOT/usr/include \\\n        -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\n    rm -rf build-tmp\n    mv $CMAKE_SYSROOT/usr/lib/librtlsdr_static.a $CMAKE_SYSROOT/usr/lib/librtlsdr.a\nfi\n\nif [ ! -e $sysroot64static/usr/lib/librtlsdr.a ]\nthen\n    export CMAKE_SYSROOT=$sysroot64static ; echo $CMAKE_SYSROOT\n    cmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-x86-64.cmake \\\n        -DLIBUSB_FOUND=1 \\\n        -DLIBUSB_LIBRARIES=$CMAKE_SYSROOT/usr/lib/libusb-1.0.a \\\n        -DLIBUSB_INCLUDE_DIRS=$CMAKE_SYSROOT/usr/include \\\n        -DBUILD_SHARED_LIBS:BOOL=OFF \\\n        -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\n    rm -rf build-tmp\n    mv $CMAKE_SYSROOT/usr/lib/librtlsdr_static.a $CMAKE_SYSROOT/usr/lib/librtlsdr.a\n    rm $CMAKE_SYSROOT/usr/lib/librtlsdr.dll.a\n    rm $CMAKE_SYSROOT/usr/bin/librtlsdr.dll\nfi\n\ncd ..\n\nif [ ! -e $sysroot64/usr/bin/SoapySDR.dll -o ! -e $sysroot64/usr/lib/SoapySDR.lib ]\nthen\n    # from https://downloads.myriadrf.org/builds/PothosSDR/\n    [ -e PothosSDR-${pothos_ver}-x64.exe ] || curl -L -O https://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-${pothos_ver}-x64.exe\n    mkdir -p pothos\n    7z x -opothos -y PothosSDR-${pothos_ver}-x64.exe\n    # workaround: 7-Zip 9.20 creates strange root directories\n    [ -e pothos/bin ] || mv pothos/*/* pothos/ || :\n    cp pothos/bin/SoapySDR.dll $sysroot64/usr/bin\n    cp -R pothos/include/SoapySDR $sysroot64/usr/include\n    cp pothos/lib/SoapySDR.lib $sysroot64/usr/lib\n    cp -R pothos/cmake $sysroot64/usr\n    $sed -i 's/.*INTERFACE_COMPILE_OPTIONS.*//g' $sysroot64/usr/cmake/SoapySDRExport.cmake\nfi\n\n# build rtl_433\n\nexport CMAKE_SYSROOT=$sysroot32 ; echo $CMAKE_SYSROOT\ncmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-i686.cmake -S $source_dir -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\nrm -rf build-tmp\n# Non-static 32-bit binary from w64 compiler is broken with\n# missing libgcc_s_sjlj-1.dll, libwinpthread-1.dll\n# neither CMAKE_EXE_LINKER_FLAGS=\"-static-libgcc\"\n# nor target_link_libraries(rtl_433 -static-libgcc)\n# fix this. Ideas welcome.\nmv $CMAKE_SYSROOT/usr/bin/rtl_433.exe $CMAKE_SYSROOT/usr/bin/rtl_433_32bit_nonstatic_broken.exe\n\nexport CMAKE_SYSROOT=$sysroot32static ; echo $CMAKE_SYSROOT\ncmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-i686.cmake -S $source_dir -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\nrm -rf build-tmp\nmv $CMAKE_SYSROOT/usr/bin/rtl_433.exe $CMAKE_SYSROOT/usr/bin/rtl_433_32bit_static.exe\n\nexport CMAKE_SYSROOT=$sysroot64 ; echo $CMAKE_SYSROOT\ncmake -DENABLE_SOAPYSDR=ON -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-x86-64.cmake -S $source_dir -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\nrm -rf build-tmp\n\nexport CMAKE_SYSROOT=$sysroot64static ; echo $CMAKE_SYSROOT\ncmake -DCMAKE_TOOLCHAIN_FILE=$source_dir/cmake/Toolchain-gcc-mingw-w64-x86-64.cmake -S $source_dir -B build-tmp && cmake --build build-tmp && cmake --install build-tmp\nrm -rf build-tmp\nmv $CMAKE_SYSROOT/usr/bin/rtl_433.exe $CMAKE_SYSROOT/usr/bin/rtl_433_64bit_static.exe\n\n# collect package\n\ncp $source_dir/.deploy/WINDOWS-MINGWW64.txt README.txt\n\necho Packing rtl_433-win-x32.zip\nzip --junk-paths rtl_433-win-x32.zip sysroot32*/usr/bin/*.dll sysroot32*/usr/bin/rtl_433*.exe README.txt\n\necho Packing rtl_433-win-x64.zip\nzip --junk-paths rtl_433-win-x64.zip sysroot64*/usr/bin/*.dll sysroot64*/usr/bin/rtl_433*.exe README.txt\n"
  },
  {
    "path": ".ci/scripts/do_tests.sh",
    "content": "#!/bin/bash\n\n# This script is for internal CI use only\n\nset -e\nset -x\n\n# prefer GNU commands\nrealpath=$(command -v grealpath || :)\nrealpath=\"${realpath:-realpath}\"\n\n# remove this script name and two dir levels to get the source root\nsource_dir=$(dirname $(dirname $(dirname $($realpath -s $0))))\n\ncd \"${source_dir}/..\"\n[ -e rtl_433_tests ] || git clone --depth 1 https://github.com/merbanan/rtl_433_tests.git\ncd rtl_433_tests\nexport PATH=\"${source_dir}/build/src:$PATH\"\ntest -f \"${source_dir}/build/src/rtl_433\"\n\n# python3 -m venv .venv\n# source .venv/bin/activate\n# pip install deepdiff\nmake test\n"
  },
  {
    "path": ".clang-format",
    "content": "UseTab: Never\nIndentWidth: 4\nContinuationIndentWidth: 8\nBreakBeforeBraces: Stroustrup\nAlignAfterOpenBracket: DontAlign\nAlignEscapedNewlines: DontAlign\nAlignConsecutiveAssignments: true\nAlignConsecutiveMacros: AcrossEmptyLines\nAllowShortIfStatementsOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: true\nIndentCaseLabels: false\nColumnLimit: 0\nSortIncludes: false\n"
  },
  {
    "path": ".deploy/WINDOWS-MINGWW64.txt",
    "content": "# rtl_433 Windows MinGW-W64 build\n\nThe \"static\" builds are self-contained but do not include SoapySDR support.\nThe regular builds depend on LibUSB, RTL-SDR, and SoapySDR libraries.\n\nThere are no TLS builds (mqtts and influxs) currently.\n\nFor the SoapySDR builds you need PothosSDR installed https://downloads.myriadrf.org/builds/PothosSDR/\nAny recent version should work, currently built with 2021.07.25-vc16:\nhttps://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2021.07.25-vc16-x64.exe\nWhen installing choose \"Add PothosSDR to the system PATH for the current user\"\n\nRemove the SoapySDR.dll in this directory, it's for testing only and won't load any driver modules.\nTo run with SoapySDR you need to copy `rtl_433.exe` to `PothosSDR\\bin` (usually `C:\\Program Files\\PothosSDR`).\nOtherwise SoapySDR driver modules won't be found.\n\nAn alternative to installing SoapySDR from PothosSDR is to extract the installer\nand copy the builds (.exe) from this release to the `bin` directory in PothosSDR.\n\n## Quick start\n\nTo run rtl_433 you have to open Windows Command Prompt (cmd.exe) or Windows Terminal,\nthen type \"cd (rtl_433 folder directory, not rtl_433.exe)\"\nExample: \"cd C:\\Users\\(name)\\Downloads\\(rtl_433 folder)\"\nIf typed correctly, the command prompt should change to your rtl_433 folder directory,\nthen type \"rtl_433 (option(s))\" and enter, rtl_433 will then start running with the options given.\n\nRunning the \"-F http\" option would look like this:\n(directory)> rtl_433 -F http\nPress enter and rtl_433 should successfully launch.\nWhen it does, go to http://127.0.0.1:8433/\n"
  },
  {
    "path": ".deploy/WINDOWS-MSVC.txt",
    "content": "# rtl_433 Windows MSVC build\n\nFor the SoapySDR builds you need PothosSDR installed https://downloads.myriadrf.org/builds/PothosSDR/\nAny recent version should work, currently built with 2021.07.25-vc16:\nhttps://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2021.07.25-vc16-x64.exe\nWhen installing choose \"Add PothosSDR to the system PATH for the current user\"\n\nRemove the SoapySDR.dll in this directory, it's for testing only and won't load any driver modules.\nTo run with SoapySDR you need to copy `rtl_433-rtlsdr-soapysdr.exe` to `PothosSDR\\bin` (usually `C:\\Program Files\\PothosSDR`).\nOtherwise SoapySDR driver modules won't be found.\n\nFor the TLS builds (mqtts and influxs) you need OpenSSL installed.\nE.g. install Chocolatey https://chocolatey.org/install\nthen run `choco install openssl.light`\n\nAn alternative to installing SoapySDR from PothosSDR is to extract the installer\nand copy the builds (.exe) from this release to the `bin` directory in PothosSDR.\n\nThis release includes the Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019.\nS.a. https://support.microsoft.com/en-us/topic/the-latest-supported-visual-c-downloads-2647da03-1eea-4433-9aff-95f26a218cc0\n\n## Quick start\n\nTo run rtl_433 you have to open Windows Command Prompt (cmd.exe) or Windows Terminal,\nthen type \"cd (rtl_433 folder directory, not rtl_433.exe)\"\nExample: \"cd C:\\Users\\(name)\\Downloads\\(rtl_433 folder)\"\nIf typed correctly, the command prompt should change to your rtl_433 folder directory,\nthen type \"rtl_433 (option(s))\" and enter, rtl_433 will then start running with the options given.\n\nRunning the \"-F http\" option would look like this:\n(directory)> rtl_433 -F http\nPress enter and rtl_433 should successfully launch.\nWhen it does, go to http://127.0.0.1:8433/\n"
  },
  {
    "path": ".deploy/gen_nightly_info.sh",
    "content": "#!/bin/bash\n\ngit log $(git describe --tags --abbrev=0 HEAD^)..HEAD --oneline |egrep -v 'minor:|build:|docs:' |sed -e 's/[^ ]*/-/' >RELEASEINFO.md\n"
  },
  {
    "path": ".deploy/gen_release_info.py",
    "content": "#!/usr/bin/env python\n\nwith open('CHANGELOG.md') as r, open('RELEASEINFO.md', 'w') as w:\n    # skip over possible 'Unreleased'\n    for line in r:\n        if line.startswith('## Release'):\n            # w.write(line[1:])\n            break\n    for line in r:\n        if line.startswith('## '):\n            break\n        # if line.startswith('#'):\n        #     line = line[1:]\n        w.write(line)\n"
  },
  {
    "path": ".github/actions/style-check/action.yml",
    "content": "name: 'Style Check'\ndescription: 'Check for common code style warnings'\nruns:\n  using: 'node20'\n  main: 'index.js'\n"
  },
  {
    "path": ".github/actions/style-check/index.js",
    "content": "#!/usr/bin/env node\n/** @file\n    Source code style checks.\n\n    Copyright (C) 2020 by Christian Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\nconst path = require('path')\nconst fs = require('fs')\nconst log = console.error\nconst MAX_LEN = 300 // this should likely be around 160\nconst replacement_character = '\\uFFFD'\n\n// console.log(process.cwd()) // /home/runner/work/rtl_433/rtl_433\n// console.log(__dirname) // /home/runner/work/rtl_433/rtl_433/.github/actions/style-check\n\nprocess.exitCode = [\n  'include',\n  'src',\n  'src/devices',\n].flatMap(glob_dir).concat([\n  'CMakeLists.txt',\n  'conf/CMakeLists.txt',\n  'tests/CMakeLists.txt',\n])\n  .filter(discard_vendor)\n  .reduce((e, f) => e + style_check(f), 0) > 0 ? 1 : 0\n\nfunction discard_vendor(filename) {\n  return filename.indexOf('/jsmn.') < 0 && filename.indexOf('/mongoose.') < 0\n}\n\nfunction glob_dir(dir) {\n  return fs.readdirSync(dir)\n    .filter(filename =>\n      filename.endsWith('.h') ||\n      filename.endsWith('.c') ||\n      filename.endsWith('.txt')\n    )\n    .map(filename =>\n      path.join(dir, filename)\n    )\n}\n\nfunction style_check(filename) {\n  const strict = filename.indexOf('/devices/') >= 0\n  const txt = filename.endsWith('.txt')\n\n  let errors = 0\n\n  let leading_tabs = 0\n  let leading_spcs = 0\n  let mixed_ws = 0\n  let need_cond = 0\n  let in_comment = false\n\n  let line_number = 0\n\n  fs.readFileSync(filename, 'utf8')\n    .split('\\n')\n    .map(line => {\n      line_number++\n      const len = line.length\n      if (len < 0) {\n        log(`::error file=${filename},line=${line_number}::READ error`)\n        errors++\n      }\n      if (len >= MAX_LEN - 1) {\n        log(`::error file=${filename},line=${line_number},col=${MAX_LEN}::LONG line error`)\n        errors++\n      }\n      if (line[len - 1] == '\\r' || (len > 1 && line[len - 2] == '\\r')) {\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::CRLF error`)\n        errors++\n      }\n      if (line.indexOf('/*')) {\n        in_comment = true\n      }\n\n      if (line[0] == '\\t') {\n        log(`::error file=${filename},line=${line_number},col=0::TAB indented line`)\n        leading_tabs++\n      }\n      if (len >= 4 && line[0] == ' ' && line[1] == ' ' && line[2] == ' ' && line[3] == ' ') {\n        leading_spcs++\n      }\n      if (line[len - 1] == ' ' || line[len - 1] == '\\t') {\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::TRAILING whitespace error`)\n        errors++\n      }\n\n      const invchr = line.indexOf(replacement_character)\n      if (invchr >= 0) {\n        log(`::error file=${filename},line=${line_number},col=${invchr + 1}::INVALID-UTF8 character error`)\n        errors++\n      }\n      const nonasc = line.search(/[^ -~]/)\n      if (!in_comment && nonasc >= 0) {\n        log(`::error file=${filename},line=${line_number},col=${nonasc + 1}::NON-ASCII character error`)\n        errors++\n      }\n      else if (nonasc >= 0) {\n        //log(`::warning file=${filename},line=${line_number},col=${nonasc + 1}::NON-ASCII character`)\n      }\n      if (line.indexOf('(r_device *decoder') >= 0 && line[len - 1] == '{') {\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::BRACE function on newline error`)\n        errors++\n      }\n\n      if (line.indexOf('){') >= 0 && line.indexOf('}') < 0) {\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::STICKY-BRACE error`)\n        errors++\n      }\n      if (!txt && (line.indexOf('if(') >= 0 || line.indexOf('for(') >= 0 || line.indexOf('while(') >= 0)) {\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::STICKY-PAREN error`)\n        errors++\n      }\n\n      if (strict && line.indexOf('stdout') >= 0) {\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::STDOUT line`)\n        errors++\n      }\n      const p = line.indexOf('printf')\n      if (strict && p >= 0) {\n        if (p == 0 || line[p - 1] < '_' || line[p - 1] > 'z') {\n          log(`::error file=${filename},line=${line_number},col=${len - 1}::PRINTF line`)\n          errors++\n        }\n      }\n      if (need_cond && line.indexOf('if (!') < 0) {\n        // we had an alloc but no check on the following line\n        log(`::error file=${filename},line=${line_number},col=${len - 1}::ALLOC check error`)\n        errors++\n      }\n      need_cond = 0\n      if (line.indexOf('alloc(') >= 0 && line.indexOf('alloc()') < 0) {\n        need_cond++\n      }\n      if (line.indexOf('strdup(') >= 0 && line.indexOf('strdup()') < 0) {\n        need_cond++\n      }\n\n      if (line.indexOf('*/')) {\n        in_comment = false\n      }\n    })\n\n  if (leading_tabs && leading_spcs) {\n    mixed_ws = leading_tabs > leading_spcs ? leading_spcs : leading_tabs\n    log(`::error file=${filename}::${mixed_ws} MIXED tab/spaces errors.`)\n  }\n\n  return errors + mixed_ws + leading_tabs\n}\n"
  },
  {
    "path": ".github/workflows/check.yml",
    "content": "name: Build Analyze Check\non: [push, pull_request]\njobs:\n  macos_check_job:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [macos-14, macos-15]\n    runs-on: ${{ matrix.os }}\n    name: Build on ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        run: brew install soapysdr librtlsdr\n      - name: Configure\n        run: cmake -B build\n      - name: Build\n        run: cmake --build build\n\n  build_check_job:\n    runs-on: ubuntu-latest\n    name: Build with CMake\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup\n        run: |\n          sudo apt-get update -q -y\n          sudo apt-get install -q -y --no-install-recommends cmake ninja-build\n          sudo apt-get install -q -y libsoapysdr-dev librtlsdr-dev\n      - name: Configure CMake+Ninja\n        run: cmake -GNinja -B ${{ runner.workspace }}/b/ninja -DENABLE_RTLSDR=OFF -DENABLE_SOAPYSDR=OFF\n      - name: Build CMake+Ninja\n        run: cmake --build ${{ runner.workspace }}/b/ninja\n      - name: Configure CMake+UnixMakefiles\n        run: cmake -G\"Unix Makefiles\" -B ${{ runner.workspace }}/b/unixmakefiles -DENABLE_RTLSDR=OFF -DENABLE_SOAPYSDR=OFF\n      - name: Build CMake+UnixMakefiles\n        run: cmake --build ${{ runner.workspace }}/b/unixmakefiles\n\n  doc_check_job:\n    runs-on: ubuntu-latest\n    name: Build documentation\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install Doxygen\n        run: |\n          sudo apt-get update -q -y\n          sudo apt-get install -q -y --no-install-recommends cmake ninja-build\n          sudo apt-get install -q -y doxygen\n      - name: Configure CMake+Ninja\n        run: cmake -GNinja -B ${{ runner.workspace }}/b/ninja -DBUILD_DOCUMENTATION=ON -DENABLE_RTLSDR=OFF -DENABLE_SOAPYSDR=OFF\n      - name: Build CMake+Ninja\n        run: cmake --build ${{ runner.workspace }}/b/ninja -- doc_doxygen\n\n  style_check_job:\n    runs-on: ubuntu-latest\n    name: Check code style\n    steps:\n      - uses: actions/checkout@v4\n      - name: Style Check\n        uses: ./.github/actions/style-check\n\n  maintainer_update_check_job:\n    runs-on: ubuntu-latest\n    name: Needs maintainer_update\n    steps:\n      - uses: actions/checkout@v4\n      - name: Working directory clean excluding untracked files\n        run: |\n          ./maintainer_update.py\n          [ -z \"$(git status --untracked-files=no --porcelain)\" ]\n\n  symbolizer_check_job:\n    runs-on: ubuntu-latest\n    name: Check symbol errors\n    steps:\n      - uses: actions/checkout@v4\n      - name: Symbolizer report\n        run: |\n          ./tests/symbolizer.py check\n\n  analyzer_check_job:\n    # https://github.com/actions/virtual-environments\n    # - Ubuntu 24.04 ubuntu-24.04\n    # - Ubuntu 22.04 ubuntu-22.04\n    # - Ubuntu 20.04 ubuntu-20.04\n    # https://apt.llvm.org/\n    # - Noble (24.04)\n    # - Jammy (22.04)\n    # - Focal (20.04)\n    # - Bionic (18.04)\n    runs-on: ubuntu-24.04\n    name: Analyze with Clang\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install Clang\n        run: |\n          wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -\n          sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' -y\n          sudo apt-get update -q\n          sudo apt-get install -q -y clang-20 lld-20 libc++-20-dev libc++abi-20-dev clang-tools-20\n      - name: Clang Analyzer\n        # excludes include/mongoose.h src/mongoose.c include/jsmn.h src/jsmn.c\n        # exit code 1 if there is output\n        run: |\n          clang -Iinclude -DTHREADS --analyze -Xanalyzer -analyzer-output=text -Xanalyzer -analyzer-disable-checker=deadcode.DeadStores include/[a-ikln-z]*.h src/[a-ikln-z]*.c src/devices/*.c 2>&1 | tee analyzer.out\n          [ ! -s analyzer.out ]\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  workflow_dispatch:\n  push:\n    tags:\n    - '*'\n\nenv:\n  # from https://downloads.myriadrf.org/builds/PothosSDR/\n  pothos-ver: 2021.07.25-vc16\n  pothos-exe: PothosSDR-2021.07.25-vc16-x64.exe\n\njobs:\n  downloads_job:\n    name: Downloads\n    #runs-on: ubuntu-latest\n    runs-on: windows-latest\n    steps:\n    - uses: actions/cache@v4\n      id: downloads\n      with:\n        path: ${{ runner.workspace }}/${{ env.pothos-exe }}\n        key: download-${{ env.pothos-ver }}\n    - name: Run Downloads\n      if: steps.downloads.outputs.cache-hit != 'true'\n      shell: bash\n      working-directory: ${{ runner.workspace }}\n      run: curl -L -O https://downloads.myriadrf.org/builds/PothosSDR/${{ env.pothos-exe }}\n\n  release_job:\n    name: Create release\n    runs-on: ubuntu-latest\n    outputs:\n      upload_url: ${{ steps.create_release.outputs.upload_url }}\n      release_version: ${{ env.RELEASE_VERSION }}\n    steps:\n    - uses: actions/checkout@v4\n      with:\n        fetch-depth: 0\n    - name: Create Release info\n      # e.g. refs/tags/23.01\n      if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/2')\n      run: |\n        echo \"RELEASE_VERSION=${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n        echo \"RELEASE_NAME=Release ${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n        echo \"PRERELEASE=false\" >> $GITHUB_ENV\n        ./.deploy/gen_release_info.py\n    - name: Create Pre-Release info\n      # e.g. refs/tags/nightly or refs/heads/master\n      if: github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/2')\n      run: |\n        echo \"RELEASE_VERSION=${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n        echo \"RELEASE_NAME=Pre-Release ${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n        echo \"PRERELEASE=true\" >> $GITHUB_ENV\n        ./.deploy/gen_nightly_info.sh\n    - uses: softprops/action-gh-release@v2\n      id: create_release\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        tag_name: ${{ env.RELEASE_VERSION }}\n        name: ${{ env.RELEASE_NAME }}\n        body_path: RELEASEINFO.md\n        #draft: true\n        prerelease: ${{ env.PRERELEASE }}\n\n  macos_build_job:\n    needs: release_job\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [macos-14]\n        feat: [rtlsdr, soapysdr]\n        include:\n          #- os: macos-13\n          #  arch: x86_64\n          - os: macos-14\n            arch: arm64\n    runs-on: ${{ matrix.os }}\n    name: Build with ${{ matrix.feat }} for ${{ matrix.arch }} on ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup tools\n        run: brew install librtlsdr openssl@3\n      - name: Setup SoapySDR deps\n        if: matrix.feat == 'soapysdr'\n        run: brew install soapysdr\n      - name: Configure\n        run: cmake -B build\n      - name: Build\n        run: cmake --build build\n      - name: Adjust binary to run with MacPorts or Homebrew\n        # Homebrew has x86 in /usr/local and arm64 in /opt/homebrew\n        run: >\n          install_name_tool\n          -change /opt/homebrew/opt/openssl@3/lib/libssl.3.dylib @rpath/libssl.3.dylib\n          -change /opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib\n          -change /opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib @rpath/libusb-1.0.0.dylib\n          -change /opt/homebrew/opt/librtlsdr/lib/librtlsdr.2.dylib @rpath/librtlsdr.2.dylib\n          -change /opt/homebrew/opt/soapysdr/lib/libSoapySDR.0.8.dylib @rpath/libSoapySDR.0.8.dylib\n          -change /usr/local/opt/openssl@3/lib/libssl.3.dylib @rpath/libssl.3.dylib\n          -change /usr/local/opt/openssl@3/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib\n          -change /usr/local/opt/libusb/lib/libusb-1.0.0.dylib @rpath/libusb-1.0.0.dylib\n          -change /usr/local/opt/librtlsdr/lib/librtlsdr.2.dylib @rpath/librtlsdr.2.dylib\n          -change /usr/local/opt/soapysdr/lib/libSoapySDR.0.8.dylib @rpath/libSoapySDR.0.8.dylib\n          -add_rpath /opt/homebrew/opt/openssl@3/lib\n          -add_rpath /opt/homebrew/opt/libusb/lib\n          -add_rpath /opt/homebrew/opt/librtlsdr/lib\n          -add_rpath /opt/homebrew/opt/soapysdr/lib\n          -add_rpath /usr/local/opt/openssl@3/lib\n          -add_rpath /usr/local/opt/libusb/lib\n          -add_rpath /usr/local/opt/librtlsdr/lib\n          -add_rpath /usr/local/opt/soapysdr/lib\n          -add_rpath /opt/local/libexec/openssl3/lib\n          -add_rpath /opt/local/lib\n          build/src/rtl_433\n      - name: Check final binary\n        run: otool -L build/src/rtl_433\n      - name: \"Upload Release Asset\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          ZIP_FILE: rtl_433-${{ matrix.feat }}-MacOS-${{ matrix.arch }}-${{ needs.release_job.outputs.release_version }}.zip\n        run: |\n          zip --junk-paths $ZIP_FILE build/src/rtl_433\n          gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n\n  native_build_job:\n    needs: release_job\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-24.04]\n        feat: [rtlsdr, soapysdr]\n        arch: [amd64]\n    runs-on: ${{ matrix.os }}\n    name: Build with ${{ matrix.feat }} for ${{ matrix.arch }} on ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup tools\n        run: |\n          sudo apt-get update -q -y\n          sudo apt-get install -y --no-install-recommends cmake ninja-build\n      - name: Setup deps\n        run: |\n          sudo apt-get install -q -y librtlsdr-dev libssl-dev\n      - name: Setup SoapySDR deps\n        if: matrix.feat == 'soapysdr'\n        run: |\n          sudo apt-get install -q -y libsoapysdr-dev\n      - name: Configure\n        run: cmake -GNinja -B build\n      - name: Build\n        run: cmake --build build\n      - name: Check final binary\n        run: ldd build/src/rtl_433\n      - name: \"Upload Release Asset\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          ZIP_FILE: rtl_433-${{ matrix.feat }}-Linux-${{ matrix.arch }}-${{ needs.release_job.outputs.release_version }}.zip\n        run: |\n          zip --junk-paths $ZIP_FILE build/src/rtl_433\n          gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n\n  cross_build_job:\n    needs: release_job\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-22.04]\n        arch: [armhf, arm64]\n        feat: [rtlsdr, soapysdr]\n        include:\n          #- arch: armel\n          #  compiler: arm-linux-gnueabi\n          - arch: armhf\n            compiler: arm-linux-gnueabihf\n          - arch: arm64\n            compiler: aarch64-linux-gnu\n    runs-on: ${{ matrix.os }}\n    name: Build with ${{ matrix.feat }} for ${{ matrix.arch }} on ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup tools\n        run: |\n          sudo apt-get update -q -y\n          sudo apt-get install -y --no-install-recommends cmake ninja-build\n      - name: Purge conflicting dev that confuse cross linking\n        run: |\n          sudo apt-get purge -y libssl-dev libusb-1.0-0-dev\n      - name: Setup compiler\n        run: |\n          sudo apt-get install -q -y gcc-${{ matrix.compiler }}\n      - name: Restrict sources.list\n        run: |\n          sudo sed -i'' -E 's/^(deb|deb-src) mirror\\+file/\\1 [arch=amd64,i386] mirror\\+file/' /etc/apt/sources.list\n      - uses: ryankurte/action-apt@v0.4.0\n        if: matrix.feat != 'soapysdr'\n        with:\n          arch: ${{ matrix.arch }}\n          packages: \"librtlsdr-dev:${{ matrix.arch }} libssl-dev:${{ matrix.arch }}\"\n      - uses: ryankurte/action-apt@v0.4.0\n        if: matrix.feat == 'soapysdr'\n        with:\n          arch: ${{ matrix.arch }}\n          packages: \"librtlsdr-dev:${{ matrix.arch }} libssl-dev:${{ matrix.arch }} libsoapysdr-dev:${{ matrix.arch }}\"\n      - name: Configure\n        run: cmake -DCMAKE_TOOLCHAIN_FILE=\"$(pwd)/cmake/Toolchain-${{ matrix.compiler }}.cmake\" -GNinja -B build\n      - name: Build\n        run: cmake --build build\n      - name: Check final binary\n        run: ${{ matrix.compiler }}-readelf -a build/src/rtl_433 | grep \"Shared library:\"\n      - name: \"Upload Release Asset\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          ZIP_FILE: rtl_433-${{ matrix.feat }}-Linux-${{ matrix.arch }}-${{ needs.release_job.outputs.release_version }}.zip\n        run: |\n          zip --junk-paths $ZIP_FILE build/src/rtl_433\n          gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n\n  container_build_job:\n    needs: release_job\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [armel]\n        feat: [rtlsdr, soapysdr]\n        include:\n          - arch: armel\n            compiler: arm-linux-gnueabi\n          #- arch: armhf\n          #  compiler: arm-linux-gnueabihf\n          #- arch: arm64\n          #  compiler: aarch64-linux-gnu\n    runs-on: ubuntu-latest\n    name: Build with ${{ matrix.feat }} for ${{ matrix.arch }}\n    container:\n      image: debian:trixie-slim\n      env:\n        GH_VERSION: 2.83.2\n    steps:\n      - name: Setup base tools\n        run: |\n          apt-get update -q -y\n          apt-get install -q -y --no-install-recommends git ca-certificates curl file zip\n      - uses: actions/checkout@v4\n      - name: Fix Ownership issue\n        run: git config --global --add safe.directory $GITHUB_WORKSPACE\n      - name: Add Architecture\n        run: |\n          dpkg --add-architecture ${{ matrix.arch }}\n          apt-get update -q -y\n      - name: Setup build tools\n        run: apt-get install -q -y --no-install-recommends cmake ninja-build\n      - name: Setup GH cli\n        run: |\n          curl -L -O https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.deb\n          dpkg -i gh_${GH_VERSION}_linux_amd64.deb\n      - name: Setup compiler\n        run: apt-get install -q -y gcc-${{ matrix.compiler }}\n      - name: Purge conflicting dev that confuse cross linking\n        run: apt-get purge -y libssl-dev libusb-1.0-0-dev\n      - name: Install deps\n        if: matrix.feat != 'soapysdr'\n        run: apt-get install -q -y librtlsdr-dev:${{ matrix.arch }} libssl-dev:${{ matrix.arch }}\n      - name: Install deps\n        if: matrix.feat == 'soapysdr'\n        run: apt-get install -q -y librtlsdr-dev:${{ matrix.arch }} libssl-dev:${{ matrix.arch }} libsoapysdr-dev:${{ matrix.arch }}\n      - name: Configure\n        run: cmake -DCMAKE_TOOLCHAIN_FILE=\"$(pwd)/cmake/Toolchain-${{ matrix.compiler }}.cmake\" -GNinja -B build\n      - name: Build\n        run: cmake --build build\n      - name: Check final binary\n        run: file build/src/rtl_433\n      - name: Check final binary\n        run: ${{ matrix.compiler }}-readelf -a build/src/rtl_433 | grep \"Shared library:\"\n      - name: \"Upload Release Asset\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          ZIP_FILE: rtl_433-${{ matrix.feat }}-Linux-${{ matrix.arch }}-${{ needs.release_job.outputs.release_version }}.zip\n        run: |\n          zip --junk-paths $ZIP_FILE build/src/rtl_433\n          gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n\n  build_mingw_job:\n    needs: release_job\n    if: ${{ needs.release_job.result != 'failure' }}\n    strategy:\n      fail-fast: false\n      matrix:\n        arch: [i686, x86-64]\n    runs-on: ubuntu-latest\n    name: Build with Mingw-w64 on ${{ matrix.arch }}\n    steps:\n    - uses: actions/checkout@v4\n    - name: \"Install Mingw-w64\"\n      run: |\n        sudo apt-get update -q -y\n        sudo apt-get install -q -y gcc-mingw-w64-base binutils-mingw-w64-${{ matrix.arch }} gcc-mingw-w64-${{ matrix.arch }} mingw-w64-${{ matrix.arch }}-dev gcc-mingw-w64 gcc-multilib p7zip-full\n    - name: \"Configure with Mingw-w64\"\n      run: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/Toolchain-gcc-mingw-w64-${{ matrix.arch }}.cmake -DENABLE_RTLSDR=OFF -B build\n    - name: \"Build with Mingw-w64\"\n      run: cmake --build build\n    - name: \"Sysroot Build with Mingw-w64\"\n      run: ./.ci/scripts/do_sysroot.sh\n    - name: \"Upload Release Asset\"\n      if: matrix.arch == 'x86-64'\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        ZIP_FILE: rtl_433-win-x64-${{ needs.release_job.outputs.release_version }}.zip\n      run: |\n        mv rtl_433-win-x64.zip $ZIP_FILE\n        gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n    - name: \"Upload Release Asset\"\n      if: matrix.arch == 'x86-64'\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        ZIP_FILE: rtl_433-win-x32-${{ needs.release_job.outputs.release_version }}.zip\n      run: |\n        mv rtl_433-win-x32.zip $ZIP_FILE\n        gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n\n  build_msvc_job:\n    needs: [downloads_job, release_job]\n    if: ${{ needs.downloads_job.result != 'failure' && needs.release_job.result != 'failure'  }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [windows-2022, windows-2025]\n        platform: [x64, Win32]\n        # from https://downloads.myriadrf.org/builds/PothosSDR/\n        #pothos-ver: [2021.07.25-vc16]\n        include:\n          - os: windows-2022\n            generator: Visual Studio 17 2022\n          - os: windows-2025\n            generator: Visual Studio 17 2022\n    runs-on: ${{ matrix.os }}\n    name: ${{ matrix.generator }} ${{ matrix.platform }} CMake (MSBuild) on ${{ matrix.os }}\n    steps:\n    - run: choco install openssl --yes --no-progress\n      if: matrix.platform == 'x64'\n    - uses: actions/checkout@v4\n    - uses: actions/cache@v4\n      id: downloads\n      if: matrix.platform == 'x64'\n      with:\n        path: ${{ runner.workspace }}/${{ env.pothos-exe }}\n        key: download-${{ env.pothos-ver }}\n    - name: Run Downloads\n      if: matrix.platform == 'x64' && steps.downloads.outputs.cache-hit != 'true'\n      shell: bash\n      working-directory: ${{ runner.workspace }}\n      run: curl -L -O https://downloads.myriadrf.org/builds/PothosSDR/${{ env.pothos-exe }}\n    - name: Install Deps\n      if: matrix.platform == 'x64'\n      shell: bash\n      working-directory: ${{ runner.workspace }}\n      run: |\n        mkdir -p pothos\n        7z x -opothos -y ${{ env.pothos-exe }}\n        echo \"POTHOS_PATH=$(pwd)/pothos\" >> $GITHUB_ENV\n    - name: Get latest CMake and Ninja\n      uses: lukka/get-cmake@latest\n    # Note: this assumes ${{ runner.workspace }} == ${{ github.workspace }}/..\n    - name: CMake+None\n      uses: lukka/run-cmake@v10\n      with:\n        configurePreset: dummy\n        configurePresetCmdString: \"[`-B`, `../b/rtl_433`, `-G${{ matrix.generator }}`, `-A${{ matrix.platform }}`, `-DENABLE_RTLSDR=OFF`, `-DENABLE_SOAPYSDR=OFF`, `-DENABLE_OPENSSL=OFF`]\"\n        buildPreset: dummy\n        buildPresetCmdString: \"[`--build`, `../b/rtl_433`, `--config`, `Release`]\"\n    - name: CMake+RTLSDR+SOAPYSDR+TLS\n      uses: lukka/run-cmake@v10\n      env:\n        CMAKE_PREFIX_PATH: ${{ runner.workspace }}\\pothos\n      if: matrix.platform == 'x64'\n      with:\n        configurePreset: dummy\n        configurePresetCmdString: \"[`-B`, `../b/rtl_433-rtlsdr-soapysdr-tls`, `-G${{ matrix.generator }}`, `-A${{ matrix.platform }}`, `-DCMAKE_PREFIX_PATH=${{ env.POTHOS_PATH }}`, `-DENABLE_RTLSDR=ON`, `-DENABLE_SOAPYSDR=ON`, `-DENABLE_OPENSSL=ON`]\"\n        buildPreset: dummy\n        buildPresetCmdString: \"[`--build`, `../b/rtl_433-rtlsdr-soapysdr-tls`, `--config`, `Release`]\"\n    - name: CMake+RTLSDR+SOAPYSDR\n      uses: lukka/run-cmake@v10\n      env:\n        CMAKE_PREFIX_PATH: ${{ runner.workspace }}\\pothos\n      if: matrix.platform == 'x64'\n      with:\n        configurePreset: dummy\n        configurePresetCmdString: \"[`-B`, `../b/rtl_433-rtlsdr-soapysdr`, `-G${{ matrix.generator }}`, `-A${{ matrix.platform }}`, `-DCMAKE_PREFIX_PATH=${{ env.POTHOS_PATH }}`, `-DENABLE_RTLSDR=ON`, `-DENABLE_SOAPYSDR=ON`, `-DENABLE_OPENSSL=OFF`]\"\n        buildPreset: dummy\n        buildPresetCmdString: \"[`--build`, `../b/rtl_433-rtlsdr-soapysdr`, `--config`, `Release`]\"\n    - name: CMake+RTLSDR+TLS\n      uses: lukka/run-cmake@v10\n      env:\n        CMAKE_PREFIX_PATH: ${{ runner.workspace }}\\pothos\n      if: matrix.platform == 'x64'\n      with:\n        configurePreset: dummy\n        configurePresetCmdString: \"[`-B`, `../b/rtl_433-rtlsdr-tls`, `-G${{ matrix.generator }}`, `-A${{ matrix.platform }}`, `-DCMAKE_PREFIX_PATH=${{ env.POTHOS_PATH }}`, `-DENABLE_RTLSDR=ON`, `-DENABLE_SOAPYSDR=OFF`, `-DENABLE_OPENSSL=ON`]\"\n        buildPreset: dummy\n        buildPresetCmdString: \"[`--build`, `../b/rtl_433-rtlsdr-tls`, `--config`, `Release`]\"\n    - name: CMake+RTLSDR\n      uses: lukka/run-cmake@v10\n      env:\n        CMAKE_PREFIX_PATH: ${{ runner.workspace }}\\pothos\n      if: matrix.platform == 'x64'\n      with:\n        configurePreset: dummy\n        configurePresetCmdString: \"[`-B`, `../b/rtl_433-rtlsdr`, `-G${{ matrix.generator }}`, `-A${{ matrix.platform }}`, `-DCMAKE_PREFIX_PATH=${{ env.POTHOS_PATH }}`, `-DENABLE_RTLSDR=ON`, `-DENABLE_SOAPYSDR=OFF`, `-DENABLE_OPENSSL=OFF`]\"\n        buildPreset: dummy\n        buildPresetCmdString: \"[`--build`, `../b/rtl_433-rtlsdr`, `--config`, `Release`]\"\n    - name: Package dist\n      if: matrix.platform == 'x64'\n      shell: bash\n      working-directory: ${{ runner.workspace }}\n      run: |\n        mkdir -p dist\n        cp pothos/bin/{SoapySDR.dll,libusb-1.0.dll,rtlsdr.dll,vcruntime140.dll,vcruntime140_1.dll,pthreadVC2.dll} dist\n        cp b/rtl_433-rtlsdr-soapysdr-tls/src/Release/rtl_433.exe dist/rtl_433-rtlsdr-soapysdr-tls.exe\n        cp b/rtl_433-rtlsdr-soapysdr/src/Release/rtl_433.exe     dist/rtl_433-rtlsdr-soapysdr.exe\n        cp b/rtl_433-rtlsdr-tls/src/Release/rtl_433.exe          dist/rtl_433-rtlsdr-tls.exe\n        cp b/rtl_433-rtlsdr/src/Release/rtl_433.exe              dist/rtl_433-rtlsdr.exe\n        cp b/rtl_433/src/Release/rtl_433.exe                     dist/rtl_433.exe\n        cp rtl_433/.deploy/WINDOWS-MSVC.txt                      dist/README.txt\n        ls -al dist\n        7z a rtl_433-win-msvc-x64.zip ./dist/*\n    - name: \"Upload Release Asset\"\n      if: matrix.os == 'windows-2022' && matrix.platform == 'x64'\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        GH_TOKEN: ${{ github.token }}\n        GH_REPO: ${{ github.repository }}\n        ZIP_FILE: rtl_433-win-msvc-x64-${{ needs.release_job.outputs.release_version }}.zip\n      shell: bash\n      working-directory: ${{ runner.workspace }}\n      run: |\n        mv rtl_433-win-msvc-x64.zip $ZIP_FILE\n        gh release upload ${{ needs.release_job.outputs.release_version }} $ZIP_FILE\n\n  build_arch_job:\n    if: ${{ false }}  # disable for now\n    needs: release_job\n    # The host should always be Linux\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      # jessie, ubuntu16.04 has no SoapySDR\n      # jessie has trouble linking libusb-1.0\n      # stretch has SoapySDR 0.5 which we don't support\n      # buster has broken librtlsdr pkg-config\n      # fedora_latest packages need to be verified\n      # alpine_latest packages need to be verified\n      matrix:\n        # arch: [armv6, armv7, aarch64, s390x, ppc64le]\n        arch: [armv7, aarch64, ppc64le]\n        # distro: [jessie, stretch, buster, ubuntu16.04, ubuntu18.04, ubuntu20.04, fedora_latest, alpine_latest]\n        distro: [stretch, buster, ubuntu16.04, ubuntu18.04, ubuntu20.04, fedora_latest]\n        include:\n          - arch: armv6\n            distro: stretch\n          - arch: armv6\n            distro: buster\n    name: Build on ${{ matrix.distro }} ${{ matrix.arch }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: uraimo/run-on-arch-action@v2.0.8\n        id: runcmd\n        with:\n          arch: ${{ matrix.arch }}\n          distro: ${{ matrix.distro }}\n\n          # Not required, speed up builds by storing container images in\n          # GitHub package registry.\n          #githubToken: ${{ github.token }}\n\n          # Install dependencies in the cached container.\n          install: |\n            case \"${{ matrix.distro }}\" in\n              jessie|stretch|ubuntu16.04)\n                apt-get update -q -y\n                apt-get install -q -y lsb-release build-essential libtool pkg-config cmake librtlsdr-dev libssl-dev\n                ;;\n              buster|ubuntu*)\n                apt-get update -q -y\n                apt-get install -q -y lsb-release build-essential libtool pkg-config cmake librtlsdr-dev libsoapysdr-dev libssl-dev\n                ;;\n              fedora*)\n                dnf -y update\n                dnf -y install redhat-lsb make gcc libtool pkg-config cmake rtl-sdr-devel SoapySDR-devel openssl-devel\n                ;;\n              alpine*)\n                apk update\n                apk add lsb-release build-essential libtool pkg-config cmake librtlsdr-dev libsoapysdr-dev libssl-dev\n                ;;\n            esac\n\n          run: |\n            uname -a\n            lsb_release -sc || echo NO lsb_release\n            echo Release ${{ needs.release_job.outputs.release_version }}\n            cmake -DCMAKE_INSTALL_PREFIX:PATH=../dist -B build\n            cmake --build build\n            cmake --build build --target install\n\n      - name: Install FPM\n        run: |\n          sudo apt-get update -q -y\n          sudo apt-get install -q -y ruby ruby-dev rubygems\n          sudo gem install --no-document fpm\n\n      - name: Package with FPM\n        run: |\n          ls -al \"${PWD}/dist\"\n          case \"${{ matrix.distro }}\" in\n            stretch)\n              libsoapy=libsoapysdr0.5-2 ;;\n            buster)\n              libsoapy=libsoapysdr0.6 ;;\n            fedora*)\n              libsoapy=SoapySDR ;;\n            *)\n              libsoapy=libsoapysdr0.7 ;;\n          esac\n          fpm -s dir -t deb -n rtl433 -v ${{ needs.release_job.outputs.release_version }}-1${{ matrix.distro }} -C \"${PWD}/dist\" \\\n            -d libusb-1.0-0 -d libssl1.1 -d librtlsdr0 -d $libsoapy \\\n            --deb-suggests soapysdr-module-all \\\n            --deb-suggests soapysdr-tools \\\n            --architecture ${{ matrix.arch }} \\\n            --maintainer zany@triq.net \\\n            --description \"Program to decode radio transmissions\" \\\n            --url https://triq.org/\n          ls -al\n          ls -al \"${PWD}/dist\"\n      - name: \"Upload Release Asset\"\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          gh release upload ${{ needs.release_job.outputs.release_version }} rtl433_${{ needs.release_job.outputs.release_version }}-1${{ matrix.distro }}_${{ matrix.arch }}.deb\n"
  },
  {
    "path": ".gitignore",
    "content": "# CMake out-of-tree builds\nbuild*/\n\n# IDE files\n.cproject\n.settings\n.project\n.cache\n.vs\n.idea\ncmake-build-debug/\n\n# Editor files\n*.orig\n*.bak\n*~\n*.tlog\n*.ipdb\n*.iobj\n*.idb\n*.lastbuildstate\n*.db\n*.opendb\n\n# Development files\n/_*/\n/rtl_433_tests\n\n# File manager files\n.DS_Store\n.history\n"
  },
  {
    "path": "AUTHORS",
    "content": "Benjamin Larsson <banan@ludd.ltu.se>\nChristian W. Zuckschwerdt <zany@triq.net>\n\nSven Killig <sven@killig.de>\nrct <rct+github@r-t.org>\njohan <johan@E6410>\nThomas Kerpe <toke@toke.de>\nJens Jensen <zerog2k@yahoo.com>\nSven <sonic@sonic-VGN-Z41WD-B.(none)>\nMartin Hauke <mardnh@gmx.de>\nmagellannh <pi@raspberrypi.(none)>\njules69350 <julien.sangouard@gmail.com>\narantius <arantius@gmail.com>\nandreaaizza <andrea@andreaaizza.com>\nTrueffelwurm <jens@trueffelwurm.de>\nTomasz Brzezina <tombrz@Bonawentura.brzezina.pl>\nPaul F-Y <pbfy00@gmail.com>\nCorné van Strien <github@atilas.nl>\nBaruch Even <baruch@ev-en.org>\nAndrea <andrea@andreaaizza.com>\nHelge Weissig <helgew@grajagan.org>\nRobert Fraczkiewicz <aromring@gmail.com>\nNicola Quiriti <nik@wifi4all.it>\nPetr Konecny <pekon@google.com>\nTom Felker <tomfelker@gmail.com>\nPasquale 'sid' Fiorillo <me@pasqualefiorillo.it>\nTommy Vestermark <tovsurf@vestermark.dk>\nAaron Spangler <aaron777@gmail.com>\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## Release 25.12 (2025-12-12)\n\n### Breaking Changes\n\n- Changed all uv fields to uvi, BREAKING change to UV sensors (#3131)\n- Removed Python 2.7 support (#3320)\n\n### Highlights\n\n- Added support for GIF2020OCECNA to Orion-Endpoint (#3392)\n- Added support for Voltcraft Energy Count 3000 (ec3k) (#3373)\n- Added support for ORIA WA150KM freezer thermometer (#3143)\n- Added support for Fine Offset Electronics WS85 weather station (#3354)\n- Added support for UniFan-24V universal 24V fan controller (#3142)\n- Added support for BM5-v2 battery monitor  (#3126)\n- Added support for Baldr-E0666TH Thermo-Hygrometer (#3360)\n- Added support for more Interlogix-Security sensor types (#3361)\n- Added support for Fine Offset WH43 air quality sensor (#3303)\n- Added support for newer smoke alarms to Interlogix-Security (#3163)\n- Added support for Orion Endpoint Badger water meter GIF2014W-OSE (#3374)\n- Added support for TFA 30.3802.02 to LaCrosse-R3 (#3366)\n- Added support for Maverick-XR50 BBQ meat thermometer (#3293)\n- Added support for Homelead-HG9901 soil sensor (#3299)\n- Added support for Nexus-Sauna (#3327)\n- Added support for ThermoPro TX-7B Outdoor Thermometer Hygrometer (#3311)\n- Added support for Oregon Scientific THGR228N (closes #3121)\n- Added support for BMW GEN2 TPMS (#3302)\n- Added support for Apator Metra E-RM 30 water meter (#3203)\n- Added support for RainPoint HCS012ARF Rain Gauge sensor (#3240)\n- Added support for Oregon Scientific V3 models (#3234)\n- Changed dB estimate to display true RSSI (#3283)\n- Added gap+pulse period distribution to analyzer (#3263)\n- Added availability online/offline MQTT LWT option (#1547)\n\n### Changed\n\n- Change Oil-SonicAdv depth to 12 bits (#3411)\n- Added TX-Button to Oregon-THGR810 (closes #3413)\n- Updated Windows cross-build to rtlsdr-2.0.2 (closes #3349)\n- Updated AVE TPMS to battery_pct\n- Fixed Visonic-Powercode decode on bad reception (#3340)\n- Fixed overflow in rfraw test data parsing (closes #3375)\n- Added decode_mc flex option\n- Fixed unit_of_measurement for windspeed to mph in rtl_433_mqtt_hass.py (#3337)\n- Fixed Steelmate pressure formula (#3209)\n- Added demod state reset between input files (#3310)\n- Fixed battery_ok in EcoWitt-WH40 (closes #3298)\n- Added UCS vendor to LandisGyr-GS (#3280)\n- Improve Truck TPMS decoder (#3272)\n- Fixed HA script class for energy_kWh (closes #3255)\n- Added Auriol HG11911 to list of supported devices (#3244)\n- Fixed negative temperatures on RainPoint-Soil (#3212)\n\n## Release 25.02 (2025-02-19)\n\n### Breaking Changes\n\n- Changed state key value to ON/OFF, BREAKING CHANGE for Waveman-Switch (#2946)\n\n### Highlights\n\n- Added support for General Motors TPMS (#3191)\n- Added Globe Thermometer for 8-in-1 sensor to Bresser-7in1 (#3193)\n- Added rain start detection feature for WS90 sensor (#3183)\n- Added id key to IDM and NETIDM (#3164)\n- Added decoder conf for Hormann remotes (#3162)\n- Added client cert option to HA script (#3160)\n- Added support for Revolt ZX-7717 power meter (#3125)\n- Added support for Gridstream RF protocol from Landis & Gyr meters (#2616)\n- Added decoder conf for Rako wireless lighting controls (#3124)\n- Added support for Quinetic Switches and Sensors (#3098)\n- Added support for DeltaDore X3D (#1911)\n- Fixed Prometheus exposition format for metrics endpoint (#3107)\n- Added support for Bresser/Explore Scientific ST1005H (#3092)\n- Improved HA script to round battery level display (#3100)\n- Fixed M-Bus Mode C Format B for wmbusmeters (#3091)\n- Added support for Thermopro TP828B Meat Thermometers 2 probes (#3085)\n- Added support for Arexx TL-3TSN, TSN-33MN and similar sensors (#3076)\n- Improved decoder conf qx-30x to support multiple switches and multi-gang versions (#3008)\n\n### Changed\n\n- Changed LandisGyr-GS location output (#3185)\n- Changed Rubicson, Nexus, Solight-TE44, Baldr-Rain priority (#3175)\n- Added energy msg to Revolt-ZX7717\n- Added broadcast flag for syslog output (#3171)\n- Fixed argument handling in sigrok file generation (#3161)\n- Fixed MQTT reconnect timer (closes #3145)\n- Added reconnect throttling to Influx output (#3135)\n- Added reconnect throttling to MQTT output (#3134)\n\n## Release 24.10 (2024-10-30)\n\n### Breaking Changes\n\n- Changed to pm_2_5_ug_m3, pm_10_ug_m3, BREAKING change to Bresser-7in1 (#2953)\n- Changed Smoke-GS558, Akhan-100F14 to default disabled BREAKING CHANGE (#2958)\n- Changed distance_km to storm_dist_km, BREAKING change to Bresser-Lightning (#2855)\n\n### Highlights\n\n- Added support for Technoline TX960 to Acurite-606TX decoder (#3078)\n- Added support for Risco 2 way Agility protocol, Risco PIR/PET Sensor RWX95PA (#3066)\n- Added support for Rosstech Digital Control Unit DCU-706/Sundance/Jacuzzi (#2612)\n- Added support for Arexx Multilogger (#2487)\n- Updated Fineoffset-WS90 to new FW timings (#3063)\n- Added decoder conf for oma blind remote (#3058)\n- Added decoder conf for iVac Pro remote (#3049)\n- Added Vevor Weather Station 7-in-1 (#3023)\n- Added support for Ecowitt WH46 air quality sensor (#3010)\n- Added open in browser script (#2158)\n- Added decoder conf for Hornbach MSRC-SAL Awning remote (#3009)\n- Added support for Geevon TX16-3 outdoor sensor (#2910)\n- Added support for Arad Master Meter water utility meter (#2984)\n- Added support for ThermoPro TP829b and Improve rtl_433_mqtt_hass.py (#2964)\n- Added Support for Chamberlain CWPIRC pir sensor (#2962)\n- Added support for Nice One to Nice-FlorS (#2960)\n- Added Support for Ecowitt WN34D and improve FineOffset WN34 (#2944)\n- Added decoder conf for rolleaseacmedia blind controller (#2947)\n- Added decoder conf for Thomson kinetic doorbell (#2940)\n- Added ST389 temperature sensor for ORIA WA50 freezer thermometer (#2937)\n- Added decoder conf for Dewenwils BH-V (#2926)\n- Added Support BMW Gen3 TPMS (#2900)\n- Added decoder conf for self powered QX-305 & QX-302 switches (#2903)\n- Improved BMW Gen4-Gen5 TPMS and Add Support Audi TPMS Pressure Alert (#2901)\n- Added support for Audi TPMS and BMW Gen4 TPMS (#2897)\n- Added support for ThermoPro TP28b (#2882)\n- Added Support for Mueller Hot Rod water meter (#2887)\n- Added Support for Thermor DG950 Weather Station (#2886)\n- Added darker colors for light terminal backgrounds (#2864)\n- Added support for Watts WFHT-RF thermostat (#2648)\n- Added honor the NO_COLOR env var\n- Added RTL433_COLOR=always/never/auto env var\n- Added support for BMW Gen5 TPMS multi-brand HUF, Continental, Schrader/Sensata (#2834)\n- Added support for Bresser CO2 PN 7009977 and HCHO/VOC PN 7009978 sensors (#2815)\n- Added support for Fine Offset WN32B (closes #2303)\n- Added support for Fine Offset / Ecowitt WH55 water leak sensor (closes #2756)\n- Added support for TechniSat IMETEO X6 and improve Holman-AOK (#2759)\n- Added Motonet MTX, MarQuant Rain note to Schou-72543 (#2686)\n\n### Changed\n\n- Added extract_bytes_uart_parity utility function\n- Fixed Acurite-590TX timings (closes #3039)\n- Changed rtl_433_mqtt_hass.py to add supercap_V key\n- examples: Added dup filtering to mqtt_relay (#3018)\n- Fixed wmbus raw data output length (#2749)\n- examples: Added ability for mqtt_relay to use a config file (#3013)\n- Added MQTT dedup republish filter example (closes #2990)\n- Added TX-Button to inFactory-TH\n- examples: Changed mqtt_relay to always use id in topic (#3014) (#3014)\n- examples: Improved mqtt_relay to make individual and json topics optional (#2975)\n- examples: Removed obsolete sigrok example scripts (#2979)\n- Changed Klimalogg-Pro max humidity (closes #2967)\n- Changed to type-safe data_append alternative (#2667)\n- Fixed rain_rate_in_h template in HA script (#2782)\n- Improved Ecowitt WH53 (#2934)\n- Fixed Bresser-5in1 omit temp/hum on error\n- Improved Wireless-MBus 3of6 decoding (#2883)\n- Added HA script compat for paho-mqtt 2.0.0 via legacy callback API (#2916)\n- Fixed Nissan TPMS pressure_PSI key (#2915)\n- Changed HA script power to energy and UV index to float (#2913)\n- Fixed Cotech-367959 lux and uv value (#2073)\n- Fixed TPMS Nissan pressure_psi value (#2906)\n- Fixed Bresser-6in1 rain vs temp (closes #2184)\n- Changed a HA script device trigger to avoid duplicate trigger (#2829)\n- Added some known fields to Marlec-Solar\n- Added log rotate support for dumper files (#2876)\n- Added contact_open reed_open mappings to rtl_433_mqtt_hass.py (#2881)\n- Added microseconds to OOK pulse data outputs\n- Added OpenMetrics/Prometheus API (#2863)\n- Added strict checks to flex argument parsing\n- Fixed EcoWitt-WS68 wind unit and decoding (#2871)\n- Fixed event callback on shutdown (closes #2869)\n- Fixed Badger-ORION return on decode success (#2854)\n- Improved Bresser lightning correct msg length and lfsr digest check\n- Improved FS20 decoding, add FHT support (#1783)\n- Fixed properly install to /etc for /usr prefix (closes #2827)\n- Added compatible model TST-507 TPMS to EezTire E618 (#2832)\n- Fixed Bresser-7in1 to exclude unavailable sensor values (#2817)\n- Added HTTP cmd API examples\n- Fixed TPMS Elantra2012 for longer desync (closes #2806)\n- Fixed KlikAanKlikUit-Switch for DIO remotes (#2789)\n- Fixed rtl_433_mqtt_hass.py rain_in value template (#2801)\n- Fixed Bresser-Lightning decoding of 'count' (#2797)\n- Fixed detection of Bresser-ProRainGauge (#2431)\n- Improved EcoWitt-WS68 LUX and UVI decoding and add units (#2790)\n- Fixed Fineoffset-WS90 to support newer firmware (closes #2732)\n- Added Ecowitt air quality sensors to rtl_433_mqtt_hass.py (#2772)\n- Added mqtt base topic option (closes #2768)\n- Added reading mqtt auth from env vars (closes #2769)\n- Fixed converting inches to mm (#2755)\n- Changed rtl_433_mqtt_hass.py to support storm_dist_km from WH31L (#2748)\n- Added channel/button to Acurite-606TX\n- Fixed TFA-303151 negative temps (closes #2538)\n- Fixed temperature for Bresser 3-in-1 Wind Gauge (closes #2523)\n- Improved code and annotations for cpplint (#2683)\n\n## Release 23.11 (2023-11-28)\n\n### Breaking Changes\n\n- Changed conf dir defaults to just SYSCONFDIR (#2660)\n- Changed verbosity of Protocols Registered output (#1700)\n- Changed example conf file to use defaults (#2670)\n- Changed help texts to print to stdout (#2542)\n- Fixed Oregon Scientific channel renumbering, BREAKING change to THN132N channel 3 (#2033)\n- Changed MQTT client ID to contain all params (#1129)\n- Fixed GEO-minim matching, BREAKING change to id field (#2363)\n- Changed Inkbird-ITH20R temperature2_C to temperature_2_C BREAKING change (#2220)\n\n### Highlights\n\n- Added support for Schou 72543 rain sensor (#2686)\n- Added support for Bresser Lightning and Bresser Air Quality (#2698)\n- Added support for Tekelek oil gauge (#2306)\n- Added support for Nissan Leaf TPMS (#2536)\n- Added support for Carchet TPMS (#2677)\n- Added support for Bresser water leakage sensor PN: 7009975 (#2590)\n- Added support for discovering Govee water sensors to rtl_433_mqtt_hass (#2605)\n- Added support for newer Chuango DWC-102 close command (#2630)\n- Added support for TFA Stratos 30.3151 and improve Fineoffset WH1050 (#2549)\n- Added support for IROX ETS69 to Ewig Emos-TTX201 (#2547)\n- Added support for ThermoPro TX-2C (#2466)\n- Added support for Fineoffset WS90 (#2448)\n- Added support for Acurite 592TX (#2457)\n- Added support for Oregon Scientific AWR129 BBQ thermometer (#2439)\n- Added support for Celsia CZC1 (#2391)\n- Added support for Baldr/Rainpoint rain gauge (#2394)\n- Added support for EezTire-E618 TPMS10ATC (#2387)\n- Added support for LaCrosse TX31U-IT (#2386)\n- Added support for Revolt Energy Monitor NC-5462 (#2361)\n- Added support for Emax EM3551H with Gust without UV/Lux to Emax-W6 (#2376)\n- Added support for Wireless M-Bus Mode T Downlink (#2366)\n- Added support for Gasmate-BA1008 (#2359)\n- Added support for Oil-SonicSmart (#2279)\n- Added support for Watchman Sonic Advanced / Plus decoder (#2323)\n- Added support for CED7000 timer (#2319)\n- Added support for Emax Weather Sensor, improves Altronics X7064 sensor (#2300)\n- Added support for TFA Dostmann 14.1504 Radio-controlled grill and meat thermometer (#2296)\n- Added support for new revision of Govee H5054 water leak detector (#2273)\n- Added support for Vauno EN8822C (#2231)\n- Added support for WEC-2103 temperature/humidity sensor (#2185)\n- Added support for Neptune R900 flow meters (#2180)\n- Added support for SRSmith SRS-2C-TX Pool Remote Control (#2147)\n- Added support for Kia Rio III (UB) and Hyundai TPMS sensors (#2083)\n- Added support for TyreGuard 400 TPMS (#1976)\n- Added support for GEO minim+ energy monitor (#1970)\n- Added support for longer EFTH800 messages (#2278)\n- Added decoder conf for PHOX garage gate opener (#2560)\n- Added decoder conf for Driveway alarm motion sensor I8-W1901 (#2493)\n- Added decoder conf for Reolink doorbell (#2277)\n- Added decoder conf for SWETUP garage door remote (#2403)\n- Added decoder conf for ELRO AB440 remote (#2066)\n- Added decoder conf for xmas tree remote 2APJZ-CW002 (#2250)\n- Added decoder conf for GE Smartremote Plus (#2249)\n- Added standardized detect key for Govee-Water (#2625)\n- Added restartable SDR device (#2411)\n- Changed to async SDR acquire thread (#1978)\n- Changed to second Ctrl-C is a hard abort\n- Added log output, log redirect, colored KV log (#2254)\n\n### Changed\n\n- Changed rtl_433_mqtt_hass.py to use moisture class (#2726)\n- Changed rtl_433_mqtt_hass.py to add battery_mV (#2725)\n- Changed Fineoffset-WS90 packet size check (#2702)\n- Fixed logging message in HA script (#2696)\n- Added Pool/Spa Thermometer PN 7009973 note to Bresser-6in1 (#2689)\n- Added php example stream script (#2687)\n- Added checksum and battery_ok to WEC-2103 (#2662)\n- Changed version number to exclude nightly tag\n- Changed EezTire-E618 checksum, include flags (#2664)\n- Changed startup help text to the actual help page (#2659)\n- Improved some strcpy strncpy to snprintf\n- Improved all plain sprintf to snprintf\n- Fixed Somfy-IOHC length check (#2655)\n- Added warning if firewall blocks loopback (#2621)\n- Improved HA script with precipitation and wind speed class (#2643)\n- Fixed Honeywell-CM921 temperature msg (#2637)\n- Added Lidl Auriol 4-LD6313 and 4-LD5972 temperature/rain sensor (#2633)\n- Improved HA script with current class (#2626)\n- Improved handling of units within HA UI (#2624)\n- Improved HA script with detailed units (#2607)\n- Fixed HA script allow list id typo (#2602)\n- Fixed Fineoffset-WH1050 return value (#2600)\n- Fixed Inkbird-ITH20R battery_ok key and range (#2596)\n- Fixed HA script remove device name from entities (#2594)\n- Fixed HA script topics (#2593)\n- Fixed malformed json on empty row print (#2588)\n- Fixed Ecowitt-WS90 extra data output (#2585)\n- Added description of startup bit to Bresser-5in1 (#2583)\n- Fixed various spelling (#2579)\n- Added lower temp bounds to Acurite-5n1, Acurite-Atlas (#2571)\n- Added humidity_1, humidity_2 to rtl_433_mqtt_hass (#2567)\n- Added timestamp to logs in rtl_433_mqtt_hass (#2566)\n- Fixed HASS script rain_rate_mm_h typo (#2565)\n- Fixed Eurochron-EFTH800 radio_clock hour decoding (#2526)\n- Changed Fineoffset-WS90 to add firmware version (#2517)\n- Changed ThermoPro TX-2C to enable humidity (#2514)\n- Added radio clock to Fineoffset-WH1050 (#2463)\n- Added battery voltage to EcoWitt-WH40 (#2488)\n- Changed EMOS-E6016 wind speed and battery  (#2484)\n- Improved Holman ws5029, Add support for AOK-5056 and correction for Emax (#2419)\n- Fixed current frequency and sample rate info (#2468)\n- Changed rtl_433_mqtt_hass to enable passing custom topics (PR #2289)\n- Added extra digit to Oregon Scientific for AWR129 (#2446)\n- Changed Acurite 896 rain gauge to default enabled (#2430)\n- Changed CM160 output to double (#2428)\n- Fixed CM160 energy calculation factor (#2427)\n- Changed r_device.fields to constant pointer (#2421)\n- Added total energy kWh to CM160 readings (#2418)\n- Fixed Fitipower FC0012 gain quirk (#2417)\n- Added unlockable rtl_tcp control (#2412)\n- Fixed rtl_tcp for slow send buffers\n- Fixed LibreSSL PSK error (#1569)\n- Fixed Oil-SonicAdv missing messages\n- Fixed LaCrosse-R1/LaCrosse-R3 rain value (#2313)\n- Added char const check to symbolizer (#2390)\n- Fixed flowis protocol decoding, alarm and back flow parameters.\n- Changed literals to string const for strict discarded-qualifiers warnings (#2379)\n- Added Schrader 3039 TPMS for Infiniti Nissan Renault note to Schrader-SMD3MA4\n- Fixed Emax units (#2374)\n- Added TLS debug info for influxs and mqtts\n- Fixed Emax LUX decoding (#2346)\n- Fixed Vauno-EN8822C false positives (#2364)\n- Added Flowis protocol decoder (#2357)\n- Added Wireless M-Bus, Mode T Downlink (#2366)\n- Fixed out-of-bounds in Klimalogg-Pro (#2362)\n- Fixed Somfy-RTS for wrong bitrate (#2356)\n- Changed all r_device declarations to const (#2352)\n- Fixed pointer restrict for C++ compilers (#2351)\n- Fixed TFA-141504v2 decoder length check (#2339)\n- Added radio clock decode to Eurochron-EFTH800 (#2331)\n- Changed Somfy-IOHC to recognize more messages (#2258)\n- Fixed Oregon Scientific negative temp, add BCD sanity checks (#2086)\n- Added ESIC/SCMplus fields to rtl_433_mqtt_hass (#2114)\n- Changed LaCrosse TX invalid humidity handling (#2335)\n- Fixed json keys for wmbus to be unique (#2316)\n- Fixed Bresser-ProRainGauge rain digits (#2312)\n- Fixed light reading on Cotech-367959 when no sensor installed (#2305)\n- Fixed HCS200 serial ID decoding (#2308)\n- Added TLS options to InfluxDB output\n- Added output log level options (#2282)\n- Added ANSI colors for Windows 10 (#2280)\n- Fixed to use pthread compat on WIN32 always\n- Fixed battery flag in AmbientWeather-WH31E/WH31B (#2272)\n- Changed more fprintf to log prints\n- Changed fprintf to log prints (#2266)\n- Changed verbosity to match log level (#2264)\n- Added basic logger (#2263)\n- Fixed JSON output string escaping\n- Fixed Win32 thread calling convention\n- Added decode_dm flex option (#2241)\n\n## Release 22.11 (2022-11-19)\n\n### Breaking Changes\n\n- Fixed irregular model names (#1883)\n    - \"Inkbird ITH-20R\" -> \"Inkbird-ITH20R\"\n    - \"LaCrosse-WS7000-27/28\" -> \"LaCrosse-WS700027\"\n    - \"LaCrosse-WS7000-22/25\" -> \"LaCrosse-WS700022\"\n    - \"LaCrosse-WS7000-16\" -> \"LaCrosse-WS700016\"\n    - \"LaCrosse-WS7000-15\" -> \"LaCrosse-WS700015\"\n    - \"LaCrosse-WS7000-20\" -> \"LaCrosse-WS700020\"\n    - \"LaCrosse-WS2500-19\" -> \"LaCrosse-WS250019\"\n    - \"Abarth 124 Spider\" -> \"Abarth-124Spider\"\n    - \"Jansite Solar\" -> \"Jansite-Solar\"\n    - \"Klimalogg Pro\" -> \"Klimalogg-Pro\"\n    - \"Secplus_v1\" -> \"Secplus-v1\"\n    - \"SCM+\" -> \"SCMplus\"\n- Changed light_klx to light_lux in Bresser-7in1 (#2061)\n- Removed VS15 project files\n\n### Highlights\n\n- Added rtl_tcp pass-through output (#1915)\n- Added realtime replay option for file inputs\n- Added support for Badger ORION water meter (#2089)\n- Added support for Rubicson pool thermometer 48942 (#2137)\n- Added support for Fine Offset WN34 temperature sensor (#2122)\n- Added support for KS200/KS300 to ELV WS2000 (#2103)\n- Added support for Maverick XR-30 (#2090)\n- Added support for Fine Offset WH45 air quality sensor\n- Added support for HCS200/HCS300 based remotes with FSK transmitters (#2052)\n- Added support for Emos E6016 Rain Gauge (#2032)\n- Added support for ANT and ANT+ devices (#2004)\n- Added support for Altronics X7064 sensor (#2000)\n- Added support for EMOS 6016 (#1983)\n- Added support for FineOffset WS80 weather station (#1965)\n- Added support for Renault-0435R TPMS (#1924)\n- Added support for AcuRite 01190 Leak Detector (#1953)\n- Added support for Regency ceiling fans (#1948)\n- Added support for Microchip HCS300 KeeLoq remotes BREAKING CHANGE (#1752)\n- Added support for Yale HSA (#1929)\n- Added support for SimpliSafe-Gen3 (#1257)\n- Added support for AVE TPMS (#1909)\n- Added decoder conf for sgooway door detector (#1707)\n- Added decoder conf for ContinentalRemote (#2125)\n- Added decoder conf for Mondeo remote (#1282)\n- Added decoder conf for LeakDetector\n- Added decoder conf for Heatilator-Gas-Log (#1963)\n- Added decoder conf for Honeywell-Fan (#1962)\n- Added decoder conf for DrivewayAlert (#1928)\n- Added decoder conf for GhostControls (#1922)\n- Added Threads if available\n\n### Changed\n\n- Added note regarding WH51 915MHz decoding (#2236)\n- Added publish secret knocks as device automations\n- Added time, channel, and button device automation triggers\n- Improved false positives for acurite_01185m (#2214)\n- Changed rtl_433_mqtt_hass rain value template round to two digits (#2210)\n- Changed rtl_433_mqtt_hass value templates round to one digit (#2209)\n- Fixed Chuango devices with some zeroes in their ID (#2205)\n- Changed Proove/Nexa/Kaku to support Smartwares SH4-90152 (#2174)\n- Changed Acurite TXR decoder to improve validation (#2162)\n- Fixed Efergy-e2CT current above 53 Amp (#2166)\n- Added sanity check to Ambient Weather F007th (#2155)\n- Added decode symbol helper and flex option (#2161)\n- Added InFactory PT-310 support to Rubicson (#2123)\n- Added HTTP API example scripts\n- Changed rain gauge multiplier for Auriol-4LD5661 (#2129)\n- Added CSV names for flex getters with unique mode\n- Added php example script (#2087)\n- Improved Ford TPMS decoder (#2071)\n- Fixed crash when reading a conf file (#2068)\n- Changed SDR data buffers to persist valid\n- Added ERT-SCM consumption data to mqtt hass mappings (#2023)\n- Added id filter to mqtt hass (#1988)\n- Fixed overflow in Acurite-00275rm (#2012)\n- Changed the PCM/NRZ/RZ naming\n- Added cmake option to select IPv6 support\n- Fixed EMOS-6016 checksum, add DCF77\n- Added bitbuffer_find_repeated_prefix function\n- Fixed pulse reset on spurious pulses (#1982)\n- Removed confusing -G register all option\n- Changed hass script duplicate dict key 'light_lux' back to 'lux' for legacy reasons (#1989)\n- Added Honeywell-cm921 Ticker and Heat Demand commands (#1985)\n- Changed most decoder-verbose to log level\n- Changed fprintf in decoders to decoder_log\n- Added decoder log to replace bitbuffer bitrow print\n- Added decoder log to replace fprintf\n- Fixed Digitech-XC0324 humidity readings (FT-005TH Sensor) (#1971)\n- Changed wt0124 to use additional checksum (#1959)\n- Added diagnostic entity category to mqtt hass example (#1952)\n- Removed invalid \"weather\" device class in HASS script (#1584)\n- Added trigger stream output (#1910)\n- Changed Hideki to accept short sync (#1908)\n\n## Release 21.12 (2021-12-14)\n\n### Highlights\n\n- Added noise stats, autolevel, and squelch (#1763)\n- Added automatic file format detection from filename\n- Added support for SmartFire Proflame 2 remote control (#1905)\n- Added support for Lacrosse TX34 rain gauge (#1890)\n- Added support for Telldus-FT0385R (#1841)\n- Added support for GE Choice Alert wireless alarm sensors (#1768)\n- Added support for Porsche Boxter/Cayman TPMS\n- Added support for Funkbus/Instafunk (#1896)\n- Added support for Truck TPMS (#1893)\n- Added support for LaCrosse LTV-W1 wind sensor (#1855)\n- Added support for Auriol 4-LD5661 temperature/rain sensor (#1843)\n- Added support for Linear Megacode Remote (#1834)\n- Added support for EnOcean ERP1 decoder (#1829)\n- Added support for Acurite-01185M (#1824)\n- Added support for ATech-WS308 temperature sensor (#1605)\n- Added support for RainPoint sensor (#1781)\n- Added support for Inkbird ITH-20R\n- Added support for Clipsal CMR112 cent-a-meter power meter (#1814)\n- Added support for Govee Water Leak Detector H5054 and Govee Contact Sensor B5023 (#1653)\n- Added support for Markisol curtain remote (#1775)\n- Added support for Oregon Scientific BTHR918 (#1767)\n- Added UV index add decimals to Bresser-7in1 (#1789)\n- Changed InfluxDB output of 'mic' from field to tag (#1773)\n- Added biastee, digital_agc, direct_samp, offset_tune option for rtlsdr and rtl_tcp (#1788)\n- Added QoS to MQTT options (#1769)\n\n### Changed\n\n- Changed PCM slicer to measure bit length in more cases (#1897)\n- Added CS8 as file input format\n- Added decoder priority stages (#1895)\n- Fixed fprintf to use an unsigned marker when an unsigned value is given (#1872)\n- Added RTL_433_REDUCE_STACK_USE to reduce size of bitbuffers (#1863)\n- Fixed rainpoint msg buffer must be initialized (#1862)\n- Fixed Reduce stack use in slicers (#1860)\n- Fixed Windows CreateTimerQueueTimer alarm handle is not valid for CloseHandle (#1859)\n- Changed HASS script to logging (#1851)\n- Added SwitchDocLabs SM23 note\n- Added SwitchDocLabs F016TH note\n- Added SwitchDocLabs FT020T note\n- Fixed Inovalley-kw9015b rain bits (#1660)\n- Added battery_ok to Bresser-7in1 (#1795)\n- Added HASS long term statistics (#1835)\n- Added bitbuffer row spill\n- Updated Govee Leak Sensor with new parity check (#1810)\n- Fixed Springfield-Soil/AlectoV1 false positives\n- Fixed LaCrosse-TH3 checksum (#1398)\n- Added cmake found lib versions output\n\n## Release 21.05 (2021-05-09)\n\n### Highlights\n\n- Last release to support Autotools (autoconf, automake) builds (#1644)\n- Last release to offer \"oldmodel\" keys (deprecated since 2020)\n- Added Github Release builds for Windows\n- Added GPSd tags option (#1636)\n- Added optional TLS support to MQTT (#1633)\n- Added OpenSSL support for influxs TLS (#1569)\n- Added support for ELK-319DWM, Alula RE101 to Interlogix (#1711)\n- Added conf for Tesla charge port opener (#1704)\n- Added support for  Hyundai-VDO TPMS (#1643)\n- Added support for TX25U dual channel temp sensor\n- Added support for Honeywell CM921/BDR91/Evohome (#1336)\n- Added support for Auriol AFT 77 B2\n- Added support for Auriol AHFL (#1683)\n- Added support for Bresser Professional Rain Gauge (#1676)\n- Added support for TFA Marbella pool thermometer (#1675)\n- Added support for Amazon Basics Meat Thermometer (#1671)\n- Added support for Owl 180i support\n- Added support for Jansite TPMS Model Solar (#1663)\n- Added support for Cavius alarms (#1648)\n- Added support for Security plus v1 (#1483)\n- Added conf for Skylink HA-434TL motion sensor (s.a. #814)\n- Added support for Burnhard BBQ thermometer (#1624)\n- Added support for wmbus water meter Maddalena (#1610)\n- Added conf for ATC Technology LMT-430 (#1600)\n- Added support for Blueline PowerCost Monitor\n- Added conf for FAN-53T (#1588)\n- Added support for Acurite 515 fridge/freezer sensors (#1579)\n- Added support for TelFix-RadioLoop (#1571)\n- Added conf for Salus RT300RF thermostat, Heatmiser PRT-W thermostat (#1573)\n\n### Changed\n\n- Added Release build workflow\n- Added pressure_kPa key for HA (#1712)\n- Added support for ELK-319DWM, Alula RE101 to Interlogix (#1711)\n- Fixed and style Honeywell CM921\n- Added option to set force_update for all sensors (#1695)\n- Added Tesla charge port opener decoder conf (#1704)\n- Added battery flags to Bresser 7in1 (#1703)\n- Fixed Hyundai-VDO TPMS\n- Added Hyundai-VDO TPMS (#1643)\n- Added X10 Dim, Bright, All Lights ON, and All Off commands (#1687)\n- Added support for TX25U dual channel temp sensor\n- Fixed code warnings\n- Fixed code style\n- Added forgotten id\n- Added Honeywell CM921/BDR91/Evohome decoder (#1336)\n- Added Auriol AFT 77 B2 protocol decoder\n- Fixed Holman-WS5029 rain count (#1686)\n- Fixed explanation of supposed PM10 (estimated) value (#1678)\n- Added support for Auriol AHFL protocol (#1683)\n- Fixed PSI calculation for Ford TPMS\n- Added support for higher pressure range in Ford TPMS\n- Fixed secplus_v1 endless loop (#1662)\n- Fixed secplus_v1 overflow\n- Added support for some Ecowitt WH41 sensor signals\n- Added comment regarding PM10 readings in the FineOffset WH0290 decoder\n- Added support for Bresser Professional Rain Gauge (#1676)\n- Added TFA Marbella pool thermometer protocol decoder (#1675)\n- Added Amazon Basics Meat Thermometer decoder (#1671)\n- Fixed exit if http server can't start\n- Fixed missing sdr_stop for WIN32\n- Added Owl 180i support\n- Added website and sensor specifications for Jansite TPMS Model Solar (#1666)\n- Added decoder for jansite solar tpms (#1663)\n- Added Ford TPMS pressure and temperature (#1654)\n- Fixed http redirect to index for Chrome\n- Added support for Cavius alarms (#1648)\n- Added mqtt token slash accepts any character\n- Added variable fm low pass filter option\n- Added support for Security plus v1 (#1483)\n- Added GPSd tags option (#1636)\n- Added FineOffset WH0290 extra fields (#1639)\n- Fixed Hideki Gust speed by Udo Kirsten\n- Added option for multiple data tags\n- Added optional TLS support to MQTT (#1633)\n- Added OpenSSL support for influxs TLS (#1569)\n- Added LaCrosse TX141TH-Bv2 checksum\n- Changed rain field format for WS2032\n- Added m_bus decoded values + HCA (#1630)\n- Changed battery_ok, rain field for WS2032\n- Fixed m_bus Show invalid dates as invalid (#1628)\n- Fixed mqtt retain on hass script (#1602)\n- Added Skylink HA-434TL motion sensor conf (s.a. #814)\n- Added support for Burnhard BBQ thermometer (#1624)\n- Added TFA 30.3208.02 note (#1622)\n- Added raw pulse printing mode\n- Added support for parsing timedate in wbus (#1616)\n- Added Battery Level for Fineoffset WH0290 Wireless Air Quality Monitor (#1617)\n- Fixed Blueline tweaks and improvements (#1590)\n- Added support for wmbus water meter Maddalena (#1610)\n- Changed soil moisture to percent display (#1595)\n- Added ATC Technology LMT-430 conf (#1600)\n- Fixed buffer length in honeywell (#1598)\n- Fixed buffer size error (#1596)\n- Fixed FineOffset WH1050 field widths (#1592)\n- Added support for Blueline PowerCost Monitor\n- Added FAN-53T decoder conf (#1588)\n- Added support for Acurite 515 fridge/freezer sensors (#1579)\n- Fixed invalid HASS \"weather\" device_class (#1548)\n- Fixed rtlsdr_read_async() abort on read stall (#1581)\n- Fixed rtlsdr_set_freq_correction non-error code\n- Added support for UV index and light intensity readings in Cotech (#1575)\n- Fixed false positive bug in Nexus (#1576)\n- Added support for TelFix-RadioLoop to Somfy (#1571)\n- Added Thermostat example conf files with mqtt outputs (#1573)\n- Added lacrosse decoder sanity checks\n\n## Release 20.11 (2020-11-13)\n\n### Highlights\n\n- HTTP server, JSON-RPC\n- Added RfRaw analyzer output and format input support\n- Added support for LaCrosse Technology View LTV-R1 Rainfall Gauge\n- Added support for ECODHOME smart socket\n- Added support for LaCrosse Technology View TH2 Thermo/Hygro sensor\n- Added support for Bresser 6-in-1, 7-in-1 weather station\n- Added support for LaCrosse Technology View TH3 Thermo/Hygro Sensor\n- Added support for LaCrosse LTV-WR1 Multi Sensor\n- Added support for Nice Flor-s remote\n- Added support for Schrader TPMS SMD3MA4 (Subaru)\n- Added support for MightyMule Driveway Alarm FM231\n- Added support for Somfy RTS\n- Added support for LaCrosse LTV-WSDTH01\n- Added support for TFA 30.3221.02 Temperature/Humidity sensor\n- Added support for Security plus v2 keyfob\n- Added support for Acurite Atlas and Atlas Lightning Detector\n- Added support for Acurite 590TX\n- Added support for ThermoPro TX2\n- Added support for IDM and NetIDM decoders\n- Added support for Insteon decoder\n- Added support for LaCrosse TX141B\n- Added support for Sharp SPC775\n- Added support for Missil ML0757\n- Added support for Fineoffset WH32\n- Added support for Abarth124 TPMS sensor\n- Added support for Fine Offset WH1080 FSK version\n- Added support for SCM+ decoder\n- Added support for Kerui WD51 Water leak sensor\n- Added support for Cotech 36-7959\n- Added support for Eurochron EFTH-800\n- Added support for Visonic Powercode devices\n- Added support for Klimalogg decoder and needed nrzs demodulator\n\n### Changed\n\n- Added support for LaCrosse Technology View R1 Rainfall Gauge (#1553)\n- Added http server (#871)\n- Added jsmn json lib\n- Added support for ECODHOME smart socket (#1544)\n- Fixed Lacrosse-THx hardcoded strings to support data extractor scripts\n- Added support for LaCrosse Technology View TH2 Thermo/Hygro sensor (#1552)\n- Added stats start time reporting\n- Fixed Analyzer FSK/OOK hint (#1557)\n- Improved unit tests for bitbuffer with extra assertions\n- Fixed UNUSED in term_ctl\n- Removed \"http\" as \"influx\" alias\n- Added arguments and docs to Home Assistant MQTT auto discovery script (#1546)\n- Changed LaCrosse LTV-WR1 to wind_avg_km_h key (#1549)\n- Fixed rfraw builder overflow (#1539)\n- Added Dooya Curtain Remote conf (#1545)\n- Added SDR loop api\n- Changed to sig_atomic_t for sighandler\n- Fixed wmbus csv output parameters\n- Fixed flags field for TPMS Jansite (#1538)\n- Added note for TFA Dostmann 30.3159.IT (#1537)\n- Added SDR runtime settings api\n- Changed exit async naming\n- Added SDR device info\n- Added support for Bresser 6-in-1, 7-in-1 weather station (#1225)\n- Added support for LaCrosse Technology View TH3 Thermo/Hygro Sensor (#1536)\n- Added support for LaCrosse LTV-WR1 Multi Sensor (#1533)\n- Added support for Nice Flor-s remote (#1526)\n- Changed remove DSC subtype key (#1522)\n- Changed Acurite subtype key to message_type (#1520)\n- Added support for WH31E RCC packet type (#1528)\n- Fixed wmbus mode S buffer length issue for Lansen meters\n- Added SoapySDR to MinGW-w64 build\n- Added output format option to flex getters (#1532)\n- Added TFA 30.3209 note to Nexus (#1516)\n- Added TFA-Dostmann 30.3161 rain scale (#1531)\n- Fixed Insteon string overflow\n- Fixed missing CSV fields, add a debug check\n- Added named output tag option (#1517)\n- Added support for Schrader TPMS SMD3MA4 (Subaru) (#1511)\n- Removed unneeded update_protocol\n- Changed width calc from r_device to slicers (#1513)\n- Added support for mightymule driveway alarm FM231 (#1407) (#1515)\n- Changed rfraw parse to accept multiple codes\n- Fixed include for memcmp in rfraw (#1507)\n- Added id key to scmplus (#1503)\n- Removed list of supported device protocols from man page (#1345)\n- Added RfRaw analyzer output support\n- Added RfRaw format input support\n- Removed FSK_PULSE_MANCHESTER_ZEROBIT from ook_demods\n- Fixed Inovalley kw9015b temp/rain fields proper\n- Fixed Inovalley kw9015b temp/rain fields (#1501)\n- Added support for Somfy RTS (#1496)\n- Added 7-bit clean strings check to actions\n- Added maintainer_update check\n- Fixed Security+ 2.0 decoder for new gap_limit rows (#1498)\n- Added clang-analyzer action\n- Added build action\n- Added style check action\n- Improved PCM NRZ 0-bit slicing precision\n- Fixed output keys for FineOffset WH51 (#1495)\n- Fixed index bug in TFA 30.3221\n- Fixed simplisafe non-printable character output\n- Added support for LaCrosse LTV-WSDTH01 (#1485)\n- Added gap_limit to PCM demod\n- Fixed invalid dumpers on ook input (#1463)\n- Added support for TFA 30.3221.02 Temperature/Humidity sensor (#1426)\n- Fixed Acurite 6045 temperature 2.0F too low (#1482 #1401)\n- Added support for Security plus v2 keyfob (#1480)\n- Fixed opus_xt300 added sanity check to data values (#1470)\n- Fixed runtime error 'left shift of 229 by 24 places cannot be represented in type int (#1479)\n- Fixed bad conf for Fan-11t (#1477)\n- Added came top432 flex decoder config (#1474)\n- Fixed wmbus raw telegram output, mainly for wmbusmeters use\n- Fixed efergy_e2_classic False Trigger (#1475)\n- FIXed check manchester_decode check decoded bit length in a consistent method, removed superfluous comment\n- FIXed check manchester_decode result length\n- Fixed current_cost 8 bytes required\n- Fixed TPMS Abarth124 false positive (#1466)\n- Fixed alectov1 csv fields (#1457)\n- Added Atlas Lightning Detector support (#1418)\n- Added Acurite Atlas support (#1124)\n- Added Nexus-TE82s compatibility note (#1455)\n- Updated idm scmplus Meter type list (#1445)\n- Added Acurite 590TX support (#1411)\n- Added ThermoPro TX2 support (#1450)\n- Improved program exit code in case of error (#1451)\n- Improved Home Assistant MQTT auto discovery (#1390)\n- Fixed Many False Positives (#1444)\n- Fixed Globaltronics QUIGG GT-TMBBQ-05 false positives (#1443)\n- Fixed Oregon Scientific SL109H false positives (#1442)\n- Added IDM and NetIDM decoders (#1421)\n- Changed Fineoffset WH32 to exclude pressure\n- Added Insteon decoder (#1285)\n- Added Friedland EVO door bell conf\n- Added support for LaCrosse TX141B (#1434)\n- Added missing parts for Sharp SPC775 decoder\n- Added Sharp SPC775 support (#1433)\n- Added support for Missil ML0757\n- Fixed use of return code in Abarth Spider decoder\n- Added conditional to data_make (#1432)\n- Added support for Fineoffset WH32 (#1431)\n- Added bit reversed output for HCS200 decoder to match official tools\n- Added reverse32 function\n- Improved x10sec add sensors, tamper, crc (#1413)\n- Improved inFactory e.g. MIC (#1325)\n- Changed Kerui to break out additional fields from state (#1018)\n- Updated rtl_433.example.conf\n- Improved validations checks for smoke_gs558 protocol\n- Added Equation/Siemens ADLM FPRF remote conf\n- Added Abarth124 tpms sensor support\n- Added missing protocol to readme\n- Added attenuation histogram output (#1387)\n- Added Fine Offset WH1080 FSK version support\n- Improved FSK demodulation of distorted signals better\n- Added SCM+ decoder (#1410)\n- Added support for Kerui WD51 Water leak sensor (#1406)\n- Fixed cancel watchdog when reading from file input\n- Fixed ERT Endpoint Type extraction (#1379)\n- Added custom data processor example\n- Improved Honeywell sensor support (#1384)\n- Added delay and low battery codes for DS10A door sensor (#1397)\n- Fixed free results from SoapySDR API\n- Fixed handle empty filenames\n- Added support for Cotech 36-7959 (#1382)\n- Removed deprecated positional flex syntax\n- Changed div 10 to mul 0.1 in all decoders\n- Changed value scaling for double to float in all decoders\n- Changed checks on Rubicson/Nexus/Solight\n- Fixed Bresser 5in1 Wind calculation (#1353)\n- Improved MQTT Home Assistant example (#1357)\n- Added decode_uart util (#1376)\n- Fixed Eurochron EFTH-800 missing mic\n- Added Eurochron EFTH-800 support (#1375)\n- Fixed MQTTT mgr free\n- Fixed Soapy string leaks\n- Added Prometheus/OpenMetrics relay example (#1371)\n- Fixed missing levels with minmax demod (fixes #1363)\n- Fixed socket portability\n- Fixed visonic device battery reporting\n- Improved visonic_powercode\n- Fixed rtl_tcp gain/rate/freq status output\n- Fixed missing WSAStartup in rtl_tcp\n- Added support for Visonic Powercode devices (#1349)\n- Added message length check for ESIC EMT7110.\n- Updated Acurite 6045 to capture all 8 bits of strike counter (#1348)\n- Added configuration file for SMC5326 (#1346)\n- Updated template guideline for verbosity (#1344)\n- Fixed failing style-check test by adding allocation check to write-sigrok\n- Added support for sigrok convert on windows (#1341)\n- Fixed flex map parse\n- Changed -l n to -Y level=n\n- Changed detector level limits to dB\n- Fixed Fineoffset-WHx080 temperature (#1327)\n- Fixed Ecowitt-WH53, Maverick-ET73 timings\n- Fixed Klimalogg device settings, tolerance was set to low\n- Added Klimalogg decoder and needed nrzs demodulator\n- Fixed negative temperatures in wmbus decoder\n- Added pulse-eval example\n\n## Release 20.02 (2020-02-17)\n\n### Highlights\n\n- Added InfluxDB output (#1192)\n- Added native Sigrok writer (#1297)\n- Changed to newmodel keys default\n- Fixed SoapySDR for 0.8 API\n- Added new minmax FSK pulse detector\n- Changed default to use new minmax detector and sample rate of 1MS/s for frequency above 800MHz\n- Changed -a and -G option to discourage usage\n- Improvements and support for many more sensors\n\n### Changed\n\n- Changed CurrentCost and EfergyOptical keys to have units (#1313)\n- Added command line information when new defaults are active\n- Added mic to csv output in the ert decoder\n- Added subtype to DSC (#1318)\n- Added meta to OOK output\n- Fixed json escaping (#1299)\n- Added ERT SCM protocol decoder\n- Added return codes for most devices\n- Changed remaining wind dir keys (see #1019)\n- Fixed optparse strtod with rounding (#1308)\n- Fixed for wmbus records parser\n- Added integrity check for Thermopro TP11 and TP12\n- Fixed conf eol comments (#1307)\n- Added config for unknown car key.\n- Fixed sync word for Honeywell CMI alarm systems\n- Fixed Wno-format-security for nixos gcc9 (#1306)\n- Fixed negative length in data_array (#1305)\n- Added native Sigrok writer (#1297)\n- Added checksum check for Rubicson 48659 meat thermometer\n- Changed Updated Fan 11t conf (#1287)\n- Fixed failure on low sample rates (#1290)\n- Improved format conversions\n- Fixed radiohead-ask buffer overflow (#1289)\n- Changed Enable IKEA Sparsnäs by default\n- Changed cmake build to static lib\n- Changed to newmodel keys default\n- Changed model TFA-Drop-30.3233.01 to TFA-Drop\n- Added config for Fan-11T fan remote (#1284)\n- Added preliminary EcoWitt WS68 Anemometer support (#1283)\n- Added EcoWitt WH40 support (#1275)\n- Improved PCM RZ bit width detection\n- Fixed for #1114 DSC Security Contact WS4945 (#1188)\n- Fixed LaCrosse TX145wsdth repeat requirement\n- Added preliminary LaCrosse TX141TH-BV3 support\n- Fixed SoapySDR for 0.8 API\n- Fixed Auriol AFW2A1 missing check\n- Changed flex decode to count as successful output\n- Added Nexus compatible sensor descriptions\n- Improved LaCrosse TX29-IT support (#1279)\n- Added LaCrosse TX145wsdth support (#1272)\n- Changed KNX-RF output\n- Added support for Lansen wmbus door/window sensor\n- Improved PCM bit period detection\n- Fixed OS PCR800 and RGR968 displayed unit name\n- Fixed battery_level in Fineoffset-WH51\n- Fixed type of battery_mv in Fineoffset-WH51 (#1274)\n- Added reflected LFSR util\n- Added support for TFA Drop 30.3233.01 (#1255)\n- Added Auriol AFW2A1 support (#1230)\n- Added Verisure Alarm config file\n- Added wmbus mode S support and KNX RF telegram support.\n- Added support for decoding Lansen and Bmeters wmbus based temperature/hygrometers\n- Improved Honeywell 2Gig support\n- Changed -a and -G option to discourage usage\n- Added support for WS2032 weather station (#1208)\n- Added timezone offset print option (#1207)\n- Added LaCrosse TX141W support\n- Added battery level to Fineoffset WH51\n- Added Archos-TBH support (#1199)\n- Added Oregon ID_THGR810a ID_WGR800a version ids (#1258)\n- Improved OWL CM180 support (#1247)\n- Added Holman iWeather WS5029 older PWM (#947)\n- Added support for FineOffset/ECOWITT WH51 (#1242)\n- Added config for 21 key remote\n- Added rtlsdr_find_tuner_gain for exact gains\n- Improved fineoffset more heuristics to separate WH65B and WH24\n- Fixed missing csv fields on default disabled\n- Improved Efergy Optical decoder (#1229)\n- Added TX-button to some decoders (#1205)\n- Improved for TFA pool temperature sensor (#1219)\n- Added pulse analyzer support for read OOK data (#1216)\n- Fixed ook input support bug from a9de888 (#1215)\n- Fixed missing hop_time when reading file (#1211)\n- Fixed Acurite 899 rain_mm conversion value (#1203)\n- Fixed build files (#1201)\n- Added more input format validation\n- Changed FSK pulse detector mode option\n- Fixed overlong msg in Radiohead (#1190)\n- Added optional CSA checker to tests\n- Added InfluxDB output (#1192)\n- Fixed Hondaremote for missing first bit\n- Fixed integer promotion for uint32_t fields (#1193)\n- Fixed data format in ELV (#1187)\n- Fixed closing brace bug in test bitbuffer (#1186)\n- Fixed issue where bt_rain  decoder uses -1 as index\n- Changed if the set frequency is > 800MHz then set sample rate to 1MS/s\n- Added new minmax FSK pulse detector\n- Added InfluxDB relay example script\n- Fixed radiohead buffer underflow (#1181)\n- Fixed range clamping on RSSI (#1179)\n- Fixed unaligned sample buffer length (#1177)\n- Fixed bad event return value in elantra (#1176)\n- Fixed accounting if decoder misbehaves (#1175)\n- Fixed ge_coloreffects undef behav (#1173)\n- Added protocol selection to test bitbuffer input\n- Added streaming test bitbuffers (#1062)\n- Fixed parsing oversized bitbuffer (#1171)\n- Added length check for interlogix device and update return codes (#1169)\n- Added preamble check for microchip hcs200 to reduce false positives (#1170)\n- Added unboxed types for data\n- Added warnings on alloc failure\n- Added alloc checks, fixes #1156\n- Improved Compact doubles in mqtt devices topics\n- Added Auriol-HG02832 support (#1166)\n- Improved Honeywell for 2Gig, RE208 (#747)\n- Changed Upgrade Mongoose 6.13+patches to 6.16+patches\n- Fixed expand Efergy-e2CT exp range (#1163)\n- Added RTL-SDR error code output\n- Added Hyundai Elantra 2012 TPMS support (#1158)\n- Added Norgo NGE101 support (#1042)\n- Improved Convert read OOK pulse_data to current sample rate (#1160)\n- Fixed Acurite 899 rain_mm format (#1154)\n- Added gt_tmbbq05 parity check\n- Changed GT-WT-03 added checksum (#1149)\n- Changed Updated QUIGG GT-TMBBQ-05 with MIC\n- Changed GT-TMBBQ-05 added ID, finetuned pulse lengths (#1152)\n- Added Sonoff RM433 conf example (#1150)\n- Added support for Globaltronics GT-WT-03 (#1149)\n- Added support for QUIGG GT-TMBBQ-05 (#1151)\n- Improved Reorder some keys, normalize some keys (#998)\n- Improved Oregon Scientific V3 preamble match\n- Added support for Oregon Scientific WGR800X (#1045)\n- Added Integration docs\n- Added RRD example script\n\n## Release 19.08 (2019-08-29)\n\n### Highlights\n\n- Added MQTT output (#1016)\n- Added stats reporting (#733)\n- Added SoapySDR general keyword settings option, e.g. antenna\n- Added new model keys option\n- Changed Normalize odd general keys on devices (#1010)\n- Changed Use battery_ok instead of battery for newmodel\n- Added report model description option (#987)\n- Added pulse data text file support (#967)\n- Added color to console help output\n- Fixed CF32 loader; addeded CS8, CF32 dumper\n\n### Changed\n\n- Added CurrentCost EnviR support (#1115)\n- Added ESIC-EMT7170 power meter (#1132)\n- Added LaCrosse-TX141Bv3 support (#1134)\n- Added channel to inFactory-TH (#1133)\n- Added man page rtl_433.1 (#1121)\n- Added color to console help output\n- Added support for Philips AJ7010 (#1047)\n- Added frequency hopping signal support for win32 (#1128)\n- Added Holman WS5029 decoder\n- Added Acurite Rain 899 support\n- Added support for Oregon scientific THGR328N and RTGR328N (#1107) (#1109)\n- Added frequency hop on USR1 signal\n- Added '-E hop' option\n- Added option for multiple hop times\n- Added sensor similar to GT-WT-02 (#1080)\n- Added Rubicson 48659 Cooking Thermometer\n- Added TFA Dostmann 30.3196 decoder (#983)\n- Added support for HCS200 KeeLoq encoder (#1081)\n- Added channel output to lacrosse_TX141TH_Bv2 (#1097)\n- Added IKEA Sparsnäs decoder.\n- Added support for Eurochron weather station sensor (#1090)\n- Added MQTT topic format strings (#1079)\n- Added two EV1527 based sample configurations (#1087)\n- Added DirecTV RC66RX Remote Control\n- Added support for Ecowitt temperature sensor\n- Added Companion WTR001 decoder (#1055)\n- Changed Thermopro TP12 also supports TP20 (#1061)\n- Added configuration for PIR-EF4 sensor (#1049)\n- Added Alecto WS-1200 v1/v2/DCF decoders to Fineoffset (#975)\n- Added TS-FT002 decoder (#1015)\n- Added Fine Offset WH32B support (#1040)\n- Added LaCrosse-WS3600 support, change LaCrosse-WS to LaCrosse-WS2310\n- Added LaCrosse WS7000 support (#1029) (#1030)\n- Changed Omit humidity on Prologue if invalid\n- Added MQTT output (#1016)\n- Added stats reporting (#733)\n- Added Interface Specification for data output (#827)\n- Added checksum to Ambient Weather TX-8300\n- Added Interlogix glassbreak subtype\n- Added Jansite TPMS support (#1020)\n- Added Oregon Scientific RTHN129 support (#941)\n- Changed Use battery_ok instead of battery for newmodel\n- Changed Update battery_low, temperatureN keys\n- Changed Normalize odd general keys on devices (#1010)\n- Fixed Efergy-e2 current reading exponent\n- Added FS20 remote decoder (#999)\n- Added SoapySDR general keyword settings option\n- Added option to select antenna on SoapySDR devices (#968)\n- Fixed CF32 loader; added CS8, CF32 dumper\n- Added ASAN to Debug builds\n- Changed tfa_twin_plus_30.3049: Add mic to output\n- Added new model keys option\n- Enhanced Kedsum, S3318, Esperanza with MIC (#985)\n- Added support for XT300/XH300 soil moisture sensor (#946)\n- Changed Schrader unit from bar to kPa\n- Added report model description option (#987)\n- Added native scale for SDRplay\n- Added Chungear BCF-0019x2 example conf\n- Changed GT-WT-02 to support newer timings; changed model name\n- Added pulse data text file support (#967)\n- Added Digitech XC0346 support to Fine Offset WH1050 (#922)\n- Added bitbuffer NRZI(NRZS/NRZM) decodes\n- Added Rosenborg/WH5 quirk to Fineoffset\n- Added support for Silverline doorbells (#942)\n- Changed TPMS Toyota to match shorter preamble\n- Changed TPMS Citroen data readings\n- Changed TPMS Renault data readings\n- Added Digitech XC-0324 temperature sensor decoder (#849)\n- Added sample rate switching\n- Added Mongoose\n\n## Release 18.12 (2018-12-16)\n\n### Highlights\n\n- Added conf file support with examples in etc/rtl_433/\n- Default KV output has pretty colors on console\n- Added meta data for levels, precision time, protocol, debug, tagging\n- Added rtl_tcp input (#894)\n- Added SoapySDR support with CU8/CS16/F32 input/output conversions (#842)\n- Added VCD output, Sigrok pulseview converter\n\n### Changed\n\n- Install example conf files will to etc/rtl_433/\n- Default output is terse with just the most important info\n- Deprecate option q and D for new v to set verbosity\n- Default KV output has pretty colors on console\n- Added debug bits output option\n- Added protocol number meta data option\n- Added precision time and time report options (#905)\n- Deprecate option t and I for new S none|all|unknown|known\n- Changed to use pulse detect to track and grab frames\n- Added rtl_tcp input (#894)\n- Added bitrow debugging output helper\n- Added bitbuffer_debug, bitrow_print, bitrow_debug\n- Changed flex to use keys for all values (#885)\n- Allow multiple input files, positional args are input files\n- Added option for output tagging\n- Added conf examples for generic SCV2260 and PT2260\n- Added a conf file parser (#790)\n- Added negative protocol numbers to disable a device\n- Added Freq/RSSI/SNR output to data_acquired_handler (#865)\n- Added flex suggestion to analyzer output, switch to unit of us\n- Added null output option (suppress default KV)\n- Added option to skip the tests to be built. (#832)\n- Added SoapySDR support (#842)\n- Added CU8/CS16 output conversion\n- Improved dumpers to allow multiple dumpers\n- Removed rtlsdr sync mode\n- Added VCD output\n- Added Sigrok pulseview converter\n- Added f32 output modes\n- Added flex getter (#786)\n- Added version (-V) and help (-h) option (#810)\n- Added example MS Visual Studio 2015 project (#789)\n- Added CS16 input and output (#773)\n- Added preamble option to flex decoder\n\n### Added and improved devices\n\n- Added Gust to Hideki, report proper mph (#891)\n- Changed raincounter_raw field to rain_inch for acurite (#893)\n- Removed EC3k, converted to flex conf\n- Removed Valeo, converted to flex conf\n- Removed Steffen, converted to flex conf\n- Changed ELV-EM1000, ELV-WS2000 to structured output\n- Changed X10-RF to structured output\n- Changed Lightwave-RF to structured output\n- Added confs ported from old devices\n- Improved Fine Offset WH-3080 to support new version Watts/m value calculation\n- Added support for Bresser Weather Center 5-in-1\n- Added Biltema rain gauge protocol decoder, disabled by default\n- Added ESA 1000/2000 protocol decoder\n- Added support for Honeywell Wireless Doorbell\n- Improved inFactory with added checks, enabled by default\n- Added Maverick et73\n- Added Ambient Weather WH31E (#882)\n- Added AmbientWeather-TX8300 (TFA 30.3211.02) support\n- Added Emos TTX201 (#782)\n- Added Hideki / Cresta temperature sensor (#858)\n- Added Fine Offset WH65B support (#845)\n- Added AcuRite 3-n-1 (#720)\n- Added PMV-107J TPMS (#825)\n- Added Fine Offset WH65b support\n- Added Fine Offset WH24 (#809)\n- Added TP08 remote thermometer (#750)\n- Added WT0124 Pool Thermometer\n- Added Hyundai WS sensor (#779)\n- Added M-Bus (EN 13757-4) - Data Link layer (#768)\n- Improved RadioHead to unify the applications\n- Added Sensible Living protocol (#742)\n- Added Oregon Scientific UVR128 UV sensor (#738)\n- Added Pacific PMV-C210 TMPS support (#717)\n- Added SimpliSafe Sensor (#721)\n\n## Release 18.05 (2018-05-02)\n\n### Highlights\n\n- Preparations for features like MQTT and SoapySDR\n- Syslog for simple network output\n- Rewritten demodulators to support a \"precise\" mode using a given tolerance and optional sync symbols\n- Simplified data output layers\n\n### Changed\n\n- Added conversion hPA/inHG, kPa/PSI (#711)\n- Added remote syslog output\n- Added a flexible general purpose decoder (#647)\n- Added git version info to usage output if available\n- Added number suffixes on e.g. frequency, samplerate, duration, hoptime\n- Added Profile build type using GPerfTools\n- Changed grab file name to gNNN_FFFM_RRRk.cu8 (#642)\n- Added option to use receiver serial number -d :SERIAL (#648)\n- Added option to stop after outputting successful event(s)\n- Changed to new data API\n- Added option to verify simulated decoding of raw data\n\n### Added and improved devices\n\n- Added decoder for Dish Network UHF Remote 6.3 (#700)\n- Added interlogix devices driver (#649)\n- Added Euroster 3000TX, Elro DB270 (#683)\n- Added x10_sec device for decoding X10 Security RF signals (#671)\n- Added device LaCrosse TX141 support to lacrosse_TX141TH_Bv2.c (#670)\n- Added GE Color Effects Remote indent and MAX_PROTOCOLS\n- Added support for Telldus variants of fineoffset (#639)\n- Added support to Oregon Scientific RTGN129\n- Added device NEXA LMST-606 magnetic sensor\n- Added support for Philips outdoor temperature sensor\n- Added support for Ford car remote.\n- Added support for the Thermopro TP-12.\n- Added infactory sensor\n- Added Renault TPMS sensor\n- Added Ford TPMS sensor\n- Added Toyota TPMS sensor\n- Added GE Color Effects remote control\n- Added Generic off-brand wireless motion sensor and alarm system\n- Added Wireless Smoke and Heat Detector GS 558\n- Added Solight TE44 wireless thermometer\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "########################################################################\n# Project setup\n########################################################################\ncmake_minimum_required(VERSION 2.6...3.10)\n# Fix behavior of CMAKE_C_STANDARD when targeting macOS.\nif(POLICY CMP0025)\n    cmake_policy(SET CMP0025 NEW)\nendif()\n# Only interpret if() arguments as variables or keywords when unquoted.\nif(POLICY CMP0054)\n    cmake_policy(SET CMP0054 NEW)\nendif()\n\nproject(rtl433 C)\n\n#select the release build type by default to get optimization flags\nif(NOT CMAKE_BUILD_TYPE)\n   set(CMAKE_BUILD_TYPE \"Release\")\n   message(STATUS \"Build type not specified: defaulting to release.\")\nendif(NOT CMAKE_BUILD_TYPE)\nset(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING \"\")\n\nlist(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)\n\n########################################################################\n# Get version info from Git\n########################################################################\ninclude(GetGitRevisionDescription)\nget_git_head_revision(GIT_REFSPEC GIT_COMMIT)\nif(GIT_COMMIT) # is a git repo\n    # shorten branch spec\n    string(REGEX REPLACE \".*/\" \"\" GIT_BRANCH \"${GIT_REFSPEC}\")\n    # use lightweight (non-annotated) tags\n    git_describe(GIT_VERSION \"--tags\" \"--exclude=nightly\")\n    git_timestamp(GIT_TIMESTAMP)\n    message(STATUS \"Found Git version: ${GIT_REFSPEC} commit ${GIT_COMMIT} from ${GIT_TIMESTAMP_ISO}\")\n    message(STATUS \"Using Git version tag: ${GIT_VERSION} on ${GIT_BRANCH} at ${GIT_TIMESTAMP}\")\n    ADD_DEFINITIONS(-DGIT_VERSION=${GIT_VERSION})\n    ADD_DEFINITIONS(-DGIT_BRANCH=${GIT_BRANCH})\n    ADD_DEFINITIONS(-DGIT_TIMESTAMP=${GIT_TIMESTAMP})\nendif()\n\n########################################################################\n# Fallback to get release version info from Changelog\n########################################################################\nif(NOT GIT_COMMIT AND EXISTS \"${CMAKE_CURRENT_SOURCE_DIR}/CHANGELOG.md\")\n    # parse the CHANGELOG.md, this is clever, i.e. might go wrong ;)\n    file(STRINGS \"${CMAKE_CURRENT_SOURCE_DIR}/CHANGELOG.md\" CHANGELOG LIMIT_COUNT 42)\n    # list(FILTER ...) needs CMake 3.6+\n    foreach(item ${CHANGELOG})\n        string(REGEX MATCH \"^## Release (.+)\" item ${item})\n        if(CMAKE_MATCH_1)\n            list(APPEND RELEASE_VERSIONS ${CMAKE_MATCH_1})\n        endif()\n    endforeach()\n    if(RELEASE_VERSIONS)\n        list(GET RELEASE_VERSIONS 0 RELEASE_VERSION)\n        message(STATUS \"Found Release version: ${RELEASE_VERSION}\")\n        ADD_DEFINITIONS(-DGIT_VERSION=${RELEASE_VERSION})\n    endif()\nendif()\n\n########################################################################\n# Compiler specific setup\n########################################################################\nset(CMAKE_C_EXTENSIONS OFF)\nset(CMAKE_C_STANDARD 99)\nif((\"${CMAKE_C_COMPILER_ID}\" STREQUAL \"GNU\" OR \"${CMAKE_C_COMPILER_ID}\" MATCHES \"Clang\") AND NOT WIN32)\n    ADD_DEFINITIONS(-Wall)\n    ADD_DEFINITIONS(-Wextra)\n    ADD_DEFINITIONS(-Wvla)\n    ADD_DEFINITIONS(-Wsign-compare)\n    ADD_DEFINITIONS(-std=c99)\n    ADD_DEFINITIONS(-pedantic)\n    ADD_DEFINITIONS(-Wshadow)\n    ADD_DEFINITIONS(-Wmissing-prototypes)\n    if(\"${CMAKE_C_COMPILER_ID}\" MATCHES \"Clang\" OR NOT \"7.0.0\" VERSION_GREATER CMAKE_C_COMPILER_VERSION)\n        ADD_DEFINITIONS(-Wimplicit-fallthrough)\n    endif()\n    #ADD_DEFINITIONS(-Wfloat-equal)\n    #ADD_DEFINITIONS(-Wbad-function-cast)\n    #ADD_DEFINITIONS(-Wdocumentation)\n    add_definitions(-Wno-deprecated-declarations)\n\n    # for strdup, setenv, use either\n    #ADD_DEFINITIONS(-D_POSIX_C_SOURCE=200809) # does not work with uClibc\n    ADD_DEFINITIONS(-D_GNU_SOURCE)\n    #http://gcc.gnu.org/wiki/Visibility\n    add_definitions(-fvisibility=hidden)\n\n    # CMake Release default for GCC/Clang is \"-O3 -DNDEBUG\"\n    # set(CMAKE_C_FLAGS_RELEASE -O2)\n    # CMake Debug default for GCC/Clang is \"-g -DNDEBUG\"\n    # set(CMAKE_C_FLAGS_DEBUG -g3 -O0)\n    # make use of ASAN\n    set(CMAKE_C_FLAGS_DEBUG \"-ggdb -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer\")\nendif()\nif(\"${CMAKE_C_COMPILER_ID}\" MATCHES \"Clang\")\n    # make sure we don't accidentally copy more than an int\n    ADD_DEFINITIONS(-Wlarge-by-value-copy=8)\nendif()\n\n# Enable Static analysis on GCC13.2.0+\nif(\"${CMAKE_C_COMPILER_ID}\" STREQUAL \"GNU\" AND NOT \"13.2.0\" VERSION_GREATER CMAKE_C_COMPILER_VERSION)\n    message(STATUS \"Using GCC Static analysis\")\n    add_definitions(-fanalyzer)\nendif()\n\n# Shut MSVC up about strdup and strtok\nif(MSVC)\n    ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE)\n    ADD_DEFINITIONS(-D_CRT_SECURE_NO_WARNINGS)\n    ADD_DEFINITIONS(-DNOMINMAX)\n    # don't warn on type truncation\n    add_compile_options(\"/wd4244\")\n    add_compile_options(\"/wd4267\")\n    add_compile_options(\"/wd4305\")\nendif()\n\n# Fix printf %zu\nif(MINGW)\n    add_definitions(-D__USE_MINGW_ANSI_STDIO)\nendif()\n\n# Make sure we get M_PI\nif(WIN32)\n    add_definitions(-D_USE_MATH_DEFINES)\nendif()\n\n# On Unix pass the SYSCONFDIR through to conf file loader\nif(NOT WIN32)\n    include(GNUInstallDirs)\n    add_definitions(-DINSTALL_SYSCONFDIR=${CMAKE_INSTALL_FULL_SYSCONFDIR})\nendif()\n\n########################################################################\n# Use pkg-config\n########################################################################\nfind_package(PkgConfig)\n\n########################################################################\n# Option to force ANSI-colored build output (for Ninja)\n########################################################################\noption(FORCE_COLORED_BUILD \"Always produce ANSI-colored build output (GNU/Clang only).\" FALSE)\nif(FORCE_COLORED_BUILD)\n    if(\"${CMAKE_C_COMPILER_ID}\" STREQUAL \"GNU\")\n       add_compile_options(-fdiagnostics-color=always)\n    elseif(\"${CMAKE_C_COMPILER_ID}\" MATCHES \"Clang\")\n       add_compile_options(-fcolor-diagnostics)\n    endif()\nendif()\n\n########################################################################\n# Enable IPv6 support\n########################################################################\noption(ENABLE_IPV6 \"Enable IPv6 support\" TRUE)\nif(ENABLE_IPV6)\n    message(STATUS \"IPv6 support enabled.\")\n    ADD_DEFINITIONS(-DMG_ENABLE_IPV6=1)\n    if(MINGW)\n        # IPv6 requires at least Vista for inet_pton, inet_ntop\n        add_definitions(-D_WIN32_WINNT=0x0600)\n    endif()\nelse()\n    message(STATUS \"IPv6 support disabled.\")\nendif()\n\n########################################################################\n# Find Threads support build dependencies\n########################################################################\nset(ENABLE_THREADS AUTO CACHE STRING \"Enable Threads support\")\nset_property(CACHE ENABLE_THREADS PROPERTY STRINGS AUTO ON OFF)\nif(ENABLE_THREADS) # AUTO / ON\n\nfind_package(Threads)\nif(Threads_FOUND)\n    message(STATUS \"Threads support will be compiled.\")\n    ADD_DEFINITIONS(-DTHREADS)\nelseif(ENABLE_THREADS STREQUAL \"AUTO\")\n    message(STATUS \"Threads support not found, some features will be disabled.\")\nelse()\n    message(FATAL_ERROR \"Threads support not found.\")\nendif()\n\nelse()\n    message(STATUS \"Threads support disabled.\")\nendif()\n\n########################################################################\n# Find OpenSSL build dependencies\n########################################################################\nset(ENABLE_OPENSSL AUTO CACHE STRING \"Enable OpenSSL TLS support\")\nset_property(CACHE ENABLE_OPENSSL PROPERTY STRINGS AUTO ON OFF)\nif(ENABLE_OPENSSL) # AUTO / ON\n\nfind_package(OpenSSL)\n# Get actual libs from pkg-config to support edge cases (static on Sparc)\n# This will break Win32 builds, exclude that platform\npkg_check_modules(PC_OPENSSL QUIET openssl)\nif(PC_OPENSSL_FOUND AND NOT WIN32)\n    set(OPENSSL_LIBRARIES ${PC_OPENSSL_LINK_LIBRARIES})\n    message(STATUS \"Using OpenSSL: ${OPENSSL_LIBRARIES}\")\nendif()\nif(OPENSSL_FOUND)\n    message(STATUS \"OpenSSL TLS support will be compiled. Found version ${OPENSSL_VERSION}\")\n    include_directories(${OPENSSL_INCLUDE_DIR})\n    list(APPEND SDR_LIBRARIES ${OPENSSL_LIBRARIES})\n    ADD_DEFINITIONS(-DOPENSSL)\n    ADD_DEFINITIONS(-DMG_ENABLE_SSL)\nelseif(ENABLE_OPENSSL STREQUAL \"AUTO\")\n    message(STATUS \"OpenSSL development files not found, TLS won't be possible.\")\nelse()\n    message(FATAL_ERROR \"OpenSSL development files not found.\")\nendif()\n\nelse()\n    message(STATUS \"OpenSSL TLS disabled.\")\nendif()\n\n########################################################################\n# Find LibRTLSDR build dependencies\n########################################################################\nset(ENABLE_RTLSDR ON CACHE STRING \"Enable RTL-SDR (lbrtlsdr) driver support\")\nset_property(CACHE ENABLE_RTLSDR PROPERTY STRINGS AUTO ON OFF)\nif(ENABLE_RTLSDR) # AUTO / ON\n\nfind_package(LibRTLSDR)\nfind_package(LibUSB)\nif(LibRTLSDR_FOUND)\n    message(STATUS \"RTL-SDR device input will be compiled. Found version ${LibRTLSDR_VERSION}\")\n    include_directories(${LibRTLSDR_INCLUDE_DIRS})\n    list(APPEND SDR_LIBRARIES ${LibRTLSDR_LIBRARIES})\n    ADD_DEFINITIONS(-DRTLSDR)\n\nif(LibUSB_FOUND)\n    message(STATUS \"libusb-1.0 error messages are available. Found version ${LibUSB_VERSION}\")\n    include_directories(${LibUSB_INCLUDE_DIRS})\n    list(APPEND SDR_LIBRARIES ${LibUSB_LIBRARIES})\n    ADD_DEFINITIONS(-DLIBUSB1)\nelse()\n    message(STATUS \"libusb-1.0 error messages are not available.\")\nendif()\n\nelseif(ENABLE_RTLSDR STREQUAL \"AUTO\")\n    message(STATUS \"RTL-SDR development files not found, RTL-SDR device input won't be possible.\")\nelse()\n    message(FATAL_ERROR \"RTL-SDR development files not found.\")\nendif()\n\nelse()\n    message(STATUS \"RTL-SDR device input disabled.\")\nendif()\n\n########################################################################\n# Find SoapySDR build dependencies\n########################################################################\nset(ENABLE_SOAPYSDR AUTO CACHE STRING \"Enable SoapySDR driver support\")\nset_property(CACHE ENABLE_SOAPYSDR PROPERTY STRINGS AUTO ON OFF)\nif(ENABLE_SOAPYSDR) # AUTO / ON\n\nfind_package(SoapySDR \"0.6\" NO_MODULE)\nif(SoapySDR_FOUND)\n    message(STATUS \"SoapySDR device input will be compiled. Found version ${SoapySDR_VERSION}\")\n    include_directories(${SoapySDR_INCLUDE_DIRS})\n    list(APPEND SDR_LIBRARIES ${SoapySDR_LIBRARIES})\n    ADD_DEFINITIONS(-DSOAPYSDR)\nelseif(ENABLE_SOAPYSDR STREQUAL \"AUTO\")\n    message(STATUS \"SoapySDR development files not found, SoapySDR device input won't be possible.\")\nelse()\n    message(FATAL_ERROR \"SoapySDR development files not found.\")\nendif()\n\nelse()\n    message(STATUS \"SoapySDR device input disabled.\")\nendif()\n\n########################################################################\n# Setup optional Profiling with GPerfTools\n########################################################################\n# cmake -DCMAKE_BUILD_TYPE=Profile ..\n# CPUPROFILE=prof.out ./src/rtl_433 ...\n# pprof -text ./src/rtl_433 prof.out\nif(\"${CMAKE_BUILD_TYPE}\" STREQUAL \"Profile\")\n    message(STATUS \"Build type set to Profile. Linking GPerfTools.\")\n    find_package(Gperftools REQUIRED)\n    include_directories(${GPERFTOOLS_INCLUDE_DIR})\n    list(APPEND SDR_LIBRARIES ${GPERFTOOLS_LIBRARIES} -Wl,-no_pie)\n    ADD_DEFINITIONS(-g)\n    ADD_DEFINITIONS(-fno-builtin-malloc)\n    ADD_DEFINITIONS(-fno-builtin-calloc)\n    ADD_DEFINITIONS(-fno-builtin-realloc)\n    ADD_DEFINITIONS(-fno-builtin-free)\nendif()\n\n########################################################################\n# Setup the include and linker paths\n########################################################################\nif(MINGW OR MSVC)\nlist(APPEND NET_LIBRARIES ws2_32 mswsock netapi32)\nendif()\n\ninclude_directories(\n    BEFORE\n    ${PROJECT_SOURCE_DIR}/include\n)\n\n########################################################################\n# Create uninstall target\n########################################################################\nconfigure_file(\n    ${PROJECT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in\n    ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake\n@ONLY)\n\nadd_custom_target(uninstall\n    ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake\n)\n\n########################################################################\n# Build documentation with Doxygen\n########################################################################\noption(BUILD_DOCUMENTATION \"Create and install the HTML based API\ndocumentation (requires Doxygen)\" OFF)\n\nfind_package(Doxygen)\nif(BUILD_DOCUMENTATION)\n    if(NOT DOXYGEN_FOUND)\n        message(FATAL_ERROR \"Doxygen is needed to build the documentation.\")\n    endif()\n\n    set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)\n    set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)\n    if(DOXYGEN_DOT_FOUND)\n        set(HAVE_DOT \"YES\")\n    else()\n        set(HAVE_DOT \"NO\")\n    endif()\n\n    configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)\n    message(STATUS \"Doxygen build started\")\n\n    # note the option ALL which allows to build the docs together with the application\n    add_custom_target(doc_doxygen ALL\n        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}\n        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}\n        COMMENT \"Generating API documentation with Doxygen\"\n        VERBATIM)\n    #    install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/html DESTINATION share/doc)\nendif()\n\n########################################################################\n# Build tests with analyzer\n########################################################################\noption(BUILD_TESTING_ANALYZER \"Build the testing tree with static\nanalyzer (requires Clang)\" OFF)\n\n########################################################################\n# Build tests\n########################################################################\ninclude(CTest) # note: this adds a BUILD_TESTING which defaults to ON\n\n########################################################################\n# Add subdirectories\n########################################################################\nadd_subdirectory(include)\nadd_subdirectory(src)\nif(BUILD_TESTING)\n    add_subdirectory(tests)\nendif(BUILD_TESTING)\nadd_subdirectory(conf)\n\n# use space-separation format for the pc file\nSTRING(REPLACE \";\" \" \" RTL433_PC_CFLAGS \"${RTL433_PC_CFLAGS}\")\nSTRING(REPLACE \";\" \" \" RTL433_PC_LIBS \"${RTL433_PC_LIBS}\")\n\n# unset these vars to avoid hard-coded paths to cross environment\nIF(CMAKE_CROSSCOMPILING)\n    UNSET(RTL433_PC_CFLAGS)\n    UNSET(RTL433_PC_LIBS)\nENDIF(CMAKE_CROSSCOMPILING)\n\nset(prefix ${CMAKE_INSTALL_PREFIX})\nset(exec_prefix \\${prefix})\nset(libdir \\${exec_prefix}/lib)\nset(includedir \\${prefix}/include)\n\nINSTALL(\n    FILES\n    DESTINATION lib/pkgconfig\n)\n\ninstall(DIRECTORY man\n    DESTINATION share\n    PATTERN \".md\" EXCLUDE)\n"
  },
  {
    "path": "COPYING",
    "content": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n\t\t\t    NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "Doxyfile.in",
    "content": "# Doxyfile 1.8.16\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = rtl433\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         = @VERSION@\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          = \"RTL-433 utility\"\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = @CMAKE_CURRENT_BINARY_DIR@/doc\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 8\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines (in the resulting output). You can put ^^ in the value part of an\n# alias to insert a newline as if a physical newline was in the original file.\n# When you need a literal { or } or , in the value part of an alias you have to\n# escape them by means of a backslash (\\), this can lead to conflicts with the\n# commands \\{ and \\} for these it is advised to use the version @{ and @} or use\n# a double escape (\\\\{ and \\\\})\n\nALIASES                =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = YES\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice,\n# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat\n# .inc files as Fortran files (default is PHP), and .f files as C (default is\n# Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 5.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 0\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# (including Cygwin) ands Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation. If\n# EXTRACT_ALL is set to YES then this flag will automatically be disabled.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered.\n# The default value is: NO.\n\nWARN_AS_ERROR          = YES\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = @CMAKE_CURRENT_SOURCE_DIR@/include \\\n                         @CMAKE_CURRENT_SOURCE_DIR@/src\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: https://www.gnu.org/software/libiconv/) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,\n# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          = *.h *.c\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                =\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       = mongoose.*\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = YES\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = YES\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = NO\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via Javascript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have Javascript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: https://developer.apple.com/xcode/), introduced with OSX\n# 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment.\n# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://www.mathjax.org/mathjax\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = NO\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: https://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = YES\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = NO\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = NO\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = @HAVE_DOT@\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               = @DOXYGEN_DOT_PATH@\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 300\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "README.md",
    "content": "# rtl_433\n\nrtl_433 (despite the name) is a generic data receiver, mainly for the 433.92 MHz, 868 MHz (SRD), 315 MHz, 345 MHz, and 915 MHz ISM bands.\n\nThe official source code is in the https://github.com/merbanan/rtl_433/ repository.\nFor more documentation and related projects see the https://triq.org/ site.\n\nIt works with [RTL-SDR](https://github.com/osmocom/rtl-sdr/) and/or [SoapySDR](https://github.com/pothosware/SoapySDR/).\nActively tested and supported are Realtek RTL2832 based DVB dongles (using RTL-SDR) and LimeSDR ([LimeSDR USB](https://www.crowdsupply.com/lime-micro/limesdr) and [LimeSDR mini](https://www.crowdsupply.com/lime-micro/limesdr-mini) engineering samples kindly provided by [MyriadRf](https://myriadrf.org/)), PlutoSDR, HackRF One (using SoapySDR drivers), as well as SoapyRemote.\n\n![rtl_433 screenshot](./docs/screenshot.png)\n\n## Building / Installation\n\nrtl_433 is written in portable C (C99 standard) and known to compile on Linux (also embedded), MacOS, and Windows systems.\nOlder compilers and toolchains are supported as a key-goal.\nLow resource consumption and very few dependencies allow rtl_433 to run on embedded hardware like (repurposed) routers.\nSystems with 32-bit i686 and 64-bit x86-64 as well as (embedded) ARM, like the Raspberry Pi and PlutoSDR are well supported.\n\nSee [BUILDING.md](docs/BUILDING.md)\n\nOn Debian (sid) or Ubuntu (19.10+), `apt-get install rtl-433` for other distros check https://repology.org/project/rtl-433/versions\n\nOn FreeBSD, `pkg install rtl-433`.\n\nOn MacOS, `brew install rtl_433`.\n\nDocker images with rtl_433 are available [on the github page of hertzg](https://github.com/hertzg/rtl_433_docker).\n\n## How to add support for unsupported sensors\n\nSee [CONTRIBUTING.md](./docs/CONTRIBUTING.md).\n\n## Running\n\n    rtl_433 -h\n\n```\n\n  A \"rtl_433.conf\" file is searched in \"./\", XDG_CONFIG_HOME e.g. \"$HOME/.config/rtl_433/\",\n  SYSCONFDIR e.g. \"/usr/local/etc/rtl_433/\", then command line args will be parsed in order.\n\t\t= General options =\n  [-V] Output the version string and exit\n  [-v] Increase verbosity (can be used multiple times).\n       -v : verbose notice, -vv : verbose info, -vvv : debug, -vvvv : trace.\n  [-c <path>] Read config options from a file\n\t\t= Tuner options =\n  [-d <RTL-SDR USB device index> | :<RTL-SDR USB device serial> | <SoapySDR device query> | rtl_tcp | help]\n  [-g <gain> | help] (default: auto)\n  [-t <settings>] apply a list of keyword=value settings to the SDR device\n       e.g. for SoapySDR -t \"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\"\n       for RTL-SDR use \"direct_samp[=1]\", \"offset_tune[=1]\", \"digital_agc[=1]\", \"biastee[=1]\"\n  [-f <frequency>] Receive frequency(s) (default: 433920000 Hz)\n  [-H <seconds>] Hop interval for polling of multiple frequencies (default: 600 seconds)\n  [-p <ppm_error>] Correct rtl-sdr tuner frequency offset error (default: 0)\n  [-s <sample rate>] Set sample rate (default: 250000 Hz)\n  [-D quit | restart | pause | manual] Input device run mode options (default: quit).\n\t\t= Demodulator options =\n  [-R <device> | help] Enable only the specified device decoding protocol (can be used multiple times)\n       Specify a negative number to disable a device decoding protocol (can be used multiple times)\n  [-X <spec> | help] Add a general purpose decoder (prepend -R 0 to disable all decoders)\n  [-Y auto | classic | minmax] FSK pulse detector mode.\n  [-Y level=<dB level>] Manual detection level used to determine pulses (-1.0 to -30.0) (0=auto).\n  [-Y minlevel=<dB level>] Manual minimum detection level used to determine pulses (-1.0 to -99.0).\n  [-Y minsnr=<dB level>] Minimum SNR to determine pulses (1.0 to 99.0).\n  [-Y autolevel] Set minlevel automatically based on average estimated noise.\n  [-Y squelch] Skip frames below estimated noise level to reduce cpu load.\n  [-Y ampest | magest] Choose amplitude or magnitude level estimator.\n\t\t= Analyze/Debug options =\n  [-A] Pulse Analyzer. Enable pulse analysis and decode attempt.\n       Disable all decoders with -R 0 if you want analyzer output only.\n  [-y <code>] Verify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n\t\t= File I/O options =\n  [-S none | all | unknown | known] Signal auto save. Creates one file per signal.\n       Note: Saves raw I/Q samples (uint8 pcm, 2 channel). Preferred mode for generating test files.\n  [-r <filename> | help] Read data from input file instead of a receiver\n  [-w <filename> | help] Save data stream to output file (a '-' dumps samples to stdout)\n  [-W <filename> | help] Save data stream to output file, overwrite existing file\n\t\t= Data output options =\n  [-F log | kv | json | csv | mqtt | influx | syslog | trigger | rtl_tcp | http | null | help] Produce decoded output in given format.\n       Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\n       Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\n  [-M time[:<options>] | protocol | level | noise[:<secs>] | stats | bits | help] Add various meta data to each output.\n  [-K FILE | PATH | <tag> | <key>=<tag>] Add an expanded token or fixed tag to every output line.\n  [-C native | si | customary] Convert units in decoded output.\n  [-n <value>] Specify number of samples to take (each sample is an I/Q pair)\n  [-T <seconds>] Specify number of seconds to run, also 12:34 or 1h23m45s\n  [-E hop | quit] Hop/Quit after outputting successful event(s)\n  [-h] Output this usage help and exit\n       Use -d, -g, -R, -X, -F, -M, -r, -w, or -W without argument for more help\n\n\n\n\t\t= Supported device protocols =\n    [01]  Silvercrest Remote Control\n    [02]  Rubicson, TFA 30.3197 or InFactory PT-310 Temperature Sensor\n    [03]  Prologue, FreeTec NC-7104, NC-7159-675 temperature sensor\n    [04]  Waveman Switch Transmitter\n    [06]* ELV EM 1000\n    [07]* ELV WS 2000\n    [08]  LaCrosse TX Temperature / Humidity Sensor\n    [10]  Acurite 896 Rain Gauge\n    [11]  Acurite 609TXC Temperature and Humidity Sensor\n    [12]  Oregon Scientific Weather Sensor\n    [13]* Mebus 433\n    [14]* Intertechno 433\n    [15]  KlikAanKlikUit Wireless Switch\n    [16]  AlectoV1 Weather Sensor (Alecto WS3500 WS4500 Ventus W155/W044 Oregon)\n    [17]  Cardin S466-TX2\n    [18]  Fine Offset Electronics, WH2, WH5, Telldus Temperature/Humidity/Rain Sensor\n    [19]  Nexus, FreeTec NC-7345, NX-3980, Solight TE82S, TFA 30.3209 temperature/humidity sensor\n    [20]  Ambient Weather F007TH, TFA 30.3208.02, SwitchDocLabs F016TH temperature sensor\n    [21]  Calibeur RF-104 Sensor\n    [22]  X10 RF\n    [23]  DSC Security Contact\n    [24]* Brennenstuhl RCS 2044\n    [25]  Globaltronics GT-WT-02 Sensor\n    [26]  Danfoss CFR Thermostat\n    [29]  Chuango Security Technology\n    [30]  Generic Remote SC226x EV1527\n    [31]  TFA-Twin-Plus-30.3049, Conrad KW9010, Ea2 BL999\n    [32]  Fine Offset Electronics WH1080/WH3080 Weather Station\n    [33]  WT450, WT260H, WT405H\n    [34]  LaCrosse WS-2310 / WS-3600 Weather Station\n    [35]  Esperanza EWS\n    [36]  Efergy e2 classic\n    [37]* Inovalley kw9015b, TFA Dostmann 30.3161 (Rain and temperature sensor)\n    [38]  Generic temperature sensor 1\n    [39]  WG-PB12V1 Temperature Sensor\n    [40]  Acurite 592TXR temp/humidity, 592TX temp, 5n1, 3n1, Atlas weather station, 515 fridge/freezer, 6045 lightning, 899 rain, 1190/1192 leak\n    [41]  Acurite 986 Refrigerator / Freezer Thermometer\n    [42]  HIDEKI TS04 Temperature, Humidity, Wind and Rain Sensor\n    [43]  Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor\n    [44]  CurrentCost Current Sensor\n    [45]  emonTx OpenEnergyMonitor\n    [46]  HT680 Remote control\n    [47]  Conrad S3318P, FreeTec NC-5849-913 temperature humidity sensor, ORIA WA50 ST389 temperature sensor\n    [48]* Akhan 100F14 remote keyless entry\n    [49]  Quhwa\n    [50]  OSv1 Temperature Sensor\n    [51]  Proove / Nexa / KlikAanKlikUit Wireless Switch\n    [52]  Bresser Thermo-/Hygro-Sensor 3CH\n    [53]  Springfield Temperature and Soil Moisture\n    [54]  Oregon Scientific SL109H Remote Thermal Hygro Sensor\n    [55]  Acurite 606TX / Technoline TX960 Temperature Sensor\n    [56]  TFA pool temperature sensor\n    [57]  Kedsum Temperature & Humidity Sensor, Pearl NC-7415\n    [58]  Blyss DC5-UK-WH\n    [59]  Steelmate TPMS\n    [60]  Schrader TPMS\n    [61]* LightwaveRF\n    [62]* Elro DB286A Doorbell\n    [63]  Efergy Optical\n    [64]* Honda Car Key\n    [67]  Radiohead ASK\n    [68]  Kerui PIR / Contact Sensor\n    [69]  Fine Offset WH1050 Weather Station\n    [70]  Honeywell Door/Window Sensor, 2Gig DW10/DW11, RE208 repeater\n    [71]  Maverick ET-732/733 BBQ Sensor\n    [72]* RF-tech\n    [73]  LaCrosse TX141-Bv2, TX141TH-Bv2, TX141-Bv3, TX141W, TX145wsdth, (TFA, ORIA) sensor\n    [74]  Acurite 00275rm,00276rm Temp/Humidity with optional probe\n    [75]  LaCrosse TX35DTH-IT, TFA Dostmann 30.3155 Temperature/Humidity sensor\n    [76]  LaCrosse TX29IT, TFA Dostmann 30.3159.IT Temperature sensor\n    [77]  Vaillant calorMatic VRT340f Central Heating Control\n    [78]  Fine Offset Electronics, WH25, WH32, WH32B, WN32B, WH24, WH65B, HP1000, Misol WS2320 Temperature/Humidity/Pressure Sensor\n    [79]  Fine Offset Electronics, WH0530 Temperature/Rain Sensor\n    [80]  IBIS beacon\n    [81]  Oil Ultrasonic STANDARD FSK\n    [82]  Citroen TPMS\n    [83]  Oil Ultrasonic STANDARD ASK\n    [84]  Thermopro TP11 Thermometer\n    [85]  Solight TE44/TE66, EMOS E0107T, NX-6876-917\n    [86]* Wireless Smoke and Heat Detector GS 558\n    [87]  Generic wireless motion sensor\n    [88]  Toyota TPMS\n    [89]  Ford TPMS\n    [90]  Renault TPMS\n    [91]  inFactory, nor-tec, FreeTec NC-3982-913 temperature humidity sensor\n    [92]  FT-004-B Temperature Sensor\n    [93]  Ford Car Key\n    [94]  Philips outdoor temperature sensor (type AJ3650)\n    [95]  Schrader TPMS EG53MA4, Saab, Opel, Vauxhall, Chevrolet\n    [96]  Nexa\n    [97]  ThermoPro TP08/TP12/TP20 thermometer\n    [98]  GE Color Effects\n    [99]  X10 Security\n    [100]  Interlogix GE UTC Security Devices\n    [101]* Dish remote 6.3\n    [102]  SimpliSafe Home Security System (May require disabling automatic gain for KeyPad decodes)\n    [103]  Sensible Living Mini-Plant Moisture Sensor\n    [104]  Wireless M-Bus, Mode C&T, 100kbps (-f 868.95M -s 1200k)\n    [105]  Wireless M-Bus, Mode S, 32.768kbps (-f 868.3M -s 1000k)\n    [106]* Wireless M-Bus, Mode R, 4.8kbps (-f 868.33M)\n    [107]* Wireless M-Bus, Mode F, 2.4kbps\n    [108]  Hyundai WS SENZOR Remote Temperature Sensor\n    [109]  WT0124 Pool Thermometer\n    [110]  PMV-107J (Toyota) TPMS\n    [111]  Emos TTX201 Temperature Sensor\n    [112]  Ambient Weather TX-8300 Temperature/Humidity Sensor\n    [113]  Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge, WS68 weather station\n    [114]  Maverick ET73\n    [115]  Honeywell ActivLink, Wireless Doorbell\n    [116]  Honeywell ActivLink, Wireless Doorbell (FSK)\n    [117]* ESA1000 / ESA2000 Energy Monitor\n    [118]* Biltema rain gauge\n    [119]  Bresser Weather Center 5-in-1\n    [120]  Digitech XC-0324 / AmbientWeather FT005TH temp/hum sensor\n    [121]  Opus/Imagintronix XT300 Soil Moisture\n    [122]  FS20 / FHT\n    [123]* Jansite TPMS Model TY02S\n    [124]  LaCrosse/ELV/Conrad WS7000/WS2500 weather sensors\n    [125]  TS-FT002 Wireless Ultrasonic Tank Liquid Level Meter With Temperature Sensor\n    [126]  Companion WTR001 Temperature Sensor\n    [127]  Ecowitt Wireless Outdoor Thermometer WH53/WH0280/WH0281A\n    [128]  DirecTV RC66RX Remote Control\n    [129]* Eurochron temperature and humidity sensor\n    [130]  IKEA Sparsnas Energy Meter Monitor\n    [131]  Microchip HCS200/HCS300 KeeLoq Hopping Encoder based remotes\n    [132]  TFA Dostmann 30.3196 T/H outdoor sensor\n    [133]  Rubicson 48659 Thermometer\n    [134]  AOK Weather Station rebrand Holman Industries iWeather WS5029, Conrad AOK-5056, Optex 990018\n    [135]  Philips outdoor temperature sensor (type AJ7010)\n    [136]  ESIC EMT7110 power meter\n    [137]  Globaltronics QUIGG GT-TMBBQ-05\n    [138]  Globaltronics GT-WT-03 Sensor\n    [139]  Norgo NGE101\n    [140]  Elantra2012 TPMS\n    [141]  Auriol HG02832, HG05124A-DCF, Rubicson 48957 temperature/humidity sensor\n    [142]  Fine Offset Electronics/Ecowitt WH51, WN31, SwitchDoc Labs SM23 Soil Moisture Sensor\n    [143]  Holman Industries iWeather WS5029 weather station (older PWM)\n    [144]  TBH weather sensor\n    [145]  WS2032 weather station\n    [146]  Auriol AFW2A1 temperature/humidity sensor\n    [147]  TFA Drop Rain Gauge 30.3233.01\n    [148]  DSC Security Contact (WS4945)\n    [149]  ERT Standard Consumption Message (SCM)\n    [150]* Klimalogg\n    [151]  Visonic powercode\n    [152]  Eurochron EFTH-800 temperature and humidity sensor\n    [153]  Cotech 36-7959, SwitchDocLabs FT020T wireless weather station with USB\n    [154]  Standard Consumption Message Plus (SCMplus)\n    [155]  Fine Offset Electronics WH1080/WH3080 Weather Station (FSK)\n    [156]  Abarth 124 Spider TPMS\n    [157]  Missil ML0757 weather station\n    [158]  Sharp SPC775 weather station\n    [159]  Insteon\n    [160]  ERT Interval Data Message (IDM)\n    [161]  ERT Interval Data Message (IDM) for Net Meters\n    [162]* ThermoPro-TX2 temperature sensor\n    [163]  Acurite 590TX Temperature with optional Humidity\n    [164]  Security+ 2.0 (Keyfob)\n    [165]  TFA Dostmann 30.3221.02 T/H Outdoor Sensor (also 30.3249.02)\n    [166]  LaCrosse Technology View LTV-WSDTH01 Breeze Pro Wind Sensor\n    [167]  Somfy RTS\n    [168]  Schrader TPMS SMD3MA4 (Subaru) 3039 (Infiniti, Nissan, Renault)\n    [169]* Nice Flor-s remote control for gates\n    [170]  LaCrosse Technology View LTV-WR1 Multi Sensor\n    [171]  LaCrosse Technology View LTV-TH Thermo/Hygro Sensor\n    [172]  Bresser Weather Center 6-in-1, 7-in-1 indoor, soil, new 5-in-1, 3-in-1 wind gauge, Froggit WH6000, Ventus C8488A\n    [173]  Bresser Weather Center 7-in-1, Air Quality PM2.5/PM10 7009970, CO2 7009977, HCHO/VOC 7009978 sensors\n    [174]  EcoDHOME Smart Socket and MCEE Solar monitor\n    [175]  LaCrosse Technology View LTV-R1, LTV-R3 Rainfall Gauge, LTV-W1/W2 Wind Sensor\n    [176]  BlueLine Innovations Power Cost Monitor\n    [177]  Burnhard BBQ thermometer\n    [178]  Security+ (Keyfob)\n    [179]  Cavius smoke, heat and water detector\n    [180]  Jansite TPMS Model Solar\n    [181]  Amazon Basics Meat Thermometer\n    [182]  TFA Marbella Pool Thermometer\n    [183]  Auriol AHFL temperature/humidity sensor\n    [184]  Auriol AFT 77 B2 temperature sensor\n    [185]  Honeywell CM921 Wireless Programmable Room Thermostat\n    [186]  Hyundai TPMS (VDO)\n    [187]  RojaFlex shutter and remote devices\n    [188]  Marlec Solar iBoost+ sensors\n    [189]  Somfy io-homecontrol\n    [190]  Ambient Weather WH31L (FineOffset WH57) Lightning-Strike sensor\n    [191]  Markisol, E-Motion, BOFU, Rollerhouse, BF-30x, BF-415 curtain remote\n    [192]  Govee Water Leak Detector H5054, Door Contact Sensor B5023\n    [193]  Clipsal CMR113 Cent-a-meter power meter\n    [194]  Inkbird ITH-20R temperature humidity sensor\n    [195]  RainPoint soil temperature and moisture sensor\n    [196]  Atech-WS308 temperature sensor\n    [197]  Acurite Grill/Meat Thermometer 01185M\n    [198]* EnOcean ERP1\n    [199]  Linear Megacode Garage/Gate Remotes\n    [200]* Auriol 4-LD5661/4-LD5972/4-LD6313 temperature/rain sensors\n    [201]  Unbranded SolarTPMS for trucks\n    [202]  Funkbus / Instafunk (Berker, Gira, Jung)\n    [203]  Porsche Boxster/Cayman TPMS\n    [204]  Jasco/GE Choice Alert Security Devices\n    [205]  Telldus weather station FT0385R sensors\n    [206]  LaCrosse TX34-IT rain gauge\n    [207]  SmartFire Proflame 2 remote control\n    [208]  AVE TPMS\n    [209]  SimpliSafe Gen 3 Home Security System\n    [210]  Yale HSA (Home Security Alarm), YES-Alarmkit\n    [211]  Regency Ceiling Fan Remote (-f 303.75M to 303.96M)\n    [212]  Renault 0435R TPMS\n    [213]  Fine Offset Electronics WS80 weather station\n    [214]  EMOS E6016 weatherstation with DCF77\n    [215]  Emax W6, rebrand Altronics x7063/4/x7064A, Optex 990040/50/51, Orium 13093/13123, Infactory FWS-1200, Newentor Q9, Otio 810025, Protmex PT3390A, Jula Marquant 014331/32, TechniSat IMETEO X6 76-4924-00, Weather Station or temperature/humidity sensor\n    [216]* ANT and ANT+ devices\n    [217]  EMOS E6016 rain gauge\n    [218]  Microchip HCS200/HCS300 KeeLoq Hopping Encoder based remotes (FSK)\n    [219]  Fine Offset Electronics WH45 air quality sensor\n    [220]  Maverick XR-30 BBQ Sensor\n    [221]  Fine Offset Electronics WN34S/L/D and Froggit DP150/D35 temperature sensor\n    [222]  Rubicson Pool Thermometer 48942\n    [223]  Badger ORION water meter, 100kbps (-f 916.45M -s 1200k)\n    [224]  GEO minim+ energy monitor\n    [225]  TyreGuard 400 TPMS\n    [226]  Kia TPMS (-s 1000k)\n    [227]  SRSmith Pool Light Remote Control SRS-2C-TX (-f 915M)\n    [228]  Neptune R900 flow meters\n    [229]  WEC-2103 temperature/humidity sensor\n    [230]  Vauno EN8822C\n    [231]  Govee Water Leak Detector H5054\n    [232]  TFA Dostmann 14.1504.V2 Radio-controlled grill and meat thermometer\n    [233]* CED7000 Shot Timer\n    [234]  Watchman Sonic Advanced / Plus, Tekelek\n    [235]  Oil Ultrasonic SMART FSK\n    [236]  Gasmate BA1008 meat thermometer\n    [237]  Flowis flow meters\n    [238]  Wireless M-Bus, Mode T, 32.768kbps (-f 868.3M -s 1000k)\n    [239]  Revolt NC-5642 Energy Meter\n    [240]  LaCrosse TX31U-IT, The Weather Channel WS-1910TWC-IT\n    [241]  EezTire E618, Carchet TPMS, TST-507 TPMS\n    [242]* Baldr / RainPoint rain gauge.\n    [243]  Celsia CZC1 Thermostat\n    [244]  Fine Offset Electronics WS90 weather station\n    [245]* ThermoPro TX-2C Thermometer and Humidity sensor\n    [246]  TFA 30.3151 Weather Station\n    [247]  Bresser water leakage\n    [248]* Nissan TPMS\n    [249]  Bresser lightning\n    [250]  Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge, TFA Dostmann 30.3252.01/47.3006.01 Rain Gauge and Thermometer, ADE WS1907\n    [251]  Fine Offset / Ecowitt WH55 water leak sensor\n    [252]  BMW Gen4-Gen5 TPMS and Audi TPMS Pressure Alert, multi-brand HUF/Beru, Continental, Schrader/Sensata, Audi\n    [253]  Watts WFHT-RF Thermostat\n    [254]  Thermor DG950 weather station\n    [255]  Mueller Hot Rod water meter\n    [256]  ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill\n    [257]  BMW Gen2 and Gen3 TPMS\n    [258]  Chamberlain CWPIRC PIR Sensor\n    [259]  ThermoPro Meat Thermometers, TP829B 4 probes with temp only\n    [260]* Arad/Master Meter Dialog3G water utility meter\n    [261]  Geevon TX16-3 outdoor sensor\n    [262]  Fine Offset Electronics WH46 air quality sensor\n    [263]  Vevor Wireless Weather Station 7-in-1\n    [264]  Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E\n    [265]  Rosstech Digital Control Unit DCU-706/Sundance/Jacuzzi\n    [266]  Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95P\n    [267]  ThermoPro Meat Thermometers, TP828B 2 probes with Temp, BBQ Target LO and HI\n    [268]  Bresser Thermo-/Hygro-Sensor Explore Scientific ST1005H\n    [269]  DeltaDore X3D devices\n    [270]* Quinetic\n    [271]  Landis & Gyr Gridstream Power Meters 9.6k\n    [272]  Landis & Gyr Gridstream Power Meters 19.2k\n    [273]  Landis & Gyr Gridstream Power Meters 38.4k\n    [274]  Revolt ZX-7717 power meter\n    [275]  GM-Aftermarket TPMS\n    [276]  RainPoint HCS012ARF Rain Gauge sensor\n    [277]  Apator Metra E-RM 30 water meter\n    [278]  ThermoPro TX-7B Outdoor Thermometer Hygrometer\n    [279]  Nexus, CRX, Prego sauna temperature sensor\n    [280]  Homelead HG9901 (Geevon, Dr.Meter, Royal Gardineer) soil moisture/temp/light level sensor\n    [281]  Maverick XR-50 BBQ Sensor\n    [282]  Orion Endpoint from Badger Meter, GIF2014W-OSE, water meter, hopping from 904.4 Mhz to 924.6Mhz (-s 1600k)\n    [283]  Fine Offset Electronics WH43 air quality sensor\n    [284]  Baldr E0666TH Thermo-Hygrometer\n    [285]  bm5-v2 12V Battery Monitor\n    [286]  Universal (Reverseable) 24V Fan Controller\n    [287]  Fine Offset Electronics WS85 weather station\n    [288]  Oria WA150KM freezer and fridge thermometer\n    [289]  Voltcraft EnergyCount 3000 (ec3k)\n    [290]  Orion Endpoint from Badger Meter, GIF2020OCECNA, water meter, hopping from 904.4 Mhz to 924.6Mhz (-s 1600k)\n    [291]  Geevon TX19-1 outdoor sensor\n    [292]  WallarGe CLTX001 Outdoor Temperature Sensor\n    [293]  Sainlogic SA8, Gevanti SA8 Weather Station\n    [294]  ThermoPro TP862b TempSpike XR Wireless Dual-Probe Meat Thermometer\n    [295]  Airpuxem TPMS TYH11_EU6_ZQ\n    [296]  Apator Metra E-ITN 30 heat cost allocator\n    [297]  ThermoPro TP211B Thermometer\n    [298]  TRW TPMS OOK OEM and Clone models\n    [299]  TRW TPMS FSK OEM and Clone models\n\n* Disabled by default, use -R n or a conf file to enable\n\n\n\t\t= Input device selection =\n\tRTL-SDR device driver is available.\n  [-d <RTL-SDR USB device index>] (default: 0)\n  [-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]\n\tTo set gain for RTL-SDR use -g <gain> to set an overall gain in dB.\n\tSoapySDR device driver is available.\n  [-d \"\"] Open default SoapySDR device\n  [-d driver=rtlsdr] Open e.g. specific SoapySDR device\n\tTo set gain for SoapySDR use -g ELEM=val,ELEM=val,... e.g. -g LNA=20,TIA=8,PGA=2 (for LimeSDR).\n  [-d rtl_tcp[:[//]host[:port]] (default: localhost:1234)\n\tSpecify host/port to connect to with e.g. -d rtl_tcp:127.0.0.1:1234\n\n\n\t\t= Gain option =\n  [-g <gain>] (default: auto)\n\tFor RTL-SDR: gain in dB (\"0\" is auto).\n\tFor SoapySDR: gain in dB for automatic distribution (\"\" is auto), or string of gain elements.\n\tE.g. \"LNA=20,TIA=8,PGA=2\" for LimeSDR.\n\n\n\t\t= Flex decoder spec =\nUse -X <spec> to add a flexible general purpose decoder.\n\n<spec> is \"key=value[,key=value...]\"\nCommon keys are:\n\tname=<name> (or: n=<name>)\n\tmodulation=<modulation> (or: m=<modulation>)\n\tshort=<short> (or: s=<short>)\n\tlong=<long> (or: l=<long>)\n\tsync=<sync> (or: y=<sync>)\n\treset=<reset> (or: r=<reset>)\n\tgap=<gap> (or: g=<gap>)\n\ttolerance=<tolerance> (or: t=<tolerance>)\n\tpriority=<n> : run decoder only as fallback\nwhere:\n<name> can be any descriptive name tag you need in the output\n<modulation> is one of:\n\tOOK_MC_ZEROBIT :  Manchester Code with fixed leading zero bit\n\tOOK_PCM :         Non Return to Zero coding (Pulse Code)\n\tOOK_RZ :          Return to Zero coding (Pulse Code)\n\tOOK_PPM :         Pulse Position Modulation\n\tOOK_PWM :         Pulse Width Modulation\n\tOOK_DMC :         Differential Manchester Code\n\tOOK_PIWM_RAW :    Raw Pulse Interval and Width Modulation\n\tOOK_PIWM_DC :     Differential Pulse Interval and Width Modulation\n\tOOK_MC_OSV1 :     Manchester Code for OSv1 devices\n\tFSK_PCM :         FSK Pulse Code Modulation\n\tFSK_PWM :         FSK Pulse Width Modulation\n\tFSK_MC_ZEROBIT :  Manchester Code with fixed leading zero bit\n<short>, <long>, <sync> are nominal modulation timings in us,\n<reset>, <gap>, <tolerance> are maximum modulation timings in us:\nPCM/RZ  short: Nominal width of pulse [us]\n         long: Nominal width of bit period [us]\nPPM     short: Nominal width of '0' gap [us]\n         long: Nominal width of '1' gap [us]\nPWM     short: Nominal width of '1' pulse [us]\n         long: Nominal width of '0' pulse [us]\n         sync: Nominal width of sync pulse [us] (optional)\ncommon    gap: Maximum gap size before new row of bits [us]\n        reset: Maximum gap size before End Of Message [us]\n    tolerance: Maximum pulse deviation [us] (optional).\nAvailable options are:\n\tbits=<n> : only match if at least one row has <n> bits\n\trows=<n> : only match if there are <n> rows\n\trepeats=<n> : only match if some row is repeated <n> times\n\t\tuse opt>=n to match at least <n> and opt<=n to match at most <n>\n\tinvert : invert all bits\n\treflect : reflect each byte (MSB first to MSB last)\n\tdecode_uart : UART 8n1 (10-to-8) decode\n\tdecode_dm : Differential Manchester decode\n\tdecode_mc : Manchester decode\n\tmatch=<bits> : only match if the <bits> are found\n\tpreamble=<bits> : match and align at the <bits> preamble\n\t\t<bits> is a row spec of {<bit count>}<bits as hex number>\n\tunique : suppress duplicate row output\n\n\tcountonly : suppress detailed row output\n\nE.g. -X \"n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repeats>=3\"\n\n\n\n\t\t= Output format option =\n  [-F log|kv|json|csv|mqtt|influx|syslog|trigger|rtl_tcp|http|null] Produce decoded output in given format.\n\tWithout this option the default is LOG and KV output. Use \"-F null\" to remove the default.\n\tAppend output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\n  [-F mqtt[s][:[//]host[:port][,<options>]] (default: localhost:1883)\n\tSpecify MQTT server with e.g. -F mqtt://localhost:1883\n\tDefault user and password are read from MQTT_USERNAME and MQTT_PASSWORD env vars.\n\tAdd MQTT options with e.g. -F \"mqtt://host:1883,opt=arg\"\n\tMQTT options are: user=foo, pass=bar, retain[=0|1], <format>[=topic]\n\tSupported MQTT formats: (default is all)\n\t  availability: posts availability (online/offline)\n\t  events: posts JSON event data, default \"<base>/events\"\n\t  states: posts JSON state data, default \"<base>/states\"\n\t  devices: posts device and sensor info in nested topics,\n\t           default \"<base>/devices[/type][/model][/subtype][/channel][/id]\"\n\tA base topic can be set with base=<topic>, default is \"rtl_433/HOSTNAME\".\n\tAny topic string overrides the base topic and will expand keys like [/model]\n\tE.g. -F \"mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]\"\n\tFor TLS use e.g. -F \"mqtts://host,tls_cert=<path>,tls_key=<path>,tls_ca_cert=<path>\"\n\tWith MQTT each rtl_433 instance needs a distinct driver selection. The MQTT Client-ID is computed from the driver string.\n\tIf you use multiple RTL-SDR, perhaps set a serial and select by that (helps not to get the wrong antenna).\n  [-F influx[:[//]host[:port][/<path and options>]]\n\tSpecify InfluxDB 2.0 server with e.g. -F \"influx://localhost:9999/api/v2/write?org=<org>&bucket=<bucket>,token=<authtoken>\"\n\tSpecify InfluxDB 1.x server with e.g. -F \"influx://localhost:8086/write?db=<db>&p=<password>&u=<user>\"\n\t  Additional parameter -M time:unix:usec:utc for correct timestamps in InfluxDB recommended\n  [-F syslog[:[//]host[:port] (default: localhost:514)\n\tSpecify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\n  [-F trigger:/path/to/file]\n\tAdd an output that writes a \"1\" to the path for each event, use with a e.g. a GPIO\n  [-F rtl_tcp[:[//]bind[:port]] (default: localhost:1234)\n\tAdd a rtl_tcp pass-through server\n  [-F http[:[//]bind[:port]] (default: 0.0.0.0:8433)\n\tAdd a HTTP API server, a UI is at e.g. http://localhost:8433/\n\n\n\t\t= Meta information option =\n  [-M time[:<options>]|protocol|level|noise[:<secs>]|stats|bits] Add various metadata to every output line.\n\tUse \"time\" to add current date and time meta data (preset for live inputs).\n\tUse \"time:rel\" to add sample position meta data (preset for read-file and stdin).\n\tUse \"time:unix\" to show the seconds since unix epoch as time meta data. This is always UTC.\n\tUse \"time:iso\" to show the time with ISO-8601 format (YYYY-MM-DD\"T\"hh:mm:ss).\n\tUse \"time:off\" to remove time meta data.\n\tUse \"time:usec\" to add microseconds to date time meta data.\n\tUse \"time:tz\" to output time with timezone offset.\n\tUse \"time:utc\" to output time in UTC.\n\t\t(this may also be accomplished by invocation with TZ environment variable set).\n\t\t\"usec\" and \"utc\" can be combined with other options, eg. \"time:iso:utc\" or \"time:unix:usec\".\n\tUse \"replay[:N]\" to replay file inputs at (N-times) realtime.\n\tUse \"protocol\" / \"noprotocol\" to output the decoder protocol number meta data.\n\tUse \"level\" to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n\tUse \"noise[:<secs>]\" to report estimated noise level at intervals (default: 10 seconds).\n\tUse \"stats[:[<level>][:<interval>]]\" to report statistics (default: 600 seconds).\n\t  level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\n\tUse \"bits\" to add bit representation to code outputs (for debug).\n\n\n\t\t= Read file option =\n  [-r <filename>] Read data from input file instead of a receiver\n\tParameters are detected from the full path, file name, and extension.\n\n\tA center frequency is detected as (fractional) number suffixed with 'M',\n\t'Hz', 'kHz', 'MHz', or 'GHz'.\n\n\tA sample rate is detected as (fractional) number suffixed with 'k',\n\t'sps', 'ksps', 'Msps', or 'Gsps'.\n\n\tFile content and format are detected as parameters, possible options are:\n\t'cu8', 'cs16', 'cf32' ('IQ' implied), and 'am.s16'.\n\n\tParameters must be separated by non-alphanumeric chars and are case-insensitive.\n\tOverrides can be prefixed, separated by colon (':')\n\n\tE.g. default detection by extension: path/filename.am.s16\n\tforced overrides: am:s16:path/filename.ext\n\n\tReading from pipes also support format options.\n\tE.g reading complex 32-bit float: CU32:-\n\n\n\t\t= Write file option =\n  [-w <filename>] Save data stream to output file (a '-' dumps samples to stdout)\n  [-W <filename>] Save data stream to output file, overwrite existing file\n\tParameters are detected from the full path, file name, and extension.\n\n\tFile content and format are detected as parameters, possible options are:\n\t'cu8', 'cs8', 'cs16', 'cf32' ('IQ' implied),\n\t'am.s16', 'am.f32', 'fm.s16', 'fm.f32',\n\t'i.f32', 'q.f32', 'logic.u8', 'ook', and 'vcd'.\n\n\tParameters must be separated by non-alphanumeric chars and are case-insensitive.\n\tOverrides can be prefixed, separated by colon (':')\n\n\tE.g. default detection by extension: path/filename.am.s16\n\tforced overrides: am:s16:path/filename.ext\n\n```\n\n\nSome examples:\n\n| Command | Description\n|---------|------------\n| `rtl_433` | Default receive mode, use the first device found, listen at 433.92 MHz at 250k sample rate.\n| `rtl_433 -C si` | Default receive mode, also convert units to metric system.\n| `rtl_433 -f 868M -s 1024k` | Listen at 868 MHz and 1024k sample rate.\n| `rtl_433 -M hires -M level` | Report microsecond accurate timestamps and add reception levels (depending on gain).\n| `rtl_433 -R 1 -R 8 -R 43` | Enable only specific decoders for desired devices.\n| `rtl_433 -A` | Enable pulse analyzer. Summarizes the timings of pulses, gaps, and periods. Can be used with `-R 0` to disable decoders.\n| `rtl_433 -S all -T 120` | Save all detected signals (`g###_###M_###k.cu8`). Run for 2 minutes.\n| `rtl_433 -K FILE -r file_name` | Read a saved data file instead of receiving live data. Tag output with filenames.\n| `rtl_433 -F json -M utc \\| mosquitto_pub -t home/rtl_433 -l` | Will pipe the output to network as JSON formatted MQTT messages. A test MQTT client can be found in `examples/mqtt_rtl_433_test_client.py`.\n| `rtl_433 -f 433.53M -f 434.02M -H 15` | Will poll two frequencies with 15 seconds hop interval.\n\n## Security\n\nPlease note: We aim to make `rtl_433` safe to use, but it should not be assumed secure.\nThere is no reason to e.g. run with `sudo`, we do read and write files without any checks.\n\nThe output is literally pulled from thin air, it's not to be trusted.\nIf you feed downstream systems with data make sure edge cases are checked and handled.\nNetwork inputs and outputs are for use in a trusted local network, will contain unfiltered data, and might overload the recipient\n(know that e.g. the MQTT output can be controlled by anyone with a radio sender).\n\n## Google Group\n\nJoin the Google group, rtl_433, for more information about rtl_433:\nhttps://groups.google.com/forum/#!forum/rtl_433\n\n\n## Troubleshooting\n\nIf you see this error:\n\n    Kernel driver is active, or device is claimed by second instance of librtlsdr.\n    In the first case, please either detach or blacklist the kernel module\n    (dvb_usb_rtl28xxu), or enable automatic detaching at compile time.\n\nthen\n\n    sudo rmmod rtl2832_sdr dvb_usb_rtl28xxu rtl2832\n\nor add\n\n    blacklist dvb_usb_rtl28xxu\n\nto /etc/modprobe.d/blacklist.conf\n\n## Releases\n\nVersion numbering scheme used is year.month. We try to keep the API compatible between releases but focus is on maintainablity.\n"
  },
  {
    "path": "cmake/Modules/FindGperftools.cmake",
    "content": "# Tries to find Gperftools.\n#\n# Usage of this module as follows:\n#\n#     find_package(Gperftools)\n#\n# Variables used by this module, they can change the default behaviour and need\n# to be set before calling find_package:\n#\n#  Gperftools_ROOT_DIR  Set this variable to the root installation of\n#                       Gperftools if the module has problems finding\n#                       the proper installation path.\n#\n# Variables defined by this module:\n#\n#  GPERFTOOLS_FOUND              System has Gperftools libs/headers\n#  GPERFTOOLS_LIBRARIES          The Gperftools libraries (tcmalloc & profiler)\n#  GPERFTOOLS_INCLUDE_DIR        The location of Gperftools headers\n\nfind_library(GPERFTOOLS_TCMALLOC\n  NAMES tcmalloc\n  HINTS ${Gperftools_ROOT_DIR}/lib)\n\nfind_library(GPERFTOOLS_PROFILER\n  NAMES profiler\n  HINTS ${Gperftools_ROOT_DIR}/lib)\n\nfind_library(GPERFTOOLS_TCMALLOC_AND_PROFILER\n  NAMES tcmalloc_and_profiler\n  HINTS ${Gperftools_ROOT_DIR}/lib)\n\nfind_path(GPERFTOOLS_INCLUDE_DIR\n  NAMES gperftools/heap-profiler.h\n  HINTS ${Gperftools_ROOT_DIR}/include)\n\nset(GPERFTOOLS_LIBRARIES ${GPERFTOOLS_TCMALLOC_AND_PROFILER})\n\ninclude(FindPackageHandleStandardArgs)\nfind_package_handle_standard_args(\n  Gperftools\n  DEFAULT_MSG\n  GPERFTOOLS_LIBRARIES\n  GPERFTOOLS_INCLUDE_DIR)\n\nmark_as_advanced(\n  Gperftools_ROOT_DIR\n  GPERFTOOLS_TCMALLOC\n  GPERFTOOLS_PROFILER\n  GPERFTOOLS_TCMALLOC_AND_PROFILER\n  GPERFTOOLS_LIBRARIES\n  GPERFTOOLS_INCLUDE_DIR)\n"
  },
  {
    "path": "cmake/Modules/FindLibRTLSDR.cmake",
    "content": "# - Try to find LibRTLSDR\n# Once done this will define\n#\n#  LibRTLSDR_FOUND - System has librtlsdr\n#  LibRTLSDR_INCLUDE_DIRS - The librtlsdr include directories\n#  LibRTLSDR_LIBRARIES - The libraries needed to use librtlsdr\n#  LibRTLSDR_DEFINITIONS - Compiler switches required for using librtlsdr\n#  LibRTLSDR_VERSION - The librtlsdr version\n#\n\nfind_package(PkgConfig)\npkg_check_modules(PC_LibRTLSDR QUIET librtlsdr)\nset(LibRTLSDR_DEFINITIONS ${PC_LibRTLSDR_CFLAGS_OTHER})\n\nfind_path(LibRTLSDR_INCLUDE_DIR NAMES rtl-sdr.h\n          HINTS ${PC_LibRTLSDR_INCLUDE_DIRS}\n          PATHS\n          /usr/include\n          /usr/local/include )\n\nfind_library(LibRTLSDR_LIBRARY NAMES rtlsdr\n             HINTS ${PC_LibRTLSDR_LIBRARY_DIRS}\n             PATHS\n             /usr/lib\n             /usr/local/lib )\n\nset(LibRTLSDR_VERSION ${PC_LibRTLSDR_VERSION})\n\ninclude(FindPackageHandleStandardArgs)\n# handle the QUIETLY and REQUIRED arguments and set LibRTLSDR_FOUND to TRUE\n# if all listed variables are TRUE\n# Note that `FOUND_VAR LibRTLSDR_FOUND` is needed for cmake 3.2 and older.\nfind_package_handle_standard_args(LibRTLSDR\n                                  FOUND_VAR LibRTLSDR_FOUND\n                                  REQUIRED_VARS LibRTLSDR_LIBRARY LibRTLSDR_INCLUDE_DIR\n                                  VERSION_VAR LibRTLSDR_VERSION)\n\nmark_as_advanced(LibRTLSDR_LIBRARY LibRTLSDR_INCLUDE_DIR LibRTLSDR_VERSION)\n\nset(LibRTLSDR_LIBRARIES ${LibRTLSDR_LIBRARY} )\nset(LibRTLSDR_INCLUDE_DIRS ${LibRTLSDR_INCLUDE_DIR} )\n"
  },
  {
    "path": "cmake/Modules/FindLibUSB.cmake",
    "content": "# - Try to find LibUSB-1.0\n# Once done this will define\n#\n#  LibUSB_FOUND - System has libusb\n#  LibUSB_INCLUDE_DIRS - The libusb include directories\n#  LibUSB_LIBRARIES - The libraries needed to use libusb\n#  LibUSB_DEFINITIONS - Compiler switches required for using libusb\n#  LibUSB_VERSION - the libusb version\n#\n\nfind_package(PkgConfig)\npkg_check_modules(PC_LibUSB QUIET libusb-1.0)\nset(LibUSB_DEFINITIONS ${PC_LibUSB_CFLAGS_OTHER})\n\nfind_path(LibUSB_INCLUDE_DIR NAMES libusb.h\n          HINTS ${PC_LibUSB_INCLUDE_DIRS}\n          PATH_SUFFIXES libusb-1.0\n          PATHS\n          /usr/include\n          /usr/local/include )\n\n#standard library name for libusb-1.0\nset(libusb1_library_names usb-1.0)\n\n#libusb-1.0 compatible library on freebsd\nif((CMAKE_SYSTEM_NAME STREQUAL \"FreeBSD\") OR (CMAKE_SYSTEM_NAME STREQUAL \"kFreeBSD\"))\n    list(APPEND libusb1_library_names usb)\nendif()\n\n#libusb-1.0 name on Windows (from PothosSDR distribution)\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    list(APPEND libusb1_library_names libusb-1.0)\nendif()\n\nfind_library(LibUSB_LIBRARY\n             NAMES ${libusb1_library_names}\n             HINTS ${PC_LibUSB_LIBRARY_DIRS}\n             PATHS\n             /usr/lib\n             /usr/local/lib )\n\nset(LibUSB_VERSION ${PC_LibUSB_VERSION})\n\ninclude(FindPackageHandleStandardArgs)\n# handle the QUIETLY and REQUIRED arguments and set LibUSB_FOUND to TRUE\n# if all listed variables are TRUE\n# Note that `FOUND_VAR LibUSB_FOUND` is needed for cmake 3.2 and older.\nfind_package_handle_standard_args(LibUSB\n                                  FOUND_VAR LibUSB_FOUND\n                                  REQUIRED_VARS LibUSB_LIBRARY LibUSB_INCLUDE_DIR\n                                  VERSION_VAR LibUSB_VERSION)\n\nmark_as_advanced(LibUSB_LIBRARY LibUSB_INCLUDE_DIR LibUSB_VERSION)\n\nset(LibUSB_LIBRARIES ${LibUSB_LIBRARY} )\nset(LibUSB_INCLUDE_DIRS ${LibUSB_INCLUDE_DIR} )\n"
  },
  {
    "path": "cmake/Modules/GetGitRevisionDescription.cmake",
    "content": "# https://github.com/rpavlik/cmake-modules\n#\n# - Returns a version string from Git\n#\n# These functions force a re-configure on each git commit so that you can\n# trust the values of the variables in your build system.\n#\n#  get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])\n#\n# Returns the refspec and sha hash of the current head revision\n#\n#  git_describe(<var> [<additional arguments to git describe> ...])\n#\n# Returns the results of git describe on the source tree, and adjusting\n# the output so that it tests false if an error occurs.\n#\n#  git_get_exact_tag(<var> [<additional arguments to git describe> ...])\n#\n# Returns the results of git describe --exact-match on the source tree,\n# and adjusting the output so that it tests false if there was no exact\n# matching tag.\n#\n# Requires CMake 2.6 or newer (uses the 'function' command)\n#\n# Original Author:\n# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>\n# http://academic.cleardefinition.com\n# Iowa State University HCI Graduate Program/VRAC\n#\n# Copyright Iowa State University 2009-2010.\n# Distributed under the Boost Software License, Version 1.0.\n# (See accompanying file LICENSE_1_0.txt or copy at\n# http://www.boost.org/LICENSE_1_0.txt)\n\nif(__get_git_revision_description)\n  return()\nendif()\nset(__get_git_revision_description YES)\n\n# We must run the following at \"include\" time, not at function call time,\n# to find the path to this module rather than the path to a calling list file\nget_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)\n\nfunction(get_git_dir _gitdir)\n  # check FORCED_GIT_DIR first\n  if(FORCED_GIT_DIR)\n    set(${_gitdir} ${FORCED_GIT_DIR} PARENT_SCOPE)\n    return()\n  endif()\n\n  # check GIT_DIR in environment\n  set(GIT_DIR $ENV{GIT_DIR})\n  if(NOT GIT_DIR)\n    set(GIT_PARENT_DIR ${CMAKE_CURRENT_SOURCE_DIR})\n    set(GIT_DIR ${GIT_PARENT_DIR}/.git)\n  endif()\n  # .git dir not found, search parent directories\n  while(NOT EXISTS ${GIT_DIR})\n    set(GIT_PREVIOUS_PARENT ${GIT_PARENT_DIR})\n    get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)\n    if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)\n      return()\n    endif()\n    set(GIT_DIR ${GIT_PARENT_DIR}/.git)\n  endwhile()\n  # check if this is a submodule\n  if(NOT IS_DIRECTORY ${GIT_DIR})\n    file(READ ${GIT_DIR} submodule)\n    string(REGEX REPLACE \"gitdir: (.*)\\n$\" \"\\\\1\" GIT_DIR_RELATIVE ${submodule})\n    get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)\n    get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)\n  endif()\n  set(${_gitdir} ${GIT_DIR} PARENT_SCOPE)\nendfunction()\n\nfunction(get_git_head_revision _refspecvar _hashvar)\n  get_git_dir(GIT_DIR)\n  if(NOT GIT_DIR)\n    return()\n  endif()\n\n  set(GIT_DATA ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data)\n  if(NOT EXISTS ${GIT_DATA})\n    file(MAKE_DIRECTORY ${GIT_DATA})\n  endif()\n\n  if(NOT EXISTS ${GIT_DIR}/HEAD)\n    return()\n  endif()\n  set(HEAD_FILE ${GIT_DATA}/HEAD)\n  configure_file(${GIT_DIR}/HEAD ${HEAD_FILE} COPYONLY)\n\n  configure_file(${_gitdescmoddir}/GetGitRevisionDescription.cmake.in\n    ${GIT_DATA}/grabRef.cmake\n    @ONLY)\n  include(${GIT_DATA}/grabRef.cmake)\n\n  set(${_refspecvar} ${HEAD_REF} PARENT_SCOPE)\n  set(${_hashvar} ${HEAD_HASH} PARENT_SCOPE)\nendfunction()\n\nfunction(git_describe _var)\n  get_git_dir(GIT_DIR)\n  if(NOT GIT_DIR)\n    return()\n  endif()\n\n  if(NOT GIT_FOUND)\n    find_package(Git QUIET)\n  endif()\n  if(NOT GIT_FOUND AND CMAKE_HOST_WIN32)\n    # fallback to VS bundled Git\n    find_program(GIT_EXECUTABLE NAMES git PATH_SUFFIXES Git/cmd Git/bin)\n    find_package_handle_standard_args(Git FOUND_VAR GIT_FOUND REQUIRED_VARS GIT_EXECUTABLE)\n  endif()\n  if(NOT GIT_FOUND)\n    set(${_var} \"GIT-NOTFOUND\" PARENT_SCOPE)\n    return()\n  endif()\n\n  get_git_head_revision(refspec hash)\n  if(NOT hash)\n    set(${_var} \"HEAD-HASH-NOTFOUND\" PARENT_SCOPE)\n    return()\n  endif()\n\n  execute_process(COMMAND\n    ${GIT_EXECUTABLE}\n    describe\n    ${hash}\n    ${ARGN}\n    WORKING_DIRECTORY\n    ${GIT_DIR}\n    RESULT_VARIABLE\n    res\n    OUTPUT_VARIABLE\n    out\n    ERROR_QUIET\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n  if(NOT res EQUAL 0)\n    set(out \"${out}-${res}-NOTFOUND\")\n  endif()\n\n  set(${_var} ${out} PARENT_SCOPE)\nendfunction()\n\nfunction(git_timestamp _var)\n  get_git_dir(GIT_DIR)\n  if(NOT GIT_DIR)\n    return()\n  endif()\n\n  if(NOT GIT_FOUND)\n    find_package(Git QUIET)\n  endif()\n  if(NOT GIT_FOUND AND CMAKE_HOST_WIN32)\n    # fallback to VS bundled Git\n    find_program(GIT_EXECUTABLE NAMES git PATH_SUFFIXES Git/cmd Git/bin)\n    find_package_handle_standard_args(Git FOUND_VAR GIT_FOUND REQUIRED_VARS GIT_EXECUTABLE)\n  endif()\n  if(NOT GIT_FOUND)\n    return()\n  endif()\n\n  get_git_head_revision(refspec hash)\n  if(NOT hash)\n    set(${_var} \"HEAD-HASH-NOTFOUND\" PARENT_SCOPE)\n    return()\n  endif()\n\n  execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%ci ${hash} ${ARGN}\n    WORKING_DIRECTORY ${GIT_DIR}\n    RESULT_VARIABLE res\n    OUTPUT_VARIABLE out\n    ERROR_QUIET\n    OUTPUT_STRIP_TRAILING_WHITESPACE)\n  if(res EQUAL 0)\n    # older Git does not have %cI, create ISO8601 from %ci\n    string(SUBSTRING ${out} 0 10 date)\n    string(SUBSTRING ${out} 10 -1 time)\n    string(REPLACE \" \" \"\" time ${time})\n    set(out \"${date}T${time}\")\n    set(${_var}_ISO ${out} PARENT_SCOPE)\n    string(REGEX REPLACE \"[-T:]\" \"\" out ${out})\n    string(SUBSTRING ${out} 0 12 out)\n  else()\n    set(out \"${out}-${res}-NOTFOUND\")\n  endif()\n\n  set(${_var} ${out} PARENT_SCOPE)\nendfunction()\n\nfunction(git_get_exact_tag _var)\n  git_describe(out --exact-match ${ARGN})\n  set(${_var} ${out} PARENT_SCOPE)\nendfunction()\n"
  },
  {
    "path": "cmake/Modules/GetGitRevisionDescription.cmake.in",
    "content": "#\n# Internal file for GetGitRevisionDescription.cmake\n#\n# Requires CMake 2.6 or newer (uses the 'function' command)\n#\n# Original Author:\n# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>\n# http://academic.cleardefinition.com\n# Iowa State University HCI Graduate Program/VRAC\n#\n# Copyright Iowa State University 2009-2010.\n# Distributed under the Boost Software License, Version 1.0.\n# (See accompanying file LICENSE_1_0.txt or copy at\n# http://www.boost.org/LICENSE_1_0.txt)\n\nset(HEAD_HASH)\n\nfile(READ \"@HEAD_FILE@\" HEAD_CONTENTS LIMIT 1024)\n\nstring(STRIP \"${HEAD_CONTENTS}\" HEAD_CONTENTS)\nif(HEAD_CONTENTS MATCHES \"ref\")\n  # named branch\n  string(REPLACE \"ref: \" \"\" HEAD_REF \"${HEAD_CONTENTS}\")\n  if(EXISTS \"@GIT_DIR@/${HEAD_REF}\")\n    configure_file(\"@GIT_DIR@/${HEAD_REF}\" \"@GIT_DATA@/head-ref\" COPYONLY)\n  elseif(EXISTS \"@GIT_DIR@/logs/${HEAD_REF}\")\n    configure_file(\"@GIT_DIR@/logs/${HEAD_REF}\" \"@GIT_DATA@/head-ref\" COPYONLY)\n    set(HEAD_HASH \"${HEAD_REF}\")\n  endif()\nelse()\n  # detached HEAD\n  configure_file(\"@GIT_DIR@/HEAD\" \"@GIT_DATA@/head-ref\" COPYONLY)\nendif()\n\nif(NOT HEAD_HASH)\n  file(READ \"@GIT_DATA@/head-ref\" HEAD_HASH LIMIT 1024)\n  string(STRIP \"${HEAD_HASH}\" HEAD_HASH)\nendif()\n"
  },
  {
    "path": "cmake/Toolchain-aarch64-linux-gnu.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Linux)\nset(CMAKE_SYSTEM_PROCESSOR aarch64)\nset(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)\nset(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)\n"
  },
  {
    "path": "cmake/Toolchain-arm-linux-gnueabi.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Linux)\nset(CMAKE_SYSTEM_PROCESSOR arm)\nset(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabi-gcc)\nset(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabi-g++)\n"
  },
  {
    "path": "cmake/Toolchain-arm-linux-gnueabihf.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Linux)\nset(CMAKE_SYSTEM_PROCESSOR arm)\nset(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc)\nset(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabihf-g++)\n"
  },
  {
    "path": "cmake/Toolchain-gcc-mingw-w64-i686.cmake",
    "content": "# CMAKE_SYSROOT and CMAKE_STAGING_PREFIX need 3.0\ncmake_minimum_required(VERSION 3.10)\n\n# Linux, Windows, or Darwin\nSET(CMAKE_SYSTEM_NAME Windows)\n\n# not really needed\nSET(CMAKE_SYSTEM_VERSION 1)\n\n# specify the base directory for the cross compiler\nIF(DEFINED ENV{tools})\n    SET(tools $ENV{tools})\nELSE()\n    SET(tools /usr)\nENDIF()\n\n# specify the cross compiler, choose 32/64\nSET(CMAKE_C_COMPILER ${tools}/bin/i686-w64-mingw32-gcc)\n#SET(CMAKE_C_COMPILER ${tools}/bin/x86_64-w64-mingw32-gcc)\n#SET(CMAKE_RC_COMPILER ${tools}/bin/i686-w64-mingw32-windres)\n\n# where is the target environment, choose 32/64\n#SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/i686-w64-mingw32/4.8)\n#SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/x86_64-w64-mingw32/4.8)\n\n# NOTE: use a sysroot with libusb and rtl-sdr if available\nIF(DEFINED ENV{CMAKE_SYSROOT})\n    SET(CMAKE_SYSROOT $ENV{CMAKE_SYSROOT})\n    SET(CMAKE_STAGING_PREFIX $ENV{CMAKE_SYSROOT}/usr)\n    #SET(CMAKE_INSTALL_PREFIX /usr)\n    #list(INSERT CMAKE_FIND_ROOT_PATH 0 $ENV{CMAKE_SYSROOT})\nENDIF()\n\n# search for programs in the build host directories\nSET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n# for libraries and headers in the target directories\nSET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nSET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nSET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n"
  },
  {
    "path": "cmake/Toolchain-gcc-mingw-w64-x86-64.cmake",
    "content": "# CMAKE_SYSROOT and CMAKE_STAGING_PREFIX need 3.0\ncmake_minimum_required(VERSION 3.10)\n\n# Linux, Windows, or Darwin\nSET(CMAKE_SYSTEM_NAME Windows)\n\n# not really needed\nSET(CMAKE_SYSTEM_VERSION 1)\n\n# specify the base directory for the cross compiler\nIF(DEFINED ENV{tools})\n    SET(tools $ENV{tools})\nELSE()\n    SET(tools /usr)\nENDIF()\n\n# specify the cross compiler, choose 32/64\n#SET(CMAKE_C_COMPILER ${tools}/bin/i686-w64-mingw32-gcc)\nSET(CMAKE_C_COMPILER ${tools}/bin/x86_64-w64-mingw32-gcc)\n#SET(CMAKE_RC_COMPILER ${tools}/bin/x86_64-w64-mingw32-windres)\n\n# where is the target environment, choose 32/64\n#SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/i686-w64-mingw32/4.8)\n#SET(CMAKE_FIND_ROOT_PATH ${tools}/lib/gcc/x86_64-w64-mingw32/4.8)\n\n# NOTE: use a sysroot with libusb and rtl-sdr if available\nIF(DEFINED ENV{CMAKE_SYSROOT})\n    SET(CMAKE_SYSROOT $ENV{CMAKE_SYSROOT})\n    SET(CMAKE_STAGING_PREFIX $ENV{CMAKE_SYSROOT}/usr)\n    #SET(CMAKE_INSTALL_PREFIX /usr)\n    #list(INSERT CMAKE_FIND_ROOT_PATH 0 $ENV{CMAKE_SYSROOT})\nENDIF()\n\n# search for programs in the build host directories\nSET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n# for libraries and headers in the target directories\nSET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nSET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nSET(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n"
  },
  {
    "path": "cmake/cmake_uninstall.cmake.in",
    "content": "# http://www.vtk.org/Wiki/CMake_FAQ#Can_I_do_.22make_uninstall.22_with_CMake.3F\n\nIF(NOT EXISTS \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\n  MESSAGE(FATAL_ERROR \"Cannot find install manifest: \\\"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\\\"\")\nENDIF(NOT EXISTS \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\")\n\nFILE(READ \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\" files)\nSTRING(REGEX REPLACE \"\\n\" \";\" files \"${files}\")\nFOREACH(file ${files})\n  MESSAGE(STATUS \"Uninstalling \\\"$ENV{DESTDIR}${file}\\\"\")\n  IF(EXISTS \"$ENV{DESTDIR}${file}\")\n    EXEC_PROGRAM(\n      \"@CMAKE_COMMAND@\" ARGS \"-E remove \\\"$ENV{DESTDIR}${file}\\\"\"\n      OUTPUT_VARIABLE rm_out\n      RETURN_VALUE rm_retval\n      )\n    IF(NOT \"${rm_retval}\" STREQUAL 0)\n      MESSAGE(FATAL_ERROR \"Problem when removing \\\"$ENV{DESTDIR}${file}\\\"\")\n    ENDIF(NOT \"${rm_retval}\" STREQUAL 0)\n  ELSEIF(IS_SYMLINK \"$ENV{DESTDIR}${file}\")\n    EXEC_PROGRAM(\n      \"@CMAKE_COMMAND@\" ARGS \"-E remove \\\"$ENV{DESTDIR}${file}\\\"\"\n      OUTPUT_VARIABLE rm_out\n      RETURN_VALUE rm_retval\n      )\n    IF(NOT \"${rm_retval}\" STREQUAL 0)\n      MESSAGE(FATAL_ERROR \"Problem when removing \\\"$ENV{DESTDIR}${file}\\\"\")\n    ENDIF(NOT \"${rm_retval}\" STREQUAL 0)\n  ELSE(EXISTS \"$ENV{DESTDIR}${file}\")\n    MESSAGE(STATUS \"File \\\"$ENV{DESTDIR}${file}\\\" does not exist.\")\n  ENDIF(EXISTS \"$ENV{DESTDIR}${file}\")\nENDFOREACH(file)\n"
  },
  {
    "path": "conf/CAME-TOP432.conf",
    "content": "# CAME-TOP432.conf\n# Copyright (C) 2020 JFORESTIER\n# Decode CAME remote control TOP-432EV, TOP-432NA, TOP-432EE.\n# This remote control is used for garage door and sliding gate.\n# It transmits on 433.92 MHz (as it is written on the case), built since 2006\n# (as said on the FCC site https://www.fcc.gov/oet/ea/fccid with reference M48 TOP-NA)\n#\n# It works with CAME radio receiver cards \"AF43S\", capable of handling 4096 codes.\n# CAME is an Italian company. These remote controls are mainly sold in Europe (France, Italy, Belgium).\n# https://www.came.com and https://www.came-europe.com .\n\n# Device information and test files:\n# https://github.com/psa-jforestier/rtl_433_tests/tree/master/tests/Came/TOP432\n# The device uses PWM encoding,\n# - 0 is encoded as 320 us gap and 640 us pulse,\n# - 1 is encoded as 640 us gap and 320 us pulse.\n# The device sends a 4 times the packet when a button on the remote control is pressed.\n# A transmission starts with a 320 us pulse. At the end of the packet, there is a minimum of 36 periods of 320us between messages (11520us)\n\ndecoder {\n  name=CAME-TOP432,\n  modulation=OOK_PWM,\n  short=320,\n  long=640,\n  reset=10000,\n  bits=13,\n  gap=830,\n  preamble={1}8,\n  get=@1:{12}:button_code\n}\n"
  },
  {
    "path": "conf/CMakeLists.txt",
    "content": "########################################################################\n# Install example configuration files\n########################################################################\nfile(GLOB RTL433_CONF_FILES \"*.conf\")\n\n# Note that apparently bare `etc` or relative CMAKE_INSTALL_SYSCONFDIR\n# always gets CMAKE_INSTALL_PREFIX prepended.\n# Use absolute CMAKE_INSTALL_FULL_SYSCONFDIR to get /etc for /usr prefix.\n# Note that CMAKE_STAGING_PREFIX should contain CMAKE_INSTALL_PREFIX but\n# that component is not stripped here. Breaks cross-compile SYSCONFDIR.\ninstall(FILES\n    ${RTL433_CONF_FILES}\n    DESTINATION ${CMAKE_STAGING_PREFIX}${CMAKE_INSTALL_FULL_SYSCONFDIR}/rtl_433\n)\n"
  },
  {
    "path": "conf/ContinentalRemote.conf",
    "content": "# Decoder for Continental / Nissan S180144020 key remote\n# FCC ID: KR5S180144014\n#\n# Tested with 2x US 2013 Nissan Altima key fobs\n#\n# Operates on 433.92MHz with FSK at +/-32kHz.  With the default low-pass\n# filter of 10% of the sample rate, this means that a sample rate strictly\n# greater than 320ksps must be used.  For a RTL-SDR, that means passing\n# \"-s 1.024e6\".\n#\n# The protocol was reverse engineered, so it may not be 100% complete.  There\n# are 3 possible packet types:\n# - sync: 128 bits of all 0\n# - code: contains the rolling code, always sent twice for redundancy\n# - time: contains the amount of time that the button was held\n#\n# Every button press generates a sequence of sync, code, sync, code, time...\n# packets.  There is always a 100ms gap between packets.  Time packets will\n# be transmitted every 100ms until the button is released.\n#\n# More than one button can be held as well, which will set more than one bit\n# in the 'button' field.\n\ndecoder {\n    name=Continental-Remote-code,\n    modulation=FSK_MC_ZEROBIT,\n    short=122,\n    reset=1000,\n    preamble=ffff,\n    bits>=104,\n\n    get=message:@0:{8}:[0x2a:HOLD_TIME 0x2c:REL_TIME 0x26:HOLD_CODE 0x24:REL_CODE],\n    get=src:@8:{16}:%x,\n    get=dest:@24:{16}:%x,\n    get=button:@40:{8}:[0x00:NONE 0x01:ALARM 0x02:TRUNK 0x04:UNLOCK 0x08:LOCK 0x40:START],\n    get=rv:@52:{2},\n    get=seq:@56:{8},\n    get=code:@64:{32}:%08x,\n    get=xorsum:@96:{8}:%02x,\n}\n\ndecoder {\n    name=Continental-Remote-time,\n    modulation=FSK_MC_ZEROBIT,\n    short=122,\n    reset=1000,\n    preamble=ffff,\n    bits>=64,\n    bits<=103,\n\n    get=message:@0:{8}:[0x2a:HOLD_TIME 0x2c:REL_TIME 0x26:HOLD_CODE 0x24:REL_CODE],\n    get=src:@8:{16}:%x,\n    get=dest:@24:{16}:%x,\n    get=button:@40:{8}:[0x00:NONE 0x01:ALARM 0x02:TRUNK 0x04:UNLOCK 0x08:LOCK 0x40:START],\n    get=clk_10.4kHz:@48:{16}:,\n}\n"
  },
  {
    "path": "conf/Dewenwils_BHV.conf",
    "content": "# Dewenwils BH-V 5-channel AC socket remote.\n#\n# These use the standard xx2260C-R4 encoder chips, with the oscillator\n# set with a 4.7M resistor, resulting in very short (~168usec) pulses.\n#\n# Decoded key values:\n#\n# 1-ON:  codes : {25}eaaacc8, {25}eaaaff8    1110 1010 1010 1010 1100 1100 1000\n# 1-OFF: codes : {25}eaaac38, {25}eaaaff8    1110 1010 1010 1010 1100 0011 1000\n#\n# 2-ON:  codes : {25}eaaa3c8, {25}eaaaff8    1110 1010 1010 1010 0011 1100 1000\n# 2-OFF: codes : {25}eaaa338, {25}eaaaff8    1110 1010 1010 1010 0011 0011 1000\n#\n# 3-ON:  codes : {25}eaa8fc8, {25}eaa8ff8    1110 1010 1010 1000 1111 1100 1000\n# 3-OFF: codes : {25}eaa8f38, {25}eaa8ff8    1110 1010 1010 1000 1111 0011 1000\n#\n# 4-ON:  codes : {25}eaa2fc8, {25}eaa2ff8    1110 1010 1010 0010 1111 1100 1000\n# 4-OFF: codes : {25}eaa2f38, {25}eaa2ff8    1110 1010 1010 0010 1111 0011 1000\n#\n# 5-ON:  codes : {25}ea8afc8, {25}ea8aff8    1110 1010 1000 1010 1111 1100 1000\n# 5-OFF: codes : {25}ea8af38, {25}ea8aff8    1110 1010 1000 1010 1111 0011 1000\n#\n# The code for this remote is 0301.\n#\n# Fred N. van Kempen <decwiz@yahoo.com> 2024/05/11.\n\ndecoder {\n        n=Dewenwils BH-V (PT2260) Remote,\n        m=OOK_PWM,\n        s=150,\n        l=500,\n        g=800,\n        r=6000,\n        t=50,\n        bits=25,\n        get=@4:{4}:code:[10:0301 14:0304],\n        get=@8:{12}:button:[2732:1 2723:2 2703:3 2607:4 2223:5],\n\tget=@20:{4}:state:[12:ON 3:OFF],\n        unique\n}\n"
  },
  {
    "path": "conf/DrivewayAlarm_I8-W1901.conf",
    "content": "#\n#  Driveway alarm sensor  SKU I8-W1901\n#  This sensor Detects motion and comes with a receiver that plays 32 different\n#  melodies at selectable volumes. The receiver with the speaker plugs into a\n#  120Vac outlet and the motion sensor is powered by 3 AAA batteries. The plastic\n#  material is high quality and has brass inserts for the battery cover screws.\n#  The receiver (that comes with the sensor) has the ability to pair with 4\n#  different sensors.\n#    Amazon: https://www.amazon.com/Driveway-Alarms-Wireless-Outside-Weatherproof/dp/B0BFDMKD98\n#    SKU: I8-W1901\n#    On Amazon it is sold by FREETIM, but the packaging does not mention this name\n#    anywhere; it  is unbranded.\n#  It is a 433.92 Mhz device and it repeatedly  sends two rows of data when motion\n#  is detected:\n#                      ----------------------------------\n#                            Data             Length\n#  - first row                8               01 bit (artifact of sync bit)\n#  - second row          <device id>          25 bits\n#                      ----------------------------------\n#  There is no additional data provided by the sensor, it simply detects motion.\n#\n#  The following flex decoder was used for the above analysis.\n#     $ rtl_433 -R 0 -X 'n=driveway,m=OOK_PWM,s=200,l=596,r=6196,g=608,t=158,y=0,repeats>=10,bits>=20'\n#       repeats>=10 and bits>=20 was used to filter out any possible noise.\n#\n#  Decoder\n#  -------\n#  After discussion with zuckschwerdt (https://github.com/merbanan/rtl_433/pull/2493)\n#  we decided to include the sync bit into the packet (for robustness) by increasing\n#  the gap limit to 900 us and then matching on 26 bits as the ID. This gives us a\n#  flex decoder like this:\n#   $ rtl_433 -R 0 -X 'n=motion,m=OOK_PWM,s=188,l=588,r=7000,g=900,t=160,y=0,match={26}c4ea674,repeats>=3'\n#\n#  How to use\n#  ----------\n#  1. Run rtl_433 -R 0 -X 'n=motion,m=OOK_PWM,s=188,l=588,r=7000,g=900,t=160,y=0,repeats>=3,bits>=26'\n#  2. Activate the motion sensor, and write down the hex value of the 26 bits you get\n#  3. Replace the value in the match= line below with the one you just got\n#\n#  Voila! Enjoy!\n#\n#  This sensor was tested at about 45 meters of distance and through layers of wood\n#  and walls and it worked well.\n#\n\ndecoder {\n        n=Driveway alarm I8-W1901,\n        m=OOK_PWM,\n        s=188,\n        l=588,\n        r=7000,\n        g=900,\n        t=160,\n        bits>=26,\n        repeats>=3,\n        countonly,\n        match={26}c4ea674\n}\n"
  },
  {
    "path": "conf/DrivewayAlert.conf",
    "content": "# Decoder for the Harbor Freight / Bunker Hill Wireless / Ironton\n# Wireless Security Alert System / Wireless Driveway Alert System\n# Passive Infrared Sensor by ZHUJI JIARONG ELECTRICAL APPLIANCE CO.,LTD.\n#\n# Operates on 433.920MHz.\n# All models emit identical id's.\n#\n# Reference:\n#   https://fccid.io/2AA9QJR-178\n#   https://triq.org/pdv/#AAB01A04010160043C15B831DC8180918180918091818180909090909255+AAB01D04190160043C15B831DC8181818180918180918091818180909090909255+AAB01D04010160043C15B831DC8181818180918180918091818180909090909355\n\ndecoder {\n    name=DrivewayAlert,\n    modulation=OOK_PWM,\n    short=368,\n    long=1108,\n    reset=5572,\n    gap=1076,\n    tolerance=296,\n    sync=0,\n    match={19}0xfb5c00,\n    bits=19,\n    unique,\n}\n"
  },
  {
    "path": "conf/EV1527-4Button-Universal-Remote.conf",
    "content": "# EV1527 4-Button Universal remote\n#\n# This decoder reads button pressed from a EV1527 based remote control.\n# The four rubber buttons are directly connected to the EV1527 data pins.\n# Therefore, pressing more than one button at the same time is possible.\n# The flex spec will match this and output a string containing the pressed\n# button combination e.g.\n# \" code : REMOTE-B      button : AC \"\n# if button A and C have been pressed simultaneously\n# All possible combinations are matched for completeness, while not all\n# are are useful because of the size of the remote.\n# You can simply not press easily more than two buttons at the same time ;-)\n#\n# The shown example can distinguish between multiple remotes (two in this case).\n# If this is not desired, a simple \"match=abcde\" can limit the detection of\n# one unique EV1527 code.\n\ndecoder {\n        n=EV1527-Remote,\n        m=OOK_PWM,\n        s=369,\n        l=1072,\n        g=1400,\n        r=12840,\n        bits>=24,\n        repeats>=3,\n        invert,\n        get=@0:{20}:code:[123456:REMOTE-A 987654:REMOTE-B],\n        get=@20:{4}:button:[1:A 2:B 3:AB 4:C 5:AC 6:BC 7:ABC 8:D 9:AD 10:BD 11:ABD 12:CD 13:ACD 14:BCD 15:ALL],\n        unique\n}\n"
  },
  {
    "path": "conf/EV1527-DDS-Sgooway.conf",
    "content": "# EV1527 based Wireless Door Sensor\n#\n# This decoder reads detections of the Sgooway EV1527 based Door sensor\n#\n# Link to this product can be found https://www.aliexpress.com/item/399798633.html?spm=a2g0s.9042311.0.0.6bf54c4dDH7uSk\n#\n# The shown example will display all sensors detected.  Each device will have a separate code that can be used to differentiate between the\n# different devices.\n#\n# One thing of note is this device does not trigger a closed even, only when the device is opened is an rf signal sent.\n\ndecoder {\n        n=EV1527-DDS,\n        m=OOK_PWM,\n        s=280,\n        l=990,\n        r=2000,\n        bits>=24,\n        bits<=25,\n        get=@0:{24}:code,\n        unique\n}\n"
  },
  {
    "path": "conf/EV1527-PIR-Sgooway.conf",
    "content": "# EV1527 based passive infrared sensor\n#\n# This decoder reads detections of the Sgooway EV1527 based PIR\n#\n# The shown example can distinguish between multiple of those PIR's (two in this case)\n# and present the different PIR's as \"channel : x\" in the output. Adopt this line\n# to your EV1527 unique codes. Put the decimal representation of the in here.\n#\n# If this is not desired, a simple \"match=abcde\" can limit the detection of\n# one unique EV1527 code.\n\ndecoder {\n        n=EV1527-PIR,\n        m=OOK_PWM,\n        s=400,\n        l=1200,\n        g=1500,\n        r=12000,\n        repeats>=4,\n        bits=25,\n        get=@0:{25}:channel:[12345678:1 98765432:2],\n        unique\n}\n"
  },
  {
    "path": "conf/FAN-53T.conf",
    "content": "# Decoder for FAN-53T ceiling fan/light remote control\n# Very similar to FAN-11T.\n#\n# https://fccid.io/2AAZPFAN-53T/User-Manual/User-manual-2228959\n#\n# Code Format: 01 <Dipswitch Code> 0 <6-bit button map>  (all bits inverted)\n# Button Map Bits: [fan1, fan2, fan3, (some remotes send this when the button is released), fan0, light]\n#\n# Dip switches are used as a unique ID to talk to a specific unit\n#\n# Some remotes have buttons labeled 0,1,2,3\n# Others are labeled Off, Hi, Med, Low\n#\n# Fan 0 = \"Off\"\n# Fan 1 = \"High\"\n# Fan 2 = \"Medium\"\n# Fan 3 = \"Low\"\n#\n# Dipswitch: 0000\n# Fan 1 - 01 0000 0 100000\n# Fan 2 - 01 0000 0 010000\n# Fan 3 - 01 0000 0 001000\n# Fan 0 - 01 0000 0 000010\n# Light - 01 0000 0 000001\n#\n# Dipswitch: 0001\n# Fan 1 - 01 0001 0 100000\n# Fan 2 - 01 0001 0 010000\n# Fan 3 - 01 0001 0 001000\n# Fan 0 - 01 0001 0 000010\n# Light - 01 0001 0 000001\n#\n# Dipswitch: 1000\n# Fan 1 - 01 1000 0 100000\n# Fan 2 - 01 1000 0 010000\n# Fan 3 - 01 1000 0 001000\n# Fan 0 - 01 1000 0 000010\n# Light - 01 1000 0 000001\n\n\nfrequency 303.900M\n\ndecoder {\n    name        = FAN-53T,\n    modulation  = OOK_PWM,\n    short       = 360,\n    long        = 700,\n    gap         = 0,\n    reset       = 2000,\n    invert,\n    bits        = 13,\n    get         = id:@2:{4},\n    get         = button:@7:{6}:[32:fan_Hi 16:fan_Med 8:fan_Low 4:button_Released 2:fan_Off 1:light 0:  ],\n}\n"
  },
  {
    "path": "conf/GhostControls.conf",
    "content": "# Decoder for the GhostControls family of automated gate controls that\n# operate on 433.920MHz.\n#\n# Tested with:\n#   AXS1 3-Button Remote\n#   AXP1 5-Button Premium Remote\n#   AXR1 Water-Resistant Remote\n#   AXWK Premium Wireless Keypad\n#\n# Reference:\n#   https://fccid.io/2AGMZGC433TX1-5\n#   https://fccid.io/2AGMZGC433WK1\n\ndecoder {\n    name=GhostControls,\n    modulation=OOK_PWM,\n    short=248,\n    long=776,\n    sync=0,\n    reset=780,\n    tolerance=211,\n    rows=1,\n    bits=42,\n    invert,\n    get=unit:@0:{4}:[1:remote 2:keypad],\n    get=options:@4:{4}:[0:none 8:party 9:vacation 15:test],\n    get=command:@8:{4}:[0:none 3:toggle],\n    get=button:@12:{8}:[0:none 1:secondary 2:primary],\n    get=id:@20:{22},\n}\n"
  },
  {
    "path": "conf/HeatmiserPRT-W.conf",
    "content": "# Decoder for Heatmiser PRT-W thermostat with mqtt output\n\n# These are thermostats principally designed to control underfloor heating systems although they also can be used to control radiators.\n\n# The thermostat transmits to a receiver which controls physical valves to provide heat to the zone where the thermostat is. I think the receiver also has a hard wire link to the boiler to tell it to wake up and get heating when this happens, as I've detected no signal to suggest it's wireless.\n\n# The thermostats all transmit on or around 869.01Mhz\n\n# I run the conf file from the command line with:\n#rtl_433 -Y classic -R 0 -c /home/russ/.config/rtl_433/HeatmiserPRT-W.conf -f 869.01M\n\n# You will need to change the prettified channel codes to match your thermostat's output.\n\n# The heatmiser's bit stream at bit no. 8 gives the receiver address for 8 bits, the next 4 bits is the on/off command and the next 4 is the thermostat ID number. The consequence of all this is that to get a meaningful unique ID we need both the receiver and the stat ID. I don't know how to concatenate the first 8bits, skip 4 and then add a further 4. To keep things simple, I just took the entire 16 bits, but this means we end up with two values for each thermostat - one for the on state and one for the off state. Thus on my system both 0x8821 and 0x8831 relate to the Living room thermostat. I then just aliased both bitwise outputs to the same text string: Living_Room. This seems to work ok, but feels like a bit of hack.\n#\n# You will also need to change the receiver location to be meaningful for your system. Mine was configured with two receivers, one on ground floor (L00) and another on the second floor (L02)\n#\n#get=@8:{16}:Stat Name:[0x8821: Living_Room 0x8831:Living_Room 0x8921:Family_Room 0x8931:Family_Room 0x8922:Kitchen 0x8932:Kitchen 0x8923:Hallway 0x8933:Hallway],\n\n\n# Report iso time:\nreport_meta time:iso\n\n# Including the 'report_meta level' switch (commented out below) means information on the signal quality and strength are added to the output. This was useful to me to determine which thermostat was which without running down 2 flights or stairs every time I wanted to make a change. Basically the higher the rssi number, generally, the closer the stat was physically to my SDR aerial.\n\n#report_meta level\n\n# specify MQTT output and formatting - replace the IP address (192.168.1.150) with your mosquitto broker's IP address and port number (I didn't need the port number)\noutput mqtt://192.168.1.150,retain=1,devices=rtl_433[/channel]\n\ndecoder {\n  name        =   Heatmiser_PRT-W_Thermostat,\n  modulation  =   FSK_PCM,\n  s           =   416,\n  l           =   416,\n  r           =   20000,\n  unique,\n  bits        >=  120,\n  preamble    =   aaaa2dd4,\n  get         =   channel:@8:{16}:[0x8821:10 0x8831:10 0x8921:20 0x8931:20 0x8922:30 0x8932:30 0x8923:40 0x8933:40],\n  get         =   StatName:@8:{16}:[0x8821: Living_Room 0x8831:Living_Room 0x8921:Family_Room 0x8931:Family_Room 0x8922:Kitchen 0x8932:Kitchen 0x8923:Hallway 0x8933:Hallway],\n  get         =   Receiver:@8:{8}:[0x88:L02 0x89:L00],\n  get         =   StatID:@20:{4},\n  get         =   event:@16:{4}:[0x3:ON 0x2:OFF],\n}\n"
  },
  {
    "path": "conf/Hormann-blue.conf",
    "content": "# Hormann-blue.conf\n# Hörmann (Hoermann/Hormann) remote for 868 MHz (Blue Button).\n#\n# This remote control is used for garage doors and gates.\n# Decodes all 868 MHz senders (blue button):\n# - HSM2 868\n# - HSM4 868\n# - HS1 868\n# - HS3 868\n# - HS4 868\n# - HSZ1 868\n# - HSZ2 868\n# - HSP4 868\n# - HSP4-C 868\n# - HSD2-A 868\n# - HSD2-C 868\n#\n# It transmits on 868.3 MHz and uses OOK PWM encoding,\n# Usually 10 repeats of 32 bits with\n# - 5328 us warmup pulse (16 clocks), 333 us gap\n# - 0 is encoded as 667 us pulse and 333 us gap.\n# - 1 is encoded as 333 us pulse and 667 us gap,\n#\n# -X 'n=Hormann,m=OOK_PWM,s=333,l=667,y=5328,r=900'\n\ndecoder {\n  name=Hormann,\n  modulation=OOK_PWM,\n  short=333,\n  long=667,\n  sync=5328,\n  reset=900,\n  bits=32,\n  unique,\n  get=@0:{32}:button_code\n}\n"
  },
  {
    "path": "conf/LeakDetector.conf",
    "content": "# Decoder for the WaterLeak Detector:\n# https://www.banggood.com/DY-SQ100B-Water-Leakage-Detector-Rustproof-Sensor-Alarm-433MHz-for-Security-Home-Alarm-System-p-1266537.html\n# by Kevin Saye\n#\n# Operates on 433.920MHz.\n# All models emit identical id's.\n\ndecoder {\n    name=LeakDetector,\n    modulation=OOK_PWM,\n    short=316,\n    long=968,\n    reset=916,\n    gap=0,\n    tolerance=261,\n    bits=33,\n    unique,\n    get=Location:@0:{16}:[42460:HotWaterHeater 5853:KevinSink 7133:KitchenSink],\n    get=Message:@16:{8}:[250:Alarm],\n    get=Battery:@24:{4}:[14:Ok 12:Low]\n}\n"
  },
  {
    "path": "conf/MightyMule-FM231.conf",
    "content": "# Decoder for the Mighty Mule FM231 Driveway alarm from GTO Inc\n# FCC Test report, including RF waveforms is here:\n#   https://fccid.io/I6HGTOFM231/Test-Report/Test-Report-1214140.pdf\n\n# Use the Accurite and similar convention for reporting battery.\n# The name is 'battery_ok' with values 1 (ok) and 0. (which are\n# numerically reversed from what the FM231 reports)\n\n# The DIP switches for setting a unique device ID are labeled 1-4\n# from left to right, but appear in the # data stream in reverse\n# order.\n\ndecoder {\n    name=MightyMule-FM231,\n    modulation=OOK_PWM,\n    short=650,\n    long=1200,\n    sync=3800,\n    reset=1100,\n    tolerance=200,\n    rows=1,\n    bits=9,\n    get=@4:{1}:battery_ok:[0:1 1:0],\n    get=@5:{4}:id,\n    unique\n}\n"
  },
  {
    "path": "conf/MondeoRemote.conf",
    "content": "# Decoder for Ford Mondeo key remote\n#\n# Tested with:\n#   UK 2013 Ford Mondeo key remote\n#\n# Operates on 433.920MHz.\n#\n# S.a https://github.com/merbanan/rtl_433/issues/1282\n#\n# The data shows the expected clear header, then encrypted data, then perhaps a 16-bit checksum.\n# It's strange though that so much of the encrypted data collides. It should be random -- it isn't.\n#\n# Example cods:\n#    9ca8 7130 449d 20 c816 0fb3 92a2 9251 1c0c\n#    9ca8 7130 449d 22 62bc a519 383a aa5b 8e6c\n#    9ca8 7130 449d 20 9846 5fe3 c2d2 c263 edc4\n#    9ca8 7130 449d 20 9846 5fe2 c2d2 c161 3284\n#    9ca8 7130 449d 20 9846 5fe2 42da c1e8 46bc\n#    9ca8 7130 449d 20 8856 4ff2 52c2 d1f6 5344\n#    9ca8 7130 449d 22 429c 8538 981a c9fb 8b38\n#    9ca8 7130 449d 20 8856 4ff2 d2ca d17f 277c\n#    9ca8 7130 449d 22 6ab4 ad11 3032 f282 fd54\n#    9ca8 7130 449d 22 6ab4 ad10 3032 f180 2214\n#    9ca8 7130 449d 22 62bc a518 b83a fa08 9d2c\n#    9ca8 7130 449d 20 9846 5fe2 c2fa c188 71e4\n#    9ca8 7130 449d 20 8856 4ff3 d2e2 d294 bb58\n#    9ca8 7130 449d 20 8856 4ff2 d2e2 d196 6418\n#    9ca8 7130 449d 20 8856 4ff2 52ea d21c 7514\n#    9ca8 7130 449d 22 62bc a518 383a e999 85ac\n#    9ca8 7130 449d 20 d806 1fa3 8212 82a4 518c\n#    9ca8 7130 449d 20 d806 1fa2 8212 81a6 8ecc\n#    9ca8 7130 449d 20 d806 1fa2 021a 822c 9fc4\n#    9ca8 7130 449d 22 c21c 05b8 989a 19ab 5f3c\n#    9ca8 7130 449d 20 c816 0fb0 9202 93b0 917c\n#    9ca8 7130 449d 22 ca14 0db0 9092 01b2 f884\n#    9ca8 7130 449d 22 c21c 05ba 989a 0bb8 5cfc\n#    9ca8 7130 449d 20 c816 0fb2 920a 91b8 9b34\n\ndecoder {\n    name=Mondeo-Remote,\n    modulation=FSK_MC_ZEROBIT,\n    short=64,\n    long=64,\n    reset=136,\n    preamble=aaae,\n}\n"
  },
  {
    "path": "conf/PHOX.conf",
    "content": "# PHOX: Remote control gate opener.\n#\n# Characteristic\n#  * Rolling code transmission\n#  * 433.92 MHz and 868.30 MHz version\n#  * 2-button and 4-button version, programmable or factory set\n#  * Possibility to obtain up to 14 channels by combining\n#    the pressure of multiple buttons\n#\n# Manufacturer:\n#   https://v2home.com/en/product/personal-pass-transmitter-433-92-868-30-mhz-rolling-code/\n#\n# TODO:\n#  * should data be inverted and reversed?\n#  * investigate meaning of first 28 bits\n#\ndecoder {\n    name        = PHOX,\n    modulation  = OOK_PWM,\n    short\t= 432,\n    long\t= 864,\n    reset\t= 32000,\n    sync\t= 2596,\n    bits\t= 52,\n    repeats    >= 2,\n    unique,\n    get          =@28:{4}:key:[7:A 11:B 13:C 14:D],\n    get          =@36:{16}:code:%04x,\n}\n"
  },
  {
    "path": "conf/Reolink-doorbell.conf",
    "content": "# Reolink doorbell\n#\n# This decoder reads button presses from a EV1527 based Reolink doorbell.\n#\n# Analyzed by @sdalu in #2277\n\ndecoder {\n        name        = Reolink-Doorbell,\n        modulation  = OOK_PWM,\n        short       = 352,\n        long        = 956,\n        tolerance   = 242,\n        gap         = 950,\n        reset       = 9800,\n        bits       >= 24,\n        rows       >= 30,\n        unique,\n        get         = @0:{24}:id,\n}\n"
  },
  {
    "path": "conf/SMC5326-Remote.conf",
    "content": "# SMC5326 remote control\n\n# This decoder reads two or four button pressed on the remote control. The\n# rubber buttons are directly connected to the SMC5326 data pins. Therefore,\n# pressing more than one button at the same time is possible. However this\n# flex spec only match a single button press\n#\n# SMC5326 are usually configured using 8-dip switch. All possible combinations\n# of keys are matched. You can simply press any button to\n# determine the remote unique keys ;-)\n#\n# see rtl_433_tests/tests/smc5326 for more information\n\ndecoder {\n        n=SMC5326-Remote,\n        m=OOK_PWM,\n        s=328,\n        l=948,\n        g=1400,\n        r=2000,\n        bits=26,\n        invert,\n        get=@0:{4}:key01:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--],\n        get=@4:{4}:key23:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--],\n        get=@8:{4}:key45:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--],\n        get=@12:{4}:key67:[15:++ 14:+f 12:+- 11:f+ 10:ff 8:f- 3:-+ 2:-f 0:--],\n        get=@16:{8}:button:[234:A 186:B 174:C 171:D]\n}\n"
  },
  {
    "path": "conf/SWETUP-garage-opener.conf",
    "content": "# SWETUP garage door opener\n# by FoxWhiskey 2023\n#\n# A four-button remote control device most likely based on EV1527 chip.\n# The device is available on AMAZON and a no-name-product branded for the german market.\n# See https://www.amazon.de/-/en/Handheld-Transmitter-Control-Universal-Wireless/dp/B09HWY1KH7\n#\n# The package includes a set of two devices. Each device, however, transmits identical signals, which suggests\n# that no ID is present and all devices are indistinguishable (if not trained to a specific code...)\n# When a button is pressed, the device transmits a 25bit code with following pattern:\n#\n# Bit 1-20  : constant\n# Bit 21-24 : BUTTON\n# Bit 25    : constant\n#\n# The pattern is being repeated as long as the button is held down. So, postprocessing is necessary if duplicates are unwanted.\n#\n#\ndecoder {\n        n=SWETUP-remote-4btn,\n        m=OOK_PWM,\n        s=264,\n        l=788,\n        r=945,\n        bits=25,\n        preamble={20}57ba1,\n        get=@0:{4}:button:[0x7:A 0xE:B 0xB:C 0xD:D],\n        unique\n}\n"
  },
  {
    "path": "conf/SalusRT300RF.conf",
    "content": "# Decoder for Salus RT300RF thermostat\n# listen on 868.286Mhz\n# rtl_433 -Y classic -R 0 -X \"name=SalusRT300RF, m=FSK_PCM, s=833, l=833, r=16000, preamble={24}0xaaaaaa,get=@0:{16}:Thermostat ID, get=@28:{4}:heat:[1:ON 2:OFF]\" -f 868.286Mhz -F \"mqtt://192.168.1.150,retain=1,devices=sensors/rtl_433/P[protocol]/C[channel]\"\n# Run with:\n#rtl_433 -Y classic -R 0 -c /home/russ/.config/rtl_433/SalusRT300RF.conf -f 868.286M\n\n# Report iso time:\nreport_meta time:iso\n\n# Including the 'report_meta level' switch (commented out immediately below) means information on the signal quality and strength are added to the output.\n#report_meta level\n\n# specify MQTT output and formatting - replace the IP address (192.168.1.150) with your mosquitto broker's IP address and port number (I didn't need the port number)\noutput mqtt://192.168.1.150,retain=1,devices=rtl_433/Salus[/id]\n\n\ndecoder {\n  name        =  SalusRT300RF,\n  m           =  FSK_PCM,\n  s           =  833,\n  l           =  833,\n  r           =  16000,\n  unique,\n  preamble    =  {24}0xaaaaaa,\n  get         =  @0:{8}:id,\n  get         =  @28:{4}:Heat:[1:ON 2:OFF]\n}\n"
  },
  {
    "path": "conf/Skylink_HA-434TL.conf",
    "content": "# Skylink HA-434TL motion sensor\n#\n# This decoder reads detections of the Skylink HA-434TL PIR motion sensor\n#\n# s.a. https://github.com/merbanan/rtl_433/pull/814\n\ndecoder {\n        n=Skylink-HA-434TL,\n        m=OOK_PPM,\n        s=500,\n        l=1500,\n        y=2000,\n        g=1800,\n        r=10000,\n        bits=17,\n        get={3}:motion:[5:motion 2:alive],\n        get=@3:{14}:id,\n        unique\n}\n"
  },
  {
    "path": "conf/Thomson-doorbell.conf",
    "content": "# Thomson doorbell\n#\n# old model with 23 bits: https://www.hornbach.de/p/p/4618196/\n# new model with 25 bits: https://www.amazon.de/dp/B09P5Y4G1B/\n#\n# This decoder reads button presses from handles harvesting energy while you press it (they have no battery).\n# A newly bought button sends roughly four complete rows before energy runs out.\n# An older button sends one complete row until it finally falls out (I have one outside working for many years).\n#\n# Due to its short pulses sampling rate must be 1M.\n# You should listen at 433.8MHz to receive a stable signal.\n# rtl_433 -R 0 -c conf/Thomson-doorbell.conf -f 433.8M -s 1024k\n#\n# Analyzed by @TheChatty #2940\n\ndecoder {\n        name        = Thomson-Doorbell,\n        modulation  = OOK_PWM,\n        short       = 50,\n        long        = 150,\n        gap         = 200,\n        reset       = 3000,\n        bits       >= 23,\n        unique,\n}\n"
  },
  {
    "path": "conf/adlm_fprf.conf",
    "content": "# Equation/Siemens ADLM FPRF on 433.863MHz\n# 3 zones heater programmer\n#\n# A 50ms wakeup pulse followed by a 5ms gap,\n# then a start pulse 5ms gap + 3ms pulse followed by 41 data pulses.\n# This is repeated 3 times with the next wakeup directly following\n# the preceding stop pulses.\n#\n# Bit width is 2000 us with\n# Short pulse: ___- 1500us gap +  500 us pulse\n# Long pulse:  _---  500us gap + 1500 us pulse\n#\n# This is a electric heater programmer sold in France by Leroy Merlin on the brand Equation.\n# It is manufactured by Siemens, and also has the mark RDE100.1 FPRF on the PCB\n\ndecoder {\n    name=ADLM FPRF,\n    modulation=OOK_PWM,\n    short=500,\n    long=1500,\n    reset=7000,\n    gap=2000,\n    bits>=40,\n    get=@8:{16}:id,\n    get=@28:{4}:zone,\n    get=@32:{4}:mode:[9:ECO 10:COMFORT 8:OFF],\n    unique\n}\n"
  },
  {
    "path": "conf/atc-technology_lmt-430.conf",
    "content": "# ATC Technology LMT-430\n#\n# This config represents the decoding settings of a handheld dimming remote for my room lamp\n\ndecoder {\n    name        = ATC Technology LMT-430,\n    modulation  = OOK_PPM,\n    short       = 420,\n    long        = 1080,\n    gap         = 1100,\n    reset       = 8060,\n    bits        = 25,\n    preamble    = 0x2cad,\n    repeats    >= 3,\n    get         = button:@16:{12}:[0x4c8:1 0x2a8:2 0x550:3 0x330:4]\n}\n"
  },
  {
    "path": "conf/car_fob.conf",
    "content": "# Unknown carfob with rolling code\n# Copyright (C) 2020 Benjamin Larsson\n#\n\n\ndecoder {\n    name=Car fob,\n    modulation=OOK_PWM,\n    short=428,\n    long=872,\n    reset=4284,\n    gap=872,\n    tolerance=176,\n    bits=66,\n    get=rolling_code:@0:{32}:,\n    get=id:@32:{24},\n    get=@56:button:{8}:[02:button_1 04:button_2],\n}\n"
  },
  {
    "path": "conf/chungear_bcf-0019x2.conf",
    "content": "# Chungear Industrial Co Ltd Fan/Light Remote Controller BCF-0019x2\n#\n# https://fccid.io/KUJCE9001/User-Manual/User-Manual-171498.pdf\n# repeats of 13 short (252 us) or long (484 us) pulses. Packet gap is 8188 us.\n\ndecoder {\n    name        = Chungear_BCF-0019x2,\n    modulation  = OOK_PWM,\n    short       = 252,\n    long        = 484,\n    gap         = 500,\n    reset       = 8500,\n    tolerance   = 100,\n    get         = id:@9:{4},\n    get         = button:@1:{6}:[47:light_on-off 55:light_dimmer 62:fan_button_0 61:fan_button_1 59:fan_button_2 31:fan_button_3 63:no_button],\n}\n\n# The getters above decode the following bits:\n#\n# 11011111 10111  light on/off\n# 11101111 10111  light dimmer\n# 11111101 10111  fan button 0\n# 11111011 10111  fan button 1\n# 11110111 10111  fan button 2\n# 10111111 10111  fan button 3\n#\n# xxxxxxxx x0xxx  dip sw 1\n# xxxxxxxx xx0xx  dip sw 2\n# xxxxxxxx xxx0x  dip sw 3\n# xxxxxxxx xxxx0  dip sw 4\n"
  },
  {
    "path": "conf/dooya_curtain.conf",
    "content": "# Dooya Curtain Remote (DC1602)\n# e.g. https://www.aliexpress.com/i/32954197821.html\n# see https://github.com/merbanan/rtl_433/issues/1545\n#\n# A 15 channel remote, with 3 functions/commands: Open/Close/Stop\n\ndecoder {\n    name=Dooya-Curtain,\n    modulation=OOK_PWM,\n    short=350,\n    long=750,\n    sync=4900,\n    gap=990,\n    reset=9900,\n    bits>=40,\n    invert,\n    get=@0:{24}:id,\n    get=@24:{8}:channel,\n    get=@32:{4}:button:[1:open 3:close 5:stop],\n    get=@36:{4}:check,\n    unique\n}\n"
  },
  {
    "path": "conf/elro_ab440r.conf",
    "content": "# ELRO AB440R remote control.\n\n# Remote switch to turn on or off power sockets.\n# The remote control has 8 buttons to control 4 sockets (on and off button)\n# and 5 dip switches to dial in a unique local channel (0-31)\n#\n# User manual: https://www.libble.eu/elro-ab440-series/online-manual-313854/\n#\n# Payload format:\n#\n#     1C1C1C1C1C 1B1B1B1B 10 1S1S 10000000\n#\n#     CCCCC: 5 bit channel number (reversed)\n#     BBBB:  1000 = button A\n#            0100 = button B\n#            0010 = button C\n#            0001 = button D\n#     SS:    10 = ON\n#            01 = OFF\n#\n# Test:\n#     rtl_433 -c conf/elro_ab440r.conf -y '{25}bbabae8'\n#\n\ndecoder {\n    name=ELRO-AB440R,\n    modulation=OOK_PWM,\n    short=330,\n    long=970,\n    gap=1200,\n    reset=9000,\n    bits=25,\n    symbol_zero={2}8,\n    symbol_one={2}c,\n    get=@0:{5}:channel,\n    get=@5:{4}:button:[8:A 4:B 2:C 1:D],\n    get=@10:{2}:toggle:[2:ON 1:OFF],\n    unique\n}\n"
  },
  {
    "path": "conf/energy_count_3000.conf",
    "content": "# EC3k Energy Count Control\n#\n# \"Voltcraft Energy Count 3000\" (868.3 MHz) sensor sold by Conrad\n# aka \"Velleman NETBSEM4\"\n# aka \"La Crosse Technology Remote Cost Control Monitor - RS3620\".\n# aka \"ELV Cost Control\"\n#\n# Stub driver\n# FSK PCM NRZ 50 us bit width, up to 12 zeros seen, package should be around 578 bits.\n#\n# Copyright (C) 2015 Tommy Vestermark\n\ndecoder {\n    name=Energy-Count-3000,\n    modulation=FSK_PCM,\n    short=50,\n    long=50,\n    reset=800,\n    bits>=550,\n    bits<=590\n}\n"
  },
  {
    "path": "conf/fan-11t.conf",
    "content": "\n#\n# FAN-11T Remote Control of Harbor Breeze Fan\n#\n# KUJCE9103 FAN-11T FAN-53T 2AAZPFAN-53T\n#\n# written: Peter Shipley\n#\n# https://www.amazon.com/s?k=Fan-11T\n#\n# Based on the Holtek the HT12E/HT12F chipsets\n#\n# HT12E : https://www.holtek.com/documents/10179/116711/2_12ev120.pdf\n# HT12F : https://www.holtek.com/documents/10179/116711/2_12dv120.pdf\n#\n# FCC ID: L3HFAN11T\n#\n# https://fccid.io/L3HFAN11T\n#\n# 0.36ms Short\n# 0.71ms Long\n#\n# 12bits of info ( 13 bits transmitted )\n#\n# receiver requires three matching transmissions optionally followed by one packet with all shorts\n#\n# The Fan-11T uses a 4 bits dip-switch as an identifier\n#\n# packets can be described as\n#\n#    <short> <long> + 4 bit ID + <short> + 6 bit command\n#\n# The follow table uses '1001' as the ID code:\n# if short is 0 and Long is 1\n#\n#    Hi      0 1 1 0 0 1 0 1 0 0 0 0 0   =   0110010100000 = 3232 = 0xca0\n#    Med     0 1 1 0 0 1 0 0 1 0 0 0 0   =   0110010010000 = 3216 = 0xc90\n#    Low     0 1 1 0 0 1 0 0 0 1 0 0 0   =   0110010001000 = 3208 = 0xc88\n#    Off     0 1 1 0 0 1 0 0 0 0 0 1 0   =   0110010000010 = 3202 = 0xc82\n#    Lit     0 1 1 0 0 1 0 0 0 0 0 0 1   =   0110010000001 = 3201 = 0xc81\n#    End?    0 1 1 0 0 1 0 0 0 0 0 0 0   =   0110010000000 = 3200 = 0xC80\n#\n#\n#\n# 0110010000010\n\n# rtl_433 -R 0 -f 302450000 -c fan-11t.conf\n\n# The FCC Documentation list the frequency at 303.9MMz but the devices tests out to between 303.4 and 303.5 (depending on battery)\n\n\n# To reduce false positives, uncomment the match\n# field and set the value to your remote's dip switch pattern\n\n# eg: if your Dip-switch setting is up-down-up=up ( '1011' )\n# set the match setting in the decoder definition to 0x6C\n# for example:\n#     match={7}0x66C\n\n\n#`    Dip Switch     match preamble\n#\t0000\t\t0x40\n#\t0001\t\t0x44\n#\t0010\t\t0x48\n#\t0011\t\t0x4C\n#\t0100\t\t0x50\n#\t0101\t\t0x54\n#\t0110\t\t0x58\n#\t0111\t\t0x5C\n#\t1000\t\t0x60\n#\t1001\t\t0x64\n#\t1010\t\t0x68\n#\t1011\t\t0x6C\n#\t1100\t\t0x70\n#\t1101\t\t0x74\n#\t1110\t\t0x78\n#\t1111\t\t0x7C\n\n\n\nfrequency 302.400M\n\ndecoder {\n    name        = Fan-11t,\n    modulation  = OOK_PWM,\n    short       = 360,\n    long        = 710,\n    gap         = 0,\n    reset       = 5000,\n    tolerance   = 50,\n    unique,\n    invert,\n    bits        = 13,\n    get         = id:@2:{4},\n    get         = button:@7:{6}:[32:fan_Hi 16:fan_Med 8:fan_Low 2:fan_Off 1:light 0: 4: ],\n}\n"
  },
  {
    "path": "conf/friedlandevo.conf",
    "content": "# Friedland EVO door bell\n#\n# This decodes the transmissions from the Friedland EVO wireless door bell buttons. The\n# Friedland doorbell system allows multiple buttons and multiple bells and will make a\n# different sound depending on which door button was pressed. This has only been tested with\n# 2 different buttons.\n#\n# In order to eliminate other transmissions the match function has been used with part of\n# the data that was the same between the two tested variants. This may not hold true for\n# different devices so be prepared to remove/adjust as needed.\n#\n# The frequency is around 433.8Mhz\n#\n# Note: The buttons send out the same data 12 times so you may need to write a filter\n# to pipe the output through to remove them\n#\n\ndecoder {\n    name        = FriedlandEvo,\n    modulation  = OOK_PCM,\n    short       = 750,\n    long        = 750,\n    reset       = 9000,\n    preamble    = AA,\n    match       = 0x410408210400,\n    unique\n}\n"
  },
  {
    "path": "conf/ge_smartremote_plus.conf",
    "content": "# GE Smartremote Plus\n# FCC ID: QOB-PT458\n#\n# House Code D\n#   Channel 0\n#      On:  ea af a8 80  : 11101010 10101111 10101000 1\n#      Off: ea af ab 80  : 11101010 10101111 10101011 1\n#                           *                      **\n#   Channel 1\n#      On:  aa af a8 80  : 10101010 10101111 10101000 1\n#      Off: aa af ab 80  : 10101010 10101111 10101011 1\n#                           *                      **\n\nfrequency 319.56M\n\ndecoder {\n\tname=GE-Smartremote-RF108,\n\tmodulation=OOK_PWM,\n\tshort=330,\n\tlong=1000,\n\treset=1500,\n\tinvert,\n\tbits>=24,\n\tbits<=25,\n\tget=channel:@1:{1},\n\tget=action:@22:{1}:[0:off 1:on],\n\tget=id:@2:{20},\n}\n"
  },
  {
    "path": "conf/heatilator.conf",
    "content": "# Decoder for Heatilator gas log remotes.\n#\n# Heatilator gas logs use OOK_PULSE_PPM encoding. The format is very similar to\n# that decoded by 'generic_remote', but seems to differ slightly in timing. The\n# device does _not_ use a discrete chip to generate the waveform; it's generated\n# in code.\n#\n# The packet starts with 380 uS start pulse followed by an eternity (14.3 mS) of silence.\n# - 0 is defined as a 1430 uS pulse followed by a 460 uS gap.\n# - 1 is defined as a 380 uS pulse followed by a 1420 uS gap.\n#\n# Transmissions consist of the start bit followed by 24 data bits. These packets are\n# repeated many times.\n#\n# Because there's such a long start bit/preamble, the decoder usually creates the first\n# row with a single bit, followed by 'n' rows with 25 bits (the 24 data bits and the\n# start bit of the following packet), then the last row with the expected 24 bits.\n#\n# Packet layout:\n#\n#      Bit number\n#      0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23\n#      - - - - - - - - - - DEVICE SERIAL NUMBER - - - - - - - - - |- COMMAND -\n#\n# The device serial number is (presumedly) burned into the device when manufactured.\n# The command is further broken down into the following bits:\n#\n#     20 21 22 23\n#     X  X  S  T\n#\n# X bits are unknown in function. S is the 'state' of the gas valve/flame. S = 0\n# means 'flame off'. S = 1 means 'flame on'. T indicates whether or not the remote\n# is in 'thermo' mode - this is a mode where the remote detects the room temperature\n# and commands the gas logs on/off to maintain the temperature selected on the remote.\n#\n# There are safety mechanisms afoot - whenever the gas logs are 'on', on with a timer,\n# or on in thermo mode, occasional 'keepalive' messages are sent to the gas logs to\n# guarantee that the remote is still in range and the batteries are not dead. Generally\n# these messages are exactly the same as the last command that the remote sent - that is,\n# if you turn the logs 'on' manually, the remote will send the same 'on' command every so\n# often.\n#\n# The COMMAND S and T bits have these meanings:\n#     S  T\n#     ----\n#     0  0 - Off, Manual mode\n#     0  1 - Off, Thermo mode (room is too warm)\n#     1  0 - On,  Manual mode.\n#     1  1 - On,  Thermo more (room is too cold)\n\nfrequency 433.92M\n\ndecoder {\n    name        = Heatilator-gas-log-remote,\n    modulation  = OOK_PWM,\n    short       = 380,\n    long        = 1420,\n    gap         = 0,\n    reset       = 1800,\n    bits        >= 24,\n    bits        <= 25,\n    get         = id:@0:{20},\n    get         = state:@22:{1}:[0:flame_off 1:flame_on],\n    get         = mode:@23:{1}:[0:manual 1:thermo],\n}\n"
  },
  {
    "path": "conf/honeywell-fan.conf",
    "content": "# Decoder for Honeywell fan remotes.\n#\n# This fan is made by Intertek (model 4003229) but is sold by Honeywell\n# as a model 'Salermo', number 10285. This fan may be sold under different\n# makes and models, YMMV.\n#\n# Honeywell fans use OOK_PULSE_PPM encoding.\n# The packet starts with a 300 uS start pulse.\n# - 0 is defined as a 300 uS gap followed by a 900 uS pulse.\n# - 1 is defined as a 900 uS gap followed by a 300 uS pulse.\n#\n# Transmissions consist of a short start bit followed by bursts of 24 bits.\n# These packets are repeated up to 23 times.\n#\n# Possible packet layout:\n#\n#     Bit number 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23\n#                -----------------------------------------------------------------------\n#     Value      0  0  0  1  0  1  1  0  1  1  0  0  1  1  0  1 |Value|  Cmd   | 1 !d  d\n#\n# It is pure supposition that the leading 0x16CD and bit 21 are fixed values.\n# I do not have more than 1 remote to test and there's no mention in the manual about\n# dip switch settings, nor are there any on the remote. It's also possible that the\n# value occupies 3 bits and the command is only two bits. It's also possible that\n# there's no such command/value distinction. It looks very suspicious that the fan\n# speed commands all share command 000 and the speed value (bit-reversed) appears in the\n# value area.\n#\n#     Button  Fixed Other Bits       Function\n#     ONE         16CD  1 0 0 0 0 1 !d d  Low speed fan\n#     TWO         16CD  0 1 0 0 0 1 !d d  Medium speed fan\n#     THREE       16CD  1 1 0 0 0 1 !d d  High speed fan\n#     OFF-M       16CD  0 0 0 1 0 1 !d d  Fan off (momentary press)\n#     OFF-C       16CD  0 0 1 0 1 1 !d d  Light off delay (continuous press)\n#     STAR-M      16CD  1 1 0 1 0 1 !d d  Light on/off (momentary press)\n#     STAR-C      16CD  0 1 1 1 0 1 !d d  Light dim/brighten (continuous press)\n#     ONE+THREE-C 16CD  1 0 1 0 1 1 !d d  Learn mode ONE+THREE (continuous press)\n#\n# The 'd' bit indicates whether the D/CFL button in the battery compartment\n# is set to 'D' (1 bit) or 'CFL' (0 bit). This switch inhibits the dim\n# function when set to CFL. The !d bit seems to just be the complement of 'd'.\n#\n# Since the COMMAND/VALUE paradigm is not verified and only seems to apply to the fan speed\n# buttons, we'll decode using the full 3rd byte right-shifted by 3 bits to omit the fixed '1'\n# and 'Dim' bits.\n#\n#     byte[2] >> 3:\n#     -------------\n#     0x10: Low fan speed\n#     0x08: Medium fan speed\n#     0x18: High fan speed\n#     0x02: Fan off, momentary press of the power button\n#     0x05: Delayed light off, extended press of the power button\n#     0x1A: Light on/off, momentary press of the 'star' button\n#     0x0E: Light dim/brighten, extended press of the 'star' button\n#     0x15: 'Learn' mode - hold ONE+THREE (low+high) for 5+ secs. Pairs remote to fan.\n\nfrequency 314.92M\n\ndecoder {\n    name        = Honeywell-Fan,\n    modulation  = OOK_PPM,\n    bits        = 24,\n    short       = 300,\n    long        = 900,\n    reset       = 1300,\n    get         = id:@0:{16},\n    get         = button:@16:{5}:[16:fan_low 8:fan_med 24:fan_hi 2:fan_off 5:light_off_delayed 26:light_on_off 14:light_dim_brighten 21:learn_mode],\n    get         = dimmable:@23:{1}:[0:no 1:yes]\n}\n"
  },
  {
    "path": "conf/hornbach-msrc-sal.conf",
    "content": "# Decoder for Hornbach awning remotes.\n#\n# The remote is labeled MSRC-SAL and has article number 6196477 printed on the\n# label [0]. On the inside there's a chip with its markings removed, but it\n# looks like an off-the-shelf, generic remote. There are multiple LED positions,\n# where only one is soldered. There's unused keys, an unlabeled one on the\n# front, which does not even emit an RC signal, and one on the back, 'time',\n# without even a button behind it, nor was there a spot on the PCB for a key.\n#\n# There is also another sort of remote, which includes a wind-sensor [1].\n# It is assumed that it functions just like a remote, taking light and wind\n# into account to send the close signal.\n#\n# It should probably be mentioned, that Hornbach itself is likely not the\n# manufacturer itself.\n#\n# The remote operates on a 433.92 MHz frequency. The PCB is labeled\n# 'DC104-HD V1.3' and seems to be produced in 2019-07-04.\n#\n# Hornbach awnings use OOK_PWM encoding.\n# - 0 is defined as a 372 µs pulse followed by a 744 µs gap.\n# - 1 is defined as a 744 µs pulse followed by a 372 µs gap.\n# - reset is defined as a 1116 gap followed by a 7812 µs gap.\n# - sync is defined as a 4836 µs pulse, followed by a 1488 µs gap.\n#\n# > __Note:__ It is unclear if the last space (gap) of a bit is always extended\n# to a certain length, or if this is an additional delimiter. One could argue\n# the reset/delimiter is a 8928 µs gap.\n#\n# Transmissions starts with a sync and gap pulse, followed by 40 bits, closed\n# off with a gap and reset pulse.\n# These seem to be repeated at least 4 times, depending on how long the button\n# is kept pressed. The exception is the light, which is never repeated more than\n# 4 times, which is also visible on the remotes activity LED.\n#\n# Possible packet layout:\n# It starts with 32 bits Remote ID, possibly a combination of vendor + function\n# as commonly seen in infra-red remote controls. These are likely remote-unique.\n# When programming the awning to match a new/different remote, one is expected\n# to press `P2` during power-on, which the awning will acknowledge with some\n# beeps. Since the code does not change when pushing `P2`, this is a fair\n# assumption, but an assumption still.\n#\n# Following the remote ID we have a single byte that indicates the button.\n#\n#   | Button   | Byte | Bits             | Function                            |\n#   |----------|------|------------------|-------------------------------------|\n#   | P2       |   33 | 0 0 1 1  0 0 1 1 | Program/Learn remote                |\n#   | MODE     |   3e | 0 0 1 1  1 1 1 0 | Unknown/Undocumented                |\n#   | STOP     |   aa | 1 0 1 0  1 0 1 0 | Stop awning at current position     |\n#   | DOWN rel |   c3 | 1 1 0 0  0 0 1 1 | Release of button DOWN              |\n#   | DOWN     |   cc | 1 1 0 0  1 1 0 0 | Open awning (via internal endstop)  |\n#   | UP rel   |   e1 | 1 1 1 0  0 0 0 1 | Release of button UP                |\n#   | UP       |   ee | 1 1 1 0  1 1 1 0 | Close awning (via internal endstop) |\n#   | LIGHT    |   f0 | 1 1 1 1  0 0 0 0 | Light high/low/off                  |\n#\n# The UP/DOWN buttons are the only buttons that sends a release event when the\n# key is released on the remote. If the button is kept pressed until the remote\n# stops the signal, the UP/DOWN (release) key is not sent. Interestingly the\n# release events is the inverse of the last nibble of the press event. They are\n# ignored by the awning, which makes sense, as it is already moving.\n\nfrequency 433.92M\n\ndecoder {\n    name       = MSRC-SAL,\n    modulation = OOK_PWM,\n    bits       = 40,\n    short      = 372,\n    long       = 744,\n    reset      = 7812,\n    sync       = 4836,\n    gap        = 1488,\n    get        = RUID:@0:{32}:%x,\n    get        = button:@32:{8}:[0x33:P2 0x3E:MODE 0xAA:STOP 0xC3:DOWN_(release) 0xCC:DOWN 0xE1 UP_(release) 0xEE:UP 0xF0:LIGHT],\n}\n\n# [0]: https://www.hornbach.nl/p/reserveonderdeel-afstandsbediening-voor-zonnescherm-6145050-6145051-6145052-10178611-10178638-10178639-10178640-6823732-6823733-6823734-10461615-104961614-10328334-10328335-10468366-10468367-10468368-10468369-10468370/6196477/\n# [1]: https://www.hornbach.de/p/windwaechter-fuer-motor-markisen-weiss-inkl-1-5-m-netzanschlussleitung/10178681/\n"
  },
  {
    "path": "conf/ivac_pro.conf",
    "content": "# iVac Pro remote control that has different channels, tools and an on/off button\n# https://github.com/merbanan/rtl_433/issues/3034\n\n# -X 'n=iVacPro,m=OOK_PWM,s=420,l=856,r=2000,bits=13,unique,get=id:@0:{6}:%d,get=CMD:@8:{1}:[0:ON 1:OFF],get=SYSTEM_ADDRESS:@6:{2}:[0:D_11 1:C_01 2:B_10 3:A_00],get=TOOL_ADDRESS:@9:{3}:[0:7_111 1:6_011 2:5_101 3:4_001 4:3_110 5:2_010 6:1_100 7:8_000],get=Parity:@12:{1}:%d'\n\n\ndecoder {\n    name=iVacPro,\n    modulation=OOK_PWM,\n    short=420,\n    long=856,\n    reset=2000,\n    bits=13,\n    unique,\n\n    get=@0:{6}:id:%d,\n    get=@8:{1}:CMD:[0:ON 1:OFF],\n    get=@6:{2}:SYSTEM_ADDRESS:[0:D_11 1:C_01 2:B_10 3:A_00],\n    get=@9:{3}:TOOL_ADDRESS:[0:7_111 1:6_011 2:5_101 3:4_001 4:3_110 5:2_010 6:1_100 7:8_000],\n    get=@12:{1}:Parity:%d\n}\n\n"
  },
  {
    "path": "conf/led-light-remote.conf",
    "content": "# Generic Remote Controller for LED Strip Lights\n# Remote decoder from issue #1112 by chaos511\n\n# first 16 bit seem to be a fixed remote id\n# last 8 bit encode the button press:\n# (the keypad has up to 7 rows of 3 columns,\n# usually some keys are missing)\n#  1  2  3\n#  4  5  6\n#  7  8  9\n# 10 11 12\n# 13 14 15\n# 16 17 18\n# 19 20 21\n\ndecoder {\n    name=LED-Light-Remote,\n    modulation=OOK_PWM,\n    short=264,\n    long=1060,\n    reset=2000,\n    gap=0,\n    tolerance=316,\n    bits=25,\n    invert,\n    get=@0:{16}:id,\n    get=@16:{8}:button\n}\n"
  },
  {
    "path": "conf/oma-blind-remote.conf",
    "content": "# Decoder for oma battery powered roller blind motor controller\n# Tested with blind motor with electronic limit switch. Mechanical limit \n# switch motors not tested.\n# Instructions (LKC040902 ver 1.0) reference motors C-C16/C-C17.\n\n# The device tested is model 8C-C18-DC-WA-A101 5 channels\n# 433.92MHz\n# https://www.omaautomation.com/\n# FCC ID: Unknown\n\n# Note. There is another controller of similar apearance, which has \n# programmable date/time functions, which uses a different (incompatable)\n# protocol. \n\n# User manual: LKC040902 ver 1.0 (hardcopy)\n\n# FCC ID: Unknown\n\n# I have tested commands for blind up, blind stop and blind down. \n\n# Short pulses have been found to work between 400 to 350 us, with long pulses\n# twice that.\n\n# Schema of signal is   <2 byte command>\n#                       <7 byte controller address>\n#                       <1 byte 1's compliment of channel number>\n# Up and down commands are sent as a pair of repeated commands.\n# Stop command is sent as a single repeated command.\n# The number of repeats received is quite variable, usually around 10.\n\n# Channel number is 1's complement of the eigth byte. Thus 0xf = channel 0\n#                                                          0xe = channel 1\n#                                                          ...\n#                                                          0x0 = channel 15    \n\n# Up commands are:\n#   Burst of about 6 repeats of:                <ee><7 byte controler address><1 byte channel>\n#   followed by a burst of about 6 repeats of:  <e1><7 byte controler address><1 byte channel>\n\n# Stop commands are:    \n#   Burst of about 6 repeats of:                <aa><7 byte controler address><1 byte channel>\n\n# Down commands are:\n#   Burst of about 6 repeats of:                <cc><7 byte controler address><1 byte channel>\n#   followed by a burst of about 6 repeats of:  <c3><7 byte controler address><1 byte channel>\n\ndecoder {\n        n=oma,\n        m=OOK_PWM,\n        s=375,\n        l=750,\n        r=11760,\n        g=0,t=0,\n        y=4824,\n        bits=40,\n        rows>=2,\n        get=command:@0:{8}:[238:up 225:up 170:stop 204:down 195:down],\n        get=@8:{28}:controller_id,\n        get=channel:@36:{4}:[15:0 14:1 13:2 12:3 11:4 10:5 9:6 8:7 7:8 6:9 5:10 4:11 3:12 2:13 1:14 0:15],\n        unique\n}\n"
  },
  {
    "path": "conf/pir-ef4.conf",
    "content": "# config for PIR-EF4SBT00003.\n# Nicolas JOURDEN - 25/04/2019\n#\n# The sensor is known with the FCC-ID: EF4SBT00003\n#\n# Some references:\n# * https://fcc.report/FCC-ID/EF4SBT00003\n# * http://certid.org/fccid/EF4SBT00003\n# * https://apps.fcc.gov/eas/GetEas731Report.do?applicationId=XkBhwjLuvh2pXcri3MP%2FWA%3D%3D&fcc_id=EF4SBT00003\n#\n#\n# The PIR-EF4 was produced by Nortek (nortekcontrol.com)\n# It was released in 1996 and works with 9 Volts battery.\n# The sensor broadcast its ID when it is triggered.\n# The ID is defined on 2 bytes.\n# There is no CRC or other data transmitted.\n# The modulation is OOK with PPM at the frequency of 315MHz. :\n# Guessing modulation: Pulse Position Modulation with fixed pulse width\n# Attempting demodulation... short_width: 848, long_width: 2132, reset_limit: 8488, sync_width: 0\n# Use a flex decoder with -X 'n=name,m=OOK_PPM,s=848,l=2132,g=2136,r=8488'\n#\n\n# PIR-EF4 configuration:\ndecoder n=\"PIR-EF4 sensor\",m=OOK_PPM,s=848,l=2116,r=8488,rows=1,bits=16,get=@0:{16}:id\n"
  },
  {
    "path": "conf/quinetic_switch.conf",
    "content": "#\n# Quinetic Switches and Sensors\n#\n# Basic Usage:\n# rtl_433 /etc/rtl_433/quinetic_switch.conf\n#\n# Recommended approach:\n# Copy this file to a new location then customise it (e.g. custom output like MQTT).\n# See 'rtl_433.example.conf' for configuration options.\n#\n# Quinetic Tuning:\n# For accurate capture of Quinetic RF packets, use sample_rate of 1024k or higher.\n# Device center frequency: 433.3Mhz +/-50Khz\n#\n\n# FREQ TUNING\nfrequency 433.4M\nsample_rate 1024k\npulse_detect minmax\n\n# ADAPTER TUNING (RTL Chip)\ngain 37\n\n# SELECTION OF PROTOCOL(S) (268=QUINETIC)\nprotocol 268\n\n# DEBUG\n#report_meta level\n#report_meta noise\n"
  },
  {
    "path": "conf/qx-30x.conf",
    "content": "# QX-30X\n#\n# This decoder reads button presses from QX-302, QX-304 and QX-305 self powered\n# switches. These switches appear to use an EV1527 based encoding, where the\n# first 20 bits contain the device ID, the next 4 bits identify which button on\n# the device was pushed, and there may be a trailing sync bit. A sample rate of\n# around 1MHz is recommended to reliably detect these devices.\n#\n\ndecoder {\n        name        = QX-30X,\n        modulation  = OOK_PWM,\n        short       = 33,\n        long        = 100,\n        gap         = 150,\n        reset       = 1500,\n        bits       >= 24,\n        bits       <= 25,\n        unique,\n        get         = get=@0:{20}:id,\n        get         = get=@20:{4}:button\n}\n"
  },
  {
    "path": "conf/rako_wireless_lighting.conf",
    "content": "# A decoder for Rako Wireless Lighting controls\n\n# Manufacturers site: https://rakocontrols.com/products/interfaces-accessories/wireless/\n# Protocol specification documented here: https://hacks.esar.org.uk/rako-wireless-protocol/\n# Tested with RCM-070\n\ndecoder {\n    name=Rako,\n    modulation=FSK_PWM,\n    short=588,\n    long=1180,\n    sync=2324,\n    reset=1012,\n    gap=0,\n    tolerance=50,\n    bits=29,\n    invert,\n    unique,\n    get=@0:{4}:msgtype:[0:Command 2:Data],\n    get=@4:{8}:houseno,\n    get=@12:{8}:roomno,\n    get=@20:{4}:channelno,\n    get=@24:{4}:commandno,\n    get=@28:{1}:checksum,\n    get=@0:{29}:fullbits\n}\n"
  },
  {
    "path": "conf/restaurant_pager.conf",
    "content": "# Restaurant pager system (EV1527-variant, 25-bit OOK PWM)\n#\n# Tested with JianTao JT-913 restaurant guest paging system.\n# Commonly found at 315 MHz or 433.92 MHz depending on region.\n# These are sold under dozens of no-name brands.\n#\n# Frame layout (25 bits):\n#   Bits  0-15: System ID (16 bits)\n#   Bits 16-19: Pager address (4 bits)\n#   Bits 20-23: Function code (4 bits)\n#   Bit  24:    Stop bit (always 1)\n#\n# Function codes: 0xD = buzz/alert, 0xF = sync.\n# Each transmission sends ~40-50 repeated frames preceded by\n# one all-ones preamble frame (0xFFFFFF + stop bit).\n#\n# Usage:\n#   rtl_433 -f 315M    -c conf/restaurant_pager.conf\n#   rtl_433 -f 433.92M -c conf/restaurant_pager.conf\n\ndecoder {\n        n=Restaurant-Pager,\n        m=OOK_PWM,\n        s=204,\n        l=636,\n        g=880,\n        r=7312,\n        bits=25,\n        repeats>=3,\n        get=@0:{16}:id,\n        get=@16:{4}:pager,\n        get=@20:{4}:function:[13:Buzz 15:Sync],\n        unique\n}\n"
  },
  {
    "path": "conf/rolleaseacmedia.conf",
    "content": "# Decoder for rolleaseacmedia mains powered roller blind motor controller\n\n# The device tested is MTRF-REM-15CLED\n# 433.92MHz\n# rolleaseacmedia.com\n# FCC ID: VYY-DD5712\n\n# Note. There is another controller of identical apearance and FCC ID which uses a different (incompatable) protocol to operate battery powered roller blinds.\n\n# Manual:\n# https://www.rolleaseacmeda.com/docs/default-source/us/automate-controllers/automate-paradigm-remote-controls/instr-mtrf-rem-v1-2-sept-2016-(003).pdf?sfvrsn=e823ee3c_8\n\n# FCC ID: https://fccid.io/VYY-DD5712\n\n# I have tested commands for blind up, blind stop and blind down.\n# The controller appears to be capable of proportional level setting,\n# but my blind motors do not appear to support this function,\n# so I have been unable to test proportional level control.\n\n# Schema of signal is <7 hex byte controller address><1 hex byte compliment of channel number><2 hex byte command>\n# Up and down commands are sent as a pair of repeated commands.\n# Stop command is sent as a single repeated command.\n# The number of repeats received is quite variable, usually between 3 and 7.\n\n# Channel number is complement of the eigth hex byte. Thus    0xf = channel 0\n#                                                            0xe = channel 1\n#                                                                ...\n#                                                            0x0 = channel 15\n\n# Up commands are:\n#    Burst of about 6 repeats of:                <7 byte controler address><1 byte channel><ee>\n#    followed by a burst of about 6 repeats of:  <7 byte controler address><1 byte channel><e1>\n\n# Stop commands are:\n#    Burst of about 6 repeats of:                <7 byte controler address><1 byte channel><aa>\n\n# Down commands are:\n#    Burst of about 6 repeats of:                <7 byte controler address><1 byte channel><cc>\n#    followed by a burst of about 6 repeats of:  <7 byte controler address><1 byte channel><c3>\n\n# Short pulses have been found to work between 350 to 460 us, with long pulses twice that.\n\ndecoder {\n        n=rolleaseacmedia,\n        m=OOK_PWM,\n        s=405,\n        l=810,\n        r=8734,\n        g=0,\n        t=0,\n        y=4249,\n        bits=40,\n        rows>=2,\n        get=@0:{28}:controller_id,\n        get=channel:@28:{4}:[15:0 14:1 13:2 12:3 11:4 10:5 9:6 8:7 7:8 6:9 5:10 4:11 3:12 2:13 1:14 0:15],\n        get=command:@32:{8}:[238:up 225:up 170:stop 204:down 195:down],\n        unique\n}\n"
  },
  {
    "path": "conf/rtl_433.example.conf",
    "content": "# config for rtl_433\n\n# A valid config line is a keyword followed by an argument to the end of line.\n# Whitespace around the keyword is ignored, whitespace is space and tab\n# Comments start with a hash sign, no inline comments, empty lines are ok.\n#\n# Boolean options can be true/false, yes/no, on/off, enable/disable, or 1/0\n#\n# All options will be applied in the order given, overwriting previous values.\n# Options given on the command line are then applied left to right.\n#\n# Config files can be nested/stacked (use multiple -c and config_file = ).\n#\n# If no -c option is given the first `rtl_433.conf` found of this list will be loaded:\n# - in the current working dir, i.e. `./rtl_433.conf`\n# - in XDG_CONFIG_HOME, e.g. `$HOME/.config/rtl_433/`\n# - in SYSCONFDIR, usually PREFIX/etc, e.g. `/usr/local/etc/rtl_433/`\n\n## General options\n\n# as command line option:\n#   [-v] Increase verbosity (can be used multiple times).\n#        -v : verbose notice, -vv : verbose info, -vvv : debug, -vvvv : trace.\n# 1=fatal, 2=critical, 3=error, 4=warning (default), 5=notice, 6=info, 7=debug, 8=trace\n#verbose\n\n# as command line option:\n#   [-c <path>] Read config options from a file\n#config_file\n\n## Tuner options\n\n# as command line option:\n#   [-d <RTL-SDR USB device index>] (default: 0)\n#   [-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]\n#   [-d \"\" Open default SoapySDR device\n#   [-d driver=rtlsdr Open e.g. specific SoapySDR device\n# default is \"0\" (RTL-SDR) or \"\" (SoapySDR)\n#device        0\n\n# as command line option:\n#   [-g <gain>] (default: 0 for auto)\n# For RTL-SDR: gain in tenths of dB (\"0\" is auto).\n# For SoapySDR: gain in dB for automatic distribution (\"\" is auto), or string of gain elements.\n# E.g. \"LNA=20,TIA=8,PGA=2\" for LimeSDR.\n#gain          0\n\n# as command line option:\n#   [-t <settings>] apply a list of keyword=value settings for SoapySDR devices\n# E.g. \"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\"\n#settings      antenna=A,bandwidth=4.5M\n\n# as command line option:\n#   [-f <frequency>] [-f...] Receive frequency(s) (default: 433920000 Hz)\n# default is \"433.92M\", other reasonable values are 315M, 345M, 915M and 868M\n#frequency     433.92M\n\n# as command line option:\n#   [-H <seconds>] Hop interval for polling of multiple frequencies (default: 600 seconds)\n# default is \"600\" seconds, only used when multiple frequencies are given\n#hop_interval  600\n\n# as command line option:\n#   [-p <ppm_error] Correct rtl-sdr tuner frequency offset error (default: 0)\n# default is \"0\"\n#ppm_error     0\n\n# as command line option:\n#   [-s <sample rate>] Set sample rate (default: 250000 Hz)\n# default is \"250k\", other valid settings are 1024k, 2048k, 3200k\n#sample_rate   250k\n\n# as command line option:\n#   [-D quit | restart | pause | manual] Input device run mode options (default: quit).\n# default is \"quit\"\n#device_mode   quit\n\n## Demodulator options\n\n# as command line option:\n#   [-R <device>] Enable only the specified device decoding protocol (can be used multiple times)\n# see \"protocol\" section below.\n\n# as command line option:\n#   [-X <spec> | help] Add a general purpose decoder (prepend -R 0 to disable all decoders)\n# see \"decoder\" section below.\n\n# as command line option:\n#   [-Y auto | classic | minmax] FSK pulse detector mode.\n#pulse_detect auto\n\n# as command line option:\n#   [-Y level=<dB level>] Manual detection level used to determine pulses (-1.0 to -30.0) (0=auto).\n#pulse_detect level=0\n\n# as command line option:\n#   [-Y minlevel=<dB level>] Manual minimum detection level used to determine pulses (-1.0 to -99.0).\n#pulse_detect minlevel=-12\n\n# as command line option:\n#   [-Y minsnr=<dB level>] Minimum SNR to determine pulses (1.0 to 99.0).\n#pulse_detect minsnr=9\n\n# as command line option:\n#   [-Y autolevel] Set minlevel automatically based on average estimated noise.\npulse_detect autolevel\n\n# as command line option:\n#   [-Y squelch] Skip frames below estimated noise level to lower cpu load.\npulse_detect squelch\n\n# as command line option:\n#   [-Y ampest | magest] Choose amplitude or magnitude level estimator.\npulse_detect magest\n\n# as command line option:\n#   [-n <value>] Specify number of samples to take (each sample is 2 bytes: 1 each of I & Q)\n#samples_to_read 0\n\n## Analyze/Debug options\n\n# as command line option:\n#   [-a] Analyze mode. Print a textual description of the signal. Disables decoding\n#analyze false\n\n# as command line option:\n#   [-A] Pulse Analyzer. Enable pulse analysis and decode attempt\n#analyze_pulses false\n\n# as command line option:\n#   [-b] Out block size: 262144 (default)\n#out_block_size\n\n# as command line option:\n#   [-M time[:<options>]|protocol|level|noise[:<secs>]|stats|bits] Add various metadata to every output line.\n# Use \"time\" to add current date and time meta data (preset for live inputs).\n# Use \"time:rel\" to add sample position meta data (preset for read-file and stdin).\n# Use \"time:unix\" to show the seconds since unix epoch as time meta data. This is always UTC.\n# Use \"time:iso\" to show the time with ISO-8601 format (YYYY-MM-DD\"T\"hh:mm:ss).\n# Use \"time:off\" to remove time meta data.\n# Use \"time:usec\" to add microseconds to date time meta data.\n# Use \"time:tz\" to output time with timezone offset.\n# Use \"time:utc\" to output time in UTC.\n#   (this may also be accomplished by invocation with TZ environment variable set).\n#   \"usec\" and \"utc\" can be combined with other options, eg. \"time:iso:utc\" or \"time:unix:usec\".\n# Use \"protocol\" / \"noprotocol\" to output the decoder protocol number meta data.\n# Use \"level\" to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n# Use \"noise[:secs]\" to report estimated noise level at intervals (default: 10 seconds).\n# Use \"stats[:[<level>][:<interval>]]\" to report statistics (default: 600 seconds).\n#   level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\n# Use \"bits\" to add bit representation to code outputs (for debug).\nreport_meta level\nreport_meta noise\nreport_meta stats\nreport_meta time:usec\nreport_meta protocol\n\n# as command line option:\n#   [-y <code>] Verify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n#test_data {25}fb2dd58\n\n## File I/O options\n\n# as command line option:\n#   [-S none|all|unknown|known] Signal auto save. Creates one file per signal.\n#     Note: Saves raw I/Q samples (uint8 pcm, 2 channel). Preferred mode for generating test files.\n#signal_grabber none\n\n# as command line option:\n#   [-r <filename>] Read data from input file instead of a receiver\n#read_file FILENAME.cu8\n\n# as command line option:\n#   [-w <filename>] Save data stream to output file (a '-' dumps samples to stdout)\n#write_file FILENAME.cu8\n\n# as command line option:\n#   [-W <filename>] Save data stream to output file, overwrite existing file\n#overwrite_file FILENAME.cu8\n\n## Data output options\n\n# as command line option:\n#   [-F log|kv|json|csv|mqtt|influx|syslog|trigger|rtl_tcp|http|null] Produce decoded output in given format.\n#     Without this option the default is LOG and KV output. Use \"-F null\" to remove the default.\n#     Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\n#   [-F mqtt[:[//]host[:port][,<options>]] (default: localhost:1883)\n#     Specify MQTT server with e.g. -F mqtt://localhost:1883\n#     Default user and password are read from MQTT_USERNAME and MQTT_PASSWORD env vars.\n#     Add MQTT options with e.g. -F \"mqtt://host:1883,opt=arg\"\n#     MQTT options are: user=foo, pass=bar, retain[=0|1], <format>[=topic]\n#     Supported MQTT formats: (default is all)\n#       events: posts JSON event data, default \"<base>/events\"\n#       states: posts JSON state data, default \"<base>/states\"\n#       devices: posts device and sensor info in nested topics,\n#                default \"<base>/devices[/type][/model][/subtype][/channel][/id]\"\n#     A base topic can be set with base=<topic>, default is \"rtl_433/HOSTNAME\".\n#     Any topic string overrides the base topic and will expand keys like [/model]\n#     E.g. -F \"mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]\"\n#     With MQTT each rtl_433 instance needs a distinct driver selection. The MQTT Client-ID is computed from the driver string.\n#     If you use multiple RTL-SDR, perhaps set a serial and select by that (helps not to get the wrong antenna).\n#   [-F influx[:[//]host[:port][/<path and options>]]\n#     Specify InfluxDB 2.0 server with e.g. -F \"influx://localhost:9999/api/v2/write?org=<org>&bucket=<bucket>,token=<authtoken>\"\n#     Specify InfluxDB 1.x server with e.g. -F \"influx://localhost:8086/write?db=<db>&p=<password>&u=<user>\"\n#       Additional parameter -M time:unix:usec:utc for correct timestamps in InfluxDB recommended\n#   [-F syslog[:[//]host[:port] (default: localhost:514)\n#     Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\n#   [-F trigger:/path/to/file]\n#     Add an output that writes a \"1\" to the path for each event, use with a e.g. a GPIO\n#   [-F rtl_tcp[:[//]bind[:port]] (default: localhost:1234)\n#     Add a rtl_tcp pass-through server\n#   [-F http[:[//]bind[:port]] (default: 0.0.0.0:8433)\n#     Add a HTTP API server, a UI is at e.g. http://localhost:8433/\n# default is \"kv\", multiple outputs can be used.\noutput json\n\n# as command line option:\n#   [-K FILE | PATH | <tag> | <key>=<tag>] Add an expanded token or fixed tag to every output line.\n# If <tag> is \"FILE\" or \"PATH\" an expanded token will be added.\n# The <tag> can also be a GPSd URL, e.g.\n#   -K gpsd,lat,lon\" (report lat and lon keys from local gpsd)\n#   -K loc=gpsd,lat,lon\" (report lat and lon in loc object)\n#   -K gpsd\" (full json TPV report, in default \"gps\" object)\n#   -K foo=gpsd://127.0.0.1:2947\" (with key and address)\n#   -K bar=gpsd,nmea\" (NMEA default GPGGA report)\n#   -K rmc=gpsd,nmea,filter='$GPRMC'\" (NMEA GPRMC report)\n# Also <tag> can be a generic tcp address, e.g.\n#   -K foo=tcp:localhost:4000\" (read lines as TCP client)\n#   -K bar=tcp://127.0.0.1:3000,init='subscribe tags\\\\r\\\\n'\"\n#   -K baz=tcp://127.0.0.1:5000,filter='a prefix to match'\"\n#output_tag mytag\n\n# as command line option:\n#   [-C] native|si|customary Convert units in decoded output.\n# default is \"native\"\nconvert si\n\n# as command line option:\n#   [-T] specify number of seconds to run\n#duration 0\n\n# as command line option:\n#   [-E hop | quit] Hop/Quit after outputting successful event(s)\n#stop_after_successful_events false\n\n## Protocols to enable (command line option \"-R\")\n\n  protocol 1   # Silvercrest Remote Control\n  protocol 2   # Rubicson, TFA 30.3197 or InFactory PT-310 Temperature Sensor\n  protocol 3   # Prologue, FreeTec NC-7104, NC-7159-675 temperature sensor\n  protocol 4   # Waveman Switch Transmitter\n# protocol 6   # ELV EM 1000\n# protocol 7   # ELV WS 2000\n  protocol 8   # LaCrosse TX Temperature / Humidity Sensor\n  protocol 10  # Acurite 896 Rain Gauge\n  protocol 11  # Acurite 609TXC Temperature and Humidity Sensor\n  protocol 12  # Oregon Scientific Weather Sensor\n# protocol 13  # Mebus 433\n# protocol 14  # Intertechno 433\n  protocol 15  # KlikAanKlikUit Wireless Switch\n  protocol 16  # AlectoV1 Weather Sensor (Alecto WS3500 WS4500 Ventus W155/W044 Oregon)\n  protocol 17  # Cardin S466-TX2\n  protocol 18  # Fine Offset Electronics, WH2, WH5, Telldus Temperature/Humidity/Rain Sensor\n  protocol 19  # Nexus, FreeTec NC-7345, NX-3980, Solight TE82S, TFA 30.3209 temperature/humidity sensor\n  protocol 20  # Ambient Weather F007TH, TFA 30.3208.02, SwitchDocLabs F016TH temperature sensor\n  protocol 21  # Calibeur RF-104 Sensor\n  protocol 22  # X10 RF\n  protocol 23  # DSC Security Contact\n# protocol 24  # Brennenstuhl RCS 2044\n  protocol 25  # Globaltronics GT-WT-02 Sensor\n  protocol 26  # Danfoss CFR Thermostat\n  protocol 29  # Chuango Security Technology\n  protocol 30  # Generic Remote SC226x EV1527\n  protocol 31  # TFA-Twin-Plus-30.3049, Conrad KW9010, Ea2 BL999\n  protocol 32  # Fine Offset Electronics WH1080/WH3080 Weather Station\n  protocol 33  # WT450, WT260H, WT405H\n  protocol 34  # LaCrosse WS-2310 / WS-3600 Weather Station\n  protocol 35  # Esperanza EWS\n  protocol 36  # Efergy e2 classic\n# protocol 37  # Inovalley kw9015b, TFA Dostmann 30.3161 (Rain and temperature sensor)\n  protocol 38  # Generic temperature sensor 1\n  protocol 39  # WG-PB12V1 Temperature Sensor\n  protocol 40  # Acurite 592TXR temp/humidity, 592TX temp, 5n1, 3n1, Atlas weather station, 515 fridge/freezer, 6045 lightning, 899 rain, 1190/1192 leak\n  protocol 41  # Acurite 986 Refrigerator / Freezer Thermometer\n  protocol 42  # HIDEKI TS04 Temperature, Humidity, Wind and Rain Sensor\n  protocol 43  # Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor\n  protocol 44  # CurrentCost Current Sensor\n  protocol 45  # emonTx OpenEnergyMonitor\n  protocol 46  # HT680 Remote control\n  protocol 47  # Conrad S3318P, FreeTec NC-5849-913 temperature humidity sensor, ORIA WA50 ST389 temperature sensor\n# protocol 48  # Akhan 100F14 remote keyless entry\n  protocol 49  # Quhwa\n  protocol 50  # OSv1 Temperature Sensor\n  protocol 51  # Proove / Nexa / KlikAanKlikUit Wireless Switch\n  protocol 52  # Bresser Thermo-/Hygro-Sensor 3CH\n  protocol 53  # Springfield Temperature and Soil Moisture\n  protocol 54  # Oregon Scientific SL109H Remote Thermal Hygro Sensor\n  protocol 55  # Acurite 606TX / Technoline TX960 Temperature Sensor\n  protocol 56  # TFA pool temperature sensor\n  protocol 57  # Kedsum Temperature & Humidity Sensor, Pearl NC-7415\n  protocol 58  # Blyss DC5-UK-WH\n  protocol 59  # Steelmate TPMS\n  protocol 60  # Schrader TPMS\n# protocol 61  # LightwaveRF\n# protocol 62  # Elro DB286A Doorbell\n  protocol 63  # Efergy Optical\n# protocol 64  # Honda Car Key\n  protocol 67  # Radiohead ASK\n  protocol 68  # Kerui PIR / Contact Sensor\n  protocol 69  # Fine Offset WH1050 Weather Station\n  protocol 70  # Honeywell Door/Window Sensor, 2Gig DW10/DW11, RE208 repeater\n  protocol 71  # Maverick ET-732/733 BBQ Sensor\n# protocol 72  # RF-tech\n  protocol 73  # LaCrosse TX141-Bv2, TX141TH-Bv2, TX141-Bv3, TX141W, TX145wsdth, (TFA, ORIA) sensor\n  protocol 74  # Acurite 00275rm,00276rm Temp/Humidity with optional probe\n  protocol 75  # LaCrosse TX35DTH-IT, TFA Dostmann 30.3155 Temperature/Humidity sensor\n  protocol 76  # LaCrosse TX29IT, TFA Dostmann 30.3159.IT Temperature sensor\n  protocol 77  # Vaillant calorMatic VRT340f Central Heating Control\n  protocol 78  # Fine Offset Electronics, WH25, WH32, WH32B, WN32B, WH24, WH65B, HP1000, Misol WS2320 Temperature/Humidity/Pressure Sensor\n  protocol 79  # Fine Offset Electronics, WH0530 Temperature/Rain Sensor\n  protocol 80  # IBIS beacon\n  protocol 81  # Oil Ultrasonic STANDARD FSK\n  protocol 82  # Citroen TPMS\n  protocol 83  # Oil Ultrasonic STANDARD ASK\n  protocol 84  # Thermopro TP11 Thermometer\n  protocol 85  # Solight TE44/TE66, EMOS E0107T, NX-6876-917\n# protocol 86  # Wireless Smoke and Heat Detector GS 558\n  protocol 87  # Generic wireless motion sensor\n  protocol 88  # Toyota TPMS\n  protocol 89  # Ford TPMS\n  protocol 90  # Renault TPMS\n  protocol 91  # inFactory, nor-tec, FreeTec NC-3982-913 temperature humidity sensor\n  protocol 92  # FT-004-B Temperature Sensor\n  protocol 93  # Ford Car Key\n  protocol 94  # Philips outdoor temperature sensor (type AJ3650)\n  protocol 95  # Schrader TPMS EG53MA4, Saab, Opel, Vauxhall, Chevrolet\n  protocol 96  # Nexa\n  protocol 97  # ThermoPro TP08/TP12/TP20 thermometer\n  protocol 98  # GE Color Effects\n  protocol 99  # X10 Security\n  protocol 100 # Interlogix GE UTC Security Devices\n# protocol 101 # Dish remote 6.3\n  protocol 102 # SimpliSafe Home Security System (May require disabling automatic gain for KeyPad decodes)\n  protocol 103 # Sensible Living Mini-Plant Moisture Sensor\n  protocol 104 # Wireless M-Bus, Mode C&T, 100kbps (-f 868.95M -s 1200k)\n  protocol 105 # Wireless M-Bus, Mode S, 32.768kbps (-f 868.3M -s 1000k)\n# protocol 106 # Wireless M-Bus, Mode R, 4.8kbps (-f 868.33M)\n# protocol 107 # Wireless M-Bus, Mode F, 2.4kbps\n  protocol 108 # Hyundai WS SENZOR Remote Temperature Sensor\n  protocol 109 # WT0124 Pool Thermometer\n  protocol 110 # PMV-107J (Toyota) TPMS\n  protocol 111 # Emos TTX201 Temperature Sensor\n  protocol 112 # Ambient Weather TX-8300 Temperature/Humidity Sensor\n  protocol 113 # Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge, WS68 weather station\n  protocol 114 # Maverick ET73\n  protocol 115 # Honeywell ActivLink, Wireless Doorbell\n  protocol 116 # Honeywell ActivLink, Wireless Doorbell (FSK)\n# protocol 117 # ESA1000 / ESA2000 Energy Monitor\n# protocol 118 # Biltema rain gauge\n  protocol 119 # Bresser Weather Center 5-in-1\n  protocol 120 # Digitech XC-0324 / AmbientWeather FT005TH temp/hum sensor\n  protocol 121 # Opus/Imagintronix XT300 Soil Moisture\n  protocol 122 # FS20 / FHT\n# protocol 123 # Jansite TPMS Model TY02S\n  protocol 124 # LaCrosse/ELV/Conrad WS7000/WS2500 weather sensors\n  protocol 125 # TS-FT002 Wireless Ultrasonic Tank Liquid Level Meter With Temperature Sensor\n  protocol 126 # Companion WTR001 Temperature Sensor\n  protocol 127 # Ecowitt Wireless Outdoor Thermometer WH53/WH0280/WH0281A\n  protocol 128 # DirecTV RC66RX Remote Control\n# protocol 129 # Eurochron temperature and humidity sensor\n  protocol 130 # IKEA Sparsnas Energy Meter Monitor\n  protocol 131 # Microchip HCS200/HCS300 KeeLoq Hopping Encoder based remotes\n  protocol 132 # TFA Dostmann 30.3196 T/H outdoor sensor\n  protocol 133 # Rubicson 48659 Thermometer\n  protocol 134 # AOK Weather Station rebrand Holman Industries iWeather WS5029, Conrad AOK-5056, Optex 990018\n  protocol 135 # Philips outdoor temperature sensor (type AJ7010)\n  protocol 136 # ESIC EMT7110 power meter\n  protocol 137 # Globaltronics QUIGG GT-TMBBQ-05\n  protocol 138 # Globaltronics GT-WT-03 Sensor\n  protocol 139 # Norgo NGE101\n  protocol 140 # Elantra2012 TPMS\n  protocol 141 # Auriol HG02832, HG05124A-DCF, Rubicson 48957 temperature/humidity sensor\n  protocol 142 # Fine Offset Electronics/Ecowitt WH51, WN31, SwitchDoc Labs SM23 Soil Moisture Sensor\n  protocol 143 # Holman Industries iWeather WS5029 weather station (older PWM)\n  protocol 144 # TBH weather sensor\n  protocol 145 # WS2032 weather station\n  protocol 146 # Auriol AFW2A1 temperature/humidity sensor\n  protocol 147 # TFA Drop Rain Gauge 30.3233.01\n  protocol 148 # DSC Security Contact (WS4945)\n  protocol 149 # ERT Standard Consumption Message (SCM)\n# protocol 150 # Klimalogg\n  protocol 151 # Visonic powercode\n  protocol 152 # Eurochron EFTH-800 temperature and humidity sensor\n  protocol 153 # Cotech 36-7959, SwitchDocLabs FT020T wireless weather station with USB\n  protocol 154 # Standard Consumption Message Plus (SCMplus)\n  protocol 155 # Fine Offset Electronics WH1080/WH3080 Weather Station (FSK)\n  protocol 156 # Abarth 124 Spider TPMS\n  protocol 157 # Missil ML0757 weather station\n  protocol 158 # Sharp SPC775 weather station\n  protocol 159 # Insteon\n  protocol 160 # ERT Interval Data Message (IDM)\n  protocol 161 # ERT Interval Data Message (IDM) for Net Meters\n# protocol 162 # ThermoPro-TX2 temperature sensor\n  protocol 163 # Acurite 590TX Temperature with optional Humidity\n  protocol 164 # Security+ 2.0 (Keyfob)\n  protocol 165 # TFA Dostmann 30.3221.02 T/H Outdoor Sensor (also 30.3249.02)\n  protocol 166 # LaCrosse Technology View LTV-WSDTH01 Breeze Pro Wind Sensor\n  protocol 167 # Somfy RTS\n  protocol 168 # Schrader TPMS SMD3MA4 (Subaru) 3039 (Infiniti, Nissan, Renault)\n# protocol 169 # Nice Flor-s remote control for gates\n  protocol 170 # LaCrosse Technology View LTV-WR1 Multi Sensor\n  protocol 171 # LaCrosse Technology View LTV-TH Thermo/Hygro Sensor\n  protocol 172 # Bresser Weather Center 6-in-1, 7-in-1 indoor, soil, new 5-in-1, 3-in-1 wind gauge, Froggit WH6000, Ventus C8488A\n  protocol 173 # Bresser Weather Center 7-in-1, Air Quality PM2.5/PM10 7009970, CO2 7009977, HCHO/VOC 7009978 sensors\n  protocol 174 # EcoDHOME Smart Socket and MCEE Solar monitor\n  protocol 175 # LaCrosse Technology View LTV-R1, LTV-R3 Rainfall Gauge, LTV-W1/W2 Wind Sensor\n  protocol 176 # BlueLine Innovations Power Cost Monitor\n  protocol 177 # Burnhard BBQ thermometer\n  protocol 178 # Security+ (Keyfob)\n  protocol 179 # Cavius smoke, heat and water detector\n  protocol 180 # Jansite TPMS Model Solar\n  protocol 181 # Amazon Basics Meat Thermometer\n  protocol 182 # TFA Marbella Pool Thermometer\n  protocol 183 # Auriol AHFL temperature/humidity sensor\n  protocol 184 # Auriol AFT 77 B2 temperature sensor\n  protocol 185 # Honeywell CM921 Wireless Programmable Room Thermostat\n  protocol 186 # Hyundai TPMS (VDO)\n  protocol 187 # RojaFlex shutter and remote devices\n  protocol 188 # Marlec Solar iBoost+ sensors\n  protocol 189 # Somfy io-homecontrol\n  protocol 190 # Ambient Weather WH31L (FineOffset WH57) Lightning-Strike sensor\n  protocol 191 # Markisol, E-Motion, BOFU, Rollerhouse, BF-30x, BF-415 curtain remote\n  protocol 192 # Govee Water Leak Detector H5054, Door Contact Sensor B5023\n  protocol 193 # Clipsal CMR113 Cent-a-meter power meter\n  protocol 194 # Inkbird ITH-20R temperature humidity sensor\n  protocol 195 # RainPoint soil temperature and moisture sensor\n  protocol 196 # Atech-WS308 temperature sensor\n  protocol 197 # Acurite Grill/Meat Thermometer 01185M\n# protocol 198 # EnOcean ERP1\n  protocol 199 # Linear Megacode Garage/Gate Remotes\n# protocol 200 # Auriol 4-LD5661/4-LD5972/4-LD6313 temperature/rain sensors\n  protocol 201 # Unbranded SolarTPMS for trucks\n  protocol 202 # Funkbus / Instafunk (Berker, Gira, Jung)\n  protocol 203 # Porsche Boxster/Cayman TPMS\n  protocol 204 # Jasco/GE Choice Alert Security Devices\n  protocol 205 # Telldus weather station FT0385R sensors\n  protocol 206 # LaCrosse TX34-IT rain gauge\n  protocol 207 # SmartFire Proflame 2 remote control\n  protocol 208 # AVE TPMS\n  protocol 209 # SimpliSafe Gen 3 Home Security System\n  protocol 210 # Yale HSA (Home Security Alarm), YES-Alarmkit\n  protocol 211 # Regency Ceiling Fan Remote (-f 303.75M to 303.96M)\n  protocol 212 # Renault 0435R TPMS\n  protocol 213 # Fine Offset Electronics WS80 weather station\n  protocol 214 # EMOS E6016 weatherstation with DCF77\n  protocol 215 # Emax W6, rebrand Altronics x7063/4/x7064A, Optex 990040/50/51, Orium 13093/13123, Infactory FWS-1200, Newentor Q9, Otio 810025, Protmex PT3390A, Jula Marquant 014331/32, TechniSat IMETEO X6 76-4924-00, Weather Station or temperature/humidity sensor\n# protocol 216 # ANT and ANT+ devices\n  protocol 217 # EMOS E6016 rain gauge\n  protocol 218 # Microchip HCS200/HCS300 KeeLoq Hopping Encoder based remotes (FSK)\n  protocol 219 # Fine Offset Electronics WH45 air quality sensor\n  protocol 220 # Maverick XR-30 BBQ Sensor\n  protocol 221 # Fine Offset Electronics WN34S/L/D and Froggit DP150/D35 temperature sensor\n  protocol 222 # Rubicson Pool Thermometer 48942\n  protocol 223 # Badger ORION water meter, 100kbps (-f 916.45M -s 1200k)\n  protocol 224 # GEO minim+ energy monitor\n  protocol 225 # TyreGuard 400 TPMS\n  protocol 226 # Kia TPMS (-s 1000k)\n  protocol 227 # SRSmith Pool Light Remote Control SRS-2C-TX (-f 915M)\n  protocol 228 # Neptune R900 flow meters\n  protocol 229 # WEC-2103 temperature/humidity sensor\n  protocol 230 # Vauno EN8822C\n  protocol 231 # Govee Water Leak Detector H5054\n  protocol 232 # TFA Dostmann 14.1504.V2 Radio-controlled grill and meat thermometer\n# protocol 233 # CED7000 Shot Timer\n  protocol 234 # Watchman Sonic Advanced / Plus, Tekelek\n  protocol 235 # Oil Ultrasonic SMART FSK\n  protocol 236 # Gasmate BA1008 meat thermometer\n  protocol 237 # Flowis flow meters\n  protocol 238 # Wireless M-Bus, Mode T, 32.768kbps (-f 868.3M -s 1000k)\n  protocol 239 # Revolt NC-5642 Energy Meter\n  protocol 240 # LaCrosse TX31U-IT, The Weather Channel WS-1910TWC-IT\n  protocol 241 # EezTire E618, Carchet TPMS, TST-507 TPMS\n# protocol 242 # Baldr / RainPoint rain gauge.\n  protocol 243 # Celsia CZC1 Thermostat\n  protocol 244 # Fine Offset Electronics WS90 weather station\n# protocol 245 # ThermoPro TX-2C Thermometer and Humidity sensor\n  protocol 246 # TFA 30.3151 Weather Station\n  protocol 247 # Bresser water leakage\n# protocol 248 # Nissan TPMS\n  protocol 249 # Bresser lightning\n  protocol 250 # Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge, TFA Dostmann 30.3252.01/47.3006.01 Rain Gauge and Thermometer, ADE WS1907\n  protocol 251 # Fine Offset / Ecowitt WH55 water leak sensor\n  protocol 252 # BMW Gen4-Gen5 TPMS and Audi TPMS Pressure Alert, multi-brand HUF/Beru, Continental, Schrader/Sensata, Audi\n  protocol 253 # Watts WFHT-RF Thermostat\n  protocol 254 # Thermor DG950 weather station\n  protocol 255 # Mueller Hot Rod water meter\n  protocol 256 # ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill\n  protocol 257 # BMW Gen2 and Gen3 TPMS\n  protocol 258 # Chamberlain CWPIRC PIR Sensor\n  protocol 259 # ThermoPro Meat Thermometers, TP829B 4 probes with temp only\n# protocol 260 # Arad/Master Meter Dialog3G water utility meter\n  protocol 261 # Geevon TX16-3 outdoor sensor\n  protocol 262 # Fine Offset Electronics WH46 air quality sensor\n  protocol 263 # Vevor Wireless Weather Station 7-in-1\n  protocol 264 # Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E\n  protocol 265 # Rosstech Digital Control Unit DCU-706/Sundance/Jacuzzi\n  protocol 266 # Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95P\n  protocol 267 # ThermoPro Meat Thermometers, TP828B 2 probes with Temp, BBQ Target LO and HI\n  protocol 268 # Bresser Thermo-/Hygro-Sensor Explore Scientific ST1005H\n  protocol 269 # DeltaDore X3D devices\n# protocol 270 # Quinetic\n  protocol 271 # Landis & Gyr Gridstream Power Meters 9.6k\n  protocol 272 # Landis & Gyr Gridstream Power Meters 19.2k\n  protocol 273 # Landis & Gyr Gridstream Power Meters 38.4k\n  protocol 274 # Revolt ZX-7717 power meter\n  protocol 275 # GM-Aftermarket TPMS\n  protocol 276 # RainPoint HCS012ARF Rain Gauge sensor\n  protocol 277 # Apator Metra E-RM 30 water meter\n  protocol 278 # ThermoPro TX-7B Outdoor Thermometer Hygrometer\n  protocol 279 # Nexus, CRX, Prego sauna temperature sensor\n  protocol 280 # Homelead HG9901 (Geevon, Dr.Meter, Royal Gardineer) soil moisture/temp/light level sensor\n  protocol 281 # Maverick XR-50 BBQ Sensor\n  protocol 282 # Orion Endpoint from Badger Meter, GIF2014W-OSE, water meter, hopping from 904.4 Mhz to 924.6Mhz (-s 1600k)\n  protocol 283 # Fine Offset Electronics WH43 air quality sensor\n  protocol 284 # Baldr E0666TH Thermo-Hygrometer\n  protocol 285 # bm5-v2 12V Battery Monitor\n  protocol 286 # Universal (Reverseable) 24V Fan Controller\n  protocol 287 # Fine Offset Electronics WS85 weather station\n  protocol 288 # Oria WA150KM freezer and fridge thermometer\n  protocol 289 # Voltcraft EnergyCount 3000 (ec3k)\n  protocol 290 # Orion Endpoint from Badger Meter, GIF2020OCECNA, water meter, hopping from 904.4 Mhz to 924.6Mhz (-s 1600k)\n  protocol 291 # Geevon TX19-1 outdoor sensor\n  protocol 292 # WallarGe CLTX001 Outdoor Temperature Sensor\n  protocol 293 # Sainlogic SA8, Gevanti SA8 Weather Station\n  protocol 294 # ThermoPro TP862b TempSpike XR Wireless Dual-Probe Meat Thermometer\n  protocol 295 # Airpuxem TPMS TYH11_EU6_ZQ\n  protocol 296 # Apator Metra E-ITN 30 heat cost allocator\n  protocol 297 # ThermoPro TP211B Thermometer\n  protocol 298 # TRW TPMS OOK OEM and Clone models\n  protocol 299 # TRW TPMS FSK OEM and Clone models\n\n## Flex devices (command line option \"-X\")\n\n# Some general decoder definitions for various devices, enable as needed.\n#\n# For details about decoder definition run \"rtl_433 -X help\"\n#\n\n# If you enable these decoders you'll likely want to add \",match=<YOUR-DEVICE-ID>\"\n\n# Elro DB270 - wireless doorbell\n#\n# Device information and test files:\n# https://github.com/merbanan/rtl_433_tests/tree/master/tests/elro/db270/01\n#\n# Output sample:\n# {\"time\" : \"2018-02-14 19:11:16\", \"model\" : \"Elro_DB270\", \"count\" : 4, \"num_rows\" : 4,\n#  \"rows\" : [{\"len\" : 25, \"data\" : \"ebeaaa8\"}, {\"len\" : 25, \"data\" : \"ebeaaa8\"},\n#            {\"len\" : 25, \"data\" : \"ebeaaa8\"}, {\"len\" : 25, \"data\" : \"ebeaaa8\"}]}\n#\n#decoder n=Elro_DB270,m=OOK_PWM,s=300,l=930,r=11000,g=1500,repeats>=4,bits=25\n\n# Euroster 3000TX - programmable room thermostat\n#\n# Device information and test files:\n# https://github.com/merbanan/rtl_433_tests/tree/master/tests/euroster/3000tx/01\n#\n# Output sample:\n# {\"time\" : \"2018-02-14 19:20:20\", \"model\" : \"Euroster_3000TX\", \"count\" : 1, \"num_rows\" : 1,\n#  \"rows\" : [{\"len\" : 32, \"data\" : \"41150515\"}]}\n#\n#decoder n=Euroster_3000TX,m=OOK_MC_ZEROBIT,s=1000,r=4800,bits=32\n\n# Byron BY series door bell\n#\n# Device information and test files:\n# https://github.com/merbanan/rtl_433_tests/tree/master/tests/Byron-BY101 and Byron-BY34\n#\n# Output sample:\n# {\"time\" : \"@1.572864s\", \"model\" : \"doorbell#1\", \"count\" : 25, \"num_rows\" : 25, \"rows\" : [{\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}, {\"len\" : 21, \"data\" : \"e768c8\"}]}\n#decoder n=Byron_BY_Doorbell,m=OOK_PWM,s=500,l=1000,r=3300,g=1200,repeats>=4,bits=21,match={2}0x3\n\n# Kerui alarm system (PIR and door sensors)\n#  short is 333 us\n#  long is 972 us\n#  packet gap 11000 us\n#decoder n=Kerui,m=OOK_PWM,s=333,l=972,r=11000,g=1100,bits=25,invert,get={20}:state,get=@20:{4}:event:[10:pir 14:open 7:close 11:tamper 15:battery_low]\n\n# Golden Security GS-WDS07 door and window sensor\n#  short is 476 us + 1344 us\n#  long is 1364 us + 448 us\n#  packet gap 13972 us\n#decoder n=gswds07,m=OOK_PWM,s=476,l=1364,r=15000,g=1600,bits>=24,bits<=25,invert\n\n# Generic SCV2260 4-button remote (see rtl_433_tests/tests/generic_remote/01)\n#  short is 472 us + 1412 us\n#  long is 1428 us + 472 us\n#decoder n=generic_remote_01,m=OOK_PWM,s=472,l=1428,r=1800,g=1600,bits=25,invert,match=13cd,get=@16:{8}:event:[192:arm 12:disarm 3:home 48:sos]\n\n# Generic PT2260 PIR (see rtl_433_tests/tests/PT2262/01)\n#  short is 440 us + 1536 us\n#  long is 1428 us + 548 us\n#  packet gap 15348 us\n#decoder n=pt2260_pir,m=OOK_PWM,s=440,l=1428,r=16000,g=1700,bits=25,invert,match=755555,countonly\n"
  },
  {
    "path": "conf/silverline_doorbell.conf",
    "content": "# Silverline doorbell\n\n# A typical x1527 OTP encoder device\n# sends 12 tristate \"bits\" encoded as 2 bits each and a sync pulse\n\ndecoder {\n    name        = Silverline-Doorbell,\n    modulation  = OOK_PWM,\n    short       = 120,\n    long        = 404,\n    gap         = 468,\n    reset       = 4472,\n    bits        = 25,\n    get         = channel:@1:555,\n    get         = sound:@17:15,\n}\n\n# The flex spec supports selecting random bits with a getter mask. E.g.\n# in this x1527 tristate example every odd bit is `1` and you want to get\n# 6 bits \"channel\" from the first 12 bits (discarding every odd bit) use:\n#     get=channel:@1:555\n# Effectively this is 11 bits (starting with 1 bit offset to discard the\n# first bit) because the mask is always aligned on the first set bit\n# (i.e. 555, aaa, 000555, ... are identical).\n#\n# Imagine the 25 bits of the transmission as:\n#     1?1?1?1?1?1?1?1?1?1?1?1?1\n# The mask `555` is:\n#     0000 0101 0101 0101\n# aligned to the first set bit\n#     10101010101\n# offset 1 bit (`@1`) it selects like this:\n#     1?1?1?1?1?1?1?1?1?1?1?1?1\n#      10101010101\n#     ->\n#      ? ? ? ? ? ?\n# resulting in that 6 bit value `??????`.\n#\n# The same way 3 bits \"sound\" is `get=sound:@17:15` (where `15` here is\n# hex for the 5-bit pattern `10101` -- or use `a8` if you want to left align\n# the pattern in the byte there).\n"
  },
  {
    "path": "conf/sonoff_rm433.conf",
    "content": "# Sonoff RM433 remote controller\n# https://www.itead.cc/sonoff-rm433-remote-controller-base.html\n\n# map the 8 buttons to A->H\n# Each remote controller has its own ID (2 first bytes)\n\ndecoder {\n    name=Sonoff-RM433,\n    modulation=OOK_PWM,\n    short=260,\n    long=744,\n    reset=8000,\n    gap=800,\n    tolerance=50,\n    bits>=24,\n    invert,\n    get=@0:{20}:id,\n    get=@20:{4}:button:[8:A 12:B 4:C 9:D 2:E 5:F 1:G 3:H],\n    unique\n}\n"
  },
  {
    "path": "conf/steffen_switch.conf",
    "content": "# Steffen Switch Transmitter, HS1527 based remote button\n\n# should abort if (bb[0][0]!=0x00 || (bb[1][0]&0x07)!=0x07 || bb[1][0]!=bb[2][0] || bb[2][0]==bb[3][0])\n\ndecoder {\n    name        = Steffen-Switch,\n    modulation  = OOK_PPM,\n    short       = 370,\n    long        = 750,\n    gap         = 1080,\n    reset       = 6000,\n    bits        = 25,\n    get         = @0:{5}:code,\n    get         = @20:{4}:button:[14:A 13:B 11:C 7:D 15:ALL],\n    get         = @16:{4}:state:[15:OFF 0:ON],\n}\n"
  },
  {
    "path": "conf/tesla_charge-port-opener.conf",
    "content": "# Tesla charge port opener\n#\n#  * When the button on the charge handle is pressed the signal is repeated at 0.15s intervals for 10 times.\n#  * There are no unique codes or transmissions variants. The code is the same for all Tesla charge port\n#    handles and there is a single button.\n#  * The transmitter hardware is reported to be using a Si4010.\n#  * tolerance=20 worked in the initial test, larger value in the configuration for additional tolerance\n\ndecoder {\n    name       = Tesla charge port opener,\n    modulation = OOK_MC_ZEROBIT,\n    short      = 400,\n    reset      = 1200,\n    tolerance  = 50,\n    match      = 094aa9b38da19,\n    rows       = 5\n    repeats    = 2,\n    countonly,\n}\n"
  },
  {
    "path": "conf/tyreguard400.conf",
    "content": "# TYREGUARD400 from DAVIES CRAIG\n#\n# https://daviescraig.com.au/product/tyreguard-400-tpms-4-sensors-kit-1015\n#\n# - Type            : TPMS\n# - Freq            : 434.1 MHz\n# - Modulation      : ASK -> OOK_MC_ZEROBIT (Manchester Code with fixed leading zero bit)\n# - Sambol duration : 100us (same for l or 0)\n# - Length          : 22 bytes long\n#\n# Packet layout:\n#\n#     bytes : 1    2    3    4    5    6    7    8   9   10  11  12  13  14  15  16  17   18   19   20   21  22\n#     coded : S/P  S/P  S/P  S/P  S/P  S/P  S/P  ID  ID  ID  ID  ID  ID  ID  Pr  Pr  Temp Temp Flg  Flg  CRC CRC\n#\n# - S/P   : preamble/sync \"0xfd5fd5f\" << always fixed\n# - ID    : 6 bytes long start with 0x6b????? ex 0x6b20d21\n# - Pr    : Last 2 bytes of pressure in psi ex : 0xe8 means XX232 psi (for XX see flags bytes)\n# - Temp  : Temperature in °C offset by +40 ex : 0x2f means (47-40)=+7°C\n# - Flg   : Flags bytes => should be read in binary format :\n#   - Bit 73 : Unknown ; maybe the 20th MSB pressure bit? The sensor is not capable to reach this so high pressure\n#   - Bit 74 : add 1024 psi (19th MSB pressure bit)\n#   - Bit 75 : add  512 psi (18th MSB pressure bit)\n#   - Bit 76 : add  256 psi (17th MSB pressure bit)\n#   - Bit 77 : Acknoldge pressure leaking 1=Ack 0=No_ack (nothing to report)\n#   - Bit 78 : Unknown\n#   - Bit 79 : Leaking pressure detected 1=Leak 0=No leak (nothing to report)\n#   - Bit 80 : Leaking pressure detected 1=Leak 0=No leak (nothing to report)\n# - CRC   : CRC poly 0x31 start value 0xdd final 0x00 from 1st bit 80th bits\n#\n# To peer a new sensor to the unit, bit 79 and 80 has to be both to 1.\n#\n# NOTE: In the datasheet, it is said that the sensor can report low batterie. During my tests/research i'm not able to see this behavior. I have fuzzed all bits nothing was reported to the reader.\n#\n\ndecoder {\n    name        = TPMS-TYREGUARD400,\n    modulation  = OOK_MC_ZEROBIT,\n    short       = 100,\n    long        = 100,\n    gap         = 0,\n    reset       = 500,\n    preamble    = fd5fd5f,\n    get         = id:@0:{28},\n    get         = pression:@57:{8},\n    get         = temp:@65:{8},\n    get         = flags:@73:{8},\n    get         = add_psi:@74:{3}:[0:no 1:256 2:512 3:768 4:1024 5:1280 6:1536 7:1792],\n    get         = AckLeaking:@77:{1}:[1: yes 0:no],\n    get         = Leaking_detected:@79:{2}:[0:no 1:yes 2:yes],\n    get         = CRC:@81:{8},\n}\n"
  },
  {
    "path": "conf/valeo_car_key.conf",
    "content": "# Valeo Car Key\n# Identifies event, but does not attempt to decrypt rolling code...\n# Copyright (C) 2015 Tommy Vestermark\n\n# preamble is actually repeated e8e8e8... (with ZEROBIT removed: d1d1d1...)\n\ndecoder {\n    name=Valeo-Car-Key,\n    modulation=OOK_MC_ZEROBIT,\n    short=106,\n    reset=400,\n    bits=461,\n    preamble=d1d1d0\n}\n"
  },
  {
    "path": "conf/verisure_alarm.conf",
    "content": "# Verisure Alarm, contact switch made by Honeywell\n# Copyright (C) 2020 Benjamin Larsson\n#\n# The payload is most likely encrypted, but you can still use the id\n# and state to figure out if the contact switch is opened or closed.\n#\n# This decoder needs the classic FSK demodulator\n#\n#55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13\n#55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13\n#55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13\n#55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13\n#55fff d87e08023c005e1b3e2ed 54000 387e17fdc3ffbe04c1d13\n#\n# The bit stream might be 2 different packets of ca 10 bytes that\n# are repeated several times.\n\ndecoder {\n    name=Verisure Alarm,\n    modulation=FSK_PCM,\n    short=208,\n    long=208,\n    reset=4025,\n    gap=2500,\n    tolerance=10,\n    match={20}0xafffe,\n    get=@24:{24}:id,\n    get=@52:{12}:state,\n}\n"
  },
  {
    "path": "conf/xmas-tree-remote-2APJZ-CW002.conf",
    "content": "# Decoder for Christmas Tree Lights Remote\n# FCC ID: 2APJZ-CW002\n#\n# Reference:\n# https://fccid.io/2APJZ-CW002\n# https://github.com/merbanan/rtl_433/issues/2247\n#\n# Remote has three buttons that provide functions:\n# - Brightness down\n# - Brightness up\n# - Change light display mode\n#\n# 32 bits are sent. The first 20 bit are fixed, assumed to be the ID.\n# The last 12 bit change depending on which of the 3 buttons are pressed, defined as the action.\n\ndecoder {\n    name        = Xmas-Tree-Remote-2APJZ-CW002,\n    modulation  = OOK_PWM,\n    short       = 1000,\n    long        = 2000,\n    gap         = 0,\n    reset       = 3000,\n    bits        = 32,\n    get         = id:@0:{20},\n    get         = action:@20:{12},\n}\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "# ignore VuePress build files\n.vuepress/dist\n.temp\n.cache\nnode_modules\nyarn.lock\n# ignore copied files\nREADME.md\nCHANGELOG.md\nEXAMPLES.md\nTESTS.md\n"
  },
  {
    "path": "docs/.vuepress/config.js",
    "content": "const { defaultTheme } = require('vuepress')\nconst { searchPlugin } = require('@vuepress/plugin-search')\n\nmodule.exports = {\n  lang: 'en-US',\n  title: 'rtl_433',\n  description: 'generic data receiver for ISM/SRD bands.',\n\n  base: '/rtl_433/',\n  markdown: {\n    code: {\n      lineNumbers: false,\n    },\n  },\n\n  plugins: [\n    searchPlugin(),\n  ],\n\n  theme: defaultTheme({\n    repo: 'merbanan/rtl_433',\n    displayAllHeaders: true,\n\n    editLink: true,\n    docsBranch: 'master',\n    docsDir: 'docs',\n\n    navbar: [\n      { text: 'Projects', link: 'https://triq.org/' },\n    ],\n\n    sidebar: [\n      { text: 'Overview', link: '/' },\n      'BUILDING',\n      'BINARY_BUILDS',\n      'STARTING',\n      'CHANGELOG',\n      'CONTRIBUTING',\n      'PRIMER',\n      'IQ_FORMATS',\n      'ANALYZE',\n      'OPERATION',\n      'DATA_FORMAT',\n      'HARDWARE',\n      'INTEGRATION',\n      'LINKS',\n      'TESTS',\n    ],\n  }),\n};\n"
  },
  {
    "path": "docs/.vuepress/styles/index.scss",
    "content": "// Missing background color on bare code sections\n.theme-default-content pre, .theme-default-content pre[class*=\"language-\"] {\n  background-color: var(--code-bg-color);\n}\n"
  },
  {
    "path": "docs/ANALYZE.md",
    "content": "# Capture and analyze devices\n\n## TL;DR\n\nCapture sample data with `-S unknown`. Note down the expected measurement values from a read-out or head unit.\nCheck the spectrogram by dropping samples on https://triq.org/pdv/ (it should [look \"busy\" like this](https://triq.org/pdv/#/honeywell/2Gig-DW10/g001_344.975M_250k.cu8))\nTry analyzing each sample with `rtl_433 -A gfile.cu8` to see if there is some real data.\nUse the analyzer hints to create a plausible `-X` decoder and demod the data codes.\nThen upload some zipped samples to an issue and post a description and tabled codes and values per sample file.\n\n## Verify a transmission\n\nrtl_433 processes radio data in multiple stages. You can follow the stages and verify the data at each point.\n\nFirst a radio data packet is found and framed.\n\nGet on overview of the band. Check if the transmission is visible and in the expected frequency range.\nUse CubicSDR, Gqrx, SigDigger, SDR#, SDRangel or similar SDR UIs to verify you receice a signal.\nIf you have the SDR receiver on a headless machine try `rtl_tcp` to transport data to a GUI.\n\n:::tip\nA quick substitute for an SDR UI is to record a sample, e.g. `-w file_433.92M_250k.cu8 -T 60` (adjust for the actual frequency and sample rate).\nNow drop that .cu8 sample file on https://triq.org/pdv/ to visually inspect the spectrogram (a sideways view of the common SDR waterfalls).\n:::\n\n:::warning\nDo not plug the receiver directly in a USB port, avoid noise and use a short USB cable.\n:::\n\n## Grab a sample\n\nNote the frequency, pick a frequency a little off, e.g 50k above or below.\nThen grab the signal with rtl_433, e.g. `rtl_433 -f 433.92M -S unknown`\nVisually verify the samples in https://triq.org/pdv\n\n:::tip\nThe modes for the sample grabber are\n- `-S all`: grab all frames found\n- `-S unknown`: grab frames that are not decoded by any decoder\n- `-S known`: grab frames successfully decoded by some decoder\n:::\n\nThe band covered is equal to the sample rate.\nAt the default `433.92M` and `250k` sample rate that's `433.67 MHz` to `434.17 MHz`.\nFor the `868M` default sample rate of `1024k` that's `867.5 MHz` to `868.5 MHz`.\nFor the `868M` it's like good to pick `868.3M` for a band of `867.8 MHz` to `868.8 MHz`.\n\nTo get a clean signal remove the receiver antenna and place the device at 10cm to the receiver, that mostly isolates the transmissions.\n\n## Analyze the data packet\n\nThen next stage is demodulation of OOK or FSK data.\nA run of pulse/gap (OOK) or mark/space (FSK) timings is generated by the demod.\nRun `rtl_433 -A SAMPLE.cu8` to get an overview of the timings,\nor `rtl_433 -w OOK:- SAMPLE.cu8` to see the raw data.\nWrite the pulses to a file with `rtl_433 -w SAMPLE.ook SAMPLE.cu8`\nand visualize the file with https://triq.org/pdv\n\n:::warning\nYou need to give the sample rate if it's not 250k, look at the file name, e.g. use `rtl_433 -s 1000k -A SAMPLE_1000k.cu8`\n:::\n\nFor advanced analysis you can also try out SigRok's Pulseview with `rtl_433 -W out.sr SAMPLE.cu8`.\n\nBe sure to also try with higher sensitivity: `-Y autolevel -Y magest -M noise -M level`\n\nTry different sample rates, for 433M try `-s 1024k`, for 868M try `-s 250k` or  `-s 2048k`.\n\nTry different demods, for 433M try `-Y minmax`, for 868M try `-Y classic`.\n\n## Build a flex decoder\n\nNow build a flex decoder to slice the data into bits.\nUse the suggestion or make a guess based on the analyzed pulse data on the coding.\n\n## Document data codes\n\nThe last stage is the protocol decoding from the bit data.\nBuild a table of codes and the expected sensor values to identify where the bytes are and what is contained.\nPreferably put the codes and annotations in a [BitBench](https://triq.org/bitbench).\n\n## Example commands\n\n- capture samples not decoded by rtl_433\n  `rtl_433 -S unknown`\n- capture samples of every received frame\n  `rtl_433 -S all`\n- analyze a capture to get an overview of the timings\n  `rtl_433 -A SAMPLE.cu8`\n- show the raw data pulse data from a captured sample\n  `rtl_433 -w OOK:- SAMPLE.cu8`\n- convert pulse data from a capture to OOK file\n  `rtl_433 -w SAMPLE.ook SAMPLE.cu8`\n- try to read codes from a captured sample\n  `rtl_433 -X '...' SAMPLE.cu8`\n- open a captured sample in SigRok Pulseview\n  `rtl_433 -W SAMPLE.sr SAMPLE.cu8`\n"
  },
  {
    "path": "docs/BINARY_BUILDS.md",
    "content": "# Binary Builds\n\nFirst check to see if your distribution already has a recent enough version packaged for you.\nE.g. check [Repology](https://repology.org/project/rtl-433/versions) for a quick overview.\n\nWe currently provide 18 [binary builds](https://github.com/merbanan/rtl_433/releases) for different OS, Platform, Version and Features.\nThis is intended to quickly test out `rtl_433` or update to the newest version.\nDue to library dependencies and versions we can't guarantee our binaries to work\n(safe to try though, the worst is a \"executable not supported\" or \"library is missing\" message).\n\nLet us know in [with a comment](https://github.com/merbanan/rtl_433/issues/2859) if a binary unexpectedly does work or does not work.\n\nOS and Platform:\n- Windows: x32 and x64\n- MacOS: x86_64/Intel and arm64/M1\n- Linux: x86_64/amd64, arm64 (Raspberry Pi OS 64-bit), and armhf (Raspberry Pi OS 64-bit)\n\nVersion (only Linux):\n- Variant for OpenSSL 1.1: Ubuntu 20.04 focal, Debian 11 Bullseye, Raspberry Pi OS Legacy\n- Variant for OpenSSL 3: Ubuntu 22.04 jammy, Debian 12 Bookworm, Raspberry Pi OS\n\nFeatures:\n- with only rtlsdr\n- with rtlsdr and SoapySDR\n\n## Choosing a binary\n\nWithout SoapySDR:\n- `rtl_433-rtlsdr-MacOS-arm64.zip`: MacOS-14 for arm64/M1\n- `rtl_433-rtlsdr-MacOS-x86_64.zip`: MacOS-12 for x86_64/Intel\n- `rtl_433-rtlsdr-openssl11-Linux-amd64.zip`: Linux for x86_64/amd64 with OpenSSL 1.1\n- `rtl_433-rtlsdr-openssl11-Linux-arm64.zip`: Linux for aarch64/arm64 with OpenSSL 1.1\n- `rtl_433-rtlsdr-openssl11-Linux-armhf.zip`: Linux for armhf with OpenSSL 1.1\n- `rtl_433-rtlsdr-openssl3-Linux-amd64.zip`: Linux for x86_64/amd64 with OpenSSL 3\n- `rtl_433-rtlsdr-openssl3-Linux-arm64.zip`: Linux for aarch64/arm64 with OpenSSL 3\n- `rtl_433-rtlsdr-openssl3-Linux-armhf.zip`: Linux for armhf with OpenSSL 3\n\nWith SoapySDR:\n- `rtl_433-soapysdr-MacOS-arm64.zip`: MacOS-arm64\n- `rtl_433-soapysdr-MacOS-x86_64.zip`: MacOS-x86_64\n- `rtl_433-soapysdr-openssl11-Linux-amd64.zip`: Linux for x86_64/amd64 with OpenSSL 1.1\n- `rtl_433-soapysdr-openssl11-Linux-arm64.zip`: Linux for aarch64/arm64 with OpenSSL 1.1\n- `rtl_433-soapysdr-openssl11-Linux-armhf.zip`: Linux for armhf with OpenSSL 1.1\n- `rtl_433-soapysdr-openssl3-Linux-amd64.zip`: Linux for x86_64/amd64 with OpenSSL 3\n- `rtl_433-soapysdr-openssl3-Linux-arm64.zip`: Linux for aarch64/arm64 with OpenSSL 3\n- `rtl_433-soapysdr-openssl3-Linux-armhf.zip`: Linux for armhf with OpenSSL 3\n\n\n## Easy Install\n\nEasiest install would be to first install a distribution provided `rtl_433` package for the dependencies,\nthen use one of these binaries instead.\n\n## Install\n\nOtherwise you need to install libusb, openssl (1.1 or 3), librtlsdr, and optionally SoapySDR (plus driver modules).\n\n### MacOS\n\nAfter unpacking the binary you need to clear the file attributes:\n```\nxattr -c rtl_433\n```\nNote that `com.apple.quarantine` attributes are a useful safety feature and you should only perform this with genuine downloads from trusted sources.\n\n:::warning\nNote that [Homebrew](https://formulae.brew.sh/formula/librtlsdr) uses librtlsdr version 2.0 (with rtl-sdr blog v4 support)\nwhile [MacPorts](https://ports.macports.org/port/rtl-sdr/details/) uses version 0.6\nbut those are compatible.\nYou'll need to update the binary to run on MacPorts:\n```\ninstall_name_tool -change @rpath/librtlsdr.2.dylib @rpath/librtlsdr.0.dylib rtl_433\n```\n:::\n\n#### MacPorts\n\n(s.a. the [MacPorts port](https://ports.macports.org/port/rtl_433/))\n```\nsudo port install libusb openssl3 rtl-sdr\n```\noptionally add\n```\nsudo port install SoapySDR\n```\n\n#### HomeBrew\n\n(s.a. the [Homebrew Formula](https://formulae.brew.sh/formula/rtl_433))\n```\nbrew install libusb\nbrew install openssl@3\nbrew install librtlsdr\n```\noptionally add\n```\nbrew install soapysdr\n```\n\n### Linux\n\nOn Debian, Ubuntu, and Raspberry Pi OS (and similar Debian-based OS supporting the `apt` package manager):\n\n```\nsudo apt-get install -y rtl-sdr openssl soapysdr-tools\n```\n\nOr with out any tools, just the libs for Bullseye / Focal\n```\nsudo apt-get install -y librtlsdr0 libssl1.1 libsoapysdr0.7\n```\n\nSimilar for Bookworm / Jammy\n```\nsudo apt-get install -y librtlsdr0 libssl3 libsoapysdr0.8\n```\n"
  },
  {
    "path": "docs/BUILDING.md",
    "content": "# Building rtl_433\n\nrtl_433 currently supports these input types:\n* [RTL-SDR](http://sdr.osmocom.org/trac/wiki/rtl-sdr) (optional, recommended)\n* [SoapySDR](https://github.com/pothosware/SoapySDR/wiki) (optional)\n* files: CU8, CS16, CF32 I/Q data, U16 AM data (built-in)\n* rtl_tcp remote data servers (built-in)\n\nBuilding rtl_433 with RTL-SDR or SoapySDR support is optional but using RTL-SDR is highly recommended.\nThe libraries and header files for RTL-SDR and/or SoapySDR should be installed beforehand.\n\n## Nightly builds\n\nSome distributions offer nightly builds.\n\n### openSUSE\n\nopenSUSE users of at least Leap 42.3 or Tumbleweed can add the repository with daily builds:\n\n    $ sudo zypper addrepo -f obs://home:mnhauke:rtl_433:nightly/rtl_433\n    rtl_433-nightly\n    $ sudo zypper install rtl_433\n\nThe usual update mechanism will now keep the rtl_433 version current.\n\n### Fedora\n\nFedora users (38, 39 and Rawhide) can add the following copr repository to get nightly builds:\n\n    $ sudo dnf copr enable tvass/rtl_433\n    $ sudo dnf install rtl_433\n\nThe usual update mechanism will now keep the rtl_433 version current.\n\n## Linux / Mac OS X\n\nDepending on your system, you may need to install the following libraries.\n\nDebian:\n\n```\nsudo apt-get install libtool libusb-1.0-0-dev librtlsdr-dev rtl-sdr build-essential cmake pkg-config\n```\n\n* If you require TLS connections, also install `libssl-dev` (`sudo apt-get install libssl-dev`).\n\nCentos/Fedora/RHEL with EPEL repo using cmake:\n\n  * If `dnf` doesn't exist, use `yum`.\n  * If you require TLS connections, install `openssl-devel`.\n\n```\nsudo dnf install libtool libusb1-devel rtl-sdr-devel rtl-sdr cmake\n```\n\nMac OS X with MacPorts:\n\n* If you require TLS connections, install `openssl` from either MacPorts or Homebrew.\n\n```\nsudo port install rtl-sdr cmake\n```\n\nMac OS X with Homebrew:\n\n    brew install rtl-sdr cmake pkg-config\n\n### CMake\n\nGet the `rtl_433` git repository if needed:\n\n    git clone https://github.com/merbanan/rtl_433.git\n\nInstallation using CMake and Make (commonly available):\n\n    cd rtl_433/\n    cmake -B build\n    cmake --build build --target install\n\nInstallation using CMake and Ninja (newer and faster):\n\n    cd rtl_433/\n    cmake -DFORCE_COLORED_BUILD:BOOL=ON -GNinja -B build\n    cmake --build build -j 4\n    cmake --build build --target install\n\nIf installing to a global prefix (e.g. the default `/usr/local`) then instead run `make install` with privileges, .i.e.\n\n    sudo cmake --build build --target install\n\nUse CMake with `-DENABLE_SOAPYSDR=ON` (default: `AUTO`) to require SoapySDR (e.g. with Debian needs the package `libsoapysdr-dev`), use `-DENABLE_RTLSDR=OFF` (default: `ON`) to disable RTL-SDR if needed.\nE.g. use:\n\n    cmake -DENABLE_SOAPYSDR=ON ..\n\n:::tip\nIf you use CMake older than 3.13 (check `cmake --version`), you need to build using e.g. `mkdir build ; cd build ; cmake .. && cmake --build .`\n:::\n\n:::tip\nIn CMake 3.6 or older the OpenSSL search seems broken, you need to use `cmake -DENABLE_OPENSSL=NO ..`\n:::\n\n:::warning\nIf you experience trouble with SoapySDR when compiling or running: you likely mixed version 0.7 and version 0.8 headers and libs.\nPurge all SoapySDR packages and source installation from /usr/local.\nThen install only from packages (version 0.7) or only from source (version 0.8).\n:::\n\n## Package maintainers\n\nTo properly configure builds without relying on automatic feature detection you should set all options explicitly, e.g.\n\n    cmake -DENABLE_RTLSDR=ON -DENABLE_SOAPYSDR=ON -DENABLE_OPENSSL=ON -DBUILD_DOCUMENTATION=OFF -DCMAKE_BUILD_TYPE=Release -GNinja -B build\n    cmake --build build -j 10\n    DESTDIR=/tmp/destdir cmake --build build --target install\n\n## Windows\n\n### Visual Studio 2017\n\nYou need [PothosSDR](https://downloads.myriadrf.org/builds/PothosSDR/) installed to get RTL-SDR and SoapySDR libraries.\nAny recent version should work, e.g. [2021.07.25-vc16](https://downloads.myriadrf.org/builds/PothosSDR/PothosSDR-2021.07.25-vc16-x64.exe).\n\nWhen installing PothosSDR choose \"Add PothosSDR to the system PATH for the current user\".\n\nFor TLS support (mqtts and influxs) you need OpenSSL installed.\nE.g. [install Chocolatey](https://chocolatey.org/install) then open a Command Prompt and\n\n    choco install openssl\n\nClone the project, e.g. open Visual Studio, change to \"Team Explorer\" > \"Projects\" > \"Manage Connections\" > \"Clone\"\nand enter `https://github.com/merbanan/rtl_433.git`\n\nIf you want to change options, in the menu select \"CMake\" > \"Change CMake Settings\" > \"rtl433\", select e.g. \"x64-Release\", change e.g.\n\n    \"buildRoot\": \"${workspaceRoot}\\\\build\",\n    \"installRoot\": \"${workspaceRoot}\\\\install\",\n\nTo start a build use in the menu e.g. \"CMake\" > \"Build all\"\n\nOr build at the Command Prompt without opening Visual Studio. Clone rtl_433 sources, then\n\n    cd rtl_433\n    cmake -G \"Visual Studio 15 2017 Win64\" -B build\n    cmake --build build\n\n### MinGW-w64\n\nYou'll probably want librtlsdr and libusb.\n\nlibusb has prebuilt binaries for windows,\nlibrtlsdr needs to be built (or extracted from the PothosSDR installer)\n\n#### librtlsdr\n\ntaken and adapted from here: https://www.onetransistor.eu/2017/03/compile-librtlsdr-windows-mingw.html\n\n* install [MinGW-w64](https://mingw-w64.org/) and [CMake](https://cmake.org/)\n    * it's easiest if you select the option to include CMake in your path, otherwise you'll need to do this manually\n* download the libusb binaries from https://sourceforge.net/projects/libusb/files/libusb-1.0/ or from https://libusb.info/\n    * take the latest release and then download the .7z file, the other file contains the sources (or 'windows binaries' on the .info website)\n* extract the archive and open the extracted folder\n* copy the contents of the include folder to `<mingw_installation_folder>/include`\n* copy the `mingw64/dll/libusb-1.0.dll.a` file to `<mingw_installation_folder>/lib\n* copy the `mingw64/dll/libusb-1.0.dll` file to `<mingw_installation_folder>/bin`\n* download the source code of librtlsdr https://github.com/steve-m/librtlsdr\n* go into the librtlsdr folder\n* open CMakeLists.txt with an editor that knows unix line endings\n* go to `# Find build dependencies` (around line 65) and comment/remove the line with `find_package(Threads)`\n* add the following lines instead:\n\n```\nSET(CMAKE_THREAD_LIBS_INIT \"-lpthread\")\nSET(CMAKE_HAVE_THREADS_LIBRARY 1)\nSET(Threads_FOUND TRUE)\n```\n\n* go into the cmake/modules folder and open FindLibUSB.cmake with a text editor\n* find the lines with the following text in them\n\n```\n/usr/include/libusb-1.0\n/usr/include\n/usr/local/include\n```\n\n* add some extra lines to point to the MinGW include folder where you extracted libusb-1.0, making it look like this\n    * take note of the \"\" around the folder names, these are needed when there are spaces in the folder name\n    * you'll need to find out the exact paths for your system\n\n```\n/usr/include/libusb-1.0\n/usr/include\n/usr/local/include\n\"C:/Program Files/mingw-w64/x86_64-8.1.0-posix-seh-rt_v6-rev0/mingw64/include\"\n\"C:/Program Files/mingw-w64/x86_64-8.1.0-posix-seh-rt_v6-rev0/mingw64/include/libusb-1.0\"\n```\n\n* open a MinGW terminal in the librtlsdr folder\n* generate makefiles for MinGW: `cmake -G \"MinGW Makefiles\" -B build`\n* build the librtlsdr library: `cmake --build build`\n\n#### rtl_433\n\n* clone the rtl_433 repository and cd into it\n* run `cmake -G \"MinGW Makefiles\" -B build` in the build directory\n* run cmake-gui (this is easiest)\n* set the source (the rtl_433 source code directory) and the build directory (one might create a build directory in the source directory)\n* click configure\n* select the grouped and advanced tickboxes\n* go into the librtlsdr config group\n* point the `LIBRTLSDR_INCLUDE_DIRS` to the include folder of the librtlsdr source\n* point the `LIBRTLSDR_LIBRARIES` to the `librtlsdr.dll.a` file in the <librtlsdr_source>/build/src folder\n    * that's the one you've built earlier\n* start a MinGW terminal and run `cmake --build build` to build\n    * when something in the tests folder doesn't build, you can disable it by commenting out `add_subdirectory(tests)` in the CMakeLists.txt file in the source folder of rtl_433\n* rtl_433.exe should be built now\n* you need to place it in the same folder as librtlsdr.dll and libusb-1.0.dll (you should have seen both of them by now)\n* good luck!\n\nIf your system is missing or you find these steps are outdated please PR an update or open an issue.\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "# Contributing guidelines\n\nThe rtl_433 project is built on the work of many contributors analyzing,\ndocumenting, and coding device support.\nWe are happy to accept your contribution of yet another sensor!\n\nPlease check if your contribution is following these guidelines\nto improve the feedback loop and decrease the burden for the maintainers.\n\n## Adding a new decoder\n\nDecoders for new device protocols are welcome.\nYou need to know some C and register the decoder with one line, the rest is automatic.\n\nTo get started follow these steps to add a new decoder:\n- Clone the repo and create a feature branch.  \n  E.g. Clone in Github, checkout and then `git checkout -b feat-mydevice`\n- Copy some decoder as template, either one that is already close to what you need or `src/devices/new_template.c`.  \n  E.g. `cp src/devices/new_template.c src/devices/my_device.c`\n- Change the new decoder (at least the `r_device` name and `.disabled = 0`).  \n  E.g. change `r_device const new_template =` to `r_device const my_device =`,\n  and `new_template_decode` to `my_device_decode`,\n  and `.disabled = 3` to `.disabled = 0`\n- Edit `include/rtl_433_devices.h`  \n  E.g. add `DECL(my_device) \\`\n- Add your files with Git (no need to commit yet)  \n  E.g. `git add src/devices/my_device.c include/rtl_433_devices.h`\n- Run `./maintainer_update.py` to add the CMake compile rules\n- Compile, add files with Git again  \n  E.g. `git add src/CMakeLists.txt`\n- Code and test your decoder, try to follow our code style (you can generally use clang-format).\n- Run `./maintainer_update.py` again for the readme files.\n- Review and commit your changes, push the changes then create a PR.\n\n## Commit messages\n\nPull-Requests (PR) will be added as squash commit\nand the commit message will likely be updated to follow this format.\n\nFor general work, e.g. adding or changing decoders\nthe commit messages should follow a format of\n\n    <verb> [<decoder_model>] <commit_message>\n\nVerb must be one of the following:\n\n- `Add`: for new additions, e.g. device support\n- `Fix`: for changes that don't change anything to input/output (security related or bug fixing)\n- `Remove`: for changes that remove behaviour (e.g. some old algorithms are cleaned up)\n- `Change`: for changes that modify input/output behaviour (e.g. added checksums, preambles)\n- `Improve`: for improvements without changes in normal output/behaviour\n\nDon't prefix general work, e.g. adding a decoder should be `Add support for TheDevice`.\n\nOther commit messages should follow the common format of\n\n    <area_of_work>: <verb> <commit_message>\n\nArea of work is optional and may be one of the following:\n\n- `minor`: other small changes that do not warrant a changelog entry\n- `build`: for build / build system / ci related work\n- `docs`: for documentation related work, both in code and readme/docs folder\n- `test`: for test related work\n- `deps`: for changes related to (external) dependencies (e.g. soapysdr is updated or mongoose is updated)\n- `examples`: for changes related to examples and scripts\n- `cosmetics`: for housekeeping work, code style changes\n\n## Supporting Additional Devices and Test Data\n\nSome device protocol decoders are disabled by default. If you have one of the default-disabled devices\nthen enable all needed device protocols with the `-R` option.\nThis will likely produce false positives, use with caution.\n\nThe first step in decoding new devices is to record the signals using `-S unknown`.\nThe signals will be stored individually in files named g**NNN**\\_**FFF**M\\_**RRR**k.cu8 :\n\n| Parameter | Description\n|---------|------------\n| **NNN** | signal grabbed number\n| **FFF** | frequency\n| **RRR** | sample rate\n\nThis file can be played back with `rtl_433 -r gNNN_FFFM_RRRk.cu8`.\n\nThese files are vital for understanding the signal format as well as the message data.  Use the analyzer\nwith `-A` to look at the recorded signal and determine the pulse characteristics, e.g. `rtl_433 -r gNNN_FFFM_RRRk.cu8 -A`.\n\nMake sure you have recorded a proper set of test signals representing different conditions together\nwith any and all information about the values that the signal should represent. For example, make a\nnote of what temperature and/or humidity is the signal encoding. Ideally, capture a range of data\nvalues, such a different temperatures, to make it easy to spot what part of the message is changing.\n\nAdd the data files, a text file describing the captured signals, pictures of the device and/or\na link the manufacturer's page (ideally with specifications) to the rtl_433_tests\ngithub repository. Follow the existing structure as best as possible and send a pull request.\n\nhttps://github.com/merbanan/rtl_433_tests\n\nPlease don't open a new github issue for device support or request decoding help from others\nuntil you've added test signals and the description to the repository.\n\nThe rtl_433_test repository is also used to help test that changes to rtl_433 haven't caused any regressions.\n\n## Code style\n\nIndentation is 4 spaces. Check with `clang-format`.\n\nIndent `data_make()` nicely tabular and surround it with\n```\n/* clang-format off */\n...\n/* clang-format on */\n```\n\nStart your file with a copyright note (indent 4 spaces) like:\n```\n/** @file\n    Bresser Weather Center 5-in-1.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n```\n\nName your decode function `foobar_decode`.\nPut documentation (markdown, no indent) before the decode function:\n```\n/**\nBresser Weather Center 7-in-1, outdoor sensor.\n\n...supported models etc.\n\nData layout:\n\n    IIII F TTT HH CC\n\n- I: 16 bit ID\n- F: 4 bit flags\n- T: 12 bit temperature, scale 10\n- H: 8 bit humidity\n- C: 8 bit CRC-8, poly 0x81\n\nFormat string:\n\n    ID:16h FLAGS:4h TEMP:12h HUMI:8h CRC:8h\n\n...Decoding notes like endianness, signedness\n*/\n```\n\nThe keys in `data_make()` need to contain\n- `\"model\"`, short unique key for this decoder\n- `\"id\", `a unique sensor ID\n- `\"mic\"`, if applicable the integrity check, e.g. `\"PARITY\"`, `\"SUM\"`, `\"CRC\"`, or `\"DIGEST\"`.\n\nSee [JSON Data fields](DATA_FORMAT.md) for common keys.\n"
  },
  {
    "path": "docs/DATA_FORMAT.md",
    "content": "# JSON Data fields\n\nSee also the discussion and rationale in https://github.com/merbanan/rtl_433/pull/827\n\n## Message Data\nThese fields are the primary data fields containing the most basic message data and used to identify the specific device.\nFor some devices these are the *only* fields contained in the message, as the message itself constitutes an event from this\nparticular device model.\n\n* **time** (string) (Required)\n  * Time stamp. String containing date and time of when the message was received. Format and timezone is dependent on\n    current locale unless options like `-M time:unix` or `-M time:iso` and `-M time:utc` are used.\n\n* **type** (string) (Optional)\n  * Classification of the general device type. Currently only used for `\"TPMS\"`.\n\n* **model** (string) (Required)\n  * Device model. Human readable string concisely describing the device by manufacturer name\n    and manufacturers model designation according to the following syntax: `\"<Manufacturer>-<Model>\"`.\n  * It is common for devices to be sold under different brands, however the Original Equipment Manufacturer name\n    shall be used, where possible to identify.\n  * Avoid redundant word like \"sensor\", \"wireless\" etc. unless it is part of the manufacturers model designation.\n  * Avoid adding device type designations like \"Switch\", \"Temperature\", \"Thermostat\", \"Weather Station\" etc. Device type can\n    be inferred from the data content.\n  * Avoid all non-alphanumeric characters, especially: `\"/&$*#+[]()\"`.\n  * Length of *model* string should be less than 32 characters.\n\n* **subtype** (string) (Optional)\n  * Device type or function in a common protocol. Examples are various sensors, triggers, keyfob in wireless security.\n\n* **id** (integer, rarely string) (Optional)\n  * Device identification. Used to differentiate between devices of same *model*.\n    Depending on device model it may be a non-volatile value programmed into the device,\n    a volatile value that changes at each power on (or battery change), or a value configurable by\n    user e.g. by switch or jumpers. No assumptions should be made to the id value other than it contains\n    a unique sequence of alphanumeric characters.\n  * Length of *id* should be less than 16 characters.\n\n* **channel** (integer, rarely string) (Optional)\n  * Secondary device identification. For devices with more than one identification value\n    (e.g. both an internal value and a switch).\n\n* **mic** (string) (Optional)\n  * Message integrity check. String describing the method used for ensuring the data integrity\n    of the message. Protocol decoders for devices without mic will be disabled by default as\n    they are prone to excessive false positives.\n  * Possible values:\n    * \"CRC\" - Cyclic Redundancy Check.\n    * \"CHECKSUM\" - Accumulated sum of data.\n    * \"PARITY\" - Parity bit (odd, even, multiple)\n\n## Common Device Data\nVarious data fields, which are common across devices of different types.\n\n* **battery_ok** (double) (Optional)\n  * Battery status indication as a level between 0 (empty) and 1 (full). If the sensor can only report a binary status the value shall be 1 for \"OK\" and 0 for \"LOW\".\n\n* **battery_V** (**battery_mV**) (double) (Optional)\n  * Battery level in Volts. Should be supplemented by *battery_ok* status indication if possible.\n\n## Sensor Data\nDue to the large variance in sensor types this list of common values is non-exhaustive. Additional data value fields should follow the form: `<Type>_<Unit>`, where *Unit* should be in sensors native units insofar possible with no conversion.\nAutomatic unit conversion can be performed with the `-C si` or `-C customary` option.\n\nExamples:\n\n* **temperature_C** (**temperature_F**) (double) (Optional)\n  * Temperature from a temperature sensor in degrees Celsius (Fahrenheit).\n\n* **setpoint_C** (**setpoint_F**) (double) (Optional)\n  * Thermal set point of a thermostat device in degrees Celsius (Fahrenheit).\n\n* **humidity** (double) (Optional)\n  * Humidity from a hygrometer sensor in % relative humidity\n\n* **moisture** (double) (Optional)\n  * Moisture from a soil probe in % relative saturation\n\n* **wind_dir_deg** (double) (Optional)\n  * Wind direction from wind sensor in compass direction degrees.\n\n* **wind_avg_m_s** (**wind_avg_km_h**, **wind_avg_mi_h**) (double) (Optional)\n  * Average wind speed from wind sensor in m/s. Averaging time is sensor dependent.\n\n* **wind_max_m_s** (**wind_max_km_h**, **wind_max_mi_h**) (double) (Optional)\n  * Gust wind speed from wind sensor in m/s.\n\n* **rain_mm** (**rain_in**) (double) (Optional)\n  * Rainfall from rain sensor in mm (inches) since last reset. Reset method is device dependent.\n\n* **rain_rate_mm_h** (**rain_rate_in_h**) (double) (Optional)\n  * Rainfall rate from rain sensor in mm per hour (inches per hour).\n\n* **pressure_hPa** (**pressure_psi**) (double) (Optional)\n  * Air pressure from barometer or Tire Pressure Monitor in hPa (psi)\n"
  },
  {
    "path": "docs/HARDWARE.md",
    "content": "# Hardware tested with rtl_433\n\nrtl_433 is known to work with or tested with the following SDR hardware:\n\n## RTL-SDR\n\nActively tested and supported are Realtek RTL2832 based DVB dongles (and other similar devices supported by RTL-SDR).\n\nSee also [RTL-SDR](https://github.com/osmocom/rtl-sdr/).\n\n## SoapySDR\n\nActively tested and supported are\n- [LimeSDR USB](https://www.crowdsupply.com/lime-micro/limesdr)\n- [LimeSDR mini](https://www.crowdsupply.com/lime-micro/limesdr-mini)\n- [LimeNet Micro](https://www.crowdsupply.com/lime-micro/limenet-micro)\n- [PlutoSDR](https://www.analog.com/en/design-center/evaluation-hardware-and-software/evaluation-boards-kits/adalm-pluto.html)\n- [SDRplay](https://www.sdrplay.com/) (RSP1A tested)\n- [HackRF One](https://greatscottgadgets.com/hackrf/) (reported, we don't have a receiver)\n- [SoapyRemote](https://github.com/pothosware/SoapyRemote/wiki)\n\nLimeSDR and LimeNet engineering samples were kindly provided by [MyriadRf](https://myriadrf.org/).\n\nSee also [SoapySDR](https://github.com/pothosware/SoapySDR/).\n\n## Not supported\n\n- Ultra cheap 1-bit (OOK) receivers, and antenna-on-a-raspi-pin\n- CC1101, and alike special purpose / non general SDR chips\n"
  },
  {
    "path": "docs/INTEGRATION.md",
    "content": "# Integration\n\nIntegration of rtl_433 output into various home automation gateways.\n\n:::tip\nIf you are a user of one these systems, please help to confirm and extend the information here.\n:::\n\n## openHAB\n\n[openHAB](https://www.openhab.org/) - open source automation software for your home\n\nSee the wiki page https://github.com/merbanan/rtl_433/wiki/How-to-integrate-rtl_433-sensors-into-openHAB-via-MQTT\n\nSome help comes from https://community.openhab.org/t/rtl-433-to-mqtt/80652/3\n\nRun\n\n    rtl_433 -F \"mqtt://192.168.178.42:1883,retain=0,devices=sensors/rtl_433/P[protocol]/C[channel]\"\n\nThis produces some topics in the broker like this:\n\n    sensors/rtl_433/P25/C1/id 147\n    sensors/rtl_433/P25/C1/temperature_C 33.200001\n    sensors/rtl_433/P25/C1/rain_mm 107.699997\n    sensors/rtl_433/P25/C1/battery_ok 1\n    sensors/rtl_433/P25/C1/mic CRC\n\nYou can easily set up some MQTT things then:\n\n    Bridge mqtt:broker:My-MQTT \"MQTT Broker\" @ \"RTL433\" [\n      host=\"192.168.x.x\",\n      secure=false,\n      port=1883,\n      qos=0,\n      retain=false,\n      clientid=\"Oh2Mqtt2Thing\",\n      keep_alive_time=30000,\n      reconnect_time=60000\n    ]\n    {\n        Thing topic RTL_433 \"433MHz Empfänger\" @ \"RTL433\"  {\n          Channels:\n              Type number : temp \"Temperatur\" [ stateTopic=\"sensors/rtl_433/P25/C1/temperature_C\" ]\n              Type number : hum  \"Luftfeuchtigkeit\" [ stateTopic=\"sensors/rtl_433/P25/C1/humidity\" ]\n              Type switch : batt \"Battery schwach\" [ stateTopic=\"sensors/rtl_433/P25/C1/battery\", transformationPattern=\"MAP:battery.map\"]\n        }\n    }\n\n## Home Assistant\n\n[Home Assistant](https://www.home-assistant.io/) - Open source home automation\n\nHome Assistant has good MQTT support and can read rtl_433 event topics.\n\nAssuming rtl_433 is started with\n\n    rtl_433 -C si -M time:unix:usec:utc -F mqtt\n\nyou can set up temperature and humidity sensors in Home Assistant with\n\n    sensor:\n\n      - name: temperature_raw\n        state_topic: \"rtl_433/host/devices/Prologue-TH/5/3/+/temperature_C\"\n        platform: mqtt\n        device_class: temperature\n        unit_of_measurement: \"°C\"\n        force_update: true\n        expire_after: 610\n\n      - name: humidity_raw\n        state_topic: \"rtl_433/host/devices/Prologue-TH/5/3/+/humidity\"\n        platform: mqtt\n        device_class: humidity\n        unit_of_measurement: \"%\"\n        force_update: true\n        expire_after: 610\n\nYou may want to postprocess the received values with something like\n\n    sensor:\n\n      - name: temperature\n        entity_id: sensor.temperature_raw\n        platform: filter\n        filters:\n          - filter: outlier\n            window_size: 2\n            radius: 3.0\n\nSee [the Home Assistant documentation](https://www.home-assistant.io/integrations/sensor.mqtt/)\nfor more information.\n\nSee also [rtl_433_mqtt_hass.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_mqtt_hass.py)\nMQTT Home Assistant auto discovery.\n\n## Domoticz\n\n[Domoticz](http://www.domoticz.com/) - Home Automation System\n\nDomoticz has built-in support for reading from rtl_433 using pipes.\n\nThere is also a newer plugin using MQTT: [enesbcs/pyrtl433](https://github.com/enesbcs/pyrtl433).\n\n:::warning\nTesting and example needed\n:::\n\n## NodeRED\n\n[NodeRED](https://nodered.org/) - Flow-based programming for the Internet of Things\n\nNode RED has built-in support for reading from MQTT and thus rtl_433 events.\n\n:::warning\nExample needed\n:::\n\n## Databases\n\nYou likely need to filter and transform rtl_433's output before sending it to a database.\nIt's recommended you read the JSON data and process it to your specific requirements.\n\nSome example pipes/relays for rtl_433 JSON data. Should work with Python 2 and also Python 3.\n\nThe `pipe` examples read JSON output from `rtl_433` using a pipe, i.e.\n\n    rtl_433 -F json ... | rtl_433_statsd_pipe.py\n\nThe `relay` examples consumes the (UDP) Syslog output from rtl_433 (or a legacy plain JSON datagram).\nBasically run `rtl_433` with `-F syslog:127.0.0.1:1433` and the relay script as an unrelated process, i.e.\n\n    rtl_433_mqtt_relay.py &\n    rtl_433 -F syslog:127.0.0.1:1433\n\n### SQL\n\nAn example to push data to SQL is at [Domifry/RTL_433_SQL_Connection](https://github.com/Domifry/RTL_433_SQL_Connection/),\nsee also [#1828](https://github.com/merbanan/rtl_433/issues/1828).\n\n### RRD\n\nSee [rtl_433_rrd_relay.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_rrd_relay.py)\n\n### Statsd\n\nSee [rtl_433_statsd_pipe.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_statsd_pipe.py)\nSee [rtl_433_statsd_relay.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_statsd_relay.py)\n\n### Collectd\n\nSee [rtl_433_collectd_pipe.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_collectd_pipe.py)\n\n### Graphite\n\nSee [rtl_433_graphite_relay.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_graphite_relay.py)\n\n### InfluxDB\n\nThere is built-in support for an InfluxDB output.\n\nSpecify an InfluxDB 2.0 server with e.g.\n\n    rtl_433 -F \"influx://localhost:9999/api/v2/write?org=<org>&bucket=<bucket>,token=<authtoken>\"\n\nSpecify an InfluxDB 1.x server with e.g.\n\n    rtl_433 -F \"influx://localhost:8086/write?db=<db>&p=<password>&u=<user>\"\n\nIt is recommended to additionally use the option `-M time:unix:usec:utc` for correct timestamps in InfluxDB.\n\nIf you want to filter messages before they are inserted into the InfluxDB or if you want to transform the data\nsee [rtl_433_influxdb_relay.py](https://github.com/merbanan/rtl_433/tree/master/examples/rtl_433_influxdb_relay.py)\nfor an example script.\n\nThe [rtl433_influx](https://github.com/azrdev/rtl433_influx/) project allows to dump the JSON output of rtl_433 into InfluxDB.\n\nInfluxDB also comes with MQTT integration through Telegraf,\nsee [MQTT Monitoring](https://www.influxdata.com/integration/mqtt-monitoring/)\nand [MQTT Consumer Input Plugin](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/mqtt_consumer).\n\n### MySQL\n\nTBD.\n\n### Sqlite\n\nTBD.\n"
  },
  {
    "path": "docs/IQ_FORMATS.md",
    "content": "# Introduction to I/Q formats\n\nSDR data is exchanged and saved in different formats.\nThere are formats for raw I/Q sample data and formats with demodulated pulse data.\n\n## I/Q sample data formats\n\nI/Q stands for \"In-phase / Quadrature\", the raw data format used by SDR receivers and transmitters.\nA sample consists of an I and Q value, each commonly of 8, 12, or 16-bit. This is called \"interleaved\" in audio or video data.\n\nThe data can be processed similar to a two-channel audio signal, although at a much higher sample rate.\n\n:::tip\nCommon sample rates with RTL-SDR receivers are 250 kHz and 1024 kHz, also 1 MHz (1000 kHz).\n:::\n\nThe nature of an I/Q sample allows to use a bandwidth equal to the sample rate\n(with a purely real signal the Nyquist-Shannon sampling theorem would only allow half the bandwidth).\n\nGenerally the data formats are header- (and thus metadata-)less,\nthe used center frequency and sample rate must be transferred separately or encoded in the filename.\n\nFormats differ in sample-(bit-)width and (bit-)number format,\nused bit-widths are 4, 8, 12, 16, 32, and 64, bit-formats are unsigned integer, signed integer, and float:\n\n- `.cu4`: Complex (I/Q), Unsigned integer, 4-bit per value (8 bit per sample)\n- `.cs4`: Signed integer\n- `.cu8` (`.data` `.complex16u`): 8-bit per value (16 bit per sample)\n- `.cs8` (`.complex16s`)\n- `.cu12`: 12-bit per value (24 bit per sample)\n- `.cs12`\n- `.cu16`: 16-bit per value (32 bit per sample)\n- `.cs16`\n- `.cu32`: 32-bit per value (64 bit per sample)\n- `.cs32`\n- `.cu64`: 64-bit per value (128 bit per sample)\n- `.cs64`\n- `.cf32` (`.cfile` `.complex`): Float, 32-bit per value (64 bit per sample)\n- `.cf64`: Double Float, 64-bit per value (128 bit per sample)\n\nAlso used but rarely supported are audio files containing I/Q data:\n- `.wav`\n- `.bwf`:  Broadcast Wave Format\n\nThe \"native\" format for RTL-SDR receivers is `.cu8`, for other receivers likely `.cs16`.\nMost receivers only sample with 12-bit per channel, using `.cs12` will be more compact although not as widely supported.\n\nThe rtl_433 program supports most of these formats and allows to read, write, or convert them, e.g.:\n\n- `rtl_433 -w FILE.cu8`: write received data to sample file\n- `rtl_433 -w FILE.cu8 FILE.cs16`: convert sample file\n\n## Pulse data formats\n\nDemodulated data can be stored in a readable text-format with file extension `.ook`, also `.fsk` or `.psk`.\nA header contains meta data about the demodulation (2-ASK / ook, 2-FSK / fsk, ...) and the extension is informational only.\nEach regular line in the file format contains a number for pulse duration and a number for gap duration.\nFor FSK or PSK demodulation these are mark and space duration.\n\nThere is also the `.vcd` format which can carry the same information and might be useful with traditional signal data software.\nIt can optionally also encode more than two states, e.g. (4-FSK), this isn't used however.\n\nA very compact format is `rfraw:`, usually just one line of code.\nThis format encodes quantized pulse/gap durations with a maximum of eight different durations.\n\nThere are also formats for demodulated but \"raw\" amplitude or frequency,\ne.g. `.am.s16`, `.fm.s16` similar to the above formats but with only one \"channel\".\n\nThe SigRok `.sr` format is a Zip and combines multiple files for easy viewing with SigRok Pulseview.\n\n:::tip\nInstall SigRok Pulseview and write a SigRok file. The overwrite option (uppercase `-W`) will automatically open Pulseview.\n:::\n\nThe rtl_433 program can create all these formats from live data or sample files, e.g.:\n\n- `rtl_433 -w FILE.ook`: write received data to ook file\n- `rtl_433 -w FILE.ook FILE.cu8`: convert sample file to ook file\n\n## File name meta data\n\nIn addition to the file extension meta data about the center frequency and sample rate are encoded in the filename.\n\n- `433.92M` : A decimal number suffixed with `M` denotes the center frequency\n- `1000k` : A decimal number suffixed with `k` denotes the sample rate\n\nEach part of the filename must be separated by an underscore.\nEven with low frequencies or high sample rates the suffix is fixed,\n\n:::warning\n`433920k` is not a valid frequency specification and `1M` is not a valid sample rate specification in filenames.\n:::\n\n## File viewers\n\nAll raw I/Q sample data formats and most demodulated pulse data formats can be visualized with\nthe [triq I/Q Spectrogram and Pulsedata viewer](https://triq.org/pdv/).\n"
  },
  {
    "path": "docs/LINKS.md",
    "content": "# Links to tools and related projects\n\n## SDR Inputs/Drivers\n\n- [RTL-SDR](https://github.com/osmocom/rtl-sdr/)\n- [SoapySDR](https://github.com/pothosware/SoapySDR/)\n\n## Analysis\n\n- [SigRok](https://sigrok.org/) [PulseView](https://sigrok.org/wiki/PulseView)\n- [Audacity](https://www.audacityteam.org/)\n- [Spectrogram](http://triq.org/pdv) to visualize sample files\n- [BitBench](http://triq.org/bitbench) to analyze data formats\n\n## Related projects\n\n- [ShinySDR](https://shinysdr.switchb.org/)\n  Web remote-controllable SDR receiver application supporting multiple simultaneous hardware devices and demodulators, including rtl_433 and other decoding tools.\n\n- [HASS addon to convert rtl433 output to mqtt](https://github.com/james-fry/hassio-addons/blob/master/rtl4332mqtt/rtl2mqtt.sh)\n\n- [rtl_fl2k_433](https://github.com/winterrace2/rtl_fl2k_433)\n   an RX/TX prototyping tool. Aims to be a comfortable, GUI-based bridge between RTL-SDR dongles on RX side and cheap FL2K dongles on TX side. Currently, the GUI is available for Win64 only.\n\n- [rtl_433 with Snap7](https://github.com/merbanan/rtl_433/issues/950)\n  to inject weather data to industrial control system (PLC - Siemens S7-300 or compatible VIPA) coming from Weather station WH1080.\n\n- [Domoticz](https://www.domoticz.com/)\n   rtl_433 is usable from domoticz with a quite good integration: Domoticz launch rtl_433 with no data detection (relaunch rtl_433 if so) and process csv output format. All command line arguments are usable.\n\n- [WeeWx](http://weewx.com/)\n  the weewx-sdr driver gets data from rtl_433 and feeds it into weewx. from there the data can be combined with data from other sources, displayed using any of the many weewx skins, and/or uploaded to many different web services. the first weewx-sdr release was in 2016.\n  S.a. https://github.com/matthewwall/weewx-sdr https://github.com/weewx/weewx/wiki#skins https://github.com/weewx/weewx/wiki#uploaders\n\n- [rtl_snr](https://github.com/hdtodd/rtl_snr): \n  snr is a pair of equivalent C and Python programs that catalog and analyze signal-to-noise ratios from devices seen by RTL_SDR dongles and logged in JSON format by rtl_433.\n\n- [rtl_sdr driver](https://f-droid.org/en/packages/marto.rtl_tcp_andro/):\n  rtl_sdr driver is a port of rtl_tcp to Android.  See also the [source code](https://github.com/signalwareltd/rtl_tcp_andro-).\n"
  },
  {
    "path": "docs/OPERATION.md",
    "content": "# Basic rtl_433 operation\n\nThe principle buildings blocks of rtl_433 are: Inputs, Loaders, Processing, Analysis, Decoders, Dumpers, Outputs.\n\nAt startup rtl_433 will read config files and parse command line arguments, then it will loop through these steps:\n\n- Inputs: rtl_tcp, RTL-SDR, SoapySDR\n- Loaders: Raw data files (cu8, cs16, ...)\n- Processing: OOK and FSK demod, pulse detector, slicers, coding\n- Analysis: Show statistics on pulses\n- Decoders: Over 200 protocols\n- Dumpers: Raw data files (cu8, cs16, ..., sr, ...)\n- Outputs: Screen (kv), JSON, CSV, MQTT, Influx, UDP (syslog), HTTP\n\nrtl_433 will either acquire a live signal from an input or read a sample file with a loader.\nThen process that signal, analyse it's properties (if enabled) and write the signal with dumpers (if enabled).\nThe raw data is run through decoders to produce decoded output data.\n\n## Inputs\n\nPossible inputs are RTL-SDR, SoapySDR, and rtl_tcp.\n\nInputs are selected with the `-d` option:\n```\n  [-d <RTL-SDR USB device index> | :<RTL-SDR USB device serial> | <SoapySDR device query> | rtl_tcp | help]\n```\n\n### RTL-SDR\n\nFor RTL-SDR use the `-d` option as:\n\n```\n  [-d <RTL-SDR USB device index>] (default: 0)\n  [-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]\n```\n\nIf RTL-SDR support is compiled in (see the first line of `rtl_433 -V`) the default input will be the first available RTL-SDR device.\nThis can also explicitly be selected with `rtl_433 -d 0`. Use e.g. `rtl_433 -d 1` to select the second device.\n\nIf you have set a serial number on your device you can use that number prefixed with a colon to select a device,\ne.g. `rtl_433 -d :NESDRSMA`.\n\nThe sample format read from RTL-SDR is always `CU8`.\n\n### SoapySDR\n\nFor SoapySDR use the `-d` option as:\n\n```\n  [-d \"\"] Open default SoapySDR device\n  [-d driver=rtlsdr] Open e.g. specific SoapySDR device\n```\n\nIf SoapySDR support is compiled in (see the first line of `rtl_433 -V`) and RTL-SDR is not then the default input will be the first available SoapySDR device.\nThis can also explicitly be selected with `rtl_433 -d \"\"`.\n\nOtherwise specify a driver string to select the SoapySDR device. Use e.g. `rtl_433 -d \"driver=rtlsdr\"` to use RTL-SDR over Soapy.\n\nUsual SoapySDR driver string are e.g. `\"driver=remote,remote=tcp://192.168.2.1:55132\"`, `\"driver=plutosdr\"`, etc.\n\nThe sample format read from SoapySDR is likely `CS16`.\nA sample format of `CU8` is tried first, but unlikely to be supported by SoapySDR drivers.\n\n### rtl_tcp\n\nFor rtl_tcp use the `-d` option as:\n\n```\n  [-d rtl_tcp[:[//]host[:port]] (default: localhost:1234)\n    Specify host/port to connect to with e.g. -d rtl_tcp:127.0.0.1:1234\n```\n\nThe rtl_tcp input is always available. The default host is \"localhost\" and default port is \"1234\".\n\nUse e.g. `rtl_433 -d rtl_tcp:192.168.2.1` or `rtl_433 -d rtl_tcp:192.168.2.1:2143` to select a specific source.\n\n### Input Gain\n\nThe input device gain can be set with the `-g` option:\n\n```\n  [-g <gain>] (default: auto)\n    For RTL-SDR: gain in dB (\"0\" is auto).\n    For SoapySDR: gain in dB for automatic distribution (\"\" is auto), or string of gain elements.\n    E.g. \"LNA=20,TIA=8,PGA=2\" for LimeSDR.\n\n```\n\nThe default gain setting will be automatic gain (AGC enabled).\n\nFor RTL-SDR the gain is given in dB, where \"0\" selects automatic gain.\n\nFor SoapySDR a gain argument of `\"\"` selects automatic gain,\na gain value in dB can be used for automatic distribution to the gain stages,\nand string of gain elements sets the given gain stages individually.\n\nUse e.g. `-g \"LNA=20,TIA=8,PGA=2\"` for LimeSDR.\n\n### Antenna and settings\n\nFor SoapySDR the antenna and various other settings can be selected with `-t`:\n\n```\n  [-t <settings>] apply a list of keyword=value settings for SoapySDR devices\n       e.g. -t \"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\"\n```\n\n### Center Frequency\n\nThe center frequency can be selected with `-f`:\n\n```\n  [-f <frequency>] Receive frequency(s) (default: 433920000 Hz)\n```\n\nThe default frequency is 433.92 MHz and can be explicitly requested with `-f 433.92M`.\n\nYou can give a frequency in Hz, like `-f 433920000` or use suffixes of `k`, `M`, or `G`,\ne.g. `-f 433920k`, or `-f 433.92M`.\n\nOther interesting frequencies are e.g. `-f 868M`, `-f 315M`, `-f 345M`, `-f 915M`.\nIf you fine tune the frequency to your sender device you should avoid hitting the sender frequency dead center.\nThe resulting DC (direct current) signal is often attenuated by receivers and hard to make out when analysing samples.\nA small offset of 10 kHz to 50 kHz works best.\n\nThe `-f` option can be used multiple times to set up a list of frequency to hop.\nUse the `-H` option to set up the time to stay on each frequency or list on `-H` per `-f` to set a stay time for each frequency.\n(The last hop time given will be the default for all frequencies.)\n\n### PPM correction\n\nA PPM error correction value can be given with `-p`:\n\n```\n  [-p <ppm_error] Correct rtl-sdr tuner frequency offset error (default: 0)\n```\n\nThe PPM error correction is most commonly used to counter the drift in warmed up RTL-SDR devices.\n\n### Sample rate\n\nA sample rate value can be given with `-s`:\n\n```\n  [-s <sample rate>] Set sample rate (default: 250000 Hz)\n```\n\nThe default sample rate is 250 kHz and can be explicitly requested with `-s 250k`.\n\nYou can give a sample rate in Hz, like `-s 250000` or use suffixes of `k`, `M`, or `G`,\ne.g. `-f 250k`, or `-f 8M`.\nNote that the suffix is metric, the 1024000 Hz sample rate common with RTL-SDR has to be given as `-s 1024k`.\n\n## Decoders\n\nDecoders can be selected with the `-R` and `-X` option:\n\n```\n  [-R <device> | help] Enable only the specified device decoding protocol (can be used multiple times)\n       Specify a negative number to disable a device decoding protocol (can be used multiple times)\n  [-X <spec> | help] Add a general purpose decoder (prepend -R 0 to disable all decoders)\n```\n\nBy default all decoders with proper validity checking are enabled.\n\nYou can disable selected decoders with any number of `-R -<number>` options.\nE.g. use `rtl_433 -R -8 -19` to disable the LaCrosse and Nexus decoders.\n\nSome decoders have little validity checking and may share very common signal characteristics.\nThis will result in lots of false-positive decodes.\nThese decoders are not enabled by default and you need to explicitly enable them with `-R <number>`.\n\nYou can enable only selected decoders with any number of `-R <number>` options.\nNote that this will override the default and not select any decoder by default.\nE.g. use `rtl_433 -R 8 19` to enable only the LaCrosse and Nexus decoders.\n\nAn output line of `Registered <n> out of <N> device decoding protocols` will tersely show the enabled decoders.\n\nLastly the `-X` option can be used to add a custom flex decoder.\nThis can be used with `-R 0` to disable all default decoders.\nE.g. `rtl_433 -R 0 -X \"<spec>\"` will only run your given custom decoder.\n\n## Flex Decoder\n\nA flexible general purpose decoder can be added with the `-X` option:\n\n```\n  [-X <spec>] to add a flexible general purpose decoder.\n      <spec> is \"key=value[,key=value...]\"\n```\nMost common keys are:\n- `name=<name>` (or: `n=<name>`)\n- `modulation=<modulation>` (or: `m=<modulation>`)\n- `short=<short>` (or: `s=<short>`)\n- `long=<long>` (or: `l=<long>`)\n- `sync=<sync>` (or: `y=<sync>`)\n- `reset=<reset>` (or: `r=<reset>`)\n- `gap=<gap>` (or: `g=<gap>`)\n- `tolerance=<tolerance>` (or: `t=<tolerance>`)\n\nwhere:\n`<name>` can be any descriptive name tag you need in the output.\n\n`<modulation>` is one of:\n- `OOK_MC_ZEROBIT` :  Manchester Code with fixed leading zero bit\n- `OOK_PCM` :         Pulse Code Modulation (RZ or NRZ)\n- `OOK_PPM` :         Pulse Position Modulation\n- `OOK_PWM` :         Pulse Width Modulation\n- `OOK_DMC` :         Differential Manchester Code\n- `OOK_PIWM_RAW` :    Raw Pulse Interval and Width Modulation\n- `OOK_PIWM_DC` :     Differential Pulse Interval and Width Modulation\n- `OOK_MC_OSV1` :     Manchester Code for OSv1 devices\n- `FSK_PCM` :         FSK Pulse Code Modulation\n- `FSK_PWM` :         FSK Pulse Width Modulation\n- `FSK_MC_ZEROBIT` :  Manchester Code with fixed leading zero bit\n\n`<short>`, `<long>`, `<sync>` are nominal modulation timings in us,\n`<reset>`, `<gap>`, `<tolerance>` are maximum modulation timings in us:\n\n- PCM\n  - `short`: Nominal width of pulse [us]\n  - `long`: Nominal width of bit period [us]\n- PPM\n  - `short`: Nominal width of `0` gap [us]\n  - `long`: Nominal width of `1` gap [us]\n- PWM\n  - `short`: Nominal width of `1` pulse [us]\n  - `long`: Nominal width of `0` pulse [us]\n  - `sync`: Nominal width of sync pulse [us] (optional)\n- common\n  - `gap`: Maximum gap size before new row of bits [us]\n  - `reset`: Maximum gap size before End Of Message [us]\n  - `tolerance`: Maximum pulse deviation [us] (optional).\n\nAdditional options are:\n- `bits=<n>` : only match if at least one row has `<n>` bits\n- `rows=<n>` : only match if there are `<n>` rows\n- `repeats=<n>` : only match if some row is repeated `<n>` times.\n  - use `opt>=n` to match at least `<n>` and `opt<=n` to match at most `<n>`\n- `invert` : invert all bits\n- `reflect` : reflect each byte (MSB first to MSB last)\n- `match=<bits>` : only match if the `<bits>` are found\n- `preamble=<bits>` : match and align at the `<bits>` preamble.\n  - `<bits>` is a row spec of `{<bit count>}<bits as hex number>`\n- `unique` : suppress duplicate row output\n- `countonly` : suppress detailed row output\n\nE.g. `-X \"n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repeats>=3\"` specifies:\n\n- `name` is doorbell\n- `modulation` is `OOK_PWM`\n- width of a `short` bit is 400 µs\n- width of a `long` bit is 800 µs\n- maximum gap width to `reset` is 7000 µs\n- maximum `gap` width to new row is 1000 µs\n- the data needs to contain the `match` of 24 bits `0xa9878c`\n- the data needs to `repeat` at least 3 times\n\nTo extract some bits:\n\nExample: `get=battery:@4:{1}:[0:Ok 1:Empty]`\nUsing colon separated keys:\n- name (`battery`)\n- at which bit to start (`@4`)\n- how many bits to read (`{1}`)\n- optional mapping (map `0` to `Full`, map `1` to `Empty`)\n- or an optional format specifier (`%x`)\n\nA key starting with `%` is a format specifier for the KV output, in practice it will be something like `%x`, `%X`, `%04x`.\n\nSee the [`conf`](https://github.com/merbanan/rtl_433/tree/master/conf) folder for some examples of flex specs.\n\n## Analysis\n\nSignal data can be analysed with `-A`, `-a`, sample data can be dumped with `-S`:\n\n```\n  [-a] Analyze mode. Print a textual description of the signal.\n  [-A] Pulse Analyzer. Enable pulse analysis and decode attempt.\n       Disable all decoders with -R 0 if you want analyzer output only.\n  [-S none | all | unknown | known] Signal auto save. Creates one file per signal.\n       Note: Saves raw I/Q samples (uint8 pcm, 2 channel). Preferred mode for generating test files.\n```\n\nThe `-a` option enables the (old) pulse decoder to print a textual description of the signal.\nThe output might not be too useful, best to use the newer `-A` option.\n\nThe `-A` option enables the (new) pulse analyzer.\nEach received transmission will be displayed in a statistical overview.\nA probable coding will be inferred and attempted to decode.\n\nThe \"Pulse width distribution\", \"Gap width distribution\", and \"Pulse period distribution\"\ncan tell you about the timing in the `width` column,\nand the coding in the `count` column.\n\nE.g. a single (or dominant count) pulse width with two gap widths is likely PPM,\nE.g. a two (or dominant count) pulse widths with a sinle gap widths or single period width is likely PWM.\n\nDisable all decoders with `-R 0` if you want to view the analyzer output only.\n\nThe `-S` option allows you to dump received transmissions for further analysis.\nUse e.g. `rtl_433 -S all` to dump all signals or `rtl_433 -S unknown` to dump only signals with no successful decodes (by enabled decoders).\n\nOn file will be created per signal, see also \"File names\".\nNote: Saves raw I/Q samples `CU8` (uint8 pcm, 2 channel) for RTL-SDR and `CS16` (int16 pcm, 2 channel) for SoapySDR.\n\n## Loaders and Dumpers\n\nSample data can be loaded or dumped with `-r`, `-w`, `-W`, and codes verified with `-y`:\n\n```\n  [-r <filename> | help] Read data from input file instead of a receiver\n  [-w <filename> | help] Save data stream to output file (a `-` dumps samples to stdout)\n  [-W <filename> | help] Save data stream to output file, overwrite existing file\n  [-y <code>] Verify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n```\n\n### Read file (loaders)\n\nUse the `-r` option or stdin to read signal data (instead of live input):\n\n```\n  [-r <filename> | help] Read data from input file instead of a receiver\n```\n\nParameters are detected from the full path, file name, and extension. See also \"File names\".\n\nFile content and format options are:\n`cu8`, `cs16`, `cf32` (`IQ` implied), and `am.s16`.\n\n### Write file (dumpers)\n\nUse the `-w` and `-W` option to dump all signal data:\n\n```\n  [-w <filename>] Save data stream to output file (a `-` dumps samples to stdout)\n  [-W <filename>] Save data stream to output file, overwrite existing file\n```\n\nParameters are detected from the full path, file name, and extension. See also \"File names\".\n\nFile content and format options are:\n`cu8`, `cs16`, `cf32` (`IQ` implied),\n`am.s16`, `am.f32`, `fm.s16`, `fm.f32`,\n`i.f32`, `q.f32`, `logic.u8`, `ook`, and `vcd`.\n\nFor example you can dump the live decoded pulse data to stdout with `rtl_433 -w OOK:-`.\n\n### Load bitbuffer code\n\nUse the `-y` option to test a known code line (bitbuffer):\n\n```\n  [-y <code>] Verify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n```\n\nIf you are developing or testing a decoder you can skip the device input or sample loading step and directly give a known code line (bitbuffer) to the enabled decoders.\n\n### File names\n\nSamples recorded using the `-S` option will automatically be given filenames with some meta-data.\nThe signals will be stored individually in files named `g<NNN>_<FFF>M_<RRR>k.cu8` :\n\n| Parameter | Description\n|---------|------------\n| **NNN** | signal grabbed number\n| **FFF** | frequency\n| **RRR** | sample rate\n\nFile names used with `-r`, and `-w` / `-W` (loaders and dumpers) also follow that convention.\n\nA center frequency is detected from the filename as (fractional) number suffixed with `M`, `Hz`, `kHz`, `MHz`, or `GHz`.\n\nA sample rate is detected from the filename as (fractional) number suffixed with `k`, `sps`, `ksps`, `Msps`, or `Gsps`.\n\nParameters must be separated by non-alphanumeric chars and are case-insensitive.\n\nFile content and format are detected by th extension, possible options are:\n\n- `cu8` (`IQ` implied)\n- `cs16` (`IQ` implied)\n- `cf32` (`IQ` implied)\n- `am.s16'`\n- `am.f32`\n- `fm.s16`\n- `fm.f32`\n- `i.f32`\n- `q.f32`\n- `logic.u8`\n- `ook`\n- `vcd`\n\nOverrides can be prefixed to the actual filename, separated by colon (`:`).\nE.g. default detection by extension: path/filename.am.s16 and forced overrides: am:s16:path/filename.ext\n\n:::warning\nNote that not all file types are supported/applicable by loaders or dumpers.\n:::\n\n## Outputs\n\nUse the `-F` option to add outputs, use `-M`, `-K`, and `-C` to configure meta-data:\n\n```\n  [-F kv | json | csv | mqtt | influx | syslog | trigger | rtl_tcp | http | null | help] Produce decoded output in given format.\n       Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\n       Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\n  [-M time[:<options>] | protocol | level | stats | bits | help] Add various meta data to each output.\n  [-K FILE | PATH | <tag>] Add an expanded token or fixed tag to every output line.\n  [-C native | si | customary] Convert units in decoded output.\n```\n\nWithout any `-F` option the default is KV output. Use `-F null` to remove that default.\n\n### KV output\n\nUse `-F kv` to add an output in KV format.\n\nA colorful, column based output intended for screen display.\n\nAppend output to file with `:<filename>` (e.g. `-F kv:log.txt`), defaults to stdout.\n\n:::warning\nNote: the `kv` output is not a machine-readable key-value format, use the JSON output for that.\n:::\n\n### JSON output\n\nUse `-F json` to add an output in JSON format.\n\nUniversally machine-readable output.\n\nAppend output to file with `:<filename>` (e.g. `-F json:log.json`), defaults to stdout.\n\n### CSV output\n\nUse `-F csv` to add an output in CSV format.\n\nAppend output to file with `:<filename>` (e.g. `-F csv:log.csv`), defaults to stdout.\n\n:::warning\nNote: the `csv` output is not recommended for post-processing, use the JSON output for a machine-readable format.\n:::\n\n### MQTT output\n\nUse `-F mqtt` to add an output in MQTT format.\n\nSpecify MQTT server with e.g. `-F mqtt://localhost:1883`.\n\nAdd MQTT options with e.g. `-F \"mqtt://host:1883,opt=arg\"`.\nSupported MQTT options are: `user=foo`, `pass=bar`, `retain[=0|1]`, `<format>[=<topic>]`.\n\nSupported MQTT formats: (default is all formats)\n- `availability`: posts availability (online/offline)\n- `events`: posts JSON event data\n- `states`: posts JSON state data\n- `devices`: posts device and sensor info in nested topics\n\nThe `<topic>` string will expand keys like `[/model]`, see below.\nE.g. `-F \"mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]\"`\n\n### MQTT Format Strings\n\nUse format strings of:\n\n- `[token]`: expand to token or nothing\n- `[token:default]` expand to token or default\n- `[/token]` expand to token with leading slash or nothing\n- `[/token:default]` expand to token or default with leading slash\n\nTokens are `type`, `model`, `subtype`, `channel`, `id`, and `protocol` for now.\n\nNote that for `protocol` to be available you first need to add it to the meta-data with `-M protocol`.\n\nExamples:\n\n- `sensors[/channel:0][/id]` : always have a channel add id if available, you can also use `sensors/[channel:0][/id]`\n- `sensors[/channel][/id]` : use channel and then id, each if available\n- `sensors[/id][/channel]` : use id and then channel, each if available\n- `sensors[/channel:0]-[id:0]` : always have a combined channel and id\n- ...\n\nDefaults are a base topic of `rtl_433/<hostname>/` continued\n- for `devices` with `devices[/type][/model][/subtype][/channel][/id]`\n- for `events` with `events`\n- for `states` with `states`\n\n### SYSLOG output\n\nUse `-F syslog` to add an output in SYSLOG format.\n\nSpecify host/port for syslog with e.g. `-F syslog:127.0.0.1:1514`\n\nA UDP output of JSON messages with Syslog compatible header data.\n\nE.g. a UDP text payload of\n```\n<165>1 2019-08-29T06:38:19Z raspi.fritz.box rtl_433 - - - {\"time\":\"2019-08-29 08:38:19\",\"model\":\"Nexus-TH\",\"id\":42,\"channel\":2,\"battery_ok\":1,\"temperature_C\":20.5,\"humidity\":83}\n```\nSee also [RFC 5424 - The Syslog Protocol](https://tools.ietf.org/html/rfc5424#page-8)\n\n### NULL output\n\nWithout any `-F` option the default is KV output. Use `-F null` to remove that default.\n\n### Meta information\n\n```\n  [-M time[:<options>]|protocol|level|noise[:<secs>]|stats|bits]\n    Add various metadata to every output line.\n```\n- Use `time` to add current date and time meta data (preset for live inputs).\n- Use `time:rel` to add sample position meta data (preset for read-file and stdin).\n- Use `time:unix` to show the seconds since unix epoch as time meta data.\n- Use `time:iso` to show the time with ISO-8601 format (`YYYY-MM-DD\"T\"hh:mm:ss`).\n- Use `time:off` to remove time meta data.\n- Use `time:usec` to add microseconds to date time meta data.\n- Use `time:tz` to output time with timezone offset.\n- Use `time:utc` to output time in UTC.\n  (this may also be accomplished by invocation with TZ environment variable set).\n  `usec` and `utc` can be combined with other options, eg. `time:unix:utc:usec`.\n- Use `replay[:N]` to replay file inputs at (N-times) realtime.\n- Use `protocol` / `noprotocol` to output the decoder protocol number meta data.\n- Use `level` to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n- Use `noise[:secs]` to report estimated noise level at intervals (default: 10 seconds).\n- Use `stats[:[<level>][:<interval>]]` to report statistics (default: 600 seconds).\n  level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\n- Use `bits` to add bit representation to code outputs (for debug).\n\n```\n  [-K FILE | PATH | <tag>] Add an expanded token or fixed tag to every output line.\n```\n\n- Use `-K FILE` to add the base file name (from a loader) to every output line.\n- Use `-K PATH` to add the full path name (from a loader) to every output line.\n- Use `-K <tag>` to add a fixed custom tag to every output line.\n\n### Data conversion\n\nYou can choose to normalize data by unit conversion with the `-C` option:\n\n```\n  [-C native | si | customary] Convert units in decoded output.\n```\n\nThe default is no conversion, you explicitly select this with `-C native`.\n\nWith `-C si` units are converted to the SI system:\n- converts fields of Fahrenheit to Celsius (`_F` to `_C`)\n- converts fields of Miles/h to km/h (`_mph` to `_kph`, `_mi_h` to `_km_h`)\n- converts fields of Inch to mm (`_in` to `_mm`)\n- converts fields of Inch/h to mm/h (`_in_h` to `_mm_h`)\n- converts fields of InchHg to hPa (`_inHg` to `_hPa`)\n- converts fields of PSI to kPa (`_PSI` to `_kPa`)\n\nWith `-C customary` units are converted to customary units:\n- converts fields of Celsius to Fahrenheit (`_C to _F`)\n- converts fields of km/h to Miles/h (`_kph to _mph`, `_km_h to _mi_h`)\n- converts fields of mm to Inch (`_mm to _in`)\n- converts fields of mm/h to Inch/h (`_mm_h to _in_h`)\n- converts fields of hPa to InchHg (`_hPa to _inHg`)\n- converts fields of kPa to PSI (`_kPa to _PSI`)\n\n## Filter output with bridges\n\nYou can grab the decoded output from rtl_433 in various ways, then process and relay it somewhere.\n\n### Pipes\n\nThe simplest (but not very flexible or stable) way is to use pipes.\nE.g. capture the decode JSON messages and relay the to MQTT with\n\n`rtl_433 -F json -M utc | mosquitto_pub -t home/rtl_433 -l`\n\nSee also\n[rtl_433_collectd_pipe.py](https://github.com/merbanan/rtl_433/blob/master/examples/rtl_433_collectd_pipe.py), and\n[rtl_433_statsd_pipe.py](https://github.com/merbanan/rtl_433/blob/master/examples/rtl_433_statsd_pipe.py)\nfor other examples of this method.\n\n### UDP\n\nA better way is to use the Syslog-compatible UDP output to capture and relay the JSON message.\n\nSee also\n[rtl_433_graphite_relay.py](https://github.com/merbanan/rtl_433/blob/master/examples/rtl_433_graphite_relay.py),\n[rtl_433_mqtt_relay.py](https://github.com/merbanan/rtl_433/blob/master/examples/rtl_433_mqtt_relay.py), and\n[rtl_433_statsd_relay.py](https://github.com/merbanan/rtl_433/blob/master/examples/rtl_433_statsd_relay.py)\nfor examples of this method.\n\n### MQTT\n\nIf you already use the MQTT output, then you can capture the MQTT data, process it and inject derived data back.\n\nSee e.g. [rtl_433_mqtt_hass.py](https://github.com/merbanan/rtl_433/blob/master/examples/rtl_433_mqtt_hass.py)\nfor an example of this method.\n"
  },
  {
    "path": "docs/PRIMER.md",
    "content": "# Primer on SDR concepts\n\nA brief introduction to some concepts, most questions really only need a keyword to look it up.\n\n> What is a signal?\n\nIn the simplest way think of it as a sound file. Load a sample in [Audacity](https://www.audacityteam.org/) and play it! But that's only the [sampled](https://en.wikipedia.org/wiki/Sampling_(signal_processing)), [quantized](https://en.wikipedia.org/wiki/Quantization_(signal_processing)) [baseband](https://en.wikipedia.org/wiki/Baseband) of the [radio wave](https://en.wikipedia.org/wiki/Radio_wave)...\n\n> 433 MHz is 433920000 Hz? and so listening to 433920001 Hz is a different frequency that would not catch any 433 MHz signals?\n\n\"433\" really means the 433.92 MHz [ISM-band](https://en.wikipedia.org/wiki/ISM_band). \"434\" would be more appropriate. It's a band of \"sample rate\"-width. E.g. 433.795 to 434.045 MHz at the default 433.92 \"center frequency\" and 250 kHz sample rate.\n\n> How can you decrypt a signal? I am getting some signals with my SDR and it would be nice to know a bit more about them?\n\nMostly it's not encrypted but only coded and [modulated](https://en.wikipedia.org/wiki/Modulation).\n\n> Is the data transmitted in binary code? Always?\n\nThere are multiple layers. Look up [ASK](https://en.wikipedia.org/wiki/Amplitude-shift_keying), 2-ASK / [OOK](https://en.wikipedia.org/wiki/On-off_keying), [FSK](https://en.wikipedia.org/wiki/Frequency-shift_keying), 2-FSK then [PCM](https://en.wikipedia.org/wiki/Pulse-code_modulation), [PWM](https://en.wikipedia.org/wiki/Pulse-width_modulation), [PPM](https://en.wikipedia.org/wiki/Pulse-position_modulation), [MANCHESTER](https://en.wikipedia.org/wiki/Manchester_code), [DMC](https://en.wikipedia.org/wiki/Differential_Manchester_encoding).\n\n> Can I know the strength of the signal to know if the transmitter is close or far?\n\nOnly if there is no [AGC](https://en.wikipedia.org/wiki/Automatic_gain_control) or the current AGC value is reported (not with rtl-sdr).\n\n> What is the meaning of the information I capture with rtl_433? It would be nice to know the meaning of each line or the different parts it shows?\n\nIt's the decoded transmission and some meta-data about the signal.\n\n> Why was I getting all zeros when I was listening to my device? How could I change this to get the correct information?\n\nWrong assumption about decoding parameters. The suggested flex decoder is just a simple statistical heuristic.\n\n> What is the information provided by the -A switch? pulse width, gap width, pulse period.....\n\nSee PWM.\n\n> Can I buy and install a bigger antenna to get better signal? Or is there some limitation on the signal that will not allow this?\n\nMore metal is in fact the only reliable way to get better reception. But there are limits, s. wave length. And you could add directionality.\n\n> Can I transmit a signal to my receiver thermometer and make it think that I am the transmission device? Does this mean that I can impersonate any transmitter device?\n\nSure. This almost always possible.\n\n> Does a sensor need some kind of computer/arduino to send signals or can it work with just the sensor and some transmitter hardware?\n\nThere are specialized chips like the [EV1527](https://www.sunrom.com/get/206000) that do this. A general-purpose [MCU](https://en.wikipedia.org/wiki/Microcontroller) isn't likely to be used.\n\n> And I would like to know a bit about other frequencies outside 433 MHz.\n\n868 is also interesting. Mostly FSK is used, where 433 mostly uses OOK (ASK).\n\n> I would like to send a doorbell button push to MQTT. rtl_433 doesn't decode it, but it's a simple button, so I only need a trigger event. With rtl_433 -a -R 0 I am able to catch the button with {25}... code. What is the correct way to send the raw data to MQTT?\n\nUse `-A` and note the `-X` line. Then use that to write a flex decoder. See e.g. [EV1527-PIR-Sgooway.conf](https://github.com/merbanan/rtl_433/blob/master/conf/EV1527-PIR-Sgooway.conf).\n\nHave fun.\n"
  },
  {
    "path": "docs/STARTING.md",
    "content": "# Getting Started\n\nA short summary how to operate `rtl_433`.\n\n## Options\n\nAdd options to the `rtl_433` command line invocation to specify the mode of operation.\n\nE.g. the option `-V` will output the version string and exit,\nthe option `-h` will output a brief usage help and exit.\n\nSome options take an argument, and you can also use those without argument or `help` or `?` to get brief usage instructions.\nE.g. `-d`, `-g`, `-R`, `-X`, `-F`, `-M`, `-r`, `-w`, or `-W` without argument will list the argument syntax.\n\nCommand line options a parsed left to right and will override each other or stack in some cases (frequency hopping).\n\nE.g. try the option `-V -h` to output the version string and exit, the `-h` option will not be reached,\nthe other way around `-h -V` you will see the help output but no version string afterwards (but the help includes the version info).\n\nThis ordering is important to keep in mind, generally go \"inputs\", \"processing options\", \"outputs\".\n\n:::tip\n    [-V] Output the version string and exit\n    [-h] Output this usage help and exit\n         Use -d, -g, -R, -X, -F, -M, -r, -w, or -W without argument for more help\n:::\n\n## Configuration files\n\nYou can also use a configuration file to give the same options.\nFiles will be read in order and options given will also override in that order.\nConfiguration files can be mixed with command line options.\n\nYou can instruct `rtl_433` to read a configuration file with the `-c <path>` option.\n\nBy default a configuration file will be searched for and loaded from\n- `rtl_433.conf` at the current directory\n- `$HOME/.config/rtl_433/rtl_433.conf`\n- `/usr/local/etc/rtl_433/rtl_433.conf`\n- `/etc/rtl_433/rtl_433.conf`\n\nAn example configuration file with information on all possible options is provided at [rtl_433.example.conf](https://github.com/merbanan/rtl_433/blob/master/conf/rtl_433.example.conf).\n\n:::tip\n    [-c <path>] Read config options from a file\n:::\n\n## Select an input\n\n`rtl_433` can read live inputs (SDR hardware and network streams), sample files, and test codes.\n\nChoose a live input with `-d`:\n- `-d <RTL-SDR USB device index>` e.g. `-d 0` for the first RTL-SDR found,\n- `-d :<RTL-SDR USB device serial>` e.g. `-d :NESDRSMA` (set the serial using the `rtl_eeprom` tool)\n- `-d <SoapySDR device query>` e.g. `-d driver=lime`\n- `-d rtl_tcp` e.g. `-d rtl_tcp://192.168.1.2:1234`\n\nThe default is to use the first RTL-SDR available (`-d 0`).\nYou can switch that to using the first SoapySDR available by using `-d \"\"`, i.e. the empty SoapySDR search string.\n\n:::warning\nWhen running multiple instances of `rtl_433` be sure to use a distinct input for each, do not rely on the auto-selection of the first available input.\n:::\n\nChoose a file input using `-r` e.g. `-r g001_433.92M_250k.cu8`\nIf you list files to read as last options then you can omit the `-r` e.g. `rtl_433 g001_433.92M_250k.cu8`\n\nIf you are testing a decoder you can list a demodulated bit pattern as input using the `-y` option, e.g. `-y \"{25}fb2dd58\"`\n\n:::tip\n    [-d <RTL-SDR USB device index> | :<RTL-SDR USB device serial> | <SoapySDR device query> | rtl_tcp | help]\n    [-r <filename> | help] Read data from input file instead of a receiver\n    [-y <code>] Verify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n:::\n\n## Configure the input\n\nLive inputs (from SDR hardware) need some settings to work, usually you at least want to specify the center frequency.\n\nThe default center frequency is `433.92M`, select a frequency using `-f <frequency>`.\nSuffixes of `M`, and `k`, `G` are accepted.\n\nMultiple center frequencies can be given to set up frequency hopping.\nThe hopping time can be given with `-H <seconds>`, the default is 10 minutes (600 s).\nMultiple hopping times can be given and apply to each frequency given in that order.\nYou can give `-E hop` to hop immediately after each received event.\n\nThe default sample rate for `433.92M` is `250k` Hz and `1000k` for higher frequencies like `868M`.\nSelect a sample rate using `-s <sample rate>` -- rates higher than `1024k` or maybe `2048k` are not recommended.\n\nSpecific settings for an SDR device can be given with `-g <gain>`, `-p <ppm_error>`,\nand even `-t <settings>` to apply a list of keyword=value settings for SoapySDR devices.\n\n:::tip\n    [-f <frequency>] Receive frequency(s) (default: 433920000 Hz)\n    [-H <seconds>] Hop interval for polling of multiple frequencies (default: 600 seconds)\n    [-E hop | quit] Hop/Quit after outputting successful event(s)\n    [-s <sample rate>] Set sample rate (default: 250000 Hz)\n    [-g <gain> | help] (default: auto)\n    [-t <settings>] apply a list of keyword=value settings for SoapySDR devices\n         e.g. -t \"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\"\n    [-p <ppm_error>] Correct rtl-sdr tuner frequency offset error (default: 0)\n:::\n\n## Verbose output\n\nIf `rtl_433` seems to \"hang\", it's usually just not receiving any signals that can be successfully decoded.\nThe default is to be silent until there is a solid data reception.\n\nInstruct `rtl_433` not to be silent, use:\n-  `-v` to show detailed notes on startup,\n-  `-vv` to show failed decoding attempts,\n-  `-vvv` to show all decoding attempts,\n-  `-A` to analyze every signal in detail.\n\n:::tip\nDisable all decoders with `-R 0` if you want analyzer output only.\n:::\n\nAlternatively get periodic status output using: `-M level` `-M noise` `-M stats:2:30`\n\n:::tip\n    [-v] Increase verbosity (can be used multiple times).\n         -v : verbose, -vv : verbose decoders, -vvv : debug decoders, -vvvv : trace decoding).\n    [-A] Pulse Analyzer. Enable pulse analysis and decode attempt.\n:::\n\n## Select outputs\n\nThe default output of `rtl_433`, if no outputs are selected, is to the screen.\nAny number of outputs can be selected:\n- `-F kv` prints to the screen\n- `-F json` prints json lines\n- `-F csv` prints a csv formatted file\n- `-F mqtt` sends to MQTT\n- `-F influx` sends to InfluxDB\n- `-F syslog` send UDP messages\n- `-F trigger` puts a `1` to the given file, can be used to e.g. on a Raspberry Pi flash the LED.\n- `-F rtl_tcp` adds a rtl_tcp pass-through server.\n- `-F http` adds a HTTP API server, a UI is at e.g. http://localhost:8433/\n\nAppend output to file with `:<filename>` (e.g. `-F csv:log.csv`), default is to print to stdout.\nSpecify host/port for `mqtt`, `influx`, `syslog`, with e.g. `-F syslog:127.0.0.1:1514`\n\n:::tip\n    [-F kv | json | csv | mqtt | influx | syslog | trigger | rtl_tcp | http | null | help] Produce decoded output in given format.\n:::\n\n## Write outputs to files\n\nYou can write all received raw data to a file with `-w <filename>` (or  `-W <filename>` to overwrite an existing file).\n\n:::tip\n    [-w <filename> | help] Save data stream to output file (a '-' dumps samples to stdout)\n    [-W <filename> | help] Save data stream to output file, overwrite existing file\n:::\n\n## Store raw sample data\n\n`rtl_433` can write a file for each received signal.\nThis is the preferred mode for generating files to later analyze or add as test cases.\nUse\n- `-S all` to write all signals to files,\n- `-S unknown` to write signals which couldn't be decoded to files,\n- `-S known` to write signals that could be decoded to files.\n\nThe saves signals are raw I/Q samples (uint8 pcm, 2 channel).\n\n:::tip\n    [-S none | all | unknown | known] Signal auto save. Creates one file per signal.\n:::\n\n## Select decoders\n\nThe `-R` option selects decoders to use. The option can be given multiple times.\nDefault is to activate all available decoders which are not default-disabled due to known problems.\nYou can disable some decoders using negative number, e.g. `-R -3`.\nYou can enable only select decoders by using some `-R` options, e.g. `-R 3`.\nYou can disable all decoders using some `-R 0`.\n\nAdditional flexible general purpose decoders can be added using `-X <spec>`.\n\n:::tip\nDisable all decoders with `-R 0` if you want only the given flex decoder.\n:::\n\n:::tip\n    [-R <device> | help] Enable only the specified device decoding protocol (can be used multiple times)\n         Specify a negative number to disable a device decoding protocol (can be used multiple times)\n    [-X <spec> | help] Add a general purpose decoder (prepend -R 0 to disable all decoders)\n:::\n\n## Demodulator options\n\nThe operation of the demodulator stage can be tuned with the `-Y` option.\n\nFor the `433.92M` frequency the `classic` pulse detector is used by default,\nfor higher frequencies like `868M` the `minmax` pulse detector is used by default.\n\nUse `-Y classic` or `-Y minmax` to force the use of a FSK pulse detector.\n\nUse `-Y autolevel` to automatically adjust the minimum detection level based on average estimated noise. Recommended.\n\nUse `-Y squelch` to skip frames below estimated noise level to reduce cpu load. Recommended.\n\n:::tip\n    [-Y auto | classic | minmax] FSK pulse detector mode.\n    [-Y level=<dB level>] Manual detection level used to determine pulses (-1.0 to -30.0) (0=auto).\n    [-Y minlevel=<dB level>] Manual minimum detection level used to determine pulses (-1.0 to -99.0).\n    [-Y minsnr=<dB level>] Minimum SNR to determine pulses (1.0 to 99.0).\n    [-Y autolevel] Set minlevel automatically based on average estimated noise.\n    [-Y squelch] Skip frames below estimated noise level to reduce cpu load.\n    [-Y ampest | magest] Choose amplitude or magnitude level estimator.\n:::\n\n## Meta-data and data conversion\n\nAdditional meta data can be added to the output using the `-M option`.\nE.g. use `-M level` to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n\nMeta data formats can be selected, e.g. use `-M time:iso:utc:usec` to use the ISO format in the UTC zone with added microseconds.\n\nVarious tags can be added to all event outputs. Use\n- `-K FILE` Add the expanded name of the input file to every output line,\n- `-K PATH` Add the expanded path of the input file to every output line,\n- `-K <tag>` Add an expanded token or fixed tag to every output line.\n- `-K <key>=<tag>` Add an expanded token or fixed tag to every output line.\n\nKnown data units can be converted to SI units or Customary (US) units.\nThe default is to output native units as received.\nUse\n- `-C native` Do not convert units in decoded output.\n- `-C si` Convert units to SI in decoded output.\n- `-C customary` Convert units to Customary (US) in decoded output.\n\n:::tip\n    [-M time[:<options>] | protocol | level | noise[:<secs>] | stats | bits] Add various metadata to every output line.\n      Use \"time\" to add current date and time meta data (preset for live inputs).\n      Use \"time:rel\" to add sample position meta data (preset for read-file and stdin).\n      Use \"time:unix\" to show the seconds since unix epoch as time meta data.\n      Use \"time:iso\" to show the time with ISO-8601 format (YYYY-MM-DD\"T\"hh:mm:ss).\n      Use \"time:off\" to remove time meta data.\n      Use \"time:usec\" to add microseconds to date time meta data.\n      Use \"time:tz\" to output time with timezone offset.\n      Use \"time:utc\" to output time in UTC.\n          (this may also be accomplished by invocation with TZ environment variable set).\n          \"usec\" and \"utc\" can be combined with other options, eg. \"time:unix:utc:usec\".\n      Use \"protocol\" / \"noprotocol\" to output the decoder protocol number meta data.\n      Use \"level\" to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n      Use \"noise[:secs]\" to report estimated noise level at intervals (default: 10 seconds).\n      Use \"stats[:[<level>][:<interval>]]\" to report statistics (default: 600 seconds).\n        level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\n\n    [-K FILE | PATH | <tag> | <key>=<tag>] Add an expanded token or fixed tag to every output line.\n      If <tag> is \"FILE\" or \"PATH\" an expanded token will be added.\n      The <tag> can also be a GPSd URL, e.g.\n          \"-K gpsd,lat,lon\" (report lat and lon keys from local gpsd)\n          \"-K loc=gpsd,lat,lon\" (report lat and lon in loc object)\n          \"-K gpsd\" (full json TPV report, in default \"gps\" object)\n          \"-K foo=gpsd://127.0.0.1:2947\" (with key and address)\n          \"-K bar=gpsd,nmea\" (NMEA default GPGGA report)\n          \"-K rmc=gpsd,nmea,filter='$GPRMC'\" (NMEA GPRMC report)\n      Also <tag> can be a generic tcp address, e.g.\n          \"-K foo=tcp:localhost:4000\" (read lines as TCP client)\n          \"-K bar=tcp://127.0.0.1:3000,init='subscribe tags\\r\\n'\"\n          \"-K baz=tcp://127.0.0.1:5000,filter='a prefix to match'\"\n\n    [-C native | si | customary] Convert units in decoded output.\n:::\n\n## Mode of operation\n\nWhen reading live inputs `rtl_433` will usually run forever, but you can limit the runtime\n- to a specific time using `-T <seconds>`, also formats like `12:34` or `1h23m45s` are accepted,\n- to a number of samples using `-n <value>` as a number of samples to take (each sample is an I/Q pair),\n- to receiving an event using `-E quit`, to quit after outputting the first event.\n\nWhen reading input from files `rtl_433` will process the data as fast as possible.\nYou can limit the processing to original (or N-times) real-time using `-M replay[:N]`.\n\n:::tip\n    [-n <value>] Specify number of samples to take (each sample is an I/Q pair)\n    [-T <seconds>] Specify number of seconds to run, also 12:34 or 1h23m45s\n    [-E hop | quit] Hop/Quit after outputting successful event(s)\n    [-M replay[:N]] to replay file inputs at (N-times) realtime.\n:::\n"
  },
  {
    "path": "docs/build-docs.sh",
    "content": "#!/bin/sh\n\n# abort on errors\nset -e\n\n# navigate to the docs directory\ncd \"${0%/*}\"\n\n# copy other docs\nsed 's/docs\\///' ../README.md >README.md\ncp ../CHANGELOG.md .\ncp ../rtl_433_tests/README.md TESTS.md\n\n# build\nyarn install\nyarn docs:build\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"rtl_433-docs\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docs:dev\": \"vuepress dev\",\n    \"docs:build\": \"vuepress build\"\n  },\n  \"devDependencies\": {\n    \"@vuepress/plugin-search\": \"^2.0.0-beta.49\",\n    \"vuepress\": \"^2.0.0-beta.49\"\n  }\n}\n"
  },
  {
    "path": "examples/README.md",
    "content": "# rtl_433 scripts (example and useful)\n\nThis directory contains a number of scripts to process the output of\nrtl_433 in various ways.  Some are truly examples; they worked for\nsomeone else and won't work for you but will help understanding.  Some\nare actually useful to a fairly broad set of people and are used in\nproduction.  Some of course are somewhere in the middle.\n\nIt is likely that a use outside what has been contemplated will\nrequire writing new code, with some kind of filtering and\ntransformation.\n\nGenerally, python scripts should work with relatively recent Python 3.\n(Python 2.7 is no longer supported.)\n\nThese scripts typically send data to some other system, store it in a\ndatabase, or process it in some way.  Recall that rtl_433's philosophy\nis to just output received transmissions, with minimal processing and\nwith no checking/correlation of adjacent repeated frames.  These\nscripts bridge the gap between raw output and useful information.\n\nThis directory has a strong bias to the use of JSON; the point of that\nencoding is that it is machine parseble and that's what we want to do.\n\nAt some point, generally-usable scripts should perhaps be promoted\nfrom examples.\n\n# Styles of Plumbing\n\nThere are two main mechanisms: piping the output json, and\nsyslog-style UDP with one json object per packet.\n\n## pipe\n\nThe `pipe` examples read JSON output from `rtl_433` using a pipe, i.e.\n\n    rtl_433 -F json ... | rtl_433_statsd_pipe.py\n\nThis is in many ways simple, but the programs must be started and\nstopped together, and only one rtl_433 can write to the processing\nscript.\n\nThere are only two pipe programs, collectd and statsd.\n\n## UDP syslog\n\nThe `relay` examples consumes the (UDP) Syslog output from rtl_433 (or a legacy plain JSON datagram).\nBasically run `rtl_433` with `-F syslog:127.0.0.1:1433` and the relay script as an unrelated process, i.e.\n\n    rtl_433_mqtt_relay.py &\n    rtl_433 -F syslog:127.0.0.1:1433\n\nWith this, one can run `tcpdump -A -i lo0 udp and port 1433`\n(substitute your loopback interface) to watch the traffic.  One can\nalso run multiple rtl_433 processes.\n\n# Orientation\n\nWe attempt to categorize and describe the scripts in this directory.\n\nHome Assistant is abbreviated HA.\n\n## Production Scripts\n\nA production script could be installed as a program, if it also had a\nman page.  Many people should be able to use it without having to edit\nthe source code.\n\n## Generally Usable Scripts\n\nA generally usable script will likely need minor tweaking.\n\n  - rtl_433_mqtt_relay.py: Send data via MQTT (e.g. to HA).\n  - rtl_433_mqtt_hass.py: Send HA autoconfiguration data, so that entities for the decoded sensors will automatically appear.\n\n## True Examples\n\nThese are not likely to to be useful, except that reading them will\nlead to better understanding, and bits of code may be useful..  \n\n  - mqtt_rtl_433_test_client.py: Connect to broker and print data from rtl topics\n  - rtl_433_custom.php: Receive json syslog packets in php\n  - rtl_433_custom.py: Receive json syslog packets in python\n  - rtl_433_gps.py: Receive json data and also gpsd data \n  - rtl_433_http_cmd.py: Custom hop controller example for rtl_433's HTTP cmd API\n  - rtl_433_http_cmd.sh: Custom hop controller example for rtl_433's HTTP cmd API\n  - rtl_433_http_events.py: Custom data handling example for rtl_433's HTTP (chunked) streaming API of JSON events\n  - rtl_433_http_stream.php: Short example of an TCP client written in PHP for rtl_433\n  - rtl_433_http_stream.py: Custom data handling example for rtl_433's HTTP (line) streaming API of JSON events\n  - rtl_433_http_ws.py: Custom data handling example for rtl_433's HTTP WebSocket API of JSON events\n\n## Uncategorized\n\nThese scripts are in the directory but have not been sorted and described.\n\n  - rtl_433_graphite_relay.py: Send data to graphite\n  - rtl_433_influxdb_relay.py: Send data to influxdb\n  - rtl_433_prometheus_relay.py: Send data to prometheus\n  - rtl_433_rrd_relay.py: Send data to rrd\n  - rtl_433_statsd_relay.py: Send data to statsd\n  - rtl_433_collectd_pipe.py: Send data to collected\n  - rtl_433_statsd_pipe.py: Send data to statsd\n  - rtl_433_json_to_rtlwmbus.py: convert rtl_433 wmbus json output to rtlwmbus output\n\n# Strategies for Processing, Transmitting and Storing\n\n(This does not belong here, but is useful to those contemplating the\nscripts, so it's here pending a proper home.)\n\nThis section is speculative.\n\n## Checksums and Repeated transmissions\n\nMany devices will send a frame multiple times, perhaps 3 or 4, as a\nform of redundancy.  Many devices have non-robust checksums.  If\ndisplaying a temperature, that is often not a big deal.  If storing it\nin a database, bad data is troublesome.  One could process multiple\nframes that arrive close in time and try to infer which are bad\ndecodes and what the consensus data is, and from that output one good\nframe.  One could further reject physically implausible data (temp of\nfridge has been 2C and we just got a 30C reading), but this needs to\nrecover from arbitrary situations so that would be tricky code to\nwrite.  There is no code yet; this is merely a suggestion to future\nhackers.\n\n## Precision\n\nActual precision of devices varies, and there is unit conversion.\nThere should be some scheme to ensure reasonable values (vs 5 decimal\ndigits of temperature).  This is also for future work.\n\n## Calibration\n\nSo far, rtl_433 does not deal with calibration; the job is to output\nwhat was received.  A system design where the json to mqtt program has\ncalibration data and applies it might be sensible, but this is far from clear.\n\n## Health monitoring\n\nThe main data is logging what was received, but it is also interesting\nto know what channel rtl_433 is listening to, noise levels, that the\ntranslation script is up, etc.   This is also for future work.\n"
  },
  {
    "path": "examples/mqtt_filter.py",
    "content": "#!/usr/bin/env python3\n\"\"\" MQTT republishing filter to rename and deduplicate rtl_433 data\n\nExample program for receiving, filtering and republishing sensor data\nfrom rtl_433 using MQTT network messages.\n\nAn MQTT broker e.g. 'mosquitto' must be running and rtl_433 must publish\nto that broker, e.g. using -F mqtt.\n\"\"\"\n\nimport json\nimport logging\nimport sys\nimport time\nimport socket\nimport re\n\nimport paho.mqtt.client as mqtt\nfrom paho.mqtt.subscribeoptions import SubscribeOptions\n\nHOSTNAME = socket.getfqdn()\n\nMQTT_SERVER = \"127.0.0.1\"  # set your MQTT broker address here\n\nMQTT_TOPIC_PREFIX = \"rtl_433/+\"  # default to use all rtl_433 senders\n# MQTT_TOPIC_PREFIX = \"rtl_433/\" + HOSTNAME  # alternative: use just the local host topics\n# MQTT_TOPIC_PREFIX = \"rtl_433/MYSERVER\"  # alternativ: use a named rtl_433 sender\n\nMQTT_TOPIC_DEVICES = MQTT_TOPIC_PREFIX + \"/devices\"  # default \"devices\" topic tree base\nMQTT_TOPIC_EVENTS = MQTT_TOPIC_PREFIX + \"/events\"  # default \"events\" topic\n\n# set source and target topics as well as deduplication in seconds, 0 disables deduplication\nDEVICE_MAPPINGS = [\n    { \"source\": \"Bresser-3CH/1/130\", \"target\": \"Livingroom\", \"dedup\": 1.5},\n    { \"source\": \"Bresser-3CH/2/175\", \"target\": \"Bedroom\", \"dedup\": 1.5},\n    { \"source\": \"LaCrosse-TX141THBv2/2/122\", \"target\": \"Garage\", \"dedup\": 0},\n]\n\nEVENT_MAPPINGS = [\n    { \"source\": { \"model\": \"Bresser-3CH\", \"id\": 130 }, \"target\": { \"nickname\": \"Livingroom\" }, \"dedup\": 1.5},\n    { \"source\": { \"model\": \"Bresser-3CH\", \"id\": 175 }, \"target\": { \"nickname\": \"Bedroom\" }, \"dedup\": 1.5},\n    { \"source\": { \"model\": \"LaCrosse-TX141THBv2\", \"id\": 122 }, \"target\": { \"nickname\": \"Garage\" }, \"dedup\": 0},\n]\n\nEVENT_REPUBLISHED_KEY = \"republished\"  # a JSON key only present in republished messages\n# EVENT_REPUBLISHED_KEY = \"nickname\"  # can also be key added with \"target\"\n\nif hasattr(mqtt, 'CallbackAPIVersion'):  # paho >= 2.0.0\n    mqtt_client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION1, client_id=\"RTL_433_Filter\")\nelse:\n    mqtt_client = mqtt.Client(client_id=\"RTL_433_Filter\")\n\nDEVICES_RE = MQTT_TOPIC_DEVICES.replace(\"+\", \".+\")\nEVENTS_RE = MQTT_TOPIC_EVENTS.replace(\"+\", \".+\")\n\ndedup_cache = {}\n\n\ndef publish_dedup(topic, payload, timeout):\n    \"\"\" Deduplicate and publish a message. \"\"\"\n    global dedup_cache\n    global mqtt_client\n\n    if timeout <= 0:\n        logging.debug(\"republishing \" + topic + \" : \" + str(payload))\n        mqtt_client.publish(topic, payload)\n        return\n\n    key = topic + str(payload)\n    now = time.time()\n    if key in dedup_cache and dedup_cache[key] > now:\n        logging.info(\"dedup \" + topic + \" : \" + str(payload))\n    else:\n        logging.debug(\"republishing \" + topic + \" : \" + str(payload))\n        mqtt_client.publish(topic, payload)\n\n    dedup_cache[key] = now + timeout\n    for k in list(dedup_cache.keys()):\n        if dedup_cache[k] < now:\n            del dedup_cache[k]\n\n\ndef filter_devices(topic, payload):\n    \"\"\" Deduplicate and republish device messages. \"\"\"\n    global mqtt_client\n\n    if not re.match(DEVICES_RE, topic):\n        return\n\n    # Loop through all device mappings\n    for map in DEVICE_MAPPINGS:\n        path = \"(\" + DEVICES_RE + \"/)\" + map[\"source\"] + \"(.+)\"\n        m = re.match(path, topic)\n        # On match republish same payload to different topic\n        if m:\n            target = m.group(1) + map[\"target\"] + m.group(2)\n            logging.debug(\"republishing \" + map[\"source\"] + \" : \" + target)\n            publish_dedup(target, payload, map[\"dedup\"])\n\n\ndef is_sub_dict(needle, haystack):\n    \"\"\" Test if all key-value pairs of needle match haystack. \"\"\"\n    for k, v in needle.items():\n        if k not in haystack or haystack[k] != v:\n            return False\n    return True\n\n\ndef filter_events(topic, payload):\n    \"\"\" Deduplicate and republish event messages. \"\"\"\n    global mqtt_client\n\n    if not re.match(EVENTS_RE, topic):\n        return\n\n    try:\n        data = json.loads(payload.decode())\n\n        # MQTT v5 noLocal might not work\n        # Ignore republished events, there should be a better way?\n        if EVENT_REPUBLISHED_KEY in data:\n            return\n\n        # Loop through all event mappings\n        for map in EVENT_MAPPINGS:\n            # Ensure all JSON data keys match\n            if not is_sub_dict(map[\"source\"], data):\n                continue\n            # On match republish modified payload to same topic\n\n            # Tag as repbulished event\n            data[EVENT_REPUBLISHED_KEY] = 1\n            # Add all \"target\" keys, e.g. a nickname\n            for k, v in map[\"target\"].items():\n                data[k] = v\n            logging.debug(\"republishing to \" + topic)\n            publish_dedup(topic, json.dumps(data), map[\"dedup\"])\n\n    except json.decoder.JSONDecodeError:\n        logging.warning(\"JSON decode error: \" + payload.decode())\n\n\ndef on_connect(client, userdata, flags, rc):\n    \"\"\" Callback for when the client receives a CONNACK response from the server. \"\"\"\n    logging.info(\"MQTT Connection: \" + mqtt.connack_string(rc))\n    if rc != 0:\n        logging.error(\"Could not connect. RC: \" + str(rc))\n        exit()\n    # Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.\n    options = SubscribeOptions(qos=1, noLocal=True)\n    logging.info(\"Subscribing to \" + MQTT_TOPIC_EVENTS)\n    client.subscribe(MQTT_TOPIC_EVENTS, options=options)\n    logging.info(\"Subscribing to \" + MQTT_TOPIC_DEVICES + \"/#\")\n    client.subscribe(MQTT_TOPIC_DEVICES + \"/#\", options=options)\n\n\ndef on_disconnect(client, userdata, rc):\n    if rc != 0:\n        logging.error(\"Unexpected disconnection. RC: \" + str(rc))\n\n\ndef on_message(client, userdata, msg):\n    \"\"\" Callback for when a PUBLISH message is received from the server. \"\"\"\n    logging.debug(\"Received: \" + msg.topic + \"\\t\" + msg.payload.decode())\n    filter_devices(msg.topic, msg.payload)\n    filter_events(msg.topic, msg.payload)\n\n\n# Setup MQTT client\nmqtt_client.on_connect = on_connect\nmqtt_client.on_disconnect = on_disconnect\nmqtt_client.on_message = on_message\nmqtt_client.connect(MQTT_SERVER)\nmqtt_client.loop_start()\n\n\ndef main():\n    \"\"\"MQTT republishing filter\"\"\"\n    logging.basicConfig(format='[%(asctime)s] %(levelname)s:%(name)s:%(message)s',datefmt='%Y-%m-%dT%H:%M:%S%z')\n    logging.getLogger().setLevel(logging.INFO)\n\n    try:\n        while True:\n            time.sleep(1)\n    except KeyboardInterrupt:\n        pass\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/mqtt_rtl_433_test_client.py",
    "content": "#!/usr/bin/env python3\n\"\"\" MQTT test client for receiving rtl_433 JSON data\n\nExample program for receiving and parsing sensor data from rtl_433 sent\nas MQTT network messages. Recommended way of sending rtl_433 data on network is:\n\n$ rtl_433 -F json -M utc | mosquitto_pub -t home/rtl_433 -l\n\nAn MQTT broker e.g. 'mosquitto' must be running on local computer\n\nCopyright (C) 2017 Tommy Vestermark\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\"\"\"\n\nimport datetime\nimport json\nimport logging\nimport multiprocessing as mp\nimport sys\nimport time\n\nimport paho.mqtt.client as mqtt\n\nMQTT_SERVER = \"127.0.0.1\"\nMQTT_TOPIC_PREFIX = \"home/rtl_433\"\nTIMEOUT_STALE_SENSOR = 600  # Seconds before showing a timeout indicator\n\n# log = logging.getLogger()  # Single process logger\nlog = mp.log_to_stderr()  # Multiprocessing capable logger\nif hasattr(mqtt, 'CallbackAPIVersion'):  # paho >= 2.0.0\n    mqtt_client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION1, client_id=\"RTL_433_Test\")\nelse:\n    mqtt_client = mqtt.Client(client_id=\"RTL_433_Test\")\n\nsensor_state = dict()  # Dictionary containing accumulated sensor state\n\n\ndef print_sensor_state():\n    \"\"\" Print accumulated sensor state \"\"\"\n    time_now = datetime.datetime.utcnow().replace(microsecond=0)\n    print(\"\\nUpdate per {} UTC\".format(time_now.isoformat(sep=' ')))\n    for model in sensor_state:\n        print(model)\n        for ID in sensor_state[model]:\n            data = sensor_state[model][ID]['data'].copy()\n            timestamp = data.pop('time')\n            timedelta = (time_now - timestamp).total_seconds()\n            indicator = \"*\" if (timedelta < 2) else \"~\" if (timedelta > TIMEOUT_STALE_SENSOR) else \" \"  # Indicator for new and stale data\n            print(\"  ID {:5} {}{} {}\".format(ID, timestamp.isoformat(sep=' '), indicator, data))\n    sys.stdout.flush()  # Print in real-time\n\n\ndef on_connect(client, userdata, flags, rc):\n    \"\"\" Callback for when the client receives a CONNACK response from the server. \"\"\"\n    log.info(\"MQTT Connection: \" + mqtt.connack_string(rc))\n    if rc != 0:\n        log.error(\"Could not connect. RC: \" + str(rc))\n        exit()\n    # Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.\n    client.subscribe(MQTT_TOPIC_PREFIX)\n\n\ndef on_disconnect(client, userdata, rc):\n    if rc != 0:\n        log.error(\"Unexpected disconnection. RC: \" + str(rc))\n\n\ndef on_message(client, userdata, msg):\n    \"\"\" Callback for when a PUBLISH message is received from the server. \"\"\"\n    if msg.topic.startswith(MQTT_TOPIC_PREFIX):\n        try:\n            # Decode JSON payload\n            d = json.loads(msg.payload.decode())\n        except json.decoder.JSONDecodeError:\n            log.warning(\"JSON decode error: \" + msg.payload.decode())\n            return\n\n        # Convert time string to datetime object\n        time_str = d.get('time', \"0000-00-00 00:00:00\")\n        time_utc = datetime.datetime.strptime(time_str, \"%Y-%m-%d %H:%M:%S\")\n        d['time'] = time_utc\n        # Update sensor_state\n        sensor_model = d.pop('model', 'unknown')\n        sensor_id = d.pop('id', 0)\n        sensor_state.setdefault(sensor_model, {}).setdefault(sensor_id, {})['data'] = d\n        print_sensor_state()\n    else:\n        log.info(\"Unknown topic: \" + msg.topic + \"\\t\" + msg.payload.decode())\n\n\n# Setup MQTT client\nmqtt_client.on_connect = on_connect\nmqtt_client.on_disconnect = on_disconnect\nmqtt_client.on_message = on_message\nmqtt_client.connect(MQTT_SERVER)\nmqtt_client.loop_start()\n\n\ndef main():\n    \"\"\"MQTT Test Client\"\"\"\n    log.setLevel(logging.INFO)\n    log.info(\"MQTT RTL_433 Test Client\")\n\n    while True:\n        time.sleep(1)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "examples/open_rtl433.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Helper command for rtl_433 to visualize a sample file in a web browser.\"\"\"\n\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nimport sys\nimport subprocess\nimport webbrowser\n\nhostName = \"localhost\"\nserverPort = 8080\n\n\ndef parseToPulseData(filename):\n    ret = subprocess.run([\"rtl_433\", \"-F\", \"null\", \"-w\", \"OOK:-\", filename], capture_output=True)\n    return ret.stdout\n\n\nclass PulseServer(BaseHTTPRequestHandler):\n    def do_GET(self):\n        global pulsedata\n\n        self.send_response(200)\n        self.send_header(\"Content-type\", \"text/html\")\n        self.end_headers()\n\n        self.wfile.write(bytes(\"\"\"\n<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    <meta name=\"mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <link rel=\"apple-touch-icon\" href=\"https://triq.org/pdv/icon.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"https://triq.org/pdv/icon.76.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"https://triq.org/pdv/icon.120.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"https://triq.org/pdv/icon.152.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"https://triq.org/pdv/icon.180.png\" />\n    <link rel=\"icon\" sizes=\"192x192\" href=\"https://triq.org/pdv/icon.192.png\">\n    <link rel=\"icon\" sizes=\"128x128\" href=\"https://triq.org/pdv/icon.128.png\">\n    <link rel=\"icon\" href=\"https://triq.org/pdv/favicon.ico\">\n    <meta name=\"description\" content=\"I/Q Spectrogram and Pulsedata.\">\n    <title>I/Q Spectrogram &amp; Pulsedata</title>\n    <link href=\"https://triq.org/pdv/css/app.css\" rel=\"stylesheet\">\n</head>\n<body>\n    <noscript><strong>We're sorry but I/Q Spectrogram &amp; Pulsedata doesn't work properly without JavaScript\n      enabled. Please enable it to continue.</strong></noscript>\n    <div id=\"app\"></div>\n<script>\nwindow.pulseData=`\n\"\"\", \"utf-8\"))\n        self.wfile.write(pulsedata)\n        self.wfile.write(bytes(\"\"\"\n`\n</script>\n<script type=\"module\" src=\"https://triq.org/pdv/js/app.js\"></script>\n</body>\n</html>\n\"\"\", \"utf-8\"))\n\n\nif __name__ == \"__main__\":\n    if len(sys.argv) != 2:\n        print(\"Usage:\\n%s FILENAME.cu8\" % (sys.argv[0]))\n        exit(1)\n\n    filename = sys.argv[1]\n    pulsedata = parseToPulseData(filename)\n\n    while serverPort < 65536:\n        try:\n            webServer = HTTPServer((hostName, serverPort), PulseServer)\n            break;\n        except OSError as e:\n            if e.errno != 48 or serverPort >= 65535:\n                raise\n            serverPort += 1  # Address already in use\n\n    print(\"If the browser doesn't open go to http://%s:%s\" % (hostName, serverPort))\n\n    try:\n        webbrowser.open(\"http://%s:%s/\" % (hostName, serverPort))\n        webServer.handle_request()  # once\n    except KeyboardInterrupt:\n        pass\n\n    webServer.server_close()\n    print(\"done.\")\n"
  },
  {
    "path": "examples/rtl_433_collectd_pipe.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Collectd monitoring probe (i.e. no plugin) for rtl_433.\"\"\"\n\n# Needs Python collectd Network plugin, s.a. https://github.com/appliedsec/collectd\n#   pip install collectd\n# -or-\n#   curl -O https://github.com/appliedsec/collectd/raw/master/collectd.py\n\nimport time\nimport socket\nimport fileinput\nimport json\nimport collectd\n\n\ndef send_stats(when, stats, sender, to):\n    for (plugin_type, plugin_inst), values in stats.items():\n        if not values:\n            continue\n        collectd.PLUGIN_TYPE = plugin_type\n        for message in collectd.messages(values, when, sender, plugin_inst):\n            collectd.sock.sendto(message, to)\n\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\")\n\n\ndef rtl_433_probe():\n    hostname = socket.getfqdn()\n    interval = 60.0  # seconds\n\n    collectd.SEND_INTERVAL = interval\n    collectd.PLUGIN_NAME = 'rtlsdr'\n\n    collectd_host = \"localhost\"\n    collectd_port = 25826\n\n    for line in fileinput.input():\n        try:\n            data = json.loads(line)\n\n            when = int(time.time())\n            label = sanitize(data[\"model\"])\n            if \"channel\" in data:\n                label += \".CH\" + str(data[\"channel\"])\n            attributes = {}\n            temperatures = {}\n\n            attributes[\"battery\"] = data[\"battery_ok\"]\n\n            attributes[\"humidity\"] = data[\"humidity\"]\n\n            temperatures[\"sensor\"] = data[\"temperature_C\"]\n\n            stats = {('gauge', label): attributes,\n                     ('temperature', label): temperatures}\n\n            send_stats(when, stats, hostname, (collectd_host, collectd_port))\n\n        except ValueError:\n            pass\n\n\nif __name__ == \"__main__\":\n    rtl_433_probe()\n"
  },
  {
    "path": "examples/rtl_433_custom.php",
    "content": "#!/usr/bin/php\n<?php\n/*\nShort example of an UDP server written in PHP for rtl_433.\n\nYou need to have `php_cli` installed.\n\nTo run this as service use these systemd service files for\nubuntu, lubuntu, xubuntu, debian, ubuntu core and so on.\n\n## Service file to start the rtl_433 process\n\n\n[Unit]\nDescription=rtl433 udp service\nAfter=network.target\n[Service]\nRestart=always\nRestartSec=5\nRemainAfterExit=no\nUser=root\nExecStart=/bin/sh -c \"/usr/bin/rtl_433 -f 433.92M -F syslog:127.0.0.1:1433\"\n\n[Install]\nWantedBy=multi-user.target\n\n\n## Service file to start the php udp server\n\n\n[Unit]\nDescription=syslog 433 udp service\nAfter=network.target\nStartLimitIntervalSec=0\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nExecStart=/usr/bin/php /home/user/rtl_433/examples/rtl_433_custom.php \"0\"\n\n[Install]\nWantedBy=multi-user.target\n*/\n\nerror_reporting(E_ALL | E_STRICT);\n\n//switch on debug mode\n$debug = \"1\";\nif (sizeof($argv) > 1)\n{\n    $debug  = $argv[1];\n}\n//udp server IP and Port for listen\n$UDP_IP = \"127.0.0.1\";\n$UDP_PORT = 1433;\n//create socket and bind them\n$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);\nsocket_bind($socket, $UDP_IP, $UDP_PORT);\n//init of this variables\n$from = '';\n$port = 0;\n\n//use the output of rtl_433 -f 433920000 -f 433920000 -H 120 -F syslog:127.0.0.1:1433\"\n//returns the json payload\nfunction parse_syslog($line)\n{\n    //Try to extract the payload from a syslog line.//\n    $line = mb_convert_encoding($line, \"ASCII\");\n\n    if (startsWith($line,\"<\"))\n    {\n        //fields should be \"VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        $fields = explode(\" \",$line, 8);\n        $line = $fields[7];\n    }\n    return $line;\n}\n\n//server main loop\nfor (;;)\n{\n    //read from $socket into $line\n    socket_recvfrom($socket, $line, 1024, 0, $from, $port);\n    try\n    {\n        //parse $line -> returns the json payload\n        $line = parse_syslog($line);\n\n        /*\n        do something with content of $line\n        for example decode $line into a array\n        $arr = json_decode($line,true);\n\n        do something with that array and\n        puted into a file as json\n\n        file_put_contents('test.json', json_encode($arr, JSON_PRETTY_PRINT);\n        */\n    }\n    catch (Exception $e) {\n        echo \"---------------------------------------------\\n\";\n        echo 'Exception intercepted: ', $e->getMessage(), \"\\n\";\n        echo \"------------------------------------------- -\\n\";\n    }\n}\n?>\n"
  },
  {
    "path": "examples/rtl_433_custom.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Custom data handling example for rtl_433.\"\"\"\n\n# Start rtl_433 (rtl_433 -F syslog::1433), then this script\n\nfrom __future__ import print_function\n\nimport socket\nimport json\n\n# You can run rtl_433 and this script on different machines,\n# start rtl_433 with `-F syslog:YOURTARGETIP:1433`, and change\n# to `UDP_IP = \"0.0.0.0\"` (listen to the whole network) below.\nUDP_IP = \"127.0.0.1\"\nUDP_PORT = 1433\n\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        fields = line.split(None, 7)\n        line = fields[-1]\n    return line\n\n\ndef rtl_433_listen():\n    \"\"\"Listen to all messages in a loop forever.\"\"\"\n    # Open a UDP socket\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n    # Bind the UDP socket to a listening address\n    sock.bind((UDP_IP, UDP_PORT))\n\n    # Loop forever\n    while True:\n        # Receive a message\n        line, addr = sock.recvfrom(1024)\n\n        try:\n            # Parse the message format\n            line = parse_syslog(line)\n            # Decode the message as JSON\n            data = json.loads(line)\n\n            #\n            # Change for your custom handling below, this is a simple example\n            #\n            label = data[\"model\"]\n            if \"channel\" in data:\n                label += \".CH\" + str(data[\"channel\"])\n            elif \"id\" in data:\n                label += \".ID\" + str(data[\"id\"])\n\n            # E.g. match `model` and `id` to a descriptive name.\n            if data[\"model\"] == \"LaCrosse-TX\" and data[\"id\"] == 123:\n                label = \"Living Room\"\n\n            if \"battery_ok\" in data:\n                if data[\"battery_ok\"] == 0:\n                    print(label + ' Battery empty!')\n\n            if \"temperature_C\" in data:\n                print(label + ' Temperature ', data[\"temperature_C\"])\n\n            if \"humidity\" in data:\n                print(label + ' Humidity ', data[\"humidity\"])\n\n            # Ignore unknown message data and continue\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\nif __name__ == \"__main__\":\n    rtl_433_listen()\n"
  },
  {
    "path": "examples/rtl_433_gps.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Read events from rtl_433 and gpsd and print out.\"\"\"\n\n# Needs gpsd (and the Python support from gpsd)\n# Start gpsd and rtl_433 (rtl_433 -F syslog::1433), then this script\n\nfrom __future__ import print_function\n\nimport socket\nimport json\nimport gps\nimport threading\n\n# rtl_433 syslog address\nUDP_IP = \"127.0.0.1\"\nUDP_PORT = 1433\n\n\nclass GpsPoller(threading.Thread):\n    def __init__(self):\n        threading.Thread.__init__(self)\n        self.gps = gps.gps(mode=gps.WATCH_ENABLE)\n        self.running = True\n\n    def run(self):\n        while self.running:\n            self.gps.next()\n\n    @property\n    def utc(self):\n        return self.gps.utc\n\n    @property\n    def fix(self):\n        return self.gps.fix\n\n    @property\n    def satellites(self):\n        return self.gps.satellites\n\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        fields = line.split(None, 7)\n        line = fields[-1]\n    return line\n\n\ndef prife(label, data, key):\n    \"\"\"Print if exists.\"\"\"\n    if key in data:\n        print(label, data[key])\n\n\n\ndef report_event(data, gpsp):\n    \"\"\"Print out an rtl_433 event with gps data.\"\"\"\n\n    # don't process if it isn't sensor data\n    if \"model\" not in data:\n        return\n\n    # don't process if it isn't TPMS data\n    if \"type\" not in data:\n        return\n    if data[\"type\"] != \"TPMS\":\n        return\n\n    # now = int(time.time())\n    print(\"----------------------------------------\")\n    print(\"Model          \", data[\"model\"])\n    prife(\"ID             \", data, \"id\")\n    prife(\"Status         \", data, \"status\")\n    prife(\"State          \", data, \"state\")\n    prife(\"Flags          \", data, \"flags\")\n    prife(\"Code           \", data, \"code\")\n    prife(\"Pressure (kPa) \", data, \"pressure_kPa\")\n    prife(\"Pressure (PSI) \", data, \"pressure_PSI\")\n    prife(\"Temperature (C)\", data, \"temperature_C\")\n    prife(\"Temperature (F)\", data, \"temperature_F\")\n    print()\n    print(\"latitude       \", gpsp.fix.latitude)\n    print(\"longitude      \", gpsp.fix.longitude)\n    print(\"time utc       \", gpsp.utc, \" + \", gpsp.fix.time)\n    print(\"altitude (m)   \", gpsp.fix.altitude)\n    print(\"eps            \", gpsp.fix.eps)\n    print(\"epx            \", gpsp.fix.epx)\n    print(\"epv            \", gpsp.fix.epv)\n    print(\"ept            \", gpsp.fix.ept)\n    print(\"speed (m/s)    \", gpsp.fix.speed)\n    print(\"climb          \", gpsp.fix.climb)\n    print(\"track          \", gpsp.fix.track)\n    print(\"mode           \", gpsp.fix.mode)\n    # print(\"sats           \", gpsp.satellites)\n\n\nif __name__ == '__main__':\n    gpsp = GpsPoller()\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\n    sock.bind((UDP_IP, UDP_PORT))\n\n    try:\n        gpsp.start()\n\n        while True:\n            line, addr = sock.recvfrom(1024)\n            try:\n                line = parse_syslog(line)\n                data = json.loads(line)\n                report_event(data, gpsp)\n\n            except KeyError:\n                pass\n\n            except ValueError:\n                pass\n\n    except (KeyboardInterrupt, SystemExit): #when you press ctrl+c\n        print(\"\\nAborted. Exiting...\")\n        sock.close()\n        gpsp.running = False\n        gpsp.join() # wait for the thread to finish\n\n    print(\"Done.\\n\")\n"
  },
  {
    "path": "examples/rtl_433_graphite_relay.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Graphite(Carbon) monitoring relay for rtl_433.\"\"\"\n\n# Start rtl_433 (rtl_433 -F syslog::1433), then this script\n\n# Option: PEP 3143 - Standard daemon process library\n# (use Python 3.x or pip install python-daemon)\n# import daemon\n\nfrom __future__ import print_function\nfrom __future__ import with_statement\n\nimport socket\nimport time\nimport json\n\nUDP_IP = \"127.0.0.1\"\nUDP_PORT = 1433\nGRAPHITE_HOST = \"127.0.0.1\"\nGRAPHITE_PORT = 2003\nGRAPHITE_PREFIX = \"rtlsdr.\"\n\n\nclass GraphiteUdpClient(object):\n    def __init__(self, host='localhost', port=2003, ipv6=False):\n        \"\"\"Create a new client.\"\"\"\n        fam = socket.AF_INET6 if ipv6 else socket.AF_INET\n        family, _, _, _, addr = socket.getaddrinfo(\n            host, port, fam, socket.SOCK_DGRAM)[0]\n        self._addr = addr\n        self._sock = socket.socket(family, socket.SOCK_DGRAM)\n\n    def _send(self, message):\n        \"\"\"Send raw data to graphite.\"\"\"\n        try:\n            self._sock.sendto(message, self._addr)\n        except (socket.error, RuntimeError):\n            pass\n\n    def push(self, path, value, timestamp=None):\n        \"\"\"Send a value to graphite.\"\"\"\n        if not timestamp:\n            timestamp = int(time.time())\n\n        message = \"{0} {1} {2}\".format(path, value, timestamp)\n        self._send(message)\n\n\nsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\nsock.bind((UDP_IP, UDP_PORT))\n\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\").replace(\"/\", \"_\").replace(\".\", \"_\").replace(\"&\", \"\")\n\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        fields = line.split(None, 7)\n        line = fields[-1]\n    return line\n\n\ndef rtl_433_probe():\n    graphite = GraphiteUdpClient(host=GRAPHITE_HOST,\n                                 port=GRAPHITE_PORT)\n\n    while True:\n        line, addr = sock.recvfrom(1024)\n\n        try:\n            line = parse_syslog(line)\n            data = json.loads(line)\n            now = int(time.time())\n\n            label = sanitize(data[\"model\"])\n            if \"channel\" in data:\n                label += \".CH\" + str(data[\"channel\"])\n            elif \"id\" in data:\n                label += \".ID\" + str(data[\"id\"])\n            path = GRAPHITE_PREFIX + label\n\n            if \"battery_ok\" in data:\n                graphite.push(path + '.battery', data[\"battery_ok\"], now)\n\n            if \"humidity\" in data:\n                graphite.push(path + '.humidity', data[\"humidity\"], now)\n\n            graphite.push(path + '.temperature', data[\"temperature_C\"], now)\n\n            # graphite.commit()  # for Pickle protocol only\n\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\ndef run():\n    # with daemon.DaemonContext(files_preserve=[sock]):\n    #  detach_process=True\n    #  uid\n    #  gid\n    #  working_directory\n    rtl_433_probe()\n\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "examples/rtl_433_http_cmd.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Custom hop controller example for rtl_433's HTTP cmd API.\"\"\"\n\n# Start rtl_433 (`rtl_433 -F http`), then this script.\n# Needs the Requests package to be installed.\n\nimport requests\nimport json\nfrom time import sleep\n\n# You can run rtl_433 and this script on different machines,\n# start rtl_433 with `-F http:0.0.0.0`, and change\n# to e.g. `HTTP_HOST = \"192.168.1.100\"` (use your server ip) below.\nHTTP_HOST = \"127.0.0.1\"\nHTTP_PORT = 8433\n\n\ndef set_freq(freq):\n    return send_cmd({'cmd': 'center_frequency', 'val': freq})\n\n\ndef set_rate(rate):\n    return send_cmd({'cmd': 'sample_rate', 'val': rate})\n\n\ndef send_cmd(params):\n    url = f'http://{HTTP_HOST}:{HTTP_PORT}/cmd'\n    headers = {'Accept': 'application/json'}\n\n    # You will receive JSON events, one per line terminated with CRLF.\n    # Use GET\n    response = requests.get(url, params=params, headers=headers, timeout=70, stream=True)\n    # or POST\n    # response = requests.post(url, data=params, headers=headers, timeout=70, stream=True)\n    print(f'Sending {params} to {url}')\n\n    # Answer is lines of JSON\n    return response.text\n\n\ndef rtl_433_control():\n    \"\"\"Simple timed control of rtl_433 in a loop forever.\"\"\"\n\n    # Loop forever\n    while True:\n        try:\n            # Set first hop\n            sleep(10)\n            print(set_freq(433920000))\n            print(set_rate(250000))\n\n            # Set second hop\n            sleep(10)\n            print(set_freq(868000000))\n            print(set_rate(1024000))\n\n        except requests.ConnectionError:\n            print('Connection failed, retrying in 60s...')\n            sleep(60)\n\n\nif __name__ == \"__main__\":\n    try:\n        rtl_433_control()\n    except KeyboardInterrupt:\n        print('\\nExiting.')\n        pass\n"
  },
  {
    "path": "examples/rtl_433_http_cmd.sh",
    "content": "#!/bin/sh\n\n# Custom hop controller example for rtl_433's HTTP cmd API.\n\n# Start rtl_433 (`rtl_433 -F http`), then this script.\n# Needs the xh tool installed (or httpie and change `xh` to `http`)\n\nwhile : ; do\n  sleep 10\n  xh :8433/cmd cmd==center_frequency val==433920000\n  xh :8433/cmd cmd==sample_rate val==250000\n  sleep 10\n  xh :8433/cmd cmd==center_frequency val==868000000\n  xh :8433/cmd cmd==sample_rate val==1024000\ndone\n"
  },
  {
    "path": "examples/rtl_433_http_events.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Custom data handling example for rtl_433's HTTP (chunked) streaming API of JSON events.\"\"\"\n\n# Start rtl_433 (`rtl_433 -F http`), then this script.\n# Needs the Requests package to be installed.\n\nimport requests\nimport json\nfrom time import sleep\n\n# You can run rtl_433 and this script on different machines,\n# start rtl_433 with `-F http:0.0.0.0`, and change\n# to e.g. `HTTP_HOST = \"192.168.1.100\"` (use your server ip) below.\nHTTP_HOST = \"127.0.0.1\"\nHTTP_PORT = 8433\n\n\ndef stream_events():\n    url = f'http://{HTTP_HOST}:{HTTP_PORT}/events'\n    headers = {'Accept': 'application/json'}\n\n    # You will receive JSON events, one per line terminated with CRLF.\n    # On Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds.\n    response = requests.get(url, headers=headers, timeout=70, stream=True)\n    print(f'Connected to {url}')\n\n    for chunk in response.iter_content(chunk_size=None):\n        yield chunk\n\n\ndef handle_event(line):\n    try:\n        # Decode the message as JSON\n        data = json.loads(line)\n\n        #\n        # Change for your custom handling below, this is a simple example\n        #\n        label = data[\"model\"]\n        if \"channel\" in data:\n            label += \".CH\" + str(data[\"channel\"])\n        elif \"id\" in data:\n            label += \".ID\" + str(data[\"id\"])\n\n        # E.g. match `model` and `id` to a descriptive name.\n        if data[\"model\"] == \"LaCrosse-TX\" and data[\"id\"] == 123:\n            label = \"Living Room\"\n\n        if \"battery_ok\" in data:\n            if data[\"battery_ok\"] == 0:\n                print(label + ' Battery empty!')\n\n        if \"temperature_C\" in data:\n            print(label + ' Temperature ', data[\"temperature_C\"])\n\n        if \"humidity\" in data:\n            print(label + ' Humidity ', data[\"humidity\"])\n\n    except KeyError:\n        # Ignore unknown message data and continue\n        pass\n\n    except ValueError as e:\n        # Warn on decoding errors\n        print(f'Event format not recognized: {e}')\n\n\ndef rtl_433_listen():\n    \"\"\"Listen to all messages in a loop forever.\"\"\"\n\n    # Loop forever\n    while True:\n        try:\n            # Open the HTTP (chunked) streaming API of JSON events\n            for chunk in stream_events():\n                # print(chunk)\n                chunk = chunk.rstrip()\n                if not chunk:\n                    # filter out keep-alive empty lines\n                    continue\n                # Decode the JSON message\n                handle_event(chunk)\n\n        except requests.ConnectionError:\n            print('Connection failed, retrying...')\n            sleep(5)\n\n\nif __name__ == \"__main__\":\n    try:\n        rtl_433_listen()\n    except KeyboardInterrupt:\n        print('\\nExiting.')\n        pass\n"
  },
  {
    "path": "examples/rtl_433_http_stream.php",
    "content": "#!/usr/bin/php\n<?php\n/*\nShort example of an TCP client written in PHP for rtl_433.\n\nYou need to have `php_cli` installed.\n\nTo run this as service use these systemd service files for\nubuntu, lubuntu, xubuntu, debian, ubuntu core and so on.\n\n## Service file to start the rtl_433 process\n\n[Unit]\nDescription=rtl433 tcp service\nAfter=network.target\n[Service]\nRestart=always\nRestartSec=5\nRemainAfterExit=no\nUser=root\nExecStart=/bin/sh -c \"/usr/bin/rtl_433 -f 433.92M -F http:127.0.0.1:8433\"\n\n[Install]\nWantedBy=multi-user.target\n\n## Service file to start the php udp server\n\n[Unit]\nDescription=http 433 tcp service\nAfter=network.target\nStartLimitIntervalSec=0\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nExecStart=/usr/bin/php /home/user/rtl_433/examples/rtl_433_http_stream.php\n\n[Install]\nWantedBy=multi-user.target\n*/\n\n\n//Function to check $haystack if\n//$needle in $haystack\nfunction str__contains($haystack,$needle)\n{\n  return (strpos($haystack, $needle) !== false);\n}\n\n\n//main\n$addr  = \"127.0.0.1\";\n$port = \"8433\";\n$url  = $addr . \":\" . $port . \"/stream\";\n\n$fp = stream_socket_client(\"tcp://\" . $url , $errno, $errstr, 70);\nif (!$fp) {\n    //optional error output\n    //echo \"$errstr ($errno)<br />\\n\";\n} else {\n    fwrite($fp, \"GET / HTTP/1.0\\r\\nHost: \" . $addr . \"\\r\\nAccept: */*\\r\\n\\r\\n\");\n    while (!feof($fp)) {\n        $line = fgets($fp, 1024);\n        //time is available in all received records, that is the filter word\n        //for sensor data\n        if(str__contains($line,\"time\"))\n        {\n          //raw output of json\n          print_r($line);\n          /*\n          do something with content of $line\n          for example decode $line into an array\n          $arr = json_decode($line,true);\n\n          do something with that array and\n          output into a file as json\n          file_put_contents('test.json', json_encode($arr, JSON_PRETTY_PRINT);\n          */\n        }\n    }\n    fclose($fp);\n}\n?>\n"
  },
  {
    "path": "examples/rtl_433_http_stream.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Custom data handling example for rtl_433's HTTP (line) streaming API of JSON events.\"\"\"\n\n# Start rtl_433 (`rtl_433 -F http`), then this script.\n# Needs the Requests package to be installed.\n\nimport requests\nimport json\nfrom time import sleep\n\n# You can run rtl_433 and this script on different machines,\n# start rtl_433 with `-F http:0.0.0.0`, and change\n# to e.g. `HTTP_HOST = \"192.168.1.100\"` (use your server ip) below.\nHTTP_HOST = \"127.0.0.1\"\nHTTP_PORT = 8433\n\n\ndef stream_lines():\n    url = f'http://{HTTP_HOST}:{HTTP_PORT}/stream'\n    headers = {'Accept': 'application/json'}\n\n    # You will receive JSON events, one per line terminated with CRLF.\n    # On Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds.\n    response = requests.get(url, headers=headers, timeout=70, stream=True)\n    print(f'Connected to {url}')\n\n    for chunk in response.iter_lines():\n        yield chunk\n\n\ndef handle_event(line):\n    try:\n        # Decode the message as JSON\n        data = json.loads(line)\n\n        #\n        # Change for your custom handling below, this is a simple example\n        #\n        label = data[\"model\"]\n        if \"channel\" in data:\n            label += \".CH\" + str(data[\"channel\"])\n        elif \"id\" in data:\n            label += \".ID\" + str(data[\"id\"])\n\n        # E.g. match `model` and `id` to a descriptive name.\n        if data[\"model\"] == \"LaCrosse-TX\" and data[\"id\"] == 123:\n            label = \"Living Room\"\n\n        if \"battery_ok\" in data:\n            if data[\"battery_ok\"] == 0:\n                print(label + ' Battery empty!')\n\n        if \"temperature_C\" in data:\n            print(label + ' Temperature ', data[\"temperature_C\"])\n\n        if \"humidity\" in data:\n            print(label + ' Humidity ', data[\"humidity\"])\n\n    except KeyError:\n        # Ignore unknown message data and continue\n        pass\n\n    except ValueError as e:\n        # Warn on decoding errors\n        print(f'Event format not recognized: {e}')\n\n\ndef rtl_433_listen():\n    \"\"\"Listen to all messages in a loop forever.\"\"\"\n\n    # Loop forever\n    while True:\n        try:\n            # Open the HTTP (line) streaming API of JSON events\n            for chunk in stream_lines():\n                # print(chunk)\n                chunk = chunk.rstrip()\n                if not chunk:\n                    # filter out keep-alive empty lines\n                    continue\n                # Decode the JSON message\n                handle_event(chunk)\n\n        except requests.ConnectionError:\n            print('Connection failed, retrying...')\n            sleep(5)\n\n\nif __name__ == \"__main__\":\n    try:\n        rtl_433_listen()\n    except KeyboardInterrupt:\n        print('\\nExiting.')\n        pass\n"
  },
  {
    "path": "examples/rtl_433_http_ws.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Custom data handling example for rtl_433's HTTP WebSocket API of JSON events.\"\"\"\n\n# Start rtl_433 (`rtl_433 -F http`), then this script.\n# Needs the websocket-client package to be installed.\n\nimport websocket\nimport json\nfrom time import sleep\n\n# You can run rtl_433 and this script on different machines,\n# start rtl_433 with `-F http:0.0.0.0`, and change\n# to e.g. `HTTP_HOST = \"192.168.1.100\"` (use your server ip) below.\nHTTP_HOST = \"127.0.0.1\"\nHTTP_PORT = 8433\n\n\ndef ws_events():\n    url = f'ws://{HTTP_HOST}:{HTTP_PORT}/ws'\n    ws = websocket.WebSocket()\n    ws.connect(url)\n\n    # You will receive JSON events, one per message.\n    print(f'Connected to {url}')\n\n    while True:\n        yield ws.recv()\n\n\ndef handle_event(line):\n    try:\n        # Decode the message as JSON\n        data = json.loads(line)\n\n        #\n        # Change for your custom handling below, this is a simple example\n        #\n        label = data[\"model\"]\n        if \"channel\" in data:\n            label += \".CH\" + str(data[\"channel\"])\n        elif \"id\" in data:\n            label += \".ID\" + str(data[\"id\"])\n\n        # E.g. match `model` and `id` to a descriptive name.\n        if data[\"model\"] == \"LaCrosse-TX\" and data[\"id\"] == 123:\n            label = \"Living Room\"\n\n        if \"battery_ok\" in data:\n            if data[\"battery_ok\"] == 0:\n                print(label + ' Battery empty!')\n\n        if \"temperature_C\" in data:\n            print(label + ' Temperature ', data[\"temperature_C\"])\n\n        if \"humidity\" in data:\n            print(label + ' Humidity ', data[\"humidity\"])\n\n    except KeyError:\n        # Ignore unknown message data and continue\n        pass\n\n    except ValueError as e:\n        # Warn on decoding errors\n        print(f'Event format not recognized: {e}')\n\n\ndef rtl_433_listen():\n    \"\"\"Listen to all messages in a loop forever.\"\"\"\n\n    # Loop forever\n    while True:\n        try:\n            # Open the HTTP WebSocket API of JSON events\n            for chunk in ws_events():\n                # print(chunk)\n                chunk = chunk.rstrip()\n                if not chunk:\n                    # filter out keep-alive empty lines\n                    continue\n                # Decode the JSON message\n                handle_event(chunk)\n\n        except ConnectionRefusedError:\n            print('Connection refused, retrying...')\n            sleep(5)\n            pass\n\n        except websocket._exceptions.WebSocketConnectionClosedException:\n            print('Connection failed, retrying...')\n            sleep(5)\n\n\nif __name__ == \"__main__\":\n    try:\n        rtl_433_listen()\n    except KeyboardInterrupt:\n        print('\\nExiting.')\n        pass\n"
  },
  {
    "path": "examples/rtl_433_influxdb_relay.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"InfluxDB monitoring relay for rtl_433.\"\"\"\n\n# Start rtl_433 (rtl_433 -F syslog::1433), then this script\n\n# Option: PEP 3143 - Standard daemon process library\n# (use Python 3.x or pip install python-daemon)\n# import daemon\n\nfrom __future__ import print_function\nfrom __future__ import with_statement\n\nfrom influxdb import InfluxDBClient\nimport socket\nfrom datetime import datetime\nimport json\nimport sys\n\nUDP_IP = \"127.0.0.1\"\nUDP_PORT = 1433\nINFLUXDB_HOST = \"127.0.0.1\"\nINFLUXDB_PORT = 8086\nINFLUXDB_USERNAME = \"\"\nINFLUXDB_PASSWORD = \"\"\nINFLUXDB_DATABASE = \"rtl433\"\n\nTAGS = [\n    \"channel\",\n    \"id\",\n]\n\nFIELDS = [\n    \"temperature_C\",\n    \"humidity\",\n    \"battery_ok\",\n    \"pressure_hPa\",\n]\n\nsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\nsock.bind((UDP_IP, UDP_PORT))\n\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\").replace(\"/\", \"_\").replace(\".\", \"_\").replace(\"&\", \"\")\n\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        fields = line.split(None, 7)\n        line = fields[-1]\n    return line\n\n\ndef rtl_433_probe():\n    client = InfluxDBClient(host=INFLUXDB_HOST, port=INFLUXDB_PORT,\n                            username=INFLUXDB_USERNAME, password=INFLUXDB_PASSWORD,\n                            database=INFLUXDB_DATABASE)\n\n    while True:\n        line, _addr = sock.recvfrom(1024)\n\n        try:\n            line = parse_syslog(line)\n            data = json.loads(line)\n\n            if not \"model\" in data:\n                continue\n            measurement = sanitize(data[\"model\"])\n\n            tags = {}\n            for tag in TAGS:\n                if tag in data:\n                    tags[tag] = data[tag]\n\n            fields = {}\n            for field in FIELDS:\n                if field in data:\n                    fields[field] = data[field]\n\n            if len(fields) == 0:\n                continue\n\n            point = {\n                \"measurement\": measurement,\n                \"time\": datetime.now().isoformat(),\n                \"tags\": tags,\n                \"fields\": fields,\n            }\n\n            try:\n                client.write_points([point])\n            except Exception as e:\n                print(\"error {} writing {}\".format(e, point), file=sys.stderr)\n\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\ndef run():\n    # with daemon.DaemonContext(files_preserve=[sock]):\n    #  detach_process=True\n    #  uid\n    #  gid\n    #  working_directory\n    rtl_433_probe()\n\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "examples/rtl_433_json_to_rtlwmbus.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\n$ rtl_433 -R 104 -F json | rtl_433_json_to_rtlwmbus.py\n\nA script to convert rtl_433 wmbus json output to rtlwmbus output\n\nCopyright (C) 2019 Benjamin Larsson\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation; either version 2 of the License, or\n(at your option) any later version.\n\"\"\"\n\nimport sys\nimport json\nimport time;\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\")\n\ndef rtl_433_wmbus():\n    dup = {}\n    seconds = 10\n    while True:\n        line = sys.stdin.readline()\n        ts = int(time.time())\n        if not line:\n            break\n\n        try:\n            event = json.loads(line)\n\n            # Duplicate check + check if dictianary is initialized\n            id = int(event['id'])\n            if id in dup:\n                #print(\"if %s D:%s T:%s\" % (id, dup[id], ts))\n                if (dup[id] + seconds) < ts:\n                    duplicate = False\n                    dup[id] = ts;\n                else:\n                    duplicate = True\n                    #print(\"Dup! %s\" % (id))\n            else:\n                #print(\"else %s\" % (id))\n                dup[id] = ts;\n                duplicate = False\n\n            if duplicate != True:\n                print(\"%s1;1;1;%s.000;54;46;%s;0x%s\" % (event['mode'], event['time'], event['id'], event['data']) )\n                sys.stdout.flush()\n\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\nif __name__ == \"__main__\":\n    dup_test = {}\n    rtl_433_wmbus()\n"
  },
  {
    "path": "examples/rtl_433_mqtt_hass.py",
    "content": "#!/usr/bin/env python3\n# coding=utf-8\n\nfrom __future__ import print_function\nfrom __future__ import with_statement\n\nAP_DESCRIPTION=\"\"\"\nPublish Home Assistant MQTT auto discovery topics for rtl_433 devices.\n\nrtl_433_mqtt_hass.py connects to MQTT and subscribes to the rtl_433\nevent stream that is published to MQTT by rtl_433. The script publishes\nadditional MQTT topics that can be used by Home Assistant to automatically\ndiscover and minimally configure new devices.\n\nThe configuration topics published by this script tell Home Assistant\nwhat MQTT topics to subscribe to in order to receive the data published\nas device topics by MQTT.\n\"\"\"\n\nAP_EPILOG=\"\"\"\nIt is strongly recommended to run rtl_433 with \"-C si\".\nThis script requires rtl_433 to publish both event messages and device\nmessages. If you've changed the device topic in rtl_433, use the same device\ntopic with the \"-T\" parameter.\n\nMQTT Username and Password can be set via the cmdline or passed in the\nenvironment: MQTT_USERNAME and MQTT_PASSWORD.\n\nPrerequisites:\n\n1. rtl_433 running separately publishing events and devices messages to MQTT.\n\n2. Python installation\n* Python 3.x preferred.\n* Needs Paho-MQTT https://pypi.python.org/pypi/paho-mqtt\n\n  Debian/raspbian:  apt install python3-paho-mqtt\n  Or\n  pip install paho-mqtt\n* Optional for running as a daemon see PEP 3143 - Standard daemon process library\n  (use Python 3.x or pip install python-daemon)\n\n\nRunning:\n\nThis script can run continually as a daemon, where it will publish\na configuration topic for the device events sent to MQTT by rtl_433\nevery 10 minutes.\n\nAlternatively if the rtl_433 devices in your environment change infrequently\nthis script can use the MQTT retain flag to make the configuration topics\npersistent. The script will only need to be run when things change or if\nthe MQTT server loses its retained messages.\n\nGetting rtl_433 devices back after Home Assistant restarts will happen\nmore quickly if MQTT retain is enabled. Note however that definitions\nfor any transitient devices/false positives will retained indefinitely.\n\nIf your sensor values change infrequently and you prefer to write the most\nrecent value even if not changed set -f to append \"force_update = true\" to\nall configs. This is useful if you're graphing the sensor data or want to\nalert on missing data.\n\nIf you have changed the topic structure from the default topics in the rtl433\nconfiguration use the -T parameter to set the same topic structure here.\n\nSuggestions:\n\nRunning this script will cause a number of Home Assistant entities (sensors\nand binary sensors) to be created. These entities can linger for a while unless\nthe topic is republished with an empty config string.  To avoid having to\ndo a lot of clean up When running this initially or debugging, set this\nscript to publish to a topic other than the one Home Assistant users (homeassistant).\n\nMQTT Explorer (http://mqtt-explorer.com/) is a very nice GUI for\nworking with MQTT. It is free, cross platform, and OSS. The structured\nhierarchical view makes it easier to understand what rtl_433 is publishing\nand how this script works with Home Assistant.\n\nMQTT Explorer also makes it easy to publish an empty config topic to delete an\nentity from Home Assistant.\n\n\nAs of 2020-10, Home Assistant MQTT auto discovery doesn't currently support\nsupplying \"friendly name\", and \"area\" key, so some configuration must be\ndone in Home Assistant.\n\nThere is a single global set of field mappings to Home Assistant meta data.\n\n\"\"\"\n\n\n\n# import daemon\n\n\nimport os\nimport argparse\nimport logging\nimport time\nimport json\nimport paho.mqtt.client as mqtt\nimport re\n\n\ndiscovery_timeouts = {}\n\n# Fields that get ignored when publishing to Home Assistant\n# (reduces noise to help spot missing field mappings)\nSKIP_KEYS = [ \"type\", \"model\", \"subtype\", \"channel\", \"id\", \"mic\", \"mod\",\n                \"freq\", \"sequence_num\", \"message_type\", \"exception\", \"raw_msg\" ]\n\n\n# Global mapping of rtl_433 field names to Home Assistant metadata.\n# @todo - should probably externalize to a config file\n# @todo - Model specific definitions might be needed\n\nmappings = {\n    \"temperature_C\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"T\",\n        \"config\": {\n            \"device_class\": \"temperature\",\n            \"name\": \"Temperature\",\n            \"unit_of_measurement\": \"°C\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"temperature_1_C\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"T1\",\n        \"config\": {\n            \"device_class\": \"temperature\",\n            \"name\": \"Temperature 1\",\n            \"unit_of_measurement\": \"°C\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"temperature_2_C\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"T2\",\n        \"config\": {\n            \"device_class\": \"temperature\",\n            \"name\": \"Temperature 2\",\n            \"unit_of_measurement\": \"°C\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"temperature_3_C\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"T3\",\n        \"config\": {\n            \"device_class\": \"temperature\",\n            \"name\": \"Temperature 3\",\n            \"unit_of_measurement\": \"°C\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"temperature_4_C\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"T4\",\n        \"config\": {\n            \"device_class\": \"temperature\",\n            \"name\": \"Temperature 4\",\n            \"unit_of_measurement\": \"°C\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"temperature_F\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"F\",\n        \"config\": {\n            \"device_class\": \"temperature\",\n            \"name\": \"Temperature\",\n            \"unit_of_measurement\": \"°F\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    # This diagnostic sensor is useful to see when a device last sent a value,\n    # even if the value didn't change.\n    # https://community.home-assistant.io/t/send-metrics-to-influxdb-at-regular-intervals/9096\n    # https://github.com/home-assistant/frontend/discussions/13687\n    \"time\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"UTC\",\n        \"config\": {\n            \"device_class\": \"timestamp\",\n            \"name\": \"Timestamp\",\n            \"entity_category\": \"diagnostic\",\n            \"enabled_by_default\": False,\n            \"icon\": \"mdi:clock-in\"\n        }\n    },\n\n    \"battery_ok\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"B\",\n        \"config\": {\n            \"device_class\": \"battery\",\n            \"name\": \"Battery\",\n            \"unit_of_measurement\": \"%\",\n            \"value_template\": \"{{ ((float(value) * 99)|round(0)) + 1 }}\",\n            \"state_class\": \"measurement\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"battery_mV\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"mV\",\n        \"config\": {\n            \"device_class\": \"voltage\",\n            \"name\": \"Battery mV\",\n            \"unit_of_measurement\": \"mV\",\n            \"value_template\": \"{{ float(value) }}\",\n            \"state_class\": \"measurement\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"supercap_V\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"V\",\n        \"config\": {\n            \"device_class\": \"voltage\",\n            \"name\": \"Supercap V\",\n            \"unit_of_measurement\": \"V\",\n            \"value_template\": \"{{ float(value) }}\",\n            \"state_class\": \"measurement\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"humidity\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"H\",\n        \"config\": {\n            \"device_class\": \"humidity\",\n            \"name\": \"Humidity\",\n            \"unit_of_measurement\": \"%\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"humidity_1\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"H1\",\n        \"config\": {\n            \"device_class\": \"humidity\",\n            \"name\": \"Humidity 1\",\n            \"unit_of_measurement\": \"%\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"humidity_2\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"H2\",\n        \"config\": {\n            \"device_class\": \"humidity\",\n            \"name\": \"Humidity 2\",\n            \"unit_of_measurement\": \"%\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"moisture\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"M\",\n        \"config\": {\n            \"device_class\": \"moisture\",\n            \"name\": \"Moisture\",\n            \"unit_of_measurement\": \"%\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"detect_wet\": {\n        \"device_type\": \"binary_sensor\",\n        \"object_suffix\": \"moisture\",\n        \"config\": {\n            \"name\": \"Water Sensor\",\n            \"device_class\": \"moisture\",\n            \"force_update\": \"true\",\n            \"payload_on\": \"1\",\n            \"payload_off\": \"0\"\n        }\n    },\n\n    \"pressure_hPa\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"P\",\n        \"config\": {\n            \"device_class\": \"pressure\",\n            \"name\": \"Pressure\",\n            \"unit_of_measurement\": \"hPa\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"pressure_kPa\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"P\",\n        \"config\": {\n            \"device_class\": \"pressure\",\n            \"name\": \"Pressure\",\n            \"unit_of_measurement\": \"kPa\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_speed_km_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"WS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind Speed\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_avg_km_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"WS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind Speed\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_avg_mi_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"WS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind Speed\",\n            \"unit_of_measurement\": \"mph\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_avg_m_s\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"WS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind Average\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ (float(value|float) * 3.6) | round(2) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_speed_m_s\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"WS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind Speed\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ float(value|float) * 3.6 }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"gust_speed_km_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"GS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Gust Speed\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_max_km_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"GS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind max speed\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_max_m_s\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"GS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Wind max\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ (float(value|float) * 3.6) | round(2) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"gust_speed_m_s\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"GS\",\n        \"config\": {\n            \"device_class\": \"wind_speed\",\n            \"name\": \"Gust Speed\",\n            \"unit_of_measurement\": \"km/h\",\n            \"value_template\": \"{{ float(value|float) * 3.6 }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"wind_dir_deg\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"WD\",\n        \"config\": {\n            \"name\": \"Wind Direction\",\n            \"unit_of_measurement\": \"°\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"rain_mm\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"RT\",\n        \"config\": {\n            \"device_class\": \"precipitation\",\n            \"name\": \"Rain Total\",\n            \"unit_of_measurement\": \"mm\",\n            \"value_template\": \"{{ value|float|round(2) }}\",\n            \"state_class\": \"total_increasing\"\n        }\n    },\n\n    \"rain_rate_mm_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"RR\",\n        \"config\": {\n            \"device_class\": \"precipitation_intensity\",\n            \"name\": \"Rain Rate\",\n            \"unit_of_measurement\": \"mm/h\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"rain_in\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"RT\",\n        \"config\": {\n            \"device_class\": \"precipitation\",\n            \"name\": \"Rain Total\",\n            \"unit_of_measurement\": \"in\",\n            \"value_template\": \"{{ value|float|round(2) }}\",\n            \"state_class\": \"total_increasing\"\n        }\n    },\n\n    \"rain_rate_in_h\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"RR\",\n        \"config\": {\n            \"device_class\": \"precipitation_intensity\",\n            \"name\": \"Rain Rate\",\n            \"unit_of_measurement\": \"in/h\",\n            \"value_template\": \"{{ value|float|round(2) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"reed_open\": {\n        \"device_type\": \"binary_sensor\",\n        \"object_suffix\": \"reed_open\",\n        \"config\": {\n            \"device_class\": \"safety\",\n            \"force_update\": \"true\",\n            \"payload_on\": \"1\",\n            \"payload_off\": \"0\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"contact_open\": {\n        \"device_type\": \"binary_sensor\",\n        \"object_suffix\": \"contact_open\",\n        \"config\": {\n            \"device_class\": \"safety\",\n            \"force_update\": \"true\",\n            \"payload_on\": \"1\",\n            \"payload_off\": \"0\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"tamper\": {\n        \"device_type\": \"binary_sensor\",\n        \"object_suffix\": \"tamper\",\n        \"config\": {\n            \"device_class\": \"safety\",\n            \"force_update\": \"true\",\n            \"payload_on\": \"1\",\n            \"payload_off\": \"0\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"alarm\": {\n        \"device_type\": \"binary_sensor\",\n        \"object_suffix\": \"alarm\",\n        \"config\": {\n            \"device_class\": \"safety\",\n            \"force_update\": \"true\",\n            \"payload_on\": \"1\",\n            \"payload_off\": \"0\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"rssi\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"rssi\",\n        \"config\": {\n            \"device_class\": \"signal_strength\",\n            \"unit_of_measurement\": \"dB\",\n            \"value_template\": \"{{ value|float|round(2) }}\",\n            \"state_class\": \"measurement\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"snr\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"snr\",\n        \"config\": {\n            \"device_class\": \"signal_strength\",\n            \"unit_of_measurement\": \"dB\",\n            \"value_template\": \"{{ value|float|round(2) }}\",\n            \"state_class\": \"measurement\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"noise\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"noise\",\n        \"config\": {\n            \"device_class\": \"signal_strength\",\n            \"unit_of_measurement\": \"dB\",\n            \"value_template\": \"{{ value|float|round(2) }}\",\n            \"state_class\": \"measurement\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n    \"depth_cm\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"D\",\n        \"config\": {\n            \"name\": \"Depth\",\n            \"unit_of_measurement\": \"cm\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"power_W\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"watts\",\n        \"config\": {\n            \"device_class\": \"power\",\n            \"name\": \"Power\",\n            \"unit_of_measurement\": \"W\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"energy_kWh\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"kwh\",\n        \"config\": {\n            \"device_class\": \"energy\",\n            \"name\": \"Energy\",\n            \"unit_of_measurement\": \"kWh\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"total_increasing\"\n        }\n    },\n\n    \"current_A\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"A\",\n        \"config\": {\n            \"device_class\": \"current\",\n            \"name\": \"Current\",\n            \"unit_of_measurement\": \"A\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"voltage_V\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"V\",\n        \"config\": {\n            \"device_class\": \"voltage\",\n            \"name\": \"Voltage\",\n            \"unit_of_measurement\": \"V\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"light_lux\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"lux\",\n        \"config\": {\n            \"device_class\": \"illuminance\",\n            \"name\": \"Outside Luminance\",\n            \"unit_of_measurement\": \"lx\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"lux\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"lux\",\n        \"config\": {\n            \"device_class\": \"illuminance\",\n            \"name\": \"Outside Luminance\",\n            \"unit_of_measurement\": \"lx\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"uv\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"uv\",\n        \"config\": {\n            \"name\": \"UV Value\",\n            \"unit_of_measurement\": \"UV\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n    \"uvi\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"uvi\",\n        \"config\": {\n            \"name\": \"UV Index\",\n            \"unit_of_measurement\": \"UV Index\",\n            \"value_template\": \"{{ value|float|round(1) }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"storm_dist_km\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"stdist\",\n        \"config\": {\n            \"name\": \"Lightning Distance\",\n            \"unit_of_measurement\": \"km\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"storm_dist\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"stdist\",\n        \"config\": {\n            \"name\": \"Lightning Distance\",\n            \"unit_of_measurement\": \"mi\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"strike_distance\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"stdist\",\n        \"config\": {\n            \"name\": \"Lightning Distance\",\n            \"unit_of_measurement\": \"mi\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"strike_count\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"strcnt\",\n        \"config\": {\n            \"name\": \"Lightning Strike Count\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"total_increasing\"\n        }\n    },\n\n    \"consumption_data\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"consumption\",\n        \"config\": {\n            \"name\": \"SCM Consumption Value\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"total_increasing\",\n        }\n    },\n\n    \"consumption\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"consumption\",\n        \"config\": {\n            \"name\": \"SCMplus Consumption Value\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"total_increasing\",\n        }\n    },\n\n    \"channel\": {\n        \"device_type\": \"device_automation\",\n        \"object_suffix\": \"CH\",\n        \"config\": {\n           \"automation_type\": \"trigger\",\n           \"type\": \"button_short_release\",\n           \"subtype\": \"button_1\",\n        }\n    },\n\n    \"button\": {\n        \"device_type\": \"device_automation\",\n        \"object_suffix\": \"BTN\",\n        \"config\": {\n           \"automation_type\": \"trigger\",\n           \"type\": \"button_short_release\",\n           \"subtype\": \"button_2\",\n        }\n    },\n\n    # WH45, WH290\n    \"pm2_5_ug_m3\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"PM25\",\n        \"config\": {\n            \"device_class\": \"pm25\",\n            \"name\": \"PM 2.5 Concentration\",\n            \"unit_of_measurement\": \"µg/m³\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    # WH45\n    \"pm10_ug_m3\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"PM10\",\n        \"config\": {\n            \"device_class\": \"pm10\",\n            \"name\": \"PM 10 Concentration\",\n            \"unit_of_measurement\": \"µg/m³\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    # WH290\n    \"estimated_pm10_0_ug_m3\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"PM10\",\n        \"config\": {\n            \"device_class\": \"pm10\",\n            \"name\": \"Estimated PM 10 Concentration\",\n            \"unit_of_measurement\": \"µg/m³\",\n            \"value_template\": \"{{ value|float }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    # WH45\n    \"co2_ppm\": {\n        \"device_type\": \"sensor\",\n        \"object_suffix\": \"CO2\",\n        \"config\": {\n            \"device_class\": \"carbon_dioxide\",\n            \"name\": \"CO2 Concentration\",\n            \"unit_of_measurement\": \"ppm\",\n            \"value_template\": \"{{ value|int }}\",\n            \"state_class\": \"measurement\"\n        }\n    },\n\n    \"ext_power\": {\n        \"device_type\": \"binary_sensor\",\n        \"object_suffix\": \"extpwr\",\n        \"config\": {\n            \"device_class\": \"power\",\n            \"name\": \"External Power\",\n            \"payload_on\": \"1\",\n            \"payload_off\": \"0\",\n            \"entity_category\": \"diagnostic\"\n        }\n    },\n\n}\n\n# Use secret_knock to trigger device automations for Honeywell ActivLink\n# doorbells. We have this outside of mappings as we need to configure two\n# different configuration topics.\nsecret_knock_mappings = [\n\n    {\n        \"device_type\": \"device_automation\",\n        \"object_suffix\": \"Knock\",\n        \"config\": {\n            \"automation_type\": \"trigger\",\n            \"type\": \"button_short_release\",\n            \"subtype\": \"button_1\",\n            \"payload\": 0,\n        }\n    },\n\n    {\n        \"device_type\": \"device_automation\",\n        \"object_suffix\": \"Secret-Knock\",\n        \"config\": {\n            \"automation_type\": \"trigger\",\n            \"type\": \"button_triple_press\",\n            \"subtype\": \"button_1\",\n            \"payload\": 1,\n        }\n    },\n\n]\n\nTOPIC_PARSE_RE = re.compile(r'\\[(?P<slash>/?)(?P<token>[^\\]:]+):?(?P<default>[^\\]:]*)\\]')\n\ndef mqtt_connect(client, userdata, flags, rc):\n    \"\"\"Callback for MQTT connects.\"\"\"\n\n    logging.info(\"MQTT connected: \" + mqtt.connack_string(rc))\n    if rc != 0:\n        logging.error(\"Could not connect. Error: \" + str(rc))\n    else:\n        logging.info(\"Subscribing to: \" + args.rtl_topic)\n        client.subscribe(args.rtl_topic)\n\n\ndef mqtt_disconnect(client, userdata, rc):\n    \"\"\"Callback for MQTT disconnects.\"\"\"\n    logging.info(\"MQTT disconnected: \" + mqtt.connack_string(rc))\n\n\ndef mqtt_message(client, userdata, msg):\n    \"\"\"Callback for MQTT message PUBLISH.\"\"\"\n    logging.debug(\"MQTT message: \" + json.dumps(msg.payload.decode()))\n\n    try:\n        # Decode JSON payload\n        data = json.loads(msg.payload.decode())\n\n    except json.decoder.JSONDecodeError:\n        logging.error(\"JSON decode error: \" + msg.payload.decode())\n        return\n\n    topicprefix = \"/\".join(msg.topic.split(\"/\", 2)[:2])\n    bridge_event_to_hass(client, topicprefix, data)\n\n\ndef sanitize(text):\n    \"\"\"Sanitize a name for Graphite/MQTT use.\"\"\"\n    return (text\n            .replace(\" \", \"_\")\n            .replace(\"/\", \"_\")\n            .replace(\".\", \"_\")\n            .replace(\"&\", \"\"))\n\ndef rtl_433_device_info(data, topic_prefix):\n    \"\"\"Return rtl_433 device topic to subscribe to for a data element, based on the\n    rtl_433 device topic argument, as well as the device identifier\"\"\"\n\n    path_elements = []\n    id_elements = []\n    last_match_end = 0\n    # The default for args.device_topic_suffix is the same topic structure\n    # as set by default in rtl433 config\n    for match in re.finditer(TOPIC_PARSE_RE, args.device_topic_suffix):\n        path_elements.append(args.device_topic_suffix[last_match_end:match.start()])\n        key = match.group(2)\n        if key in data:\n            # If we have this key, prepend a slash if needed\n            if match.group(1):\n                path_elements.append('/')\n            element = sanitize(str(data[key]))\n            path_elements.append(element)\n            id_elements.append(element)\n        elif match.group(3):\n            path_elements.append(match.group(3))\n        last_match_end = match.end()\n\n    path = ''.join(list(filter(lambda item: item, path_elements)))\n    id = '-'.join(id_elements)\n    return (f\"{topic_prefix}/{path}\", id)\n\n\ndef publish_config(mqttc, topic, model, object_id, mapping, key=None):\n    \"\"\"Publish Home Assistant auto discovery data.\"\"\"\n    global discovery_timeouts\n\n    device_type = mapping[\"device_type\"]\n    object_suffix = mapping[\"object_suffix\"]\n    object_name = \"-\".join([object_id, object_suffix])\n\n    path = \"/\".join([args.discovery_prefix, device_type, object_id, object_name, \"config\"])\n\n    # check timeout\n    now = time.time()\n    if path in discovery_timeouts:\n        if discovery_timeouts[path] > now:\n            logging.debug(\"Discovery timeout in the future for: \" + path)\n            return False\n\n    discovery_timeouts[path] = now + args.discovery_interval\n\n    config = mapping[\"config\"].copy()\n\n    # Device Automation configuration is in a different structure compared to\n    # all other mqtt discovery types.\n    # https://www.home-assistant.io/integrations/device_trigger.mqtt/\n    if device_type == 'device_automation':\n        config[\"topic\"] = topic\n        config[\"platform\"] = 'mqtt'\n    else:\n        readable_name = mapping[\"config\"][\"name\"] if \"name\" in mapping[\"config\"] else key\n        config[\"state_topic\"] = topic\n        config[\"unique_id\"] = object_name\n        config[\"name\"] = readable_name\n    config[\"device\"] = { \"identifiers\": [object_id], \"name\": object_id, \"model\": model, \"manufacturer\": \"rtl_433\" }\n\n    if args.force_update:\n        config[\"force_update\"] = \"true\"\n\n    if args.expire_after:\n        config[\"expire_after\"] = args.expire_after\n\n    logging.debug(path + \":\" + json.dumps(config))\n\n    mqttc.publish(path, json.dumps(config), retain=args.retain)\n\n    return True\n\ndef bridge_event_to_hass(mqttc, topic_prefix, data):\n    \"\"\"Translate some rtl_433 sensor data to Home Assistant auto discovery.\"\"\"\n\n    if \"model\" not in data:\n        # not a device event\n        logging.debug(\"Model is not defined. Not sending event to Home Assistant.\")\n        return\n\n    model = sanitize(data[\"model\"])\n\n    skipped_keys = []\n    published_keys = []\n\n    base_topic, device_id = rtl_433_device_info(data, topic_prefix)\n    if not device_id:\n        # no unique device identifier\n        logging.warning(\"No suitable identifier found for model: %s\", model)\n        return\n\n    if args.ids and \"id\" in data and data.get(\"id\") not in args.ids:\n        # not in the safe list\n        logging.debug(\"Device (%s) is not in the desired list of device ids: [%s]\" % (data[\"id\"], ids))\n        return\n\n    # detect known attributes\n    for key in data.keys():\n        if key in mappings:\n            # topic = \"/\".join([topicprefix,\"devices\",model,instance,key])\n            topic = \"/\".join([base_topic, key])\n            if publish_config(mqttc, topic, model, device_id, mappings[key], key):\n                published_keys.append(key)\n        else:\n            if key not in SKIP_KEYS:\n                skipped_keys.append(key)\n\n    if \"secret_knock\" in data.keys():\n        for m in secret_knock_mappings:\n            topic = \"/\".join([base_topic, \"secret_knock\"])\n            if publish_config(mqttc, topic, model, device_id, m, \"secret_knock\"):\n                published_keys.append(\"secret_knock\")\n\n    if published_keys:\n        logging.info(\"Published %s: %s\" % (device_id, \", \".join(published_keys)))\n\n        if skipped_keys:\n            logging.info(\"Skipped %s: %s\" % (device_id, \", \".join(skipped_keys)))\n\n\ndef rtl_433_bridge():\n    \"\"\"Run a MQTT Home Assistant auto discovery bridge for rtl_433.\"\"\"\n\n    if hasattr(mqtt, 'CallbackAPIVersion'):  # paho >= 2.0.0\n        mqttc = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION1)\n    else:\n        mqttc = mqtt.Client()\n\n    if args.debug:\n        mqttc.enable_logger()\n\n    if args.user is not None:\n        mqttc.username_pw_set(args.user, args.password)\n\n    if args.ca_cert is not None:\n        mqttc.tls_set(certfile=args.cert, keyfile=args.key, ca_certs=args.ca_cert)\n\n    mqttc.on_connect = mqtt_connect\n    mqttc.on_disconnect = mqtt_disconnect\n    mqttc.on_message = mqtt_message\n    mqttc.connect_async(args.host, args.port, 60)\n    logging.debug(\"MQTT Client: Starting Loop\")\n    mqttc.loop_start()\n\n    while True:\n        time.sleep(1)\n\n\ndef run():\n    \"\"\"Run main or daemon.\"\"\"\n    # with daemon.DaemonContext(files_preserve=[sock]):\n    #  detach_process=True\n    #  uid\n    #  gid\n    #  working_directory\n    rtl_433_bridge()\n\n\nif __name__ == \"__main__\":\n    logging.basicConfig(format='[%(asctime)s] %(levelname)s:%(name)s:%(message)s',datefmt='%Y-%m-%dT%H:%M:%S%z')\n    logging.getLogger().setLevel(logging.INFO)\n\n    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,\n                                     description=AP_DESCRIPTION,\n                                     epilog=AP_EPILOG)\n\n    parser.add_argument(\"-d\", \"--debug\", action=\"store_true\")\n    parser.add_argument(\"-q\", \"--quiet\", action=\"store_true\")\n    parser.add_argument(\"-u\", \"--user\", type=str, help=\"MQTT username\")\n    parser.add_argument(\"-P\", \"--password\", type=str, help=\"MQTT password\")\n    parser.add_argument(\"-H\", \"--host\", type=str, default=\"127.0.0.1\",\n                        help=\"MQTT hostname to connect to (default: %(default)s)\")\n    parser.add_argument(\"-p\", \"--port\", type=int, default=1883,\n                        help=\"MQTT port (default: %(default)s)\")\n    parser.add_argument(\"-c\", \"--ca_cert\", type=str, help=\"MQTT TLS CA certificate path\")\n    parser.add_argument(\"--cert\", type=str, help=\"MQTT TLS certificate path\")\n    parser.add_argument(\"--key\", type=str, help=\"MQTT TLS certificate key path\")\n    parser.add_argument(\"-r\", \"--retain\", action=\"store_true\")\n    parser.add_argument(\"-f\", \"--force_update\", action=\"store_true\",\n                        help=\"Append 'force_update = true' to all configs.\")\n    parser.add_argument(\"-R\", \"--rtl-topic\", type=str,\n                        default=\"rtl_433/+/events\",\n                        dest=\"rtl_topic\",\n                        help=\"rtl_433 MQTT event topic to subscribe to (default: %(default)s)\")\n    parser.add_argument(\"-D\", \"--discovery-prefix\", type=str,\n                        dest=\"discovery_prefix\",\n                        default=\"homeassistant\",\n                        help=\"Home Assistant MQTT topic prefix (default: %(default)s)\")\n    # This defaults to the rtl433 config default, so we assemble the same topic structure\n    parser.add_argument(\"-T\", \"--device-topic_suffix\", type=str,\n                        dest=\"device_topic_suffix\",\n                        default=\"devices[/type][/model][/subtype][/channel][/id]\",\n                        help=\"rtl_433 device topic suffix (default: %(default)s)\")\n    parser.add_argument(\"-i\", \"--interval\", type=int,\n                        dest=\"discovery_interval\",\n                        default=600,\n                        help=\"Interval to republish config topics in seconds (default: %(default)d)\")\n    parser.add_argument(\"-x\", \"--expire-after\", type=int,\n                        dest=\"expire_after\",\n                        help=\"Number of seconds with no updates after which the sensor becomes unavailable\")\n    parser.add_argument(\"-I\", \"--ids\", type=int, nargs=\"+\",\n                        help=\"ID's of devices that will be discovered (omit for all)\")\n    args = parser.parse_args()\n\n    if args.debug and args.quiet:\n        logging.critical(\"Debug and quiet can not be specified at the same time\")\n        exit(1)\n\n    if args.debug:\n        logging.info(\"Enabling debug logging\")\n        logging.getLogger().setLevel(logging.DEBUG)\n    if args.quiet:\n        logging.getLogger().setLevel(logging.ERROR)\n\n    # allow setting MQTT username and password via environment variables\n    if not args.user and 'MQTT_USERNAME' in os.environ:\n        args.user = os.environ['MQTT_USERNAME']\n\n    if not args.password and 'MQTT_PASSWORD' in os.environ:\n        args.password = os.environ['MQTT_PASSWORD']\n\n    if not args.user or not args.password:\n        logging.warning(\"User or password is not set. Check credentials if subscriptions do not return messages.\")\n\n    if args.ids:\n        ids = ', '.join(str(id) for id in args.ids)\n        logging.info(\"Only discovering devices with ids: [%s]\" % ids)\n    else:\n        logging.info(\"Discovering all devices\")\n\n    run()\n"
  },
  {
    "path": "examples/rtl_433_mqtt_relay.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"MQTT monitoring relay for rtl_433 communication.\"\"\"\n\n# This program listens on a UDP socket for syslog messages with a json\n# payload, and publishes the data via MQTT.  The broker connection is\n# kept open (and automatically reconnects on failure).  Each device\n# is mapped to its own topic,\n\n# Dependencies:\n#   Paho-MQTT; see https://pypi.python.org/pypi/paho-mqtt\n\n#   Optionally: PEP 3143 - Standard daemon process library\n#      (on 2.7,  pip install python-daemon)\n\n# To enable daemon support, uncomment the following line and adjust\n# run().  Note that print() is still used.\n# import daemon\n\nfrom __future__ import print_function\nfrom __future__ import with_statement\n\nimport json\nimport logging\nimport socket\nimport time\n\nimport paho.mqtt.client as mqtt\n\n\f\n# The config class represents a config object.  The constructor takes\n# an optional pathname, and will switch on the suffix (.yaml for now)\n# and read a dictionary.\nclass rtlconfig(object):\n\n    # Initialize with default values.\n    c = {\n        # Log level info (False) or debug (True)\n        'DEBUG': False,\n\n        # Address to listen on for syslog/json messages from rtl_433\n        'UDP_IP': \"127.0.0.1\",\n        'UDP_PORT': 1433,\n        \n        # MQTT broker address and credentials\n        'MQTT_HOST': \"127.0.0.1\",\n        'MQTT_PORT': 1883,\n        'MQTT_USERNAME': None,\n        'MQTT_PASSWORD': None,\n        'MQTT_TLS': False,\n\n        # MQTT content\n        'MQTT_PREFIX': \"sensor/rtl_433\",\n        'MQTT_DEDUP': True,\n        'MQTT_INDIVIDUAL_TOPICS': True,\n        'MQTT_JSON_TOPIC': True,\n    }\n    \n    def __init__(self, f=None):\n        fdict = None\n\n        # Try to read a dictionary from f.\n        if f:\n            try:\n                # Assume yaml. \\todo Check and support other formats\n                import yaml\n                with open(f) as fh:\n                    fdict = yaml.safe_load(fh)\n            except:\n                print('Did not read {f} (no yaml, not found, bad?).'.format(f=f))\n            \n        # Merge fdict into configdict.\n        if fdict:\n            for (k, v) in fdict.items():\n                self.c[k] = v\n\n    # Support c['name'] references.\n    def __getitem__(self, k):\n        return self.c[k]\n\nclass dedup(object):\n    \"\"\" A dedup class object supports deduping a stream of reports by\n        answering if a report is interesting relative to the history.  While\n        more complicated deduping is allowed by the interface, for now it is\n        very simple, keeping track of only the previous interesting object.\n        For now, we more or less require that all reports have the same keys. \"\"\"\n\n    # \\todo Consider a cache with several entries.\n\n    def __init__(self):\n        # Make this long enough to skip repeats, but allow messages\n        # every 10s to come through.\n        self.duration = 5\n        # Exclude reception metadata (time and RF).\n        self.boring_keys = ('time', 'freq', 'freq1', 'freq2', 'rssi', 'snr', 'noise', 'raw_msg')\n        # Initialize storage for what was last sent.\n        (self.last_report, self.last_now) = (None, None)\n    \n    def send_store(self, report, n):\n        \"\"\" Record report, n as the last report declared interesting, and\n            return True (to denote interesting). \"\"\"\n        (self.last_report, self.last_now) = (report, n)\n        return True\n\n    def equiv(self, j1, j2):\n        \"\"\" Return True if j1 and j2 are the same, except for boring_keys. \"\"\"\n        for (k, v) in j1.items():\n            # If in boring, we don't care.\n            if k not in self.boring_keys:\n                # If in j1 and not j2, they are different.\n                if k not in j2:\n                    logging.debug(\"equiv: %s in j1 and not j2\" % (k))\n                    return False\n                if j1[k] != j2[k]:\n                    logging.debug(\"equiv: %s differs j1=%s and j2=%s\" % (k, j1[k], j2[k]))\n                    return False\n        # If the lengths are different, they must be different.\n        if len(j1) != len(j2): \n            logging.debug(\"equiv: len(j1) %d != len(j2) %d\" % (len(j1), len(j2)))\n            return False\n\n        # If we get here, then the lengths are the same, and all\n        # non-boring keys in j1 exist in j2, and have the same value.\n        # It could be that j2 is missing a boring key and also has a\n        # new non-boring key, but boring keys in particular should not\n        # be variable.\n        return True\n\n    # report is a python dictionary\n    def is_interesting(self, report):\n        \"\"\" If report is intersting, return True and update records of the\n            most recent interesting report.  Otherwise return False. \"\"\"\n        n = time.time()\n\n        # If previous interesting is missing or empty, accept this one.\n        if self.last_report is None or self.last_now is None:\n            logging.debug(\"interesting: no previous\")\n            return self.send_store(report, n)\n\n        # If previous one was too long ago, accept this one.\n        if n - self.last_now > self.duration:\n            logging.debug(\"interesting: time\")\n            return self.send_store(report, n)\n\n        if not self.equiv(self.last_report, report):\n            logging.debug(\"interesting: different\")\n            return self.send_store(report, n)\n\n        return False\n\f\n# Create a config object, defaults modified by the config file if present.\nc = rtlconfig(\"rtl_433_mqtt_relay.yaml\")\n\n# Create a dedup object for later use, even if it's configured off.\nd = dedup()\n\ndef mqtt_connect(client, userdata, flags, rc):\n    \"\"\"Handle MQTT connection callback.\"\"\"\n    logging.info(\"MQTT connected: \" + mqtt.connack_string(rc))\n\n\ndef mqtt_disconnect(client, userdata, rc):\n    \"\"\"Handle MQTT disconnection callback.\"\"\"\n    logging.info(\"MQTT disconnected: \" + mqtt.connack_string(rc))\n\n\n# Create listener for incoming json string packets.\nsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\nsock.bind((c['UDP_IP'], c['UDP_PORT']))\n\n\n# Map characters that will cause problems or be confusing in mqtt\n# topics.\ndef sanitize(text):\n    \"\"\"Sanitize a name for Graphite/MQTT use.\"\"\"\n    return (text\n            .replace(\" \", \"_\")\n            .replace(\"/\", \"_\")\n            .replace(\".\", \"_\")\n            .replace(\"&\", \"\"))\n\n\ndef publish_sensor_to_mqtt(mqttc, data, line):\n    \"\"\"Publish rtl_433 sensor data to MQTT.\"\"\"\n\n    if c['MQTT_DEDUP']:\n        # If this data is not novel relative to recent data, just skip it.\n        # Otherwise, send it via MQTT.\n        if not d.is_interesting(data):\n            logging.debug(\"  not interesting\")\n            return\n        logging.debug(  \"INTERESTING\")\n\n    # Construct a topic from the information that identifies which\n    # device this frame is from.\n    # NB: id is only used if channel is not present.\n    path = c['MQTT_PREFIX']\n    if \"model\" in data:\n        path += \"/\" + sanitize(data[\"model\"])\n    if \"channel\" in data:\n        path += \"/\" + str(data[\"channel\"])\n    if \"id\" in data:\n        path += \"/\" + str(data[\"id\"])\n\n    if c['MQTT_INDIVIDUAL_TOPICS']:\n        # Publish some specific items on subtopics.\n        if \"battery_ok\" in data:\n            mqttc.publish(path + \"/battery\", data[\"battery_ok\"])\n\n        if \"humidity\" in data:\n            mqttc.publish(path + \"/humidity\", data[\"humidity\"])\n\n        if \"temperature_C\" in data:\n            mqttc.publish(path + \"/temperature\", data[\"temperature_C\"])\n\n        if \"depth_cm\" in data:\n            mqttc.publish(path + \"/depth\", data[\"depth_cm\"])\n\n    if c['MQTT_JSON_TOPIC']:\n        # Publish the entire json string on the main topic.\n        mqttc.publish(path, line)\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # Fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload.\n        # The payload might have spaces, so force split to stop after the sixth space.\n        fields = line.split(None, 7)\n        line = fields[-1]\n    else:\n        # Hope that the line was just json without the syslog header.\n        pass\n    return line\n\n\ndef rtl_433_probe():\n    \"\"\"Run a rtl_433 UDP listener.\"\"\"\n\n    ## Connect to MQTT\n    if hasattr(mqtt, 'CallbackAPIVersion'):  # paho >= 2.0.0\n        mqttc = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION1)\n    else:\n        mqttc = mqtt.Client()\n    mqttc.on_connect = mqtt_connect\n    mqttc.on_disconnect = mqtt_disconnect\n    if c['MQTT_USERNAME'] != None:\n        mqttc.username_pw_set(c['MQTT_USERNAME'], password=c['MQTT_PASSWORD'])\n    if c['MQTT_TLS']:\n        mqttc.tls_set()\n    mqttc.connect_async(c['MQTT_HOST'], c['MQTT_PORT'], 60)\n    mqttc.loop_start()\n\n    ## Receive UDP datagrams, extract json, and publish.\n    while True:\n        line, addr = sock.recvfrom(1024)\n        try:\n            line = parse_syslog(line)\n            data = json.loads(line)\n            logging.debug(\"received %s\" % line)\n            publish_sensor_to_mqtt(mqttc, data, line)\n\n        except ValueError:\n            pass\n\n\ndef run():\n    \"\"\"Run main or daemon.\"\"\"\n    # with daemon.DaemonContext(files_preserve=[sock]):\n    #  detach_process=True\n    #  uid\n    #  gid\n    #  working_directory\n\n    # Set up logging at INFO, and change to DEBUG if config asks for that.\n    logging.basicConfig(format='[%(asctime)s] %(levelname)s:%(name)s:%(message)s',datefmt='%Y-%m-%dT%H:%M:%S%z')\n    logging.getLogger().setLevel(logging.INFO)\n    if c['DEBUG']:\n        logging.getLogger().setLevel(logging.DEBUG)\n        logging.debug(\"DEBUG LOGGING ENABLED\")\n\n    rtl_433_probe()\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "examples/rtl_433_prometheus_relay.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"\nPrometheus/OpenMetrics relay for rtl_433.\n\nCan either take in JSON from stdin:\n\nrtl_433 ... -F json | examples/rtl_433_prometheus_relay.py\n\nor through UDP syslog packets:\n\nrtl_433 ... -F syslog:0:4433\nexamples/rtl_433_prometheus_relay.py 4433\n\nThen have Prometheus scrape it on port 30584, or change _SRV_PORT below.\n\nBuilt for and tested with Oregon Scientific weather sensors, but *should*\nwork for other kinds (but only for numeric data).\n\"\"\"\n\nimport collections\nimport json\nimport os\nimport socket\nimport sys\nimport threading\nimport time\n\nimport dateutil.parser\nimport http.server\n\n## 0x77 w 0x78 x -> port 30584\n_SRV_PORT = 0x7778\n\n\nclass rtl_433(object):\n    # These fields are used to separate and tag unique sensors, instead of being exported\n    # as readings/values.\n    _ID_FIELDS = [\n        (\"model\", str),\n        (\"type\", str),\n        (\"subtype\", str),\n        (\"channel\", int),\n        (\"id\", int),\n    ]\n    _MIN_REPEAT_SECS = 3600  # See a device at least twice in this timespan before sharing it.\n    _EXPORT_TIMESTAMPS = True\n\n    _MAX_AGE_SECS = 300  # Used for gc, and max age to show *anything* for given id\n    _BACKLOG_SECS = 60   # If multiple samples within this timestamp, share them all\n\n    _LOG_CLEAN_INTERVAL = 1000  # Number of iterations\n\n    log = []  # [(timestamp, id_fields, variable name, value), ...]\n\n    def __init__(self, stream):\n        self.stream = stream\n\n    def loop(self):\n        n = 0\n        last = {}\n        for line in self.stream:\n            now = time.time()\n            print(line.strip())\n            try:\n                pkt = json.loads(line)\n            except json.decoder.JSONDecodeError as e:\n                print(\"Parse error: %s\" % e.msg)\n                continue\n            if not isinstance(pkt, dict):\n                print(\"Not a dict/object: %r\" % pkt)\n                continue\n            try:\n                ts = dateutil.parser.isoparse(pkt.pop(\"time\")).timestamp()\n            except KeyError:\n                ts = now\n            if int(ts) == ts:\n                # Check above in case we start getting fed non-integer timestamps already.\n                # If not, use current precise time instead.\n                if abs(now - (ts + 0.5)) < 1:\n                    ts = now\n\n            id = self.grab_id(pkt)\n            ago = ts - last.get(id, 0)\n            last[id] = ts\n            if ago < self._MIN_REPEAT_SECS:\n                new = []\n                for k, v in pkt.items():\n                    try:\n                        new.append((ts, id, k, float(v)))\n                    except (ValueError, TypeError):\n                        print(\"%s{%r} has non-numeric value %s\" % (k, id, v))\n                        continue\n                self.log += new\n\n            n += 1\n            if (n % self._LOG_CLEAN_INTERVAL) == 0:\n                self.clean_log()\n\n    @staticmethod\n    def grab_id(pkt):\n        ret = []\n        for field, ftype in rtl_433._ID_FIELDS:\n            v = pkt.pop(field, None)\n            try:\n                v = ftype(v)\n            except (ValueError, TypeError):\n                pass  # Stick to existing type\n            ret.append(v)\n        return tuple(ret)\n\n    def metrics(self):\n        ret = collections.defaultdict(list)\n        now = time.time()\n        seen = {}  # value should be most recent timestamp this id has sent data for.\n        for ts, id, var, val in reversed(self.log):\n            if now - ts > self._MAX_AGE_SECS:\n                break\n            if now - ts > self._BACKLOG_SECS and ts < seen.get(id, 0):\n                continue\n            if id not in seen:\n                seen[id] = ts\n\n            id_s = \",\".join((\"%s=\\\"%s\\\"\" % (f[0], v)) for f, v in zip(self._ID_FIELDS, id) if v is not None)\n            if self._EXPORT_TIMESTAMPS:\n                ret[var].append(\"%s{%s} %f %d\" % (var, id_s, val, ts * 1000))\n            elif ts == seen[id]:\n                ret[var].append(\"%s{%s} %f\" % (var, id_s, val))\n\n        return (\"\\n\".join(\"\\n\".join(lines) for lines in ret.values())) + \"\\n\"\n\n    def clean_log(self):\n        if not self.log:\n            return\n        if self.log[0][0] - self.log[-1][0] > 60:\n            # Time has gone the wrong way by a long time. Drop the table.\n            self.log = []\n            return\n\n        min_ts = time.time() - self._MAX_AGE_SECS\n        for i, e in enumerate(self.log):\n            if e[0] >= min_ts:\n                break\n\n        # I think this should be safe even if we're serving /metrics since\n        # I'm replacing not modifying the log.\n        self.log = self.log[i:]\n\n\ndef syslog_reader(udp_port, udp_ip=\"0.0.0.0\"):\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\n    sock.bind((udp_ip, udp_port))\n    while True:\n        pkt, _ = sock.recvfrom(4096)\n        bits = pkt.split(None, 7)\n        if len(bits) == 8:\n            yield str(bits[7], \"utf-8\")\n\n\nif len(sys.argv) == 1:\n    r = rtl_433(sys.stdin)\nelse:\n    r = rtl_433(syslog_reader(int(sys.argv[1])))\n\n\nclass MetricsHandler(http.server.BaseHTTPRequestHandler):\n    def do_GET(self):\n        # boilerplate-- :<\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"text/plain; charset=utf-8\")\n        self.end_headers()\n        self.wfile.write(r.metrics().encode(\"utf-8\"))\n\nclass MetricsServer(http.server.HTTPServer):\n    def handle_error(self, req, addr):\n        super().handle_error(req, addr)\n        os._exit(6)\n\nhttps = MetricsServer((\"0.0.0.0\", _SRV_PORT), MetricsHandler)\nhttpd = threading.Thread(name=\"httpd\", target=https.serve_forever, daemon=True)\nhttpd.start()\n\n\nr.loop()\n"
  },
  {
    "path": "examples/rtl_433_rrd_relay.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"RRDtool monitoring relay for rtl_433.\"\"\"\n\n# Start rtl_433 (rtl_433 -C si -F syslog:127.0.0.1:1433), then this script\n\nfrom __future__ import print_function\nfrom __future__ import with_statement\n\nimport sys\nimport socket\nimport time\nimport json\nimport rrdtool\n\n# Option: PEP 3143 - Standard daemon process library\n# (pip install python-daemon)\ntry:\n    import daemon\nexcept ImportError:\n    daemon = None\n\nUDP_IP = \"127.0.0.1\"\nUDP_PORT = 1433\nRRD_PATH = \"\" # e.g. \"/var/lib/rtl_433/rrd/\"\nGRAPH_PATH = \"\"  # e.g. \"/var/www/rrd/html/\"\nGRAPH_INTERVAL = 30 * 60  # in seconds, i.e. 30 minutes\n\n\ndef create_rrd(rrdfile):\n    # print(\"Creating\", rrdfile)\n    return rrdtool.create(rrdfile,\n                          \"--step\", \"1800\", \"--start\", '0',\n                          \"DS:temperature:GAUGE:2000:U:U\",\n                          \"DS:humidity:GAUGE:2000:U:U\",\n                          \"RRA:AVERAGE:0.5:1:600\",\n                          \"RRA:AVERAGE:0.5:6:700\",\n                          \"RRA:AVERAGE:0.5:24:775\",\n                          \"RRA:AVERAGE:0.5:288:797\",\n                          \"RRA:MAX:0.5:1:600\",\n                          \"RRA:MAX:0.5:6:700\",\n                          \"RRA:MAX:0.5:24:775\",\n                          \"RRA:MAX:0.5:444:797\")\n\n\ndef update_rrd(rrdfile, temperature, humidity):\n    # print(\"Updating\", rrdfile, temperature, humidity)\n    return rrdtool.update(rrdfile, \"N:%s:%s\" % (temperature, humidity))\n\n\ndef graph_rrd(rrdfile, label, path=\"\"):\n    for sched in ['daily' , 'weekly', 'monthly', 'hourly']:\n        period = sched[0] # 'w', 'd', 'm', 'h'\n        # print(\"Graphing\", sched, label, rrdfile)\n        ret = rrdtool.graph(\"%smetrics-%s.%s.png\" % (path, sched, label),\n                            \"--start\", \"-1%s\" % (period),\n                            \"--title\", label,\n                            \"--vertical-label=C\",\n                            \"--right-axis-label=%\",\n                            '--watermark=rtl_433',\n                            \"-w 800\", \"-h 200\",\n                            \"DEF:t=%s:temperature:AVERAGE\" % (rrdfile),\n                            \"DEF:h=%s:humidity:AVERAGE\" % (rrdfile),\n                            \"LINE1:t#00FF00:temperature\\r\",\n                            \"LINE2:h#0000FF:humidity\\r\",\n                            \"GPRINT:t:AVERAGE:Temp avg %6.1lf C\",\n                            \"GPRINT:t:MAX:Temp max %6.1lf C\\r\",\n                            \"GPRINT:h:AVERAGE:Hum avg %6.0lf %%\",\n                            \"GPRINT:h:MAX:Hum max %6.0lf %%\\r\")\n    return ret\n\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\").replace(\"/\", \"_\").replace(\".\", \"_\").replace(\"&\", \"\")\n\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        fields = line.split(None, 7)\n        line = fields[-1]\n    return line\n\n\ndef rtl_433_probe(sock):\n    next_graph = {}\n\n    while True:\n        line, _addr = sock.recvfrom(1024)\n\n        try:\n            line = parse_syslog(line)\n            data = json.loads(line)\n            now = int(time.time())\n\n            label = sanitize(data[\"model\"])\n            if \"channel\" in data:\n                label += \".CH\" + str(data[\"channel\"])\n            elif \"id\" in data:\n                label += \".ID\" + str(data[\"id\"])\n            rrd_file = RRD_PATH + label + \".rrd\"\n\n            if \"type\" in data and data[\"type\"] == \"TPMS\":\n                continue\n\n            if \"temperature_C\" not in data:\n                continue\n            temperature = data[\"temperature_C\"]\n\n            humidity = \"U\"\n            if \"humidity\" in data:\n                humidity = data[\"humidity\"]\n\n            try:\n                rrdtool.info(rrd_file)\n            except rrdtool.OperationalError:\n                create_rrd(rrd_file)\n\n            update_rrd(rrd_file, temperature, humidity)\n\n            if label not in next_graph or next_graph[label] < now:\n                graph_rrd(rrd_file, label, GRAPH_PATH)\n                next_graph[label] = now + GRAPH_INTERVAL\n\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\ndef run(run_as_daemon=False):\n    # check optional python imports\n    if run_as_daemon and not daemon:\n        print(\"Error: pip install python-daemon\")\n        return\n\n    # setup input and output\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\n    sock.bind((UDP_IP, UDP_PORT))\n\n    # run, as daemon if requested\n    if run_as_daemon:\n        with daemon.DaemonContext(files_preserve=[sock]):\n            rtl_433_probe(sock)\n    else:\n        rtl_433_probe(sock)\n\n\nif __name__ == \"__main__\":\n    # simple argument parsing\n    run_as_daemon = len(sys.argv) > 1 and sys.argv[1] == \"-d\"\n    try:\n        run(run_as_daemon)\n    except KeyboardInterrupt:\n        pass\n"
  },
  {
    "path": "examples/rtl_433_statsd_pipe.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Statsd monitoring for rtl_433 using pipes.\"\"\"\n\n# Needs Python statsd Network plugin, s.a. https://github.com/jsocol/pystatsd\n#   pip install pystatsd\n# -or-\n#   curl -o statsd.py https://github.com/jsocol/pystatsd/raw/v3.2/statsd/client.py\n\nimport sys\nimport json\nfrom statsd import StatsClient\n\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\")\n\n\ndef rtl_433_probe():\n    statsd_host = \"127.0.0.1\"\n    statsd_port = 8125\n    statsd_prefix = 'rtlsdr'\n\n    statsd = StatsClient(host=statsd_host,\n                         port=statsd_port,\n                         prefix=statsd_prefix)\n\n    while True:\n        line = sys.stdin.readline()\n        if not line:\n            break\n        try:\n            data = json.loads(line)\n\n            label = sanitize(data[\"model\"])\n            if \"channel\" in data:\n                label += \".CH\" + str(data[\"channel\"])\n\n            if \"battery_ok\" in data:\n                statsd.gauge(label + '.battery', data[\"battery_ok\"])\n\n            if \"humidity\" in data:\n                statsd.gauge(label + '.humidity', data[\"humidity\"])\n\n            statsd.gauge(label + '.temperature', data[\"temperature_C\"])\n\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\nif __name__ == \"__main__\":\n    rtl_433_probe()\n"
  },
  {
    "path": "examples/rtl_433_statsd_relay.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Statsd monitoring relay for rtl_433.\"\"\"\n\n# Needs Python statsd Network plugin, s.a. https://github.com/jsocol/pystatsd\n#   pip install pystatsd\n# -or-\n#   curl -o statsd.py https://github.com/jsocol/pystatsd/raw/v3.2/statsd/client.py\n# Start rtl_433 (rtl_433 -F syslog::1433), then this script\n\nfrom __future__ import print_function\n\nimport socket\nimport json\nfrom statsd import StatsClient\n\nUDP_IP = \"127.0.0.1\"\nUDP_PORT = 1433\nSTATSD_HOST = \"127.0.0.1\"\nSTATSD_PORT = 8125\nSTATSD_PREFIX = \"rtlsdr\"\n\nsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\nsock.bind((UDP_IP, UDP_PORT))\n\n\ndef sanitize(text):\n    return text.replace(\" \", \"_\")\n\n\ndef parse_syslog(line):\n    \"\"\"Try to extract the payload from a syslog line.\"\"\"\n    line = line.decode(\"ascii\")  # also UTF-8 if BOM\n    if line.startswith(\"<\"):\n        # fields should be \"<PRI>VER\", timestamp, hostname, command, pid, mid, sdata, payload\n        fields = line.split(None, 7)\n        line = fields[-1]\n    return line\n\n\ndef rtl_433_probe():\n    statsd = StatsClient(host=STATSD_HOST,\n                         port=STATSD_PORT,\n                         prefix=STATSD_PREFIX)\n\n    while True:\n        line, addr = sock.recvfrom(1024)\n\n        try:\n            line = parse_syslog(line)\n            data = json.loads(line)\n\n            label = sanitize(data[\"model\"])\n            if \"channel\" in data:\n                label += \".CH\" + str(data[\"channel\"])\n\n            if \"battery_ok\" in data:\n                statsd.gauge(label + '.battery', data[\"battery_ok\"])\n\n            if \"humidity\" in data:\n                statsd.gauge(label + '.humidity', data[\"humidity\"])\n\n            statsd.gauge(label + '.temperature', data[\"temperature_C\"])\n\n        except KeyError:\n            pass\n\n        except ValueError:\n            pass\n\n\nif __name__ == \"__main__\":\n    rtl_433_probe()\n"
  },
  {
    "path": "include/CMakeLists.txt",
    "content": "########################################################################\n# Install public header files\n########################################################################\ninstall(FILES\n    rtl_433.h\n    rtl_433_devices.h\n    DESTINATION include\n)\n"
  },
  {
    "path": "include/abuf.h",
    "content": "/** @file\n    array buffer (string builder).\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_ABUF_H_\n#define INCLUDE_ABUF_H_\n\n#if defined _MSC_VER || defined ESP32 // Microsoft Visual Studio or ESP32\n    // MSC and ESP32 have something like C99 restrict as __restrict\n    #ifndef restrict\n    #define restrict  __restrict\n    #endif\n#endif\n// Defined in newer <sal.h> for MSVC.\n#ifndef _Printf_format_string_\n#define _Printf_format_string_\n#endif\n\n#include <stddef.h>\n\ntypedef struct abuf {\n    char *head;\n    char *tail;\n    size_t left;\n} abuf_t;\n\nvoid abuf_init(abuf_t *buf, char *dst, size_t len);\n\nvoid abuf_setnull(abuf_t *buf);\n\nchar *abuf_push(abuf_t *buf);\n\nvoid abuf_pop(abuf_t *buf, char *end);\n\nvoid abuf_cat(abuf_t *buf, const char *str);\n\nint abuf_printf(abuf_t *buf, _Printf_format_string_ char const *restrict format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 2, 3)))\n#endif\n        ;\n\n#endif /* INCLUDE_ABUF_H_ */\n"
  },
  {
    "path": "include/am_analyze.h",
    "content": "/** @file\n    AM signal analyzer.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_AM_ANALYZE_H_\n#define INCLUDE_AM_ANALYZE_H_\n\n#include <stdint.h>\n#include \"samp_grab.h\"\n\n#define PULSE_DATA_SIZE 4000 /* maximum number of pulses */\n\ntypedef struct am_analyze {\n    int level_limit;\n    int override_short;\n    int override_long;\n    uint32_t *frequency;\n    uint32_t *samp_rate;\n    int *sample_size;\n\n    /* state */\n    unsigned counter;\n    unsigned print;\n    unsigned print2;\n    unsigned pulses_found;\n    unsigned prev_pulse_start;\n    unsigned pulse_start;\n    unsigned pulse_end;\n    unsigned pulse_avg;\n    unsigned signal_start;\n    unsigned signal_pulse_counter;\n    unsigned signal_pulse_data[4000][3];\n} am_analyze_t;\n\n/// Create an AM-Analyzer. Might fail and return NULL.\nam_analyze_t *am_analyze_create(void);\n\nvoid am_analyze_free(am_analyze_t *a);\n\nvoid am_analyze_skip(am_analyze_t *a, unsigned n_samples);\n\nvoid am_analyze(am_analyze_t *a, int16_t *am_buf, unsigned n_samples, int debug_output, samp_grab_t *g);\n\nvoid am_analyze_classify(am_analyze_t *aa);\n\n#endif /* INCLUDE_AM_ANALYZE_H_ */\n"
  },
  {
    "path": "include/baseband.h",
    "content": "/** @file\n    Various functions for baseband sample processing.\n\n    Copyright (C) 2012 by Benjamin Larsson <benjamin@southpole.se>\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_BASEBAND_H_\n#define INCLUDE_BASEBAND_H_\n\n#include <stdint.h>\n#include <math.h>\n\n/** This will give a noisy envelope of OOK/ASK signals.\n\n    Subtract the bias (-128) and get an envelope estimation (absolute squared).\n    @param iq_buf input samples (I/Q samples in interleaved uint8)\n    @param[out] y_buf output buffer\n    @param len number of samples to process\n    @return the average level in dB\n*/\nfloat envelope_detect(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len);\n\n// for evaluation\nfloat envelope_detect_nolut(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len);\nfloat magnitude_est_cu8(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len);\nfloat magnitude_true_cu8(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len);\nfloat magnitude_est_cs16(int16_t const *iq_buf, uint16_t *y_buf, uint32_t len);\nfloat magnitude_true_cs16(int16_t const *iq_buf, uint16_t *y_buf, uint32_t len);\n\n#define AMP_TO_DB(x) (10.0f * ((x) > 0 ? log10f(x) : 0) - 42.1442f)  // 10*log10f(16384.0f)\n#define MAG_TO_DB(x) (20.0f * ((x) > 0 ? log10f(x) : 0) - 84.2884f)  // 20*log10f(16384.0f)\n#ifdef __exp10f\n#define _exp10f(x) __exp10f(x)\n#else\n#define _exp10f(x) powf(10, x)\n#endif\n#define DB_TO_AMP(x) ((int)(_exp10f(((x) + 42.1442f) / 10.0f)))  // 10*log10f(16384.0f)\n#define DB_TO_MAG(x) ((int)(_exp10f(((x) + 84.2884f) / 20.0f)))  // 20*log10f(16384.0f)\n#define DB_TO_AMP_F(x) ((int)(0.5 + _exp10f((x) / 10.0f)))\n#define DB_TO_MAG_F(x) ((int)(0.5 + _exp10f((x) / 20.0f)))\n\n/*\ntabulated magnitude and amplitude values:\n10^(( 3 + 84.2884) / 20) = 23143        10^(( 3 + 42.1442) / 10) = 32690\n10^(( 2 + 84.2884) / 20) = 20626        10^(( 2 + 42.1442) / 10) = 25967\n10^(( 1 + 84.2884) / 20) = 18383        10^(( 1 + 42.1442) / 10) = 20626\n10^(( 0 + 84.2884) / 20) = 16384        10^(( 0 + 42.1442) / 10) = 16384\n10^((-1 + 84.2884) / 20) = 14602        10^((-1 + 42.1442) / 10) = 13014\n10^((-2 + 84.2884) / 20) = 13014        10^((-2 + 42.1442) / 10) = 10338\n10^((-3 + 84.2884) / 20) = 11599        10^((-3 + 42.1442) / 10) =  8211\n10^((-4 + 84.2884) / 20) = 10338        10^((-4 + 42.1442) / 10) =  6523\n10^((-5 + 84.2884) / 20) =  9213        10^((-5 + 42.1442) / 10) =  5181\n10^((-6 + 84.2884) / 20) =  8211        10^((-6 + 42.1442) / 10) =  4115\n10^((-7 + 84.2884) / 20) =  7318        10^((-7 + 42.1442) / 10) =  3269\n10^((-8 + 84.2884) / 20) =  6523        10^((-8 + 42.1442) / 10) =  2597\n10^((-9 + 84.2884) / 20) =  5813        10^((-9 + 42.1442) / 10) =  2063\n10^((-10 + 84.2884) / 20) = 5181        10^((-10 + 42.1442) / 10) = 1638\n10^((-11 + 84.2884) / 20) = 4618        10^((-11 + 42.1442) / 10) = 1301\n10^((-12 + 84.2884) / 20) = 4115        10^((-12 + 42.1442) / 10) = 1034\n10^((-13 + 84.2884) / 20) = 3668        10^((-13 + 42.1442) / 10) =  821\n10^((-14 + 84.2884) / 20) = 3269        10^((-14 + 42.1442) / 10) =  652\n10^((-15 + 84.2884) / 20) = 2914        10^((-15 + 42.1442) / 10) =  518\n10^((-16 + 84.2884) / 20) = 2597        10^((-16 + 42.1442) / 10) =  412\n10^((-17 + 84.2884) / 20) = 2314        10^((-17 + 42.1442) / 10) =  327\n10^((-18 + 84.2884) / 20) = 2063        10^((-18 + 42.1442) / 10) =  260\n10^((-19 + 84.2884) / 20) = 1838        10^((-19 + 42.1442) / 10) =  206\n10^((-20 + 84.2884) / 20) = 1638        10^((-20 + 42.1442) / 10) =  164\n10^((-21 + 84.2884) / 20) = 1460        10^((-21 + 42.1442) / 10) =  130\n10^((-22 + 84.2884) / 20) = 1301        10^((-22 + 42.1442) / 10) =  103\n10^((-23 + 84.2884) / 20) = 1160        10^((-23 + 42.1442) / 10) =   82\n10^((-24 + 84.2884) / 20) = 1034        10^((-24 + 42.1442) / 10) =   65\n10^((-25 + 84.2884) / 20) =  921        10^((-25 + 42.1442) / 10) =   52\n10^((-26 + 84.2884) / 20) =  821        10^((-26 + 42.1442) / 10) =   41\n10^((-27 + 84.2884) / 20) =  732        10^((-27 + 42.1442) / 10) =   33\n10^((-28 + 84.2884) / 20) =  652        10^((-28 + 42.1442) / 10) =   26\n10^((-29 + 84.2884) / 20) =  581        10^((-29 + 42.1442) / 10) =   21\n10^((-30 + 84.2884) / 20) =  518        10^((-30 + 42.1442) / 10) =   16\n10^((-31 + 84.2884) / 20) =  462        10^((-31 + 42.1442) / 10) =   13\n10^((-32 + 84.2884) / 20) =  412        10^((-32 + 42.1442) / 10) =   10\n*/\n\n#define FILTER_ORDER 1\n\n/// Filter state buffer.\ntypedef struct filter_state {\n    int16_t y[FILTER_ORDER];\n    int16_t x[FILTER_ORDER];\n} filter_state_t;\n\n/// FM_Demod state buffer.\ntypedef struct demodfm_state {\n    int32_t xr;        ///< Last I/Q sample, real part\n    int32_t xi;        ///< Last I/Q sample, imag part\n    int32_t xf;        ///< Last Instantaneous frequency\n    int32_t yf;        ///< Last Instantaneous frequency, low pass filtered\n    uint32_t rate;     ///< Current sample rate\n    int32_t alp_16[2]; ///< Current low pass filter A coeffs, 16 bit\n    int32_t blp_16[2]; ///< Current low pass filter B coeffs, 16 bit\n    int64_t alp_32[2]; ///< Current low pass filter A coeffs, 32 bit\n    int64_t blp_32[2]; ///< Current low pass filter B coeffs, 32 bit\n} demodfm_state_t;\n\n/** Reset the lowpass filter to an initial state. */\nvoid baseband_low_pass_filter_reset(filter_state_t *lowpass_filter);\n\n/** Lowpass filter.\n\n    Function is stateful.\n    @param x_buf input samples to be filtered\n    @param[out] y_buf output from filter\n    @param len number of samples to process\n    @param[in,out] state State to store between chunk processing\n*/\nvoid baseband_low_pass_filter(filter_state_t *state, uint16_t const *x_buf, int16_t *y_buf, uint32_t len);\n\n/** Reset the FM demodulator to an initial state. */\nvoid baseband_demod_FM_reset(demodfm_state_t *demod_fm);\n\n/** FM demodulator.\n\n    Function is stateful.\n    @param[in,out] state State to store between chunk processing\n    @param x_buf input samples (I/Q samples in interleaved uint8)\n    @param[out] y_buf output from FM demodulator\n    @param num_samples number of samples to process\n    @param samp_rate sample rate of samples to process\n    @param low_pass Low-pass filter frequency or ratio\n*/\nvoid baseband_demod_FM(demodfm_state_t *state, uint8_t const *x_buf, int16_t *y_buf, unsigned long num_samples, uint32_t samp_rate, float low_pass);\n\n/// For evaluation.\nvoid baseband_demod_FM_cs16(demodfm_state_t *state, int16_t const *x_buf, int16_t *y_buf, unsigned long num_samples, uint32_t samp_rate, float low_pass);\n\n/** Initialize tables and constants.\n    Should be called once at startup.\n*/\nvoid baseband_init(void);\n\n#endif /* INCLUDE_BASEBAND_H_ */\n"
  },
  {
    "path": "include/bit_util.h",
    "content": "/** @file\n    Various utility functions for use by device drivers.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_BIT_UTIL_H_\n#define INCLUDE_BIT_UTIL_H_\n\n#include <stdint.h>\n\n/// Reverse (reflect) the bits in an 32 bit byte.\n///\n/// @param x input byte\n/// @return bit reversed byte\nuint32_t reverse32(uint32_t x);\n\n\n/// Reverse (reflect) the bits in an 8 bit byte.\n///\n/// @param x input byte\n/// @return bit reversed byte\nuint8_t reverse8(uint8_t x);\n\n/// Reflect (reverse LSB to MSB) each byte of a number of bytes.\n///\n/// @param message bytes of message data\n/// @param num_bytes number of bytes to reflect\nvoid reflect_bytes(uint8_t message[], unsigned num_bytes);\n\n/// Reflect (reverse LSB to MSB) each nibble in an 8 bit byte, preserves nibble order.\n///\n/// @param x input byte\n/// @return reflected nibbles\nuint8_t reflect4(uint8_t x);\n\n/// Reflect (reverse LSB to MSB) each nibble in a number of bytes.\n///\n/// @param message bytes of nibble message data\n/// @param num_bytes number of bytes to reflect\nvoid reflect_nibbles(uint8_t message[], unsigned num_bytes);\n\n/// Unstuff nibbles with 1-bit separator (4B1S) to bytes, returns number of successfully unstuffed nibbles.\n///\n/// @param message bytes of message data\n/// @param offset_bits start offset of message in bits\n/// @param num_bits message length in bits\n/// @param dst target buffer for extracted nibbles, at least num_bits/5 size\n/// @return number of successfully unstuffed nibbles.\nunsigned extract_nibbles_4b1s(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst);\n\n/// UART \"8n1\" (10-to-8) decoder with 1 start bit (0), no parity, 1 stop bit (1), LSB-first bit-order.\n///\n/// @param message bytes of message data\n/// @param offset_bits start offset of message in bits\n/// @param num_bits message length in bits\n/// @param dst target buffer for extracted bytes, at least num_bits/10 size\n/// @return number of successful decoded bytes\nunsigned extract_bytes_uart(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst);\n\n/// UART \"8o1\" (11-to-8) decoder with 1 start bit (1), odd parity, 1 stop bit (0), MSB-first bit-order.\n///\n/// @param message bytes of message data\n/// @param offset_bits start offset of message in bits\n/// @param num_bits message length in bits\n/// @param dst target buffer for extracted bytes, at least num_bits/11 size\n/// @return number of successful decoded bytes\nunsigned extract_bytes_uart_parity(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst);\n\n/// Decode symbols to bits.\n///\n/// @param message bytes of message data\n/// @param offset_bits start offset of message in bits\n/// @param num_bits message length in bits\n/// @param zero symbol for zero bit, bits MSB aligned, count in LSB\n/// @param one symbol for one bit, bits MSB aligned, count in LSB\n/// @param sync symbol for sync bit, ignored at start, terminates at end\n/// @param dst target buffer for extracted bits, at least num_bits/symbol_x_len size\n/// @return number of successful decoded bits\nunsigned extract_bits_symbols(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint32_t zero, uint32_t one, uint32_t sync, uint8_t *dst);\n\n/// CRC-4.\n///\n/// @param message array of bytes to check\n/// @param nBytes number of bytes in message\n/// @param polynomial CRC polynomial\n/// @param init starting crc value\n/// @return CRC value\nuint8_t crc4(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init);\n\n/// CRC-7.\n///\n/// @param message array of bytes to check\n/// @param nBytes number of bytes in message\n/// @param polynomial CRC polynomial\n/// @param init starting crc value\n/// @return CRC value\nuint8_t crc7(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init);\n\n/// Generic Cyclic Redundancy Check CRC-8.\n///\n/// Example polynomial: 0x31 = x8 + x5 + x4 + 1 (x8 is implicit)\n/// Example polynomial: 0x80 = x8 + x7 (a normal bit-by-bit parity XOR)\n///\n/// @param message array of bytes to check\n/// @param nBytes number of bytes in message\n/// @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one)\n/// @param init starting crc value\n/// @return CRC value\nuint8_t crc8(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init);\n\n/// \"Little-endian\" Cyclic Redundancy Check CRC-8 LE\n/// Input and output are reflected, i.e. least significant bit is shifted in first.\n///\n/// @param message array of bytes to check\n/// @param nBytes number of bytes in message\n/// @param polynomial CRC polynomial\n/// @param init starting crc value\n/// @return CRC value\nuint8_t crc8le(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init);\n\n/// CRC-16 LSB.\n/// Input and output are reflected, i.e. least significant bit is shifted in first.\n/// Note that poly and init already need to be reflected.\n///\n/// @param message array of bytes to check\n/// @param nBytes number of bytes in message\n/// @param polynomial CRC polynomial\n/// @param init starting crc value\n/// @return CRC value\nuint16_t crc16lsb(uint8_t const message[], unsigned nBytes, uint16_t polynomial, uint16_t init);\n\n/// CRC-16.\n///\n/// @param message array of bytes to check\n/// @param nBytes number of bytes in message\n/// @param polynomial CRC polynomial\n/// @param init starting crc value\n/// @return CRC value\nuint16_t crc16(uint8_t const message[], unsigned nBytes, uint16_t polynomial, uint16_t init);\n\n/// Digest-8 by \"LFSR-based Toeplitz hash\", bits MSB to LSB.\n///\n/// @param message bytes of message data\n/// @param bytes number of bytes to digest\n/// @param gen key stream generator, needs to includes the MSB for ROR if the LFSR is rolling\n/// @param key initial key\n/// @return digest value\nuint8_t lfsr_digest8(uint8_t const message[], unsigned bytes, uint8_t gen, uint8_t key);\n\n/// Digest-8 by \"LFSR-based Toeplitz hash\", byte reversed, bits MSB to LSB.\n///\n/// @param message bytes of message data, read in reverse\n/// @param bytes number of bytes to digest\n/// @param gen key stream generator, needs to includes the MSB for ROR if the LFSR is rolling\n/// @param key initial key\n/// @return digest value\nuint8_t lfsr_digest8_reverse(uint8_t const message[], int bytes, uint8_t gen, uint8_t key);\n\n/// Digest-8 by \"LFSR-based Toeplitz hash\", byte reversed, bit reflect (LSB to MSB).\n///\n/// @param message bytes of message data, read in reverse\n/// @param bytes number of bytes to digest\n/// @param gen key stream generator, needs to includes the LSB for ROL if the LFSR is rolling\n/// @param key initial key\n/// @return digest value\nuint8_t lfsr_digest8_reflect(uint8_t const message[], int bytes, uint8_t gen, uint8_t key);\n\n/// Digest-16 by \"LFSR-based Toeplitz hash\".\n///\n/// @param message bytes of message data\n/// @param bytes number of bytes to digest\n/// @param gen key stream generator, needs to includes the MSB if the LFSR is rolling\n/// @param key initial key\n/// @return digest value\nuint16_t lfsr_digest16(uint8_t const message[], unsigned bytes, uint16_t gen, uint16_t key);\n\n/// Apply CCITT data whitening to a buffer.\n///\n/// The CCITT data whitening process is built around a 9-bit Linear Feedback Shift Register (LFSR).\n/// The LFSR polynomial is the same polynomial as for IBM data whitening (x9 + x5 + 1).\n/// The initial value of the data whitening key is set to all ones, 0x1FF.\n/// s.a. https://www.nxp.com/docs/en/application-note/AN5070.pdf s.5.2\n///\n/// @param buffer bytes of message data\n/// @param buffer_size number of bytes to process\nvoid ccitt_whitening(uint8_t *buffer, unsigned buffer_size);\n\n/// Apply IBM data whitening to a buffer.\n///\n/// The IBM data whitening process is built around a 9-bit Linear Feedback Shift Register (LFSR).\n/// CCITT data whitening processes data packets byte-per-byte, whereas IBM data\n/// whitening processes the data packet bit-per-bit\n/// Same, the initial value of the data whitening key is set to all ones, 0x1FF.\n/// s.a. https://www.nxp.com/docs/en/application-note/AN5070.pdf s.5.1\n///\n/// @param buffer bytes of message data\n/// @param buffer_size number of bytes to process\nvoid ibm_whitening(uint8_t *buffer, unsigned buffer_size);\n\n/// Compute bit parity of a single byte (8 bits).\n///\n/// @param byte single byte to check\n/// @return 1 odd parity, 0 even parity\nint parity8(uint8_t byte);\n\n/// Compute bit parity of a number of bytes.\n///\n/// @param message bytes of message data\n/// @param num_bytes number of bytes to sum\n/// @return 1 odd parity, 0 even parity\nint parity_bytes(uint8_t const message[], unsigned num_bytes);\n\n/// Compute XOR (byte-wide parity) of a number of bytes.\n///\n/// @param message bytes of message data\n/// @param num_bytes number of bytes to sum\n/// @return summation value, per bit-position 1 odd parity, 0 even parity\nuint8_t xor_bytes(uint8_t const message[], unsigned num_bytes);\n\n/// Compute Addition of a number of bytes.\n///\n/// @param message bytes of message data\n/// @param num_bytes number of bytes to sum\n/// @return summation value\nint add_bytes(uint8_t const message[], unsigned num_bytes);\n\n/// Compute Addition of a number of nibbles (byte wise).\n///\n/// @param message bytes (of two nibbles) of message data\n/// @param num_bytes number of bytes to sum\n/// @return summation value\nint add_nibbles(uint8_t const message[], unsigned num_bytes);\n\n#endif /* INCLUDE_BIT_UTIL_H_ */\n"
  },
  {
    "path": "include/bitbuffer.h",
    "content": "/** @file\n    A two-dimensional bit buffer consisting of bytes.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_BITBUFFER_H_\n#define INCLUDE_BITBUFFER_H_\n\n#include <stdint.h>\n\n// NOTE: Wireless mbus protocol needs at least ((256+16*2+3)*12)/8 => 437 bytes\n//       which fits even if RTL_433_REDUCE_STACK_USE is defined because of row spilling\n#ifdef RTL_433_REDUCE_STACK_USE\n#define BITBUF_COLS 40 // Number of bytes in a column\n#define BITBUF_ROWS 25\n#else\n#define BITBUF_COLS 128 // Number of bytes in a column\n#define BITBUF_ROWS 50\n#endif\n#define BITBUF_MAX_ROW_BITS (BITBUF_ROWS * BITBUF_COLS * 8) // Maximum number of bits per row, max UINT16_MAX\n#define BITBUF_MAX_PRINT_BITS 50 // Maximum number of bits to print (in addition to hex values)\n\ntypedef uint8_t bitrow_t[BITBUF_COLS];\ntypedef bitrow_t bitarray_t[BITBUF_ROWS];\n\n/// Bit buffer.\ntypedef struct bitbuffer {\n    uint16_t num_rows;                      ///< Number of active rows\n    uint16_t free_row;                      ///< Index of next free row\n    uint16_t bits_per_row[BITBUF_ROWS];     ///< Number of active bits per row\n    uint16_t syncs_before_row[BITBUF_ROWS]; ///< Number of sync pulses before row\n    bitarray_t bb;                          ///< The actual bits buffer\n} bitbuffer_t;\n\n/// Clear the content of the bitbuffer.\nvoid bitbuffer_clear(bitbuffer_t *bits);\n\n/// Add a single bit at the end of the bitbuffer (MSB first).\nvoid bitbuffer_add_bit(bitbuffer_t *bits, int bit);\n\n/// Add a new row to the bitbuffer.\nvoid bitbuffer_add_row(bitbuffer_t *bits);\n\n/// Increment sync counter, add new row if not empty.\nvoid bitbuffer_add_sync(bitbuffer_t *bits);\n\n/// Extract (potentially unaligned) bytes from the bit buffer. Len is bits.\nvoid bitbuffer_extract_bytes(bitbuffer_t *bitbuffer, unsigned row,\n        unsigned pos, uint8_t *out, unsigned len);\n\n/// Invert all bits in the bitbuffer (do not invert the empty bits).\nvoid bitbuffer_invert(bitbuffer_t *bits);\n\n/// Non-Return-to-Zero Space (NRZI) decode the bitbuffer.\n/// \"One\" is represented by no change in level, \"Zero\" is represented by change in level.\nvoid bitbuffer_nrzs_decode(bitbuffer_t *bits);\n\n/// Non-Return-to-Zero Mark (NRZI) decode the bitbuffer.\n/// \"One\" is represented by change in level, \"Zero\" is represented by no change in level.\nvoid bitbuffer_nrzm_decode(bitbuffer_t *bits);\n\n/// Print the content of the bitbuffer.\n/// @deprecated For debug only, use decoder_log_bitbuffer otherwise\nvoid bitbuffer_print(const bitbuffer_t *bits);\n\n/// Debug the content of the bitbuffer.\n/// @deprecated For debug only, use decoder_log_bitbuffer otherwise\nvoid bitbuffer_debug(const bitbuffer_t *bits);\n\n/// Print the content of a bit row (byte buffer).\n/// @deprecated For debug only, use decoder_log_bitrow otherwise\nvoid bitrow_print(uint8_t const *bitrow, unsigned bit_len);\n\n/// Debug the content of a bit row (byte buffer).\n/// @deprecated For debug only, use decoder_log_bitrow otherwise\nvoid bitrow_debug(uint8_t const *bitrow, unsigned bit_len);\n\n/// Print the content of a bit row (byte buffer) to a string buffer.\n///\n/// Write at most @p size - 1 characters,\n/// the output is always null-terminated, unless size is 0.\n///\n/// @param bitrow the row of bytes to print\n/// @param bit_len the number of bits in @p bitrow to print\n/// @param str an output string buffer of sufficient size\n/// @param size the size of @p str\n///\n/// @return the number of characters printed (not including the trailing `\\0`).\nint bitrow_snprint(uint8_t const *bitrow, unsigned bit_len, char *str, unsigned size);\n\n/// Parse a string into a bitbuffer.\n///\n/// The (optionally \"0x\" prefixed) hex code is processed into a bitbuffer_t.\n/// Each row is optionally prefixed with a length enclosed in braces \"{}\" or\n/// separated with a slash \"/\" character. Whitespace is ignored.\nvoid bitbuffer_parse(bitbuffer_t *bits, const char *code);\n\n/// Search the specified row of the bitbuffer, starting from bit 'start', for\n/// the pattern provided.\n///\n/// The pattern starts in the high bit. For example if searching for 011011\n/// the byte pointed to by 'pattern' would be 0xAC. (011011xx).\n///\n/// @return the location of the first match, or the end of the row if no match is found.\nunsigned bitbuffer_search(bitbuffer_t *bitbuffer, unsigned row, unsigned start,\n        const uint8_t *pattern, unsigned pattern_bits_len);\n\n/// Manchester decoding from one bitbuffer into another, starting at the\n/// specified row and start bit.\n///\n/// Decode at most 'max' data bits (i.e. 2*max) bits from the input buffer).\n/// Manchester per IEEE 802.3 conventions, i.e. high-low is a 0 bit, low-high is a 1 bit.\n///\n/// @return the bit position in the input row\n/// (i.e. returns start + 2*outbuf->bits_per_row[0]).\nunsigned bitbuffer_manchester_decode(bitbuffer_t *inbuf, unsigned row, unsigned start,\n        bitbuffer_t *outbuf, unsigned max);\n\n/// Differential Manchester decoding from one bitbuffer into another, starting at the\n/// specified row and start bit.\n///\n/// Decode at most 'max' data bits (i.e. 2*max) bits from the input buffer).\n///\n/// @return the bit position in the input row\n/// (i.e. returns start + 2*outbuf->bits_per_row[0]).\nunsigned bitbuffer_differential_manchester_decode(bitbuffer_t *inbuf, unsigned row, unsigned start,\n        bitbuffer_t *outbuf, unsigned max);\n\n/// Compares two given rows of a bitbuffer.\n///\n/// If @p max_bits is greater than 0 then only up that many bits are compared.\nint bitbuffer_compare_rows(bitbuffer_t *bits, unsigned row_a, unsigned row_b, unsigned max_bits);\n\n/// Count the number of repeats of row at index @p row.\n///\n/// If @p max_bits is greater than 0 then only up that many bits are compared.\n/// The returned count will include the given row and will be at least 1.\nunsigned bitbuffer_count_repeats(bitbuffer_t *bits, unsigned row, unsigned max_bits);\n\n/// Find a row repeated at least @p min_repeats times and with at least @p min_bits bits length,\n/// all bits in the repeats need to match.\n/// @return the row index or -1.\nint bitbuffer_find_repeated_row(bitbuffer_t *bits, unsigned min_repeats, unsigned min_bits);\n\n/// Find a row repeated at least @p min_repeats times and with at least @p min_bits bits length,\n/// a prefix of at most @p min_bits bits will be compared.\n/// @return the row index or -1.\nint bitbuffer_find_repeated_prefix(bitbuffer_t *bits, unsigned min_repeats, unsigned min_bits);\n\n/// Return a single bit from a bitrow at bit_idx position.\nstatic inline uint8_t bitrow_get_bit(uint8_t const *bitrow, unsigned bit_idx)\n{\n    return bitrow[bit_idx >> 3] >> (7 - (bit_idx & 7)) & 1;\n}\n\n/// Return a single byte from a bitrow at bit_idx position (which may be unaligned).\nstatic inline uint8_t bitrow_get_byte(uint8_t const *bitrow, unsigned bit_idx)\n{\n    return (uint8_t)((bitrow[(bit_idx >> 3)] << (bit_idx & 7)) |\n                     (bitrow[(bit_idx >> 3) + 1] >> (8 - (bit_idx & 7))));\n}\n\n#endif /* INCLUDE_BITBUFFER_H_ */\n"
  },
  {
    "path": "include/c_util.h",
    "content": "/** @file\n    General C language utility macro.\n\n    Copyright (C) 2018 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_C_UTIL_H_\n#define INCLUDE_C_UTIL_H_\n\n// Helper macros, collides with MSVC's stdlib.h unless NOMINMAX is used\n#ifndef MAX\n#define MAX(a,b) ((a) > (b) ? (a) : (b))\n#endif\n#ifndef MIN\n#define MIN(a,b) ((a) < (b) ? (a) : (b))\n#endif\n\n#endif /* INCLUDE_C_UTIL_H_ */\n"
  },
  {
    "path": "include/compat_paths.h",
    "content": "/** @file\n    compat_paths addresses compatibility with default OS path names.\n\n    topic: default search paths for config file\n    issue: Linux and Windows use different common paths for config files\n    solution: provide specific default paths for each system\n*/\n\n#ifndef INCLUDE_COMPAT_PATHS_H_\n#define INCLUDE_COMPAT_PATHS_H_\n\n/// Get default search paths for rtl_433 config file.\nchar **compat_get_default_conf_paths(void);\n\n#endif  /* INCLUDE_COMPAT_PATHS_H_ */\n"
  },
  {
    "path": "include/compat_pthread.h",
    "content": "#ifndef INCLUDE_COMPAT_PTHREAD_H_\n#define INCLUDE_COMPAT_PTHREAD_H_\n\n#ifndef THREADS\n\n// explicit request for \"no threads\"\n\n// no pthreads only on MSC, use compat on all WIN32 anyway\n//#elif _MSC_VER>=1200\n#elif _WIN32\n\n#include <windows.h>\n#include <process.h>\n#define THREAD_CALL                     __stdcall\n#define THREAD_RETURN                   unsigned int\ntypedef HANDLE                          pthread_t;\n#define pthread_create(tp, x, p, d)     ((*tp=(HANDLE)_beginthreadex(NULL, 0, p, d, 0, NULL)) == NULL ? -1 : 0)\n#define pthread_cancel(th)              (!TerminateThread(th, 0))\n#define pthread_join(th, p)             (WaitForSingleObject(th, INFINITE))\n#define pthread_equal(a, b)             ((a) == (b))\n#define pthread_self()                  (GetCurrentThread())\n\ntypedef HANDLE                          pthread_mutex_t;\n#define pthread_mutex_init(mp, a)       ((*mp = CreateMutex(NULL, FALSE, NULL)) == NULL ? -1 : 0)\n#define pthread_mutex_destroy(mp)       (CloseHandle(*mp) == 0 ? -1 : 0)\n#define pthread_mutex_lock(mp)          (WaitForSingleObject(*mp, INFINITE) == WAIT_OBJECT_0 ? 0 : -1)\n#define pthread_mutex_unlock(mp)        (ReleaseMutex(*mp) == 0 ? -1 : 0)\n\ntypedef CONDITION_VARIABLE              pthread_cond_t;\n#define pthread_cond_init(cp, a)        (InitializeConditionVariable(cp))\n#define pthread_cond_destroy(cp)        (0)\n#define pthread_cond_wait(cp, mp)       (SleepConditionVariableCS(cp, *mp, INFINITE) ? 0 : 1)\n#define pthread_cond_signal(cp)         (WakeConditionVariable(cp))\n#define pthread_cond_broadcast(cp)      (WakeAllConditionVariable(cp))\n\n// #elif __GNUC__>3 || (__GNUC__==3 && __GNUC_MINOR__>3)\n#else\n\n#include <pthread.h>\n#define THREAD_CALL\n#define THREAD_RETURN                   void*\n\n#endif\n\n#endif /* INCLUDE_COMPAT_PTHREAD_H_ */\n"
  },
  {
    "path": "include/compat_time.h",
    "content": "/** @file\n    compat_time addresses compatibility time functions.\n\n    topic: high-resolution timestamps\n    issue: <sys/time.h> is not available on Windows systems\n    solution: provide a compatible version for Windows systems\n*/\n\n#ifndef INCLUDE_COMPAT_TIME_H_\n#define INCLUDE_COMPAT_TIME_H_\n\n// ensure struct timeval is known\n#ifdef _WIN32\n#include <winsock2.h>\n#define timegm _mkgmtime\n#else\n#include <sys/time.h>\n#endif\n\n/** Subtract `struct timeval` values.\n\n    @param[out] result time difference result\n    @param x first time value\n    @param y second time value\n    @return 1 if the difference is negative, otherwise 0.\n*/\nint timeval_subtract(struct timeval *result, struct timeval const *x, struct timeval const *y);\n\n// platform-specific functions\n\n#ifdef _WIN32\nint gettimeofday(struct timeval *tv, void *tz);\n#endif\n\n#endif  /* INCLUDE_COMPAT_TIME_H_ */\n"
  },
  {
    "path": "include/confparse.h",
    "content": "/** @file\n    Light-weight (i.e. dumb) config-file parser.\n\n    Copyright (C) 2018 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_CONFPARSE_H_\n#define INCLUDE_CONFPARSE_H_\n\nstruct conf_keywords {\n    char const *keyword;\n    int key;\n};\n\n/** Check if a file exists and can be read.\n\n    @param path input file name\n    @return 1 if the file exists and is readable, 0 otherwise\n*/\nint hasconf(char const *path);\n\n/** Open a config file, read contents to memory.\n\n    @param path input file name\n    @return allocated memory containing the config file\n*/\nchar *readconf(char const *path);\n\n/** Return the next keyword token and set the optional argument.\n\n    @param conf current position in conf\n    @param keywords list of possible keywords\n    @param arg optional out pointer to a argument string\n    @return the next keyword token, -1 otherwise.\n*/\nint getconf(char **conf, struct conf_keywords const keywords[], char **arg);\n\n#endif /* INCLUDE_CONFPARSE_H_ */\n"
  },
  {
    "path": "include/data.h",
    "content": "/** @file\n    A general structure for extracting hierarchical data from the devices;\n    typically key-value pairs, but allows for more rich data as well.\n\n    Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#ifndef INCLUDE_DATA_H_\n#define INCLUDE_DATA_H_\n\n#if defined _WIN32 || defined __CYGWIN__\n    #if defined data_EXPORTS\n        #define R_API __stdcall __declspec(dllexport) // Note: actually gcc seems to also supports this syntax.\n    #elif defined data_IMPORTS\n        #define R_API __stdcall __declspec(dllimport) // Note: actually gcc seems to also supports this syntax.\n    #else\n        #define R_API // for static linking\n    #endif\n    #define R_API_CALLCONV __stdcall\n#else\n    #if __GNUC__ >= 4\n        #define R_API __attribute__((visibility (\"default\")))\n    #else\n        #define R_API\n    #endif\n    #define R_API_CALLCONV\n#endif\n\n#include <stddef.h>\n#include <stdint.h>\n\ntypedef enum {\n    DATA_DATA,   /**< pointer to data is stored */\n    DATA_INT,    /**< pointer to integer is stored */\n    DATA_DOUBLE, /**< pointer to a double is stored */\n    DATA_STRING, /**< pointer to a string is stored */\n    DATA_ARRAY,  /**< pointer to an array of values is stored */\n    DATA_COUNT,  /**< invalid */\n    DATA_FORMAT, /**< indicates the following value is formatted */\n    DATA_COND,   /**< add data only if condition is true, skip otherwise */\n} data_type_t;\n\ntypedef struct data_array {\n    int         num_values;\n    data_type_t type;\n    void        *values;\n} data_array_t;\n\n// Note: Do not unwrap a packed array to data_value_t,\n// on 32-bit the union has different size/alignment than a pointer.\ntypedef union data_value {\n    int         v_int;  /**< A data value of type int, 4 bytes size/alignment */\n    double      v_dbl;  /**< A data value of type double, 8 bytes size/alignment */\n    void        *v_ptr; /**< A data value pointer, 4/8 bytes size/alignment */\n} data_value_t;\n\ntypedef struct data {\n    struct data *next; /**< chaining to the next element in the linked list; NULL indicates end-of-list */\n    char        *key;\n    char        *pretty_key; /**< the name used for displaying data to user in with a nicer name */\n    char        *format; /**< if not null, contains special formatting string */\n    data_value_t value;\n    data_type_t type;\n    unsigned    retain; /**< incremented on data_retain, data_free only frees if this is zero */\n} data_t;\n\n/** Constructs a structured data object.\n\n    Example:\n    data_make(\n            \"key\",      \"Pretty key\",   DATA_INT, 42,\n            \"others\",   \"More data\",    DATA_DATA, data_make(\"foo\", DATA_DOUBLE, 42.0, NULL),\n            \"zoom\",     NULL,           data_array(2, DATA_STRING, (char*[]){\"hello\", \"World\"}),\n            \"double\",   \"Double\",       DATA_DOUBLE, 10.0/3,\n            NULL);\n\n    Most of the time the function copies perhaps what you expect it to. Things\n    it copies:\n    - string contents for keys and values\n    - numerical arrays\n    - string arrays (copied deeply)\n\n    Things it moves:\n    - recursive data_t* and data_array_t* values\n\n    The rule is: if an object is boxed (look at the dmt structure in the data.c)\n    and it has a array_elementwise_import in the same structure, then it is\n    copied deeply. Otherwise, it is copied shallowly.\n\n    @param key Name of the first value to put in.\n    @param pretty_key Pretty name for the key. Use \"\" if to omit pretty label for this field completely,\n                      or NULL if to use key name for it.\n    @param ... Type and then value of the item to put in, followed by the rest of the\n               key-type-values. The list is terminated with a NULL.\n\n    @return A constructed data_t* object or NULL if there was a memory allocation error.\n*/\nR_API data_t *data_make(const char *key, const char *pretty_key, ...);\n\n/** Adds to a structured data object, by prepending data.\n\n    @see data_make()\n*/\nR_API data_t *data_prepend(data_t *tail, data_t *head);\n\n/** Adds to a structured data object, by appending `int` data.\n\n    Type-safe alternative to `data_make()` and `data_append()`.\n*/\nR_API data_t *data_int(data_t *first, char const *key, char const *pretty_key, char const *format, int val);\n\n/** Adds to a structured data object, by appending `double` data.\n\n    Type-safe alternative to `data_make()` and `data_append()`.\n*/\nR_API data_t *data_dbl(data_t *first, char const *key, char const *pretty_key, char const *format, double val);\n\n/** Adds to a structured data object, by appending string data.\n\n    Type-safe alternative to `data_make()` and `data_append()`.\n*/\nR_API data_t *data_str(data_t *first, char const *key, char const *pretty_key, char const *format, char const *val);\n\n/** Adds to a structured data object, by appending `data_array_t` data.\n\n    Type-safe alternative to `data_make()` and `data_append()`.\n*/\nR_API data_t *data_ary(data_t *first, char const *key, char const *pretty_key, char const *format, data_array_t *val);\n\n/** Adds to a structured data object, by appending `data_t` data.\n\n    Type-safe alternative to `data_make()` and `data_append()`.\n*/\nR_API data_t *data_dat(data_t *first, char const *key, char const *pretty_key, char const *format, data_t *val);\n\n/** Adds to a structured data object, by appending hex string data.\n\n    Type-safe alternative to `data_make()` and `data_append()`.\n\n    If `format` is NULL or empty then a default of \"%02x\" is used.\n\n    Caller needs to provide a sufficiently sized buffer.\n*/\nR_API data_t *data_hex(data_t *first, char const *key, char const *pretty_key, char const *format, uint8_t const *val, unsigned len, char *buf);\n\n/** Constructs an array from given data of the given uniform type.\n\n    @param num_values The number of values to be copied.\n    @param type The type of values to be copied.\n    @param ptr The contents pointed by the argument are copied in.\n\n    @return The constructed data array object, typically placed inside a data_t or NULL\n            if there was a memory allocation error.\n*/\nR_API data_array_t *data_array(int num_values, data_type_t type, void const *ptr);\n\n/** Releases a data array. */\nR_API void data_array_free(data_array_t *array);\n\n/** Retain a structure object, returns the structure object passed in. */\nR_API data_t *data_retain(data_t *data);\n\n/** Releases a structure object if retain is zero, decrement retain otherwise. */\nR_API void data_free(data_t *data);\n\nstruct data_output;\n\ntypedef struct data_output {\n    void (R_API_CALLCONV *print_data)(struct data_output *output, data_t *data, char const *format);\n    void (R_API_CALLCONV *print_array)(struct data_output *output, data_array_t *data, char const *format);\n    void (R_API_CALLCONV *print_string)(struct data_output *output, const char *data, char const *format);\n    void (R_API_CALLCONV *print_double)(struct data_output *output, double data, char const *format);\n    void (R_API_CALLCONV *print_int)(struct data_output *output, int data, char const *format);\n    void (R_API_CALLCONV *output_start)(struct data_output *output, char const *const *fields, int num_fields);\n    void (R_API_CALLCONV *output_print)(struct data_output *output, data_t *data);\n    void (R_API_CALLCONV *output_free)(struct data_output *output);\n    int log_level; ///< the maximum log level (verbosity) allowed, more verbose messages must be ignored.\n} data_output_t;\n\n/** Setup known field keys and start output, used by CSV only.\n\n    @param output the data_output handle from data_output_x_create\n    @param fields the list of fields to accept and expect. Array is copied, but the actual\n                  strings not. The list may contain duplicates and they are eliminated.\n    @param num_fields number of fields\n*/\nR_API void data_output_start(struct data_output *output, char const *const *fields, int num_fields);\n\n/** Prints a structured data object, flushes the output if applicable. */\nR_API void data_output_print(struct data_output *output, data_t *data);\n\nR_API void data_output_free(struct data_output *output);\n\n/* data output helpers */\n\nR_API void print_value(data_output_t *output, data_type_t type, data_value_t value, char const *format);\n\nR_API void print_array_value(data_output_t *output, data_array_t *array, char const *format, int idx);\n\nR_API size_t data_print_jsons(data_t *data, char *dst, size_t len);\n\n#endif // INCLUDE_DATA_H_\n"
  },
  {
    "path": "include/data_tag.h",
    "content": "/** @file\n    Custom data tags for data struct.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_TAGS_H_\n#define INCLUDE_TAGS_H_\n\nstruct gpsd_client;\nstruct mg_mgr;\nstruct data;\n\ntypedef struct data_tag {\n    char const *key;\n    char const *val;\n    char const **includes;\n    struct gpsd_client *gpsd_client;\n} data_tag_t;\n\n/// Create a data tag. Might fail and return NULL.\ndata_tag_t *data_tag_create(char *params, struct mg_mgr *mgr);\n\n/// Free a data tag.\nvoid data_tag_free(data_tag_t *tag);\n\n/// Apply a data tag.\nstruct data *data_tag_apply(data_tag_t *tag, struct data *data, char const *filename);\n\n#endif /* INCLUDE_TAGS_H_ */\n"
  },
  {
    "path": "include/decoder.h",
    "content": "/** @file\n    Meta include for all decoders.\n*/\n\n#ifndef INCLUDE_DECODER_H_\n#define INCLUDE_DECODER_H_\n\n#include <string.h>\n#include <stdio.h>\n#include \"r_device.h\"\n#include \"bitbuffer.h\"\n#include \"data.h\"\n#include \"bit_util.h\"\n#include \"decoder_util.h\"\n#include \"c_util.h\"\n\n#endif /* INCLUDE_DECODER_H_ */\n"
  },
  {
    "path": "include/decoder_util.h",
    "content": "/** @file\n    High-level utility functions for decoders.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_DECODER_UTIL_H_\n#define INCLUDE_DECODER_UTIL_H_\n\n#include <stdarg.h>\n#include \"bitbuffer.h\"\n#include \"data.h\"\n#include \"r_device.h\"\n\n#if defined _MSC_VER || defined ESP32 // Microsoft Visual Studio or ESP32\n    // MSC and ESP32 have something like C99 restrict as __restrict\n    #ifndef restrict\n    #define restrict  __restrict\n    #endif\n#endif\n// Defined in newer <sal.h> for MSVC.\n#ifndef _Printf_format_string_\n#define _Printf_format_string_\n#endif\n\n/// Create a new r_device, copy from dev_template if not NULL.\n///\n/// A user data memory of `user_data_size` bytes will be allocated if not `0`.\nr_device *decoder_create(r_device const *dev_template, unsigned user_data_size);\n\n/// Get the user data pointer, otherwise NULL.\n///\n/// The memory can be freely used by a decoder and is of the size given to `decoder_create()`.\nvoid *decoder_user_data(r_device *decoder);\n\n/// Output data.\nvoid decoder_output_data(r_device *decoder, data_t *data);\n\n/// Output log.\nvoid decoder_output_log(r_device *decoder, int level, data_t *data);\n\n// be terse, a maximum msg length of 60 characters is supported on the decoder_log_ functions\n// e.g. \"FoobarCorp-XY3000: unexpected type code %02x\"\n\n/// Get the current verbosity level for the decoder.\n///\n/// @deprecated Should not be used, consider using only `decoder_log_` functions.\nint decoder_verbose(r_device *decoder);\n\n/// Output a log message.\nvoid decoder_log(r_device *decoder, int level, char const *func, char const *msg);\n\n/// Output a formatted log message.\nvoid decoder_logf(r_device *decoder, int level, char const *func, _Printf_format_string_ const char *format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 4, 5)))\n#endif\n        ;\n\n/// Output a log message with the content of the bitbuffer.\nvoid decoder_log_bitbuffer(r_device *decoder, int level, char const *func, const bitbuffer_t *bitbuffer, char const *msg);\n\n/// Output a formatted log message with the content of the bitbuffer.\nvoid decoder_logf_bitbuffer(r_device *decoder, int level, char const *func, const bitbuffer_t *bitbuffer, _Printf_format_string_ const char *format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 5, 6)))\n#endif\n        ;\n\n/// Output a log message with the content of a bit row (byte buffer).\nvoid decoder_log_bitrow(r_device *decoder, int level, char const *func, uint8_t const *bitrow, unsigned bit_len, char const *msg);\n\n/// Output a formatted log message with the content of a bit row (byte buffer).\nvoid decoder_logf_bitrow(r_device *decoder, int level, char const *func, uint8_t const *bitrow, unsigned bit_len, _Printf_format_string_ const char *format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 6, 7)))\n#endif\n        ;\n\n#endif /* INCLUDE_DECODER_UTIL_H_ */\n"
  },
  {
    "path": "include/fatal.h",
    "content": "/** @file\n    Fatal abort and warning macros for allocs.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_FATAL_H_\n#define INCLUDE_FATAL_H_\n\n#include <stdio.h> // fprintf\n\n#define STRINGIFYX(x) #x\n#define STRINGIFY(x) STRINGIFYX(x)\n#define FILE_LINE __FILE__ \":\" STRINGIFY(__LINE__)\n#define FATAL(what) do { fprintf(stderr, \"FATAL: \" what \" from \" FILE_LINE \"\\n\"); exit(1); } while (0)\n#define FATAL_MALLOC(what) FATAL(\"low memory? malloc() failed in \" what)\n#define FATAL_CALLOC(what) FATAL(\"low memory? calloc() failed in \" what)\n#define FATAL_REALLOC(what) FATAL(\"low memory? realloc() failed in \" what)\n#define FATAL_STRDUP(what) FATAL(\"low memory? strdup() failed in \" what)\n#define WARN(what) fprintf(stderr, \"WARNING: \" what \" from \" FILE_LINE \"\\n\")\n#define WARN_MALLOC(what) WARN(\"low memory? malloc() failed in \" what)\n#define WARN_CALLOC(what) WARN(\"low memory? calloc() failed in \" what)\n#define WARN_REALLOC(what) WARN(\"low memory? realloc() failed in \" what)\n#define WARN_STRDUP(what) WARN(\"low memory? strdup() failed in \" what)\n\n/*\n    Use like this:\n\n    char *buf = malloc(size);\n    if (!buf) {\n        FATAL_MALLOC(\"my_func()\");\n    }\n\n*/\n\n#endif /* INCLUDE_FATAL_H_ */\n"
  },
  {
    "path": "include/fileformat.h",
    "content": "/** @file\n    Various utility functions handling file formats.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_FILEFORMAT_H_\n#define INCLUDE_FILEFORMAT_H_\n\n#include <stdint.h>\n#include <stdio.h>\n\nchar const *file_basename(char const *path);\n\n/// a single handy number to define the file type.\n/// bitmask: RRRR LLLL WWWWWWWW 00CC 00FS\nenum file_type {\n    // format bits\n    F_UNSIGNED = 0 << 0,\n    F_SIGNED   = 1 << 0,\n    F_INT      = 0 << 1,\n    F_FLOAT    = 1 << 1,\n    F_1CH      = 1 << 4,\n    F_2CH      = 2 << 4,\n    F_W8       = 8 << 8,\n    F_W12      = 12 << 8,\n    F_W16      = 16 << 8,\n    F_W32      = 32 << 8,\n    F_W64      = 64 << 8,\n    // content types\n    F_I        = 1 << 16,\n    F_Q        = 2 << 16,\n    F_AM       = 3 << 16,\n    F_FM       = 4 << 16,\n    F_IQ       = F_I | F_Q << 4,\n    F_LOGIC    = 5 << 16,\n    F_VCD      = 6 << 16,\n    F_OOK      = 7 << 16,\n    // format types\n    F_U8       = F_1CH | F_UNSIGNED | F_INT | F_W8,\n    F_S8       = F_1CH | F_SIGNED   | F_INT | F_W8,\n    F_CU8      = F_2CH | F_UNSIGNED | F_INT | F_W8,\n    F_CS8      = F_2CH | F_SIGNED   | F_INT | F_W8,\n    F_U16      = F_1CH | F_UNSIGNED | F_INT | F_W16,\n    F_S16      = F_1CH | F_SIGNED   | F_INT | F_W16,\n    F_CU16     = F_2CH | F_UNSIGNED | F_INT | F_W16,\n    F_CS16     = F_2CH | F_SIGNED   | F_INT | F_W16,\n    F_U32      = F_1CH | F_UNSIGNED | F_INT | F_W32,\n    F_S32      = F_1CH | F_SIGNED   | F_INT | F_W32,\n    F_CU32     = F_2CH | F_UNSIGNED | F_INT | F_W32,\n    F_CS32     = F_2CH | F_SIGNED   | F_INT | F_W32,\n    F_F32      = F_1CH | F_SIGNED   | F_FLOAT | F_W32,\n    F_CF32     = F_2CH | F_SIGNED   | F_FLOAT | F_W32,\n    // compound types\n    CU8_IQ     = F_CU8 | F_IQ,\n    CS8_IQ     = F_CS8 | F_IQ,\n    S16_AM     = F_S16 | F_AM,\n    S16_FM     = F_S16 | F_FM,\n    CS16_IQ    = F_CS16 | F_IQ,\n    CF32_IQ    = F_CF32 | F_IQ,\n    F32_AM     = F_F32 | F_AM,\n    F32_FM     = F_F32 | F_FM,\n    F32_I      = F_F32 | F_I,\n    F32_Q      = F_F32 | F_Q,\n    U8_LOGIC   = F_LOGIC | F_U8,\n    VCD_LOGIC  = F_VCD,\n    PULSE_OOK  = F_OOK,\n};\n\ntypedef struct {\n    uint32_t format;\n    uint32_t raw_format;\n    uint32_t center_frequency;\n    uint32_t sample_rate;\n    char const *spec;\n    char const *path;\n    FILE *file;\n} file_info_t;\n\n/// Clear all file info.\n///\n/// @param[in,out] info the file info to clear\nvoid file_info_clear(file_info_t *info);\n\n/// Parse file info from a filename, optionally prefixed with overrides.\n///\n/// Detects tags in the file name delimited by non-alphanum\n/// and prefixes delimited with a colon.\n///\n/// Parse \"[0-9]+(\\.[0-9]+)?[A-Za-z]\"\n/// - as frequency (suffix \"M\" or \"[kMG]?Hz\")\n/// - or sample rate (suffix \"k\" or \"[kMG]?sps\")\n///\n/// Parse \"[A-Za-z][0-9A-Za-z]+\" as format or content specifier:\n/// - 2ch formats: \"cu8\", \"cs8\", \"cs16\", \"cs32\", \"cf32\"\n/// - 1ch formats: \"u8\", \"s8\", \"s16\", \"u16\", \"s32\", \"u32\", \"f32\"\n/// - text formats: \"vcd\", \"ook\"\n/// - content types: \"iq\", \"i\", \"q\", \"am\", \"fm\", \"logic\"\n///\n/// Parses left to right, with the exception of a prefix up to the last colon \":\"\n/// This prefix is the forced override, parsed last and removed from the filename.\n///\n/// All matches are case-insensitive.\n///\n/// - default detection, e.g.: path/filename.am.s16\n/// - overrides, e.g.: am:s16:path/filename.ext\n/// - other styles are detected but discouraged, e.g.:\n///   am-s16:path/filename.ext, am.s16:path/filename.ext, path/filename.am_s16\n///\n/// @param[in,out] info the file info to parse into\n/// @param filename a file name with optional override prefix to parse\n/// @return the detected file format, 0 otherwise\nint file_info_parse_filename(file_info_t *info, const char *filename);\n\n/// Check if the format in this file info is supported for reading,\n/// print a warning and exit otherwise.\n///\n/// @param info the file info to check\nvoid file_info_check_read(file_info_t *info);\n\n/// Check if the format in this file info is supported for reading,\n/// print a warning and exit otherwise.\n///\n/// @param info the file info to check\nvoid file_info_check_write(file_info_t *info);\n\n/// Return a string describing the format in this file info.\n///\n/// @param info the file info to check\n/// @return a string describing the format\nchar const *file_info_string(file_info_t *info);\n\n#endif /* INCLUDE_FILEFORMAT_H_ */\n"
  },
  {
    "path": "include/http_server.h",
    "content": "/**\n * RESTful HTTP control and WS interface\n *\n * Copyright (C) 2018 Christian Zuckschwerdt\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef INCLUDE_HTTP_SERVER_H_\n#define INCLUDE_HTTP_SERVER_H_\n\n#include \"data.h\"\n\nstruct mg_mgr;\nstruct r_cfg;\n\nstruct data_output *data_output_http_create(struct mg_mgr *mgr, const char *host, const char *port, struct r_cfg *cfg);\n\n#endif /* INCLUDE_HTTP_SERVER_H_ */\n"
  },
  {
    "path": "include/jsmn.h",
    "content": "#ifndef __JSMN_H_\n#define __JSMN_H_\n\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/**\n * JSON type identifier. Basic types are:\n * \to Object\n * \to Array\n * \to String\n * \to Other primitive: number, boolean (true/false) or null\n */\ntypedef enum {\n\tJSMN_UNDEFINED = 0,\n\tJSMN_OBJECT = 1,\n\tJSMN_ARRAY = 2,\n\tJSMN_STRING = 3,\n\tJSMN_PRIMITIVE = 4\n} jsmntype_t;\n\nenum jsmnerr {\n\t/* Not enough tokens were provided */\n\tJSMN_ERROR_NOMEM = -1,\n\t/* Invalid character inside JSON string */\n\tJSMN_ERROR_INVAL = -2,\n\t/* The string is not a full JSON packet, more bytes expected */\n\tJSMN_ERROR_PART = -3\n};\n\n/**\n * JSON token description.\n * type\t\ttype (object, array, string etc.)\n * start\tstart position in JSON data string\n * end\t\tend position in JSON data string\n */\ntypedef struct {\n\tjsmntype_t type;\n\tint start;\n\tint end;\n\tint size;\n#ifdef JSMN_PARENT_LINKS\n\tint parent;\n#endif\n} jsmntok_t;\n\n/**\n * JSON parser. Contains an array of token blocks available. Also stores\n * the string being parsed now and current position in that string\n */\ntypedef struct {\n\tunsigned int pos; /* offset in the JSON string */\n\tunsigned int toknext; /* next token to allocate */\n\tint toksuper; /* superior token node, e.g parent object or array */\n} jsmn_parser;\n\n/**\n * Create JSON parser over an array of tokens\n */\nvoid jsmn_init(jsmn_parser *parser);\n\n/**\n * Run JSON parser. It parses a JSON data string into and array of tokens, each describing\n * a single JSON object.\n */\nint jsmn_parse(jsmn_parser *parser, const char *js, size_t len,\n\t\tjsmntok_t *tokens, unsigned int num_tokens);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* __JSMN_H_ */\n"
  },
  {
    "path": "include/list.h",
    "content": "/** @file\n    Generic list.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_LIST_H_\n#define INCLUDE_LIST_H_\n\n#include <stddef.h>\n\n/// Dynamically growing list, elems is always NULL terminated, call list_ensure_size() to alloc elems.\ntypedef struct list {\n    void **elems;\n    size_t size;\n    size_t len;\n} list_t;\n\ntypedef void (*list_elem_free_fn)(void *);\n\n/// Alloc elems if needed and ensure the list has room for at least min_size elements.\nvoid list_ensure_size(list_t *list, size_t min_size);\n\n/// Add to the end of elems, allocs or grows the list if needed and ensures the list has a terminating NULL.\nvoid list_push(list_t *list, void *p);\n\n/// Adds all elements of a NULL terminated list to the end of elems, allocs or grows the list if needed and ensures the list has a terminating NULL.\nvoid list_push_all(list_t *list, void **p);\n\n/// Remove element from the list, frees element with fn.\nvoid list_remove(list_t *list, size_t idx, list_elem_free_fn elem_free);\n\n/// Clear the list, frees each element with fn, does not free backing or list itself.\nvoid list_clear(list_t *list, list_elem_free_fn elem_free);\n\n/// Clear the list, free backing, does not free list itself.\nvoid list_free_elems(list_t *list, list_elem_free_fn elem_free);\n\n#endif /* INCLUDE_LIST_H_ */\n"
  },
  {
    "path": "include/logger.h",
    "content": "/** @file\n    Basic logging, API.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_LOGGER_H_\n#define INCLUDE_LOGGER_H_\n\n// Defined in newer <sal.h> for MSVC.\n#ifndef _Printf_format_string_\n#define _Printf_format_string_\n#endif\n\n/// Log levels, copied from and compatible with SoapySDR.\n/// LOG_FATAL, LOG_ERROR, LOG_WARNING are abnormal program states, other levels are normal information.\n/// LOG_FATAL is not actually used, fatal errors usually fprintf and terminate.\ntypedef enum log_level {\n    LOG_FATAL    = 1, //!< A fatal error. The application will most likely terminate. This is the highest priority.\n    LOG_CRITICAL = 2, //!< A critical error. The application might not be able to continue running successfully.\n    LOG_ERROR    = 3, //!< An error. An operation did not complete successfully, but the application as a whole is not affected.\n    LOG_WARNING  = 4, //!< A warning. An operation completed with an unexpected result.\n    LOG_NOTICE   = 5, //!< A notice, which is an information with just a higher priority.\n    LOG_INFO     = 6, //!< An informational message, usually denoting the successful completion of an operation.\n    LOG_DEBUG    = 7, //!< A debugging message.\n    LOG_TRACE    = 8, //!< A tracing message. This is the lowest priority.\n} log_level_t;\n\ntypedef void (*r_logger_handler)(log_level_t level, char const *src, char const *msg, void *userdata);\n\n/** Set the log handler.\n\n    @param handler the handler to use, NULL to reset to default handler\n    @param userdata user data passed back to the handler\n*/\nvoid r_logger_set_log_handler(r_logger_handler const handler, void *userdata);\n\n/** Log a message string.\n\n    @param level a log level\n    @param src the log source, typically the function name (\"__func__\") or a module (\"SoapySDR\")\n    @param msg a log message\n*/\nvoid print_log(log_level_t level, char const *src, char const *msg);\n\n/** Log a message format string.\n\n    Be terse, messages should be shorter than 100 and a maximum length of 200 characters.\n\n    @param level a log level\n    @param src the log source, typically the function name (\"__func__\") or a module (\"SoapySDR\")\n    @param fmt a log message format string\n*/\nvoid print_logf(log_level_t level, char const *src, _Printf_format_string_ char const *fmt, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 3, 4)))\n#endif\n        ;\n\n#endif /* INCLUDE_LOGGER_H_ */\n"
  },
  {
    "path": "include/mongoose.h",
    "content": "#ifndef INCLUDE_MONGOOSE_H_\n#define INCLUDE_MONGOOSE_H_\n\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_common.h\"\n#endif\n/*\n * Copyright (c) 2004-2013 Sergey Lyubka\n * Copyright (c) 2013-2015 Cesanta Software Limited\n * All rights reserved\n *\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n#ifndef CS_MONGOOSE_SRC_COMMON_H_\n#define CS_MONGOOSE_SRC_COMMON_H_\n\n#define MG_VERSION \"6.16\"\n\n/* Local tweaks, applied before any of Mongoose's own headers. */\n#ifdef MG_LOCALS\n#include <mg_locals.h>\n#endif\n\n#endif /* CS_MONGOOSE_SRC_COMMON_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platform.h\"\n#endif\n#ifndef CS_COMMON_PLATFORM_H_\n#define CS_COMMON_PLATFORM_H_\n\n/*\n * For the \"custom\" platform, includes and dependencies can be\n * provided through mg_locals.h.\n */\n#define CS_P_CUSTOM 0\n#define CS_P_UNIX 1\n#define CS_P_WINDOWS 2\n#define CS_P_ESP32 15\n#define CS_P_ESP8266 3\n#define CS_P_CC3100 6\n#define CS_P_CC3200 4\n#define CS_P_CC3220 17\n#define CS_P_MSP432 5\n#define CS_P_TM4C129 14\n#define CS_P_MBED 7\n#define CS_P_WINCE 8\n#define CS_P_NXP_LPC 13\n#define CS_P_NXP_KINETIS 9\n#define CS_P_NRF51 12\n#define CS_P_NRF52 10\n#define CS_P_PIC32 11\n#define CS_P_RS14100 18\n#define CS_P_STM32 16\n/* Next id: 19 */\n\n/* If not specified explicitly, we guess platform by defines. */\n#ifndef CS_PLATFORM\n\n#if defined(TARGET_IS_MSP432P4XX) || defined(__MSP432P401R__)\n#define CS_PLATFORM CS_P_MSP432\n#elif defined(cc3200) || defined(TARGET_IS_CC3200)\n#define CS_PLATFORM CS_P_CC3200\n#elif defined(cc3220) || defined(TARGET_IS_CC3220)\n#define CS_PLATFORM CS_P_CC3220\n#elif defined(__unix__) || defined(__APPLE__)\n#define CS_PLATFORM CS_P_UNIX\n#elif defined(WINCE)\n#define CS_PLATFORM CS_P_WINCE\n#elif defined(_WIN32)\n#define CS_PLATFORM CS_P_WINDOWS\n#elif defined(__MBED__)\n#define CS_PLATFORM CS_P_MBED\n#elif defined(__USE_LPCOPEN)\n#define CS_PLATFORM CS_P_NXP_LPC\n#elif defined(FRDM_K64F) || defined(FREEDOM)\n#define CS_PLATFORM CS_P_NXP_KINETIS\n#elif defined(PIC32)\n#define CS_PLATFORM CS_P_PIC32\n#elif defined(ESP_PLATFORM)\n#define CS_PLATFORM CS_P_ESP32\n#elif defined(ICACHE_FLASH)\n#define CS_PLATFORM CS_P_ESP8266\n#elif defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || \\\n    defined(TARGET_IS_TM4C129_RA2)\n#define CS_PLATFORM CS_P_TM4C129\n#elif defined(RS14100)\n#define CS_PLATFORM CS_P_RS14100\n#elif defined(STM32)\n#define CS_PLATFORM CS_P_STM32\n#endif\n\n#ifndef CS_PLATFORM\n#error \"CS_PLATFORM is not specified and we couldn't guess it.\"\n#endif\n\n#endif /* !defined(CS_PLATFORM) */\n\n#define MG_NET_IF_SOCKET 1\n#define MG_NET_IF_SIMPLELINK 2\n#define MG_NET_IF_LWIP_LOW_LEVEL 3\n#define MG_NET_IF_PIC32 4\n#define MG_NET_IF_NULL 5\n\n#define MG_SSL_IF_OPENSSL 1\n#define MG_SSL_IF_MBEDTLS 2\n#define MG_SSL_IF_SIMPLELINK 3\n\n/* Amalgamated: #include \"common/platforms/platform_unix.h\" */\n/* Amalgamated: #include \"common/platforms/platform_windows.h\" */\n/* Amalgamated: #include \"common/platforms/platform_esp32.h\" */\n/* Amalgamated: #include \"common/platforms/platform_esp8266.h\" */\n/* Amalgamated: #include \"common/platforms/platform_cc3100.h\" */\n/* Amalgamated: #include \"common/platforms/platform_cc3200.h\" */\n/* Amalgamated: #include \"common/platforms/platform_cc3220.h\" */\n/* Amalgamated: #include \"common/platforms/platform_mbed.h\" */\n/* Amalgamated: #include \"common/platforms/platform_nrf51.h\" */\n/* Amalgamated: #include \"common/platforms/platform_nrf52.h\" */\n/* Amalgamated: #include \"common/platforms/platform_wince.h\" */\n/* Amalgamated: #include \"common/platforms/platform_nxp_lpc.h\" */\n/* Amalgamated: #include \"common/platforms/platform_nxp_kinetis.h\" */\n/* Amalgamated: #include \"common/platforms/platform_pic32.h\" */\n/* Amalgamated: #include \"common/platforms/platform_rs14100.h\" */\n/* Amalgamated: #include \"common/platforms/platform_stm32.h\" */\n#if CS_PLATFORM == CS_P_CUSTOM\n#include <platform_custom.h>\n#endif\n\n/* Common stuff */\n\n#if !defined(PRINTF_LIKE)\n#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)\n#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a)))\n#else\n#define PRINTF_LIKE(f, a)\n#endif\n#endif\n\n#if !defined(WEAK)\n#if (defined(__GNUC__) || defined(__clang__) || \\\n     defined(__TI_COMPILER_VERSION__)) &&       \\\n    !defined(_WIN32)\n#define WEAK __attribute__((weak))\n#else\n#define WEAK\n#endif\n#endif\n\n#ifdef __GNUC__\n#define NORETURN __attribute__((noreturn))\n#define NOINLINE __attribute__((noinline))\n#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))\n#define NOINSTR __attribute__((no_instrument_function))\n#define DO_NOT_WARN_UNUSED __attribute__((unused))\n#else\n#define NORETURN\n#define NOINLINE\n#define WARN_UNUSED_RESULT\n#define NOINSTR\n#define DO_NOT_WARN_UNUSED\n#endif /* __GNUC__ */\n\n#ifndef ARRAY_SIZE\n#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))\n#endif\n\n#endif /* CS_COMMON_PLATFORM_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_windows.h\"\n#endif\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_\n#if CS_PLATFORM == CS_P_WINDOWS\n\n/*\n * MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)\n * MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)\n * MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)\n * MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)\n * MSVC++ 9.0  _MSC_VER == 1500 (Visual Studio 2008)\n * MSVC++ 8.0  _MSC_VER == 1400 (Visual Studio 2005)\n * MSVC++ 7.1  _MSC_VER == 1310 (Visual Studio 2003)\n * MSVC++ 7.0  _MSC_VER == 1300\n * MSVC++ 6.0  _MSC_VER == 1200\n * MSVC++ 5.0  _MSC_VER == 1100\n */\n#ifdef _MSC_VER\n#pragma warning(disable : 4127) /* FD_SET() emits warning, disable it */\n#pragma warning(disable : 4204) /* missing c99 support */\n#endif\n\n#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS\n#define _WINSOCK_DEPRECATED_NO_WARNINGS 1\n#endif\n\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include <assert.h>\n#include <direct.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <io.h>\n#include <limits.h>\n#include <signal.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/stat.h>\n#include <time.h>\n#include <ctype.h>\n\n#ifdef _MSC_VER\n#pragma comment(lib, \"ws2_32.lib\") /* Linking with winsock library */\n#endif\n\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#include <windows.h>\n#include <process.h>\n\n#if defined(_MSC_VER) && (_MSC_VER < 1700)\ntypedef int bool;\n#else\n#include <stdbool.h>\n#endif\n\n#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_DEBUG)\n#define strdup _strdup\n#endif\n\n#ifndef EINPROGRESS\n#define EINPROGRESS WSAEINPROGRESS\n#endif\n#ifndef EWOULDBLOCK\n#define EWOULDBLOCK WSAEWOULDBLOCK\n#endif\n#ifndef __func__\n#define STRX(x) #x\n#define STR(x) STRX(x)\n#define __func__ __FILE__ \":\" STR(__LINE__)\n#endif\n#define snprintf _snprintf\n#define vsnprintf _vsnprintf\n#define to64(x) _atoi64(x)\n#define rmdir _rmdir\n#if !defined(__MINGW32__) && !defined(__MINGW64__)\n#define popen(x, y) _popen((x), (y))\n#define pclose(x) _pclose(x)\n#define fileno _fileno\n#endif\n#if defined(_MSC_VER) && _MSC_VER >= 1400\n#define fseeko(x, y, z) _fseeki64((x), (y), (z))\n#else\n#define fseeko(x, y, z) fseek((x), (y), (z))\n#endif\n#if defined(_MSC_VER) && _MSC_VER <= 1200\ntypedef unsigned long uintptr_t;\ntypedef long intptr_t;\n#endif\ntypedef int socklen_t;\n#if _MSC_VER >= 1700\n#include <stdint.h>\n#else\ntypedef signed char int8_t;\ntypedef unsigned char uint8_t;\ntypedef int int32_t;\ntypedef unsigned int uint32_t;\ntypedef short int16_t;\ntypedef unsigned short uint16_t;\ntypedef __int64 int64_t;\ntypedef unsigned __int64 uint64_t;\n#endif\ntypedef SOCKET sock_t;\ntypedef uint32_t in_addr_t;\n#ifndef UINT16_MAX\n#define UINT16_MAX 65535\n#endif\n#ifndef UINT32_MAX\n#define UINT32_MAX 4294967295\n#endif\n#ifndef pid_t\n#define pid_t HANDLE\n#endif\n#define INT64_FMT \"I64d\"\n#define INT64_X_FMT \"I64x\"\n#define SIZE_T_FMT \"Iu\"\ntypedef struct _stati64 cs_stat_t;\n#ifndef S_ISDIR\n#define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR)\n#endif\n#ifndef S_ISREG\n#define S_ISREG(x) (((x) &_S_IFMT) == _S_IFREG)\n#endif\n#define DIRSEP '\\\\'\n#define CS_DEFINE_DIRENT\n\n#ifndef va_copy\n#ifdef __va_copy\n#define va_copy __va_copy\n#else\n#define va_copy(x, y) (x) = (y)\n#endif\n#endif\n\n#ifndef MG_MAX_HTTP_REQUEST_SIZE\n#define MG_MAX_HTTP_REQUEST_SIZE 8192\n#endif\n\n#ifndef MG_MAX_HTTP_SEND_MBUF\n#define MG_MAX_HTTP_SEND_MBUF 4096\n#endif\n\n#ifndef MG_MAX_HTTP_HEADERS\n#define MG_MAX_HTTP_HEADERS 40\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifndef MG_ENABLE_BROADCAST\n#define MG_ENABLE_BROADCAST 1\n#endif\n\n#ifndef MG_ENABLE_DIRECTORY_LISTING\n#define MG_ENABLE_DIRECTORY_LISTING 1\n#endif\n\n#ifndef MG_ENABLE_FILESYSTEM\n#define MG_ENABLE_FILESYSTEM 1\n#endif\n\n#ifndef MG_ENABLE_HTTP_CGI\n#define MG_ENABLE_HTTP_CGI MG_ENABLE_FILESYSTEM\n#endif\n\n#ifndef MG_NET_IF\n#define MG_NET_IF MG_NET_IF_SOCKET\n#endif\n\nunsigned int sleep(unsigned int seconds);\n\n/* https://stackoverflow.com/questions/16647819/timegm-cross-platform */\n#define timegm _mkgmtime\n\n#define gmtime_r(a, b) (gmtime_s((b), (a)), (b))\n#define localtime_r(a, b) (localtime_s((b), (a)), (b))\n\n#endif /* CS_PLATFORM == CS_P_WINDOWS */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_unix.h\"\n#endif\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_\n#if CS_PLATFORM == CS_P_UNIX\n\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 600\n#endif\n\n/* <inttypes.h> wants this for C++ */\n#ifndef __STDC_FORMAT_MACROS\n#define __STDC_FORMAT_MACROS\n#endif\n\n/* C++ wants that for INT64_MAX */\n#ifndef __STDC_LIMIT_MACROS\n#define __STDC_LIMIT_MACROS\n#endif\n\n/* Enable fseeko() and ftello() functions */\n#ifndef _LARGEFILE_SOURCE\n#define _LARGEFILE_SOURCE\n#endif\n\n/* Enable 64-bit file offsets */\n#ifndef _FILE_OFFSET_BITS\n#define _FILE_OFFSET_BITS 64\n#endif\n\n#include <arpa/inet.h>\n#include <assert.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <limits.h>\n#include <math.h>\n#include <netdb.h>\n#include <netinet/in.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/param.h>\n#include <sys/socket.h>\n#include <sys/select.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#ifdef __APPLE__\n#include <machine/endian.h>\n#ifndef BYTE_ORDER\n#define LITTLE_ENDIAN __DARWIN_LITTLE_ENDIAN\n#define BIG_ENDIAN __DARWIN_BIG_ENDIAN\n#define PDP_ENDIAN __DARWIN_PDP_ENDIAN\n#define BYTE_ORDER __DARWIN_BYTE_ORDER\n#endif\n#endif\n\n/*\n * osx correctly avoids defining strtoll when compiling in strict ansi mode.\n * c++ 11 standard defines strtoll as well.\n * We require strtoll, and if your embedded pre-c99 compiler lacks one, please\n * implement a shim.\n */\n#if !(defined(__cplusplus) && __cplusplus >= 201103L) && \\\n    !(defined(__DARWIN_C_LEVEL) && __DARWIN_C_LEVEL >= 200809L)\nlong long strtoll(const char *, char **, int);\n#endif\n\ntypedef int sock_t;\n#define INVALID_SOCKET (-1)\n#define SIZE_T_FMT \"zu\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n\n#ifndef __cdecl\n#define __cdecl\n#endif\n\n#ifndef va_copy\n#ifdef __va_copy\n#define va_copy __va_copy\n#else\n#define va_copy(x, y) (x) = (y)\n#endif\n#endif\n\n#define closesocket(x) close(x)\n\n#ifndef MG_MAX_HTTP_REQUEST_SIZE\n#define MG_MAX_HTTP_REQUEST_SIZE 8192\n#endif\n\n#ifndef MG_MAX_HTTP_SEND_MBUF\n#define MG_MAX_HTTP_SEND_MBUF 4096\n#endif\n\n#ifndef MG_MAX_HTTP_HEADERS\n#define MG_MAX_HTTP_HEADERS 40\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifndef MG_ENABLE_BROADCAST\n#define MG_ENABLE_BROADCAST 1\n#endif\n\n#ifndef MG_ENABLE_DIRECTORY_LISTING\n#define MG_ENABLE_DIRECTORY_LISTING 1\n#endif\n\n#ifndef MG_ENABLE_FILESYSTEM\n#define MG_ENABLE_FILESYSTEM 1\n#endif\n\n#ifndef MG_ENABLE_HTTP_CGI\n#define MG_ENABLE_HTTP_CGI MG_ENABLE_FILESYSTEM\n#endif\n\n#ifndef MG_NET_IF\n#define MG_NET_IF MG_NET_IF_SOCKET\n#endif\n\n#ifndef MG_HOSTS_FILE_NAME\n#define MG_HOSTS_FILE_NAME \"/etc/hosts\"\n#endif\n\n#ifndef MG_RESOLV_CONF_FILE_NAME\n#define MG_RESOLV_CONF_FILE_NAME \"/etc/resolv.conf\"\n#endif\n\n#endif /* CS_PLATFORM == CS_P_UNIX */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_UNIX_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_esp32.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_\n#if CS_PLATFORM == CS_P_ESP32\n\n#include <assert.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <machine/endian.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define __cdecl\n#define _FILE_OFFSET_BITS 32\n\n#define MG_LWIP 1\n\n#ifndef MG_NET_IF\n#define MG_NET_IF MG_NET_IF_SOCKET\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#endif /* CS_PLATFORM == CS_P_ESP32 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP32_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_esp8266.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_\n#if CS_PLATFORM == CS_P_ESP8266\n\n#include <assert.h>\n#include <ctype.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <machine/endian.h>\n#include <stdbool.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#if !defined(MGOS_VFS_DEFINE_DIRENT)\n#define CS_DEFINE_DIRENT\n#endif\n\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define __cdecl\n#define _FILE_OFFSET_BITS 32\n\n#define MG_LWIP 1\n\n/* struct timeval is defined in sys/time.h. */\n#define LWIP_TIMEVAL_PRIVATE 0\n\n#ifndef MG_NET_IF\n#include <lwip/opt.h>\n#if LWIP_SOCKET /* RTOS SDK has LWIP sockets */\n#define MG_NET_IF MG_NET_IF_SOCKET\n#else\n#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL\n#endif\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#define inet_ntop(af, src, dst, size)                                          \\\n  (((af) == AF_INET) ? ipaddr_ntoa_r((const ip_addr_t *) (src), (dst), (size)) \\\n                     : NULL)\n#define inet_pton(af, src, dst) \\\n  (((af) == AF_INET) ? ipaddr_aton((src), (ip_addr_t *) (dst)) : 0)\n\n#endif /* CS_PLATFORM == CS_P_ESP8266 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_ESP8266_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_cc3100.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_\n#if CS_PLATFORM == CS_P_CC3100\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#define MG_NET_IF MG_NET_IF_SIMPLELINK\n#define MG_SSL_IF MG_SSL_IF_SIMPLELINK\n\n/*\n * CC3100 SDK and STM32 SDK include headers w/out path, just like\n * #include \"simplelink.h\". As result, we have to add all required directories\n * into Makefile IPATH and do the same thing (include w/out path)\n */\n\n#include <simplelink.h>\n#include <netapp.h>\n#undef timeval\n\ntypedef int sock_t;\n#define INVALID_SOCKET (-1)\n\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define SIZE_T_FMT \"u\"\n\n#define SOMAXCONN 8\n\nconst char *inet_ntop(int af, const void *src, char *dst, socklen_t size);\nchar *inet_ntoa(struct in_addr in);\nint inet_pton(int af, const char *src, void *dst);\n\n#endif /* CS_PLATFORM == CS_P_CC3100 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3100_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_cc3200.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_\n#if CS_PLATFORM == CS_P_CC3200\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#ifndef __TI_COMPILER_VERSION__\n#include <fcntl.h>\n#include <sys/time.h>\n#endif\n\n#define MG_NET_IF MG_NET_IF_SIMPLELINK\n#define MG_SSL_IF MG_SSL_IF_SIMPLELINK\n\n/* Only SPIFFS supports directories, SLFS does not. */\n#if defined(CC3200_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING)\n#define MG_ENABLE_DIRECTORY_LISTING 1\n#endif\n\n/* Amalgamated: #include \"common/platforms/simplelink/cs_simplelink.h\" */\n\ntypedef int sock_t;\n#define INVALID_SOCKET (-1)\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define __cdecl\n\n#define fileno(x) -1\n\n/* Some functions we implement for Mongoose. */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __TI_COMPILER_VERSION__\nstruct SlTimeval_t;\n#define timeval SlTimeval_t\nint gettimeofday(struct timeval *t, void *tz);\nint settimeofday(const struct timeval *tv, const void *tz);\n\nint asprintf(char **strp, const char *fmt, ...);\n\n#endif\n\n/* TI's libc does not have stat & friends, add them. */\n#ifdef __TI_COMPILER_VERSION__\n\n#include <file.h>\n\ntypedef unsigned int mode_t;\ntypedef size_t _off_t;\ntypedef long ssize_t;\n\nstruct stat {\n  int st_ino;\n  mode_t st_mode;\n  int st_nlink;\n  time_t st_mtime;\n  off_t st_size;\n};\n\nint _stat(const char *pathname, struct stat *st);\nint stat(const char *pathname, struct stat *st);\n\n#define __S_IFMT 0170000\n\n#define __S_IFDIR 0040000\n#define __S_IFCHR 0020000\n#define __S_IFREG 0100000\n\n#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))\n\n#define S_IFDIR __S_IFDIR\n#define S_IFCHR __S_IFCHR\n#define S_IFREG __S_IFREG\n#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)\n#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)\n\n/* 5.x series compilers don't have va_copy, 16.x do. */\n#if __TI_COMPILER_VERSION__ < 16000000\n#define va_copy(apc, ap) ((apc) = (ap))\n#endif\n\n#endif /* __TI_COMPILER_VERSION__ */\n\n#ifdef CC3200_FS_SLFS\n#define MG_FS_SLFS\n#endif\n\n#if (defined(CC3200_FS_SPIFFS) || defined(CC3200_FS_SLFS)) && \\\n    !defined(MG_ENABLE_FILESYSTEM)\n#define MG_ENABLE_FILESYSTEM 1\n#define CS_DEFINE_DIRENT\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_PLATFORM == CS_P_CC3200 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_cc3220.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_CC3220_H_\n#if CS_PLATFORM == CS_P_CC3220\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#ifndef __TI_COMPILER_VERSION__\n#include <fcntl.h>\n#include <sys/time.h>\n#endif\n\n#define MG_NET_IF MG_NET_IF_SIMPLELINK\n#ifndef MG_SSL_IF\n#define MG_SSL_IF MG_SSL_IF_SIMPLELINK\n#endif\n\n/* Only SPIFFS supports directories, SLFS does not. */\n#if defined(CC3220_FS_SPIFFS) && !defined(MG_ENABLE_DIRECTORY_LISTING)\n#define MG_ENABLE_DIRECTORY_LISTING 1\n#endif\n\n/* Amalgamated: #include \"common/platforms/simplelink/cs_simplelink.h\" */\n\ntypedef int sock_t;\n#define INVALID_SOCKET (-1)\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define __cdecl\n\n#define fileno(x) -1\n\n/* Some functions we implement for Mongoose. */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __TI_COMPILER_VERSION__\nstruct SlTimeval_t;\n#define timeval SlTimeval_t\nint gettimeofday(struct timeval *t, void *tz);\nint settimeofday(const struct timeval *tv, const void *tz);\n\nint asprintf(char **strp, const char *fmt, ...);\n\n#endif\n\n/* TI's libc does not have stat & friends, add them. */\n#ifdef __TI_COMPILER_VERSION__\n\n#include <file.h>\n\ntypedef unsigned int mode_t;\ntypedef size_t _off_t;\ntypedef long ssize_t;\n\nstruct stat {\n  int st_ino;\n  mode_t st_mode;\n  int st_nlink;\n  time_t st_mtime;\n  off_t st_size;\n};\n\nint _stat(const char *pathname, struct stat *st);\nint stat(const char *pathname, struct stat *st);\n\n#define __S_IFMT 0170000\n\n#define __S_IFDIR 0040000\n#define __S_IFCHR 0020000\n#define __S_IFREG 0100000\n\n#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))\n\n#define S_IFDIR __S_IFDIR\n#define S_IFCHR __S_IFCHR\n#define S_IFREG __S_IFREG\n#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)\n#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)\n\n#endif /* __TI_COMPILER_VERSION__ */\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_PLATFORM == CS_P_CC3220 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_CC3200_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_msp432.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_\n#if CS_PLATFORM == CS_P_MSP432\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#ifndef __TI_COMPILER_VERSION__\n#include <fcntl.h>\n#include <sys/time.h>\n#endif\n\n#define MG_NET_IF MG_NET_IF_SIMPLELINK\n#define MG_SSL_IF MG_SSL_IF_SIMPLELINK\n\n/* Amalgamated: #include \"common/platforms/simplelink/cs_simplelink.h\" */\n\ntypedef int sock_t;\n#define INVALID_SOCKET (-1)\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define __cdecl\n\n#define fileno(x) -1\n\n/* Some functions we implement for Mongoose. */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifdef __TI_COMPILER_VERSION__\nstruct SlTimeval_t;\n#define timeval SlTimeval_t\nint gettimeofday(struct timeval *t, void *tz);\n#endif\n\n/* TI's libc does not have stat & friends, add them. */\n#ifdef __TI_COMPILER_VERSION__\n\n#include <file.h>\n\ntypedef unsigned int mode_t;\ntypedef size_t _off_t;\ntypedef long ssize_t;\n\nstruct stat {\n  int st_ino;\n  mode_t st_mode;\n  int st_nlink;\n  time_t st_mtime;\n  off_t st_size;\n};\n\nint _stat(const char *pathname, struct stat *st);\n#define stat(a, b) _stat(a, b)\n\n#define __S_IFMT 0170000\n\n#define __S_IFDIR 0040000\n#define __S_IFCHR 0020000\n#define __S_IFREG 0100000\n\n#define __S_ISTYPE(mode, mask) (((mode) &__S_IFMT) == (mask))\n\n#define S_IFDIR __S_IFDIR\n#define S_IFCHR __S_IFCHR\n#define S_IFREG __S_IFREG\n#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)\n#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)\n\n/* As of 5.2.7, TI compiler does not support va_copy() yet. */\n#define va_copy(apc, ap) ((apc) = (ap))\n\n#endif /* __TI_COMPILER_VERSION__ */\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#if (defined(CC3200_FS_SPIFFS) || defined(CC3200_FS_SLFS)) && \\\n    !defined(MG_ENABLE_FILESYSTEM)\n#define MG_ENABLE_FILESYSTEM 1\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_PLATFORM == CS_P_MSP432 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_MSP432_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_tm4c129.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_TM4C129_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_TM4C129_H_\n#if CS_PLATFORM == CS_P_TM4C129\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#ifndef __TI_COMPILER_VERSION__\n#include <fcntl.h>\n#include <sys/time.h>\n#endif\n\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define __cdecl\n\n#ifndef MG_NET_IF\n#include <lwip/opt.h>\n#if LWIP_SOCKET\n#define MG_NET_IF MG_NET_IF_SOCKET\n#else\n#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL\n#endif\n#define MG_LWIP 1\n#elif MG_NET_IF == MG_NET_IF_SIMPLELINK\n/* Amalgamated: #include \"common/platforms/simplelink/cs_simplelink.h\" */\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifdef __TI_COMPILER_VERSION__\n/* As of 5.2.8, TI compiler does not support va_copy() yet. */\n#define va_copy(apc, ap) ((apc) = (ap))\n#endif /* __TI_COMPILER_VERSION__ */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_PLATFORM == CS_P_TM4C129 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_TM4C129_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_mbed.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_MBED_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_MBED_H_\n#if CS_PLATFORM == CS_P_MBED\n\n/*\n * mbed.h contains C++ code (e.g. templates), thus, it should be processed\n * only if included directly to startup file (ex: main.cpp)\n */\n#ifdef __cplusplus\n/* Amalgamated: #include \"mbed.h\" */\n#endif /* __cplusplus */\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include <stdio.h>\n\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n/*\n * mbed can be compiled with the ARM compiler which\n * just doesn't come with a gettimeofday shim\n * because it's a BSD API and ARM targets embedded\n * non-unix platforms.\n */\n#if defined(__ARMCC_VERSION) || defined(__ICCARM__)\n#define _TIMEVAL_DEFINED\n#define gettimeofday _gettimeofday\n\n/* copied from GCC on ARM; for some reason useconds are signed */\ntypedef long suseconds_t; /* microseconds (signed) */\nstruct timeval {\n  time_t tv_sec;       /* seconds */\n  suseconds_t tv_usec; /* and microseconds */\n};\n\n#endif\n\n#if MG_NET_IF == MG_NET_IF_SIMPLELINK\n\n#define MG_SIMPLELINK_NO_OSI 1\n\n#include <simplelink.h>\n\ntypedef int sock_t;\n#define INVALID_SOCKET (-1)\n\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT PRId64\n#define INT64_X_FMT PRIx64\n#define SIZE_T_FMT \"u\"\n\n#define SOMAXCONN 8\n\nconst char *inet_ntop(int af, const void *src, char *dst, socklen_t size);\nchar *inet_ntoa(struct in_addr in);\nint inet_pton(int af, const char *src, void *dst);\nint inet_aton(const char *cp, struct in_addr *inp);\nin_addr_t inet_addr(const char *cp);\n\n#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */\n\n#endif /* CS_PLATFORM == CS_P_MBED */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_MBED_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_nrf51.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_\n#if CS_PLATFORM == CS_P_NRF51\n\n#include <assert.h>\n#include <ctype.h>\n#include <inttypes.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#define to64(x) strtoll(x, NULL, 10)\n\n#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL\n#define MG_LWIP 1\n#define MG_ENABLE_IPV6 1\n\n/*\n * For ARM C Compiler, make lwip to export `struct timeval`; for other\n * compilers, suppress it.\n */\n#if !defined(__ARMCC_VERSION)\n#define LWIP_TIMEVAL_PRIVATE 0\n#else\nstruct timeval;\nint gettimeofday(struct timeval *tp, void *tzp);\n#endif\n\n#define INT64_FMT PRId64\n#define SIZE_T_FMT \"u\"\n\n/*\n * ARM C Compiler doesn't have strdup, so we provide it\n */\n#define CS_ENABLE_STRDUP defined(__ARMCC_VERSION)\n\n#endif /* CS_PLATFORM == CS_P_NRF51 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_NRF51_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_nrf52.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_\n#if CS_PLATFORM == CS_P_NRF52\n\n#include <assert.h>\n#include <ctype.h>\n#include <errno.h>\n#include <inttypes.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <time.h>\n\n#define to64(x) strtoll(x, NULL, 10)\n\n#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL\n#define MG_LWIP 1\n#define MG_ENABLE_IPV6 1\n\n#if !defined(ENOSPC)\n#define ENOSPC 28 /* No space left on device */\n#endif\n\n/*\n * For ARM C Compiler, make lwip to export `struct timeval`; for other\n * compilers, suppress it.\n */\n#if !defined(__ARMCC_VERSION)\n#define LWIP_TIMEVAL_PRIVATE 0\n#endif\n\n#define INT64_FMT PRId64\n#define SIZE_T_FMT \"u\"\n\n/*\n * ARM C Compiler doesn't have strdup, so we provide it\n */\n#define CS_ENABLE_STRDUP defined(__ARMCC_VERSION)\n\n#endif /* CS_PLATFORM == CS_P_NRF52 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_NRF52_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/cs_simplelink.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_\n#define CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_\n\n#if defined(MG_NET_IF) && MG_NET_IF == MG_NET_IF_SIMPLELINK\n\n/* If simplelink.h is already included, all bets are off. */\n#if !defined(__SIMPLELINK_H__)\n\n#include <stdbool.h>\n\n#ifndef __TI_COMPILER_VERSION__\n#undef __CONCAT\n#undef FD_CLR\n#undef FD_ISSET\n#undef FD_SET\n#undef FD_SETSIZE\n#undef FD_ZERO\n#undef fd_set\n#endif\n\n#if CS_PLATFORM == CS_P_CC3220\n#include <ti/drivers/net/wifi/porting/user.h>\n#include <ti/drivers/net/wifi/simplelink.h>\n#include <ti/drivers/net/wifi/sl_socket.h>\n#include <ti/drivers/net/wifi/netapp.h>\n#else\n/* We want to disable SL_INC_STD_BSD_API_NAMING, so we include user.h ourselves\n * and undef it. */\n#define PROVISIONING_API_H_\n#include <simplelink/user.h>\n#undef PROVISIONING_API_H_\n#undef SL_INC_STD_BSD_API_NAMING\n\n#include <simplelink/include/simplelink.h>\n#include <simplelink/include/netapp.h>\n#endif /* CS_PLATFORM == CS_P_CC3220 */\n\n/* Now define only the subset of the BSD API that we use.\n * Notably, close(), read() and write() are not defined. */\n#define AF_INET SL_AF_INET\n\n#define socklen_t SlSocklen_t\n#define sockaddr SlSockAddr_t\n#define sockaddr_in SlSockAddrIn_t\n#define in_addr SlInAddr_t\n\n#define SOCK_STREAM SL_SOCK_STREAM\n#define SOCK_DGRAM SL_SOCK_DGRAM\n\n#define htonl sl_Htonl\n#define ntohl sl_Ntohl\n#define htons sl_Htons\n#define ntohs sl_Ntohs\n\n#ifndef EACCES\n#define EACCES SL_EACCES\n#endif\n#ifndef EAFNOSUPPORT\n#define EAFNOSUPPORT SL_EAFNOSUPPORT\n#endif\n#ifndef EAGAIN\n#define EAGAIN SL_EAGAIN\n#endif\n#ifndef EBADF\n#define EBADF SL_EBADF\n#endif\n#ifndef EINVAL\n#define EINVAL SL_EINVAL\n#endif\n#ifndef ENOMEM\n#define ENOMEM SL_ENOMEM\n#endif\n#ifndef EWOULDBLOCK\n#define EWOULDBLOCK SL_EWOULDBLOCK\n#endif\n\n#define SOMAXCONN 8\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nconst char *inet_ntop(int af, const void *src, char *dst, socklen_t size);\nchar *inet_ntoa(struct in_addr in);\nint inet_pton(int af, const char *src, void *dst);\n\nstruct mg_mgr;\nstruct mg_connection;\n\ntypedef void (*mg_init_cb)(struct mg_mgr *mgr);\nbool mg_start_task(int priority, int stack_size, mg_init_cb mg_init);\n\nvoid mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg);\n\nint sl_fs_init(void);\n\nvoid sl_restart_cb(struct mg_mgr *mgr);\n\nint sl_set_ssl_opts(int sock, struct mg_connection *nc);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* !defined(__SIMPLELINK_H__) */\n\n/* Compatibility with older versions of SimpleLink */\n#if SL_MAJOR_VERSION_NUM < 2\n\n#define SL_ERROR_BSD_EAGAIN SL_EAGAIN\n#define SL_ERROR_BSD_EALREADY SL_EALREADY\n#define SL_ERROR_BSD_ENOPROTOOPT SL_ENOPROTOOPT\n#define SL_ERROR_BSD_ESECDATEERROR SL_ESECDATEERROR\n#define SL_ERROR_BSD_ESECSNOVERIFY SL_ESECSNOVERIFY\n#define SL_ERROR_FS_FAILED_TO_ALLOCATE_MEM SL_FS_ERR_FAILED_TO_ALLOCATE_MEM\n#define SL_ERROR_FS_FILE_HAS_NOT_BEEN_CLOSE_CORRECTLY \\\n  SL_FS_FILE_HAS_NOT_BEEN_CLOSE_CORRECTLY\n#define SL_ERROR_FS_FILE_NAME_EXIST SL_FS_FILE_NAME_EXIST\n#define SL_ERROR_FS_FILE_NOT_EXISTS SL_FS_ERR_FILE_NOT_EXISTS\n#define SL_ERROR_FS_NO_AVAILABLE_NV_INDEX SL_FS_ERR_NO_AVAILABLE_NV_INDEX\n#define SL_ERROR_FS_NOT_ENOUGH_STORAGE_SPACE SL_FS_ERR_NO_AVAILABLE_BLOCKS\n#define SL_ERROR_FS_NOT_SUPPORTED SL_FS_ERR_NOT_SUPPORTED\n#define SL_ERROR_FS_WRONG_FILE_NAME SL_FS_WRONG_FILE_NAME\n#define SL_ERROR_FS_INVALID_HANDLE SL_FS_ERR_INVALID_HANDLE\n#define SL_NETCFG_MAC_ADDRESS_GET SL_MAC_ADDRESS_GET\n#define SL_SOCKET_FD_ZERO SL_FD_ZERO\n#define SL_SOCKET_FD_SET SL_FD_SET\n#define SL_SOCKET_FD_ISSET SL_FD_ISSET\n#define SL_SO_SECURE_DOMAIN_NAME_VERIFICATION SO_SECURE_DOMAIN_NAME_VERIFICATION\n\n#define SL_FS_READ FS_MODE_OPEN_READ\n#define SL_FS_WRITE FS_MODE_OPEN_WRITE\n\n#define SL_FI_FILE_SIZE(fi) ((fi).FileLen)\n#define SL_FI_FILE_MAX_SIZE(fi) ((fi).AllocatedLen)\n\n#define SlDeviceVersion_t SlVersionFull\n#define sl_DeviceGet sl_DevGet\n#define SL_DEVICE_GENERAL SL_DEVICE_GENERAL_CONFIGURATION\n#define SL_LEN_TYPE _u8\n#define SL_OPT_TYPE _u8\n\n#else /* SL_MAJOR_VERSION_NUM >= 2 */\n\n#define FS_MODE_OPEN_CREATE(max_size, flag) \\\n  (SL_FS_CREATE | SL_FS_CREATE_MAX_SIZE(max_size))\n#define SL_FI_FILE_SIZE(fi) ((fi).Len)\n#define SL_FI_FILE_MAX_SIZE(fi) ((fi).MaxSize)\n\n#define SL_LEN_TYPE _u16\n#define SL_OPT_TYPE _u16\n\n#endif /* SL_MAJOR_VERSION_NUM < 2 */\n\nint slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token);\n\n#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */\n\n#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_CS_SIMPLELINK_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_wince.h\"\n#endif\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_\n\n#if CS_PLATFORM == CS_P_WINCE\n\n/*\n * MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015)\n * MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013)\n * MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012)\n * MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010)\n * MSVC++ 9.0  _MSC_VER == 1500 (Visual Studio 2008)\n * MSVC++ 8.0  _MSC_VER == 1400 (Visual Studio 2005)\n * MSVC++ 7.1  _MSC_VER == 1310 (Visual Studio 2003)\n * MSVC++ 7.0  _MSC_VER == 1300\n * MSVC++ 6.0  _MSC_VER == 1200\n * MSVC++ 5.0  _MSC_VER == 1100\n */\n#pragma warning(disable : 4127) /* FD_SET() emits warning, disable it */\n#pragma warning(disable : 4204) /* missing c99 support */\n\n#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS\n#define _WINSOCK_DEPRECATED_NO_WARNINGS 1\n#endif\n\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif\n\n#include <assert.h>\n#include <limits.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#pragma comment(lib, \"ws2.lib\") /* Linking with WinCE winsock library */\n\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#include <windows.h>\n\n#define strdup _strdup\n\n#ifndef EINPROGRESS\n#define EINPROGRESS WSAEINPROGRESS\n#endif\n\n#ifndef EWOULDBLOCK\n#define EWOULDBLOCK WSAEWOULDBLOCK\n#endif\n\n#ifndef EAGAIN\n#define EAGAIN EWOULDBLOCK\n#endif\n\n#ifndef __func__\n#define STRX(x) #x\n#define STR(x) STRX(x)\n#define __func__ __FILE__ \":\" STR(__LINE__)\n#endif\n\n#define snprintf _snprintf\n#define fileno _fileno\n#define vsnprintf _vsnprintf\n#define sleep(x) Sleep((x) *1000)\n#define to64(x) _atoi64(x)\n#define rmdir _rmdir\n\n#if defined(_MSC_VER) && _MSC_VER >= 1400\n#define fseeko(x, y, z) _fseeki64((x), (y), (z))\n#else\n#define fseeko(x, y, z) fseek((x), (y), (z))\n#endif\n\ntypedef int socklen_t;\n\n#if _MSC_VER >= 1700\n#include <stdint.h>\n#else\ntypedef signed char int8_t;\ntypedef unsigned char uint8_t;\ntypedef int int32_t;\ntypedef unsigned int uint32_t;\ntypedef short int16_t;\ntypedef unsigned short uint16_t;\ntypedef __int64 int64_t;\ntypedef unsigned __int64 uint64_t;\n#endif\n\ntypedef SOCKET sock_t;\ntypedef uint32_t in_addr_t;\n\n#ifndef UINT16_MAX\n#define UINT16_MAX 65535\n#endif\n\n#ifndef UINT32_MAX\n#define UINT32_MAX 4294967295\n#endif\n\n#ifndef pid_t\n#define pid_t HANDLE\n#endif\n\n#define INT64_FMT \"I64d\"\n#define INT64_X_FMT \"I64x\"\n/* TODO(alashkin): check if this is correct */\n#define SIZE_T_FMT \"u\"\n\n#define DIRSEP '\\\\'\n#define CS_DEFINE_DIRENT\n\n#ifndef va_copy\n#ifdef __va_copy\n#define va_copy __va_copy\n#else\n#define va_copy(x, y) (x) = (y)\n#endif\n#endif\n\n#ifndef MG_MAX_HTTP_REQUEST_SIZE\n#define MG_MAX_HTTP_REQUEST_SIZE 8192\n#endif\n\n#ifndef MG_MAX_HTTP_SEND_MBUF\n#define MG_MAX_HTTP_SEND_MBUF 4096\n#endif\n\n#ifndef MG_MAX_HTTP_HEADERS\n#define MG_MAX_HTTP_HEADERS 40\n#endif\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#define abort() DebugBreak();\n\n#ifndef BUFSIZ\n#define BUFSIZ 4096\n#endif\n/*\n * Explicitly disabling MG_ENABLE_THREADS for WinCE\n * because they are enabled for _WIN32 by default\n */\n#ifndef MG_ENABLE_THREADS\n#define MG_ENABLE_THREADS 0\n#endif\n\n#ifndef MG_ENABLE_FILESYSTEM\n#define MG_ENABLE_FILESYSTEM 1\n#endif\n\n#ifndef MG_NET_IF\n#define MG_NET_IF MG_NET_IF_SOCKET\n#endif\n\ntypedef struct _stati64 {\n  uint32_t st_mtime;\n  uint32_t st_size;\n  uint32_t st_mode;\n} cs_stat_t;\n\n/*\n * WinCE 6.0 has a lot of useful definitions in ATL (not windows.h) headers\n * use #ifdefs to avoid conflicts\n */\n\n#ifndef ENOENT\n#define ENOENT ERROR_PATH_NOT_FOUND\n#endif\n\n#ifndef EACCES\n#define EACCES ERROR_ACCESS_DENIED\n#endif\n\n#ifndef ENOMEM\n#define ENOMEM ERROR_NOT_ENOUGH_MEMORY\n#endif\n\n#ifndef _UINTPTR_T_DEFINED\ntypedef unsigned int *uintptr_t;\n#endif\n\n#define _S_IFREG 2\n#define _S_IFDIR 4\n\n#ifndef S_ISDIR\n#define S_ISDIR(x) (((x) &_S_IFDIR) != 0)\n#endif\n\n#ifndef S_ISREG\n#define S_ISREG(x) (((x) &_S_IFREG) != 0)\n#endif\n\nint open(const char *filename, int oflag, int pmode);\nint _wstati64(const wchar_t *path, cs_stat_t *st);\nconst char *strerror();\n\n#endif /* CS_PLATFORM == CS_P_WINCE */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_nxp_lpc.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_\n\n#if CS_PLATFORM == CS_P_NXP_LPC\n\n#include <ctype.h>\n#include <stdint.h>\n#include <string.h>\n\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define INT64_FMT \"lld\"\n#define INT64_X_FMT \"llx\"\n#define __cdecl\n\n#define MG_LWIP 1\n\n#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL\n\n/*\n * LPCXpress comes with 3 C library implementations: Newlib, NewlibNano and\n *Redlib.\n * See https://community.nxp.com/message/630860 for more details.\n *\n * Redlib is the default and lacks certain things, so we provide them.\n */\n#ifdef __REDLIB_INTERFACE_VERSION__\n\n/* Let LWIP define timeval for us. */\n#define LWIP_TIMEVAL_PRIVATE 1\n\n#define va_copy(d, s) __builtin_va_copy(d, s)\n\n#define CS_ENABLE_TO64 1\n#define to64(x) cs_to64(x)\n\n#define CS_ENABLE_STRDUP 1\n\n#else\n\n#include <sys/time.h>\n#define LWIP_TIMEVAL_PRIVATE 0\n#define to64(x) strtoll(x, NULL, 10)\n\n#endif\n\n#endif /* CS_PLATFORM == CS_P_NXP_LPC */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_NXP_LPC_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_nxp_kinetis.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_\n\n#if CS_PLATFORM == CS_P_NXP_KINETIS\n\n#include <ctype.h>\n#include <inttypes.h>\n#include <string.h>\n#include <sys/time.h>\n\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT \"lld\"\n#define INT64_X_FMT \"llx\"\n#define __cdecl\n\n#define MG_LWIP 1\n\n#define MG_NET_IF MG_NET_IF_LWIP_LOW_LEVEL\n\n/* struct timeval is defined in sys/time.h. */\n#define LWIP_TIMEVAL_PRIVATE 0\n\n#endif /* CS_PLATFORM == CS_P_NXP_KINETIS */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_NXP_KINETIS_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_pic32.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_\n\n#if CS_PLATFORM == CS_P_PIC32\n\n#define MG_NET_IF MG_NET_IF_PIC32\n\n#include <stdint.h>\n#include <time.h>\n#include <ctype.h>\n#include <stdlib.h>\n\n#include <system_config.h>\n#include <system_definitions.h>\n\n#include <sys/types.h>\n\ntypedef TCP_SOCKET sock_t;\n#define to64(x) strtoll(x, NULL, 10)\n\n#define SIZE_T_FMT \"lu\"\n#define INT64_FMT \"lld\"\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\nchar *inet_ntoa(struct in_addr in);\n\n#endif /* CS_PLATFORM == CS_P_PIC32 */\n\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_PIC32_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_rs14100.h\"\n#endif\n/*\n * Copyright (c) 2014-2019 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_\n#if CS_PLATFORM == CS_P_RS14100\n\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n\n#ifdef MGOS_HAVE_VFS_COMMON\n#include <mgos_vfs.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT \"lld\"\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifndef MG_ENABLE_FILESYSTEM\n#define MG_ENABLE_FILESYSTEM 1\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_PLATFORM == CS_P_RS14100 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_RS14100_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/platform_stm32.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PLATFORM_STM32_H_\n#define CS_COMMON_PLATFORMS_PLATFORM_STM32_H_\n#if CS_PLATFORM == CS_P_STM32\n\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <dirent.h>\n\n#include <stm32_sdk_hal.h>\n\n#define to64(x) strtoll(x, NULL, 10)\n#define INT64_FMT \"lld\"\n#define SIZE_T_FMT \"u\"\ntypedef struct stat cs_stat_t;\n#define DIRSEP '/'\n\n#ifndef CS_ENABLE_STDIO\n#define CS_ENABLE_STDIO 1\n#endif\n\n#ifndef MG_ENABLE_FILESYSTEM\n#define MG_ENABLE_FILESYSTEM 1\n#endif\n\n#endif /* CS_PLATFORM == CS_P_STM32 */\n#endif /* CS_COMMON_PLATFORMS_PLATFORM_STM32_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/lwip/mg_lwip.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_\n#define CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_\n\n#ifndef MG_LWIP\n#define MG_LWIP 0\n#endif\n\n#if MG_LWIP\n\n/*\n * When compiling for nRF5x chips with arm-none-eabi-gcc, it has BYTE_ORDER\n * already defined, so in order to avoid warnings in lwip, we have to undefine\n * it.\n *\n * TODO: Check if in the future versions of nRF5 SDK that changes.\n *       Current version of nRF51 SDK: 0.8.0\n *                          nRF5 SDK:  0.9.0\n */\n#if CS_PLATFORM == CS_P_NRF51 || CS_PLATFORM == CS_P_NRF52\n#undef BYTE_ORDER\n#endif\n\n#include <lwip/opt.h>\n#include <lwip/err.h>\n#include <lwip/ip_addr.h>\n#include <lwip/inet.h>\n#include <lwip/netdb.h>\n#include <lwip/dns.h>\n\n#ifndef LWIP_PROVIDE_ERRNO\n#include <errno.h>\n#endif\n\n#if LWIP_SOCKET\n#include <lwip/sockets.h>\n#else\n/* We really need the definitions from sockets.h. */\n#undef LWIP_SOCKET\n#define LWIP_SOCKET 1\n#include <lwip/sockets.h>\n#undef LWIP_SOCKET\n#define LWIP_SOCKET 0\n#endif\n\n#define INVALID_SOCKET (-1)\n#define SOMAXCONN 10\ntypedef int sock_t;\n\n#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL\nstruct mg_mgr;\nstruct mg_connection;\nvoid mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,\n                                  int interval, int count);\n#endif\n\n/* For older version of LWIP */\n#ifndef ipX_2_ip\n#define ipX_2_ip(x) (x)\n#endif\n\n#endif /* MG_LWIP */\n\n#endif /* CS_COMMON_PLATFORMS_LWIP_MG_LWIP_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_md5.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_MD5_H_\n#define CS_COMMON_MD5_H_\n\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifndef CS_DISABLE_MD5\n#define CS_DISABLE_MD5 0\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef struct {\n  uint32_t buf[4];\n  uint32_t bits[2];\n  unsigned char in[64];\n} cs_md5_ctx;\n\nvoid cs_md5_init(cs_md5_ctx *c);\nvoid cs_md5_update(cs_md5_ctx *c, const unsigned char *data, size_t len);\nvoid cs_md5_final(unsigned char *md, cs_md5_ctx *c);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_MD5_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_sha1.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_SHA1_H_\n#define CS_COMMON_SHA1_H_\n\n#ifndef CS_DISABLE_SHA1\n#define CS_DISABLE_SHA1 0\n#endif\n\n#if !CS_DISABLE_SHA1\n\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef struct {\n  uint32_t state[5];\n  uint32_t count[2];\n  unsigned char buffer[64];\n} cs_sha1_ctx;\n\nvoid cs_sha1_init(cs_sha1_ctx *);\nvoid cs_sha1_update(cs_sha1_ctx *, const unsigned char *data, uint32_t len);\nvoid cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *);\nvoid cs_hmac_sha1(const unsigned char *key, size_t key_len,\n                  const unsigned char *text, size_t text_len,\n                  unsigned char out[20]);\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_DISABLE_SHA1 */\n\n#endif /* CS_COMMON_SHA1_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_time.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_CS_TIME_H_\n#define CS_COMMON_CS_TIME_H_\n\n#include <time.h>\n\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/* Sub-second granularity time(). */\ndouble cs_time(void);\n\n/*\n * Similar to (non-standard) timegm, converts broken-down time into the number\n * of seconds since Unix Epoch.\n */\ndouble cs_timegm(const struct tm *tm);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_CS_TIME_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/mg_str.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_MG_STR_H_\n#define CS_COMMON_MG_STR_H_\n\n#include <stddef.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Describes chunk of memory */\nstruct mg_str {\n  const char *p; /* Memory chunk pointer */\n  size_t len;    /* Memory chunk length */\n};\n\n/*\n * Helper function for creating mg_str struct from plain C string.\n * `NULL` is allowed and becomes `{NULL, 0}`.\n */\nstruct mg_str mg_mk_str(const char *s);\n\n/*\n * Like `mg_mk_str`, but takes string length explicitly.\n */\nstruct mg_str mg_mk_str_n(const char *s, size_t len);\n\n/* Macro for initializing mg_str. */\n#define MG_MK_STR(str_literal) \\\n  { str_literal, sizeof(str_literal) - 1 }\n#define MG_MK_STR_N(str_literal, len) \\\n  { str_literal, len }\n#define MG_NULL_STR \\\n  { NULL, 0 }\n\n/*\n * Cross-platform version of `strcmp()` where where first string is\n * specified by `struct mg_str`.\n */\nint mg_vcmp(const struct mg_str *str2, const char *str1);\n\n/*\n * Cross-platform version of `strncasecmp()` where first string is\n * specified by `struct mg_str`.\n */\nint mg_vcasecmp(const struct mg_str *str2, const char *str1);\n\n/* Creates a copy of s (heap-allocated). */\nstruct mg_str mg_strdup(const struct mg_str s);\n\n/*\n * Creates a copy of s (heap-allocated).\n * Resulting string is NUL-terminated (but NUL is not included in len).\n */\nstruct mg_str mg_strdup_nul(const struct mg_str s);\n\n/*\n * Locates character in a string.\n */\nconst char *mg_strchr(const struct mg_str s, int c);\n\n/*\n * Compare two `mg_str`s; return value is the same as `strcmp`.\n */\nint mg_strcmp(const struct mg_str str1, const struct mg_str str2);\n\n/*\n * Like `mg_strcmp`, but compares at most `n` characters.\n */\nint mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);\n\n/*\n * Free the string (assuming it was heap allocated).\n */\nvoid mg_strfree(struct mg_str *s);\n\n/*\n * Finds the first occurrence of a substring `needle` in the `haystack`.\n */\nconst char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);\n\n/* Strip whitespace at the start and the end of s */\nstruct mg_str mg_strstrip(struct mg_str s);\n\n/* Returns 1 if s starts with the given prefix. */\nint mg_str_starts_with(struct mg_str s, struct mg_str prefix);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_COMMON_MG_STR_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/mbuf.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * Mbufs are mutable/growing memory buffers, like C++ strings.\n * Mbuf can append data to the end of a buffer or insert data into arbitrary\n * position in the middle of a buffer. The buffer grows automatically when\n * needed.\n */\n\n#ifndef CS_COMMON_MBUF_H_\n#define CS_COMMON_MBUF_H_\n\n#include <stdlib.h>\n/* Amalgamated: #include \"common/platform.h\" */\n\n#if defined(__cplusplus)\nextern \"C\" {\n#endif\n\n#ifndef MBUF_SIZE_MULTIPLIER\n#define MBUF_SIZE_MULTIPLIER 1.5\n#endif\n\n#ifndef MBUF_SIZE_MAX_HEADROOM\n#ifdef BUFSIZ\n#define MBUF_SIZE_MAX_HEADROOM BUFSIZ\n#else\n#define MBUF_SIZE_MAX_HEADROOM 1024\n#endif\n#endif\n\n/* Memory buffer descriptor */\nstruct mbuf {\n  char *buf;   /* Buffer pointer */\n  size_t len;  /* Data length. Data is located between offset 0 and len. */\n  size_t size; /* Buffer size allocated by realloc(1). Must be >= len */\n};\n\n/*\n * Initialises an Mbuf.\n * `initial_capacity` specifies the initial capacity of the mbuf.\n */\nvoid mbuf_init(struct mbuf *, size_t initial_capacity);\n\n/* Frees the space allocated for the mbuffer and resets the mbuf structure. */\nvoid mbuf_free(struct mbuf *);\n\n/*\n * Appends data to the Mbuf.\n *\n * Returns the number of bytes appended or 0 if out of memory.\n */\nsize_t mbuf_append(struct mbuf *, const void *data, size_t data_size);\n\n/*\n * Appends data to the Mbuf and frees it (data must be heap-allocated).\n *\n * Returns the number of bytes appended or 0 if out of memory.\n * data is freed irrespective of return value.\n */\nsize_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size);\n\n/*\n * Inserts data at a specified offset in the Mbuf.\n *\n * Existing data will be shifted forwards and the buffer will\n * be grown if necessary.\n * Returns the number of bytes inserted.\n */\nsize_t mbuf_insert(struct mbuf *, size_t, const void *, size_t);\n\n/* Removes `data_size` bytes from the beginning of the buffer. */\nvoid mbuf_remove(struct mbuf *, size_t data_size);\n\n/*\n * Resizes an Mbuf.\n *\n * If `new_size` is smaller than buffer's `len`, the\n * resize is not performed.\n */\nvoid mbuf_resize(struct mbuf *, size_t new_size);\n\n/* Moves the state from one mbuf to the other. */\nvoid mbuf_move(struct mbuf *from, struct mbuf *to);\n\n/* Removes all the data from mbuf (if any). */\nvoid mbuf_clear(struct mbuf *);\n\n/* Shrinks an Mbuf by resizing its `size` to `len`. */\nvoid mbuf_trim(struct mbuf *);\n\n#if defined(__cplusplus)\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_MBUF_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_base64.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_CS_BASE64_H_\n#define CS_COMMON_CS_BASE64_H_\n\n#ifndef DISABLE_BASE64\n#define DISABLE_BASE64 0\n#endif\n\n#if !DISABLE_BASE64\n\n#include <stdio.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef void (*cs_base64_putc_t)(char, void *);\n\nstruct cs_base64_ctx {\n  /* cannot call it putc because it's a macro on some environments */\n  cs_base64_putc_t b64_putc;\n  unsigned char chunk[3];\n  int chunk_size;\n  void *user_data;\n};\n\nvoid cs_base64_init(struct cs_base64_ctx *ctx, cs_base64_putc_t putc,\n                    void *user_data);\nvoid cs_base64_update(struct cs_base64_ctx *ctx, const char *str, size_t len);\nvoid cs_base64_finish(struct cs_base64_ctx *ctx);\n\nvoid cs_base64_encode(const unsigned char *src, int src_len, char *dst);\nvoid cs_fprint_base64(FILE *f, const unsigned char *src, int src_len);\n\n/*\n * Decodes a base64 string `s` length `len` into `dst`.\n * `dst` must have enough space to hold the result.\n * `*dec_len` will contain the resulting length of the string in `dst`\n * while return value will return number of processed bytes in `src`.\n * Return value == len indicates successful processing of all the data.\n */\nint cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* DISABLE_BASE64 */\n\n#endif /* CS_COMMON_CS_BASE64_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/str_util.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_STR_UTIL_H_\n#define CS_COMMON_STR_UTIL_H_\n\n#include <stdarg.h>\n#include <stdlib.h>\n\n/* Amalgamated: #include \"common/mg_str.h\" */\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifndef CS_ENABLE_STRDUP\n#define CS_ENABLE_STRDUP 0\n#endif\n\n#ifndef CS_ENABLE_TO64\n#define CS_ENABLE_TO64 0\n#endif\n\n/*\n * Expands to a string representation of its argument: e.g.\n * `CS_STRINGIFY_LIT(5) expands to \"5\"`\n */\n#if !defined(_MSC_VER) || _MSC_VER >= 1900\n#define CS_STRINGIFY_LIT(...) #__VA_ARGS__\n#else\n#define CS_STRINGIFY_LIT(x) #x\n#endif\n\n/*\n * Expands to a string representation of its argument, which is allowed\n * to be a macro: e.g.\n *\n * #define FOO 123\n * CS_STRINGIFY_MACRO(FOO)\n *\n * expands to 123.\n */\n#define CS_STRINGIFY_MACRO(x) CS_STRINGIFY_LIT(x)\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n * Equivalent of standard `strnlen()`.\n */\nsize_t c_strnlen(const char *s, size_t maxlen);\n\n/*\n * Equivalent of standard `snprintf()`.\n */\nint c_snprintf(char *buf, size_t buf_size, const char *format, ...)\n    PRINTF_LIKE(3, 4);\n\n/*\n * Equivalent of standard `vsnprintf()`.\n */\nint c_vsnprintf(char *buf, size_t buf_size, const char *format, va_list ap);\n\n/*\n * Find the first occurrence of find in s, where the search is limited to the\n * first slen characters of s.\n */\nconst char *c_strnstr(const char *s, const char *find, size_t slen);\n\n/*\n * Stringify binary data. Output buffer size must be 2 * size_of_input + 1\n * because each byte of input takes 2 bytes in string representation\n * plus 1 byte for the terminating \\0 character.\n */\nvoid cs_to_hex(char *to, const unsigned char *p, size_t len);\n\n/*\n * Convert stringified binary data back to binary.\n * Does the reverse of `cs_to_hex()`.\n */\nvoid cs_from_hex(char *to, const char *p, size_t len);\n\n#if CS_ENABLE_STRDUP\n/*\n * Equivalent of standard `strdup()`, defined if only `CS_ENABLE_STRDUP` is 1.\n */\nchar *strdup(const char *src);\n#endif\n\n#if CS_ENABLE_TO64\n#include <stdint.h>\n/*\n * Simple string -> int64 conversion routine.\n */\nint64_t cs_to64(const char *s);\n#endif\n\n/*\n * Cross-platform version of `strncasecmp()`.\n */\nint mg_ncasecmp(const char *s1, const char *s2, size_t len);\n\n/*\n * Cross-platform version of `strcasecmp()`.\n */\nint mg_casecmp(const char *s1, const char *s2);\n\n/*\n * Prints message to the buffer. If the buffer is large enough to hold the\n * message, it returns buffer. If buffer is to small, it allocates a large\n * enough buffer on heap and returns allocated buffer.\n * This is a supposed use case:\n *\n * ```c\n *    char buf[5], *p = buf;\n *    mg_avprintf(&p, sizeof(buf), \"%s\", \"hi there\");\n *    use_p_somehow(p);\n *    if (p != buf) {\n *      free(p);\n *    }\n * ```\n *\n * The purpose of this is to avoid malloc-ing if generated strings are small.\n */\nint mg_asprintf(char **buf, size_t size, const char *fmt, ...)\n    PRINTF_LIKE(3, 4);\n\n/* Same as mg_asprintf, but takes varargs list. */\nint mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);\n\n/*\n * A helper function for traversing a comma separated list of values.\n * It returns a list pointer shifted to the next value or NULL if the end\n * of the list found.\n * The value is stored in a val vector. If the value has a form \"x=y\", then\n * eq_val vector is initialised to point to the \"y\" part, and val vector length\n * is adjusted to point only to \"x\".\n * If the list is just a comma separated list of entries, like \"aa,bb,cc\" then\n * `eq_val` will contain zero-length string.\n *\n * The purpose of this function is to parse comma separated string without\n * any copying/memory allocation.\n */\nconst char *mg_next_comma_list_entry(const char *list, struct mg_str *val,\n                                     struct mg_str *eq_val);\n\n/*\n * Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`.\n * NB: Test return value's .p, not .len. On last itreation that yields result\n * .len will be 0 but .p will not. When finished, .p will be NULL.\n */\nstruct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,\n                                         struct mg_str *eq_val);\n\n/*\n * Matches 0-terminated string (mg_match_prefix) or string with given length\n * mg_match_prefix_n against a glob pattern. Glob syntax:\n * ```\n * - * matches zero or more characters until a slash character /\n * - ** matches zero or more characters\n * - ? Matches exactly one character which is not a slash /\n * - | or ,  divides alternative patterns\n * - any other character matches itself\n * ```\n * Match is case-insensitive. Return number of bytes matched.\n * Examples:\n * ```\n * mg_match_prefix(\"a*f\", len, \"abcdefgh\") == 6\n * mg_match_prefix(\"a*f\", len, \"abcdexgh\") == 0\n * mg_match_prefix(\"a*f|de*,xy\", len, \"defgh\") == 5\n * mg_match_prefix(\"?*\", len, \"abc\") == 3\n * mg_match_prefix(\"?*\", len, \"\") == 0\n * ```\n */\nsize_t mg_match_prefix(const char *pattern, int pattern_len, const char *str);\n\n/*\n * Like `mg_match_prefix()`, but takes `pattern` and `str` as `struct mg_str`.\n */\nsize_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_COMMON_STR_UTIL_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/queue.h\"\n#endif\n/* clang-format off */\n/*-\n * Copyright (c) 1991, 1993\n *\tThe Regents of the University of California.  All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions\n * are met:\n * 1. Redistributions of source code must retain the above copyright\n *    notice, this list of conditions and the following disclaimer.\n * 2. Redistributions in binary form must reproduce the above copyright\n *    notice, this list of conditions and the following disclaimer in the\n *    documentation and/or other materials provided with the distribution.\n * 4. Neither the name of the University nor the names of its contributors\n *    may be used to endorse or promote products derived from this software\n *    without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE\n * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\n * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS\n * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY\n * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n * SUCH DAMAGE.\n *\n *\t@(#)queue.h\t8.5 (Berkeley) 8/20/94\n * $FreeBSD$\n */\n\n#ifndef _SYS_QUEUE_H_\n#define\t_SYS_QUEUE_H_\n\n/*\n * This file defines four types of data structures: singly-linked lists,\n * singly-linked tail queues, lists and tail queues.\n *\n * A singly-linked list is headed by a single forward pointer. The elements\n * are singly linked for minimum space and pointer manipulation overhead at\n * the expense of O(n) removal for arbitrary elements. New elements can be\n * added to the list after an existing element or at the head of the list.\n * Elements being removed from the head of the list should use the explicit\n * macro for this purpose for optimum efficiency. A singly-linked list may\n * only be traversed in the forward direction.  Singly-linked lists are ideal\n * for applications with large datasets and few or no removals or for\n * implementing a LIFO queue.\n *\n * A singly-linked tail queue is headed by a pair of pointers, one to the\n * head of the list and the other to the tail of the list. The elements are\n * singly linked for minimum space and pointer manipulation overhead at the\n * expense of O(n) removal for arbitrary elements. New elements can be added\n * to the list after an existing element, at the head of the list, or at the\n * end of the list. Elements being removed from the head of the tail queue\n * should use the explicit macro for this purpose for optimum efficiency.\n * A singly-linked tail queue may only be traversed in the forward direction.\n * Singly-linked tail queues are ideal for applications with large datasets\n * and few or no removals or for implementing a FIFO queue.\n *\n * A list is headed by a single forward pointer (or an array of forward\n * pointers for a hash table header). The elements are doubly linked\n * so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before\n * or after an existing element or at the head of the list. A list\n * may be traversed in either direction.\n *\n * A tail queue is headed by a pair of pointers, one to the head of the\n * list and the other to the tail of the list. The elements are doubly\n * linked so that an arbitrary element can be removed without a need to\n * traverse the list. New elements can be added to the list before or\n * after an existing element, at the head of the list, or at the end of\n * the list. A tail queue may be traversed in either direction.\n *\n * For details on the use of these macros, see the queue(3) manual page.\n *\n *\n *\t\t\t\tSLIST\tLIST\tSTAILQ\tTAILQ\n * _HEAD\t\t\t+\t+\t+\t+\n * _CLASS_HEAD\t\t\t+\t+\t+\t+\n * _HEAD_INITIALIZER\t\t+\t+\t+\t+\n * _ENTRY\t\t\t+\t+\t+\t+\n * _CLASS_ENTRY\t\t\t+\t+\t+\t+\n * _INIT\t\t\t+\t+\t+\t+\n * _EMPTY\t\t\t+\t+\t+\t+\n * _FIRST\t\t\t+\t+\t+\t+\n * _NEXT\t\t\t+\t+\t+\t+\n * _PREV\t\t\t-\t+\t-\t+\n * _LAST\t\t\t-\t-\t+\t+\n * _FOREACH\t\t\t+\t+\t+\t+\n * _FOREACH_FROM\t\t+\t+\t+\t+\n * _FOREACH_SAFE\t\t+\t+\t+\t+\n * _FOREACH_FROM_SAFE\t\t+\t+\t+\t+\n * _FOREACH_REVERSE\t\t-\t-\t-\t+\n * _FOREACH_REVERSE_FROM\t-\t-\t-\t+\n * _FOREACH_REVERSE_SAFE\t-\t-\t-\t+\n * _FOREACH_REVERSE_FROM_SAFE\t-\t-\t-\t+\n * _INSERT_HEAD\t\t\t+\t+\t+\t+\n * _INSERT_BEFORE\t\t-\t+\t-\t+\n * _INSERT_AFTER\t\t+\t+\t+\t+\n * _INSERT_TAIL\t\t\t-\t-\t+\t+\n * _CONCAT\t\t\t-\t-\t+\t+\n * _REMOVE_AFTER\t\t+\t-\t+\t-\n * _REMOVE_HEAD\t\t\t+\t-\t+\t-\n * _REMOVE\t\t\t+\t+\t+\t+\n * _SWAP\t\t\t+\t+\t+\t+\n *\n */\n#ifdef QUEUE_MACRO_DEBUG\n/* Store the last 2 places the queue element or head was altered */\nstruct qm_trace {\n\tunsigned long\t lastline;\n\tunsigned long\t prevline;\n\tconst char\t*lastfile;\n\tconst char\t*prevfile;\n};\n\n#define\tTRACEBUF\tstruct qm_trace trace;\n#define\tTRACEBUF_INITIALIZER\t{ __LINE__, 0, __FILE__, NULL } ,\n#define\tTRASHIT(x)\tdo {(x) = (void *)-1;} while (0)\n#define\tQMD_SAVELINK(name, link)\tvoid **name = (void *)&(link)\n\n#define\tQMD_TRACE_HEAD(head) do {\t\t\t\t\t\\\n\t(head)->trace.prevline = (head)->trace.lastline;\t\t\\\n\t(head)->trace.prevfile = (head)->trace.lastfile;\t\t\\\n\t(head)->trace.lastline = __LINE__;\t\t\t\t\\\n\t(head)->trace.lastfile = __FILE__;\t\t\t\t\\\n} while (0)\n\n#define\tQMD_TRACE_ELEM(elem) do {\t\t\t\t\t\\\n\t(elem)->trace.prevline = (elem)->trace.lastline;\t\t\\\n\t(elem)->trace.prevfile = (elem)->trace.lastfile;\t\t\\\n\t(elem)->trace.lastline = __LINE__;\t\t\t\t\\\n\t(elem)->trace.lastfile = __FILE__;\t\t\t\t\\\n} while (0)\n\n#else\n#define\tQMD_TRACE_ELEM(elem)\n#define\tQMD_TRACE_HEAD(head)\n#define\tQMD_SAVELINK(name, link)\n#define\tTRACEBUF\n#define\tTRACEBUF_INITIALIZER\n#define\tTRASHIT(x)\n#endif\t/* QUEUE_MACRO_DEBUG */\n\n#ifdef __cplusplus\n/*\n * In C++ there can be structure lists and class lists:\n */\n#define\tQUEUE_TYPEOF(type) type\n#else\n#define\tQUEUE_TYPEOF(type) struct type\n#endif\n\n/*\n * Singly-linked List declarations.\n */\n#define\tSLIST_HEAD(name, type)\t\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tstruct type *slh_first;\t/* first element */\t\t\t\\\n}\n\n#define\tSLIST_CLASS_HEAD(name, type)\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tclass type *slh_first;\t/* first element */\t\t\t\\\n}\n\n#define\tSLIST_HEAD_INITIALIZER(head)\t\t\t\t\t\\\n\t{ NULL }\n\n#define\tSLIST_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tstruct type *sle_next;\t/* next element */\t\t\t\\\n}\n\n#define\tSLIST_CLASS_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tclass type *sle_next;\t\t/* next element */\t\t\\\n}\n\n/*\n * Singly-linked List functions.\n */\n#define\tSLIST_EMPTY(head)\t((head)->slh_first == NULL)\n\n#define\tSLIST_FIRST(head)\t((head)->slh_first)\n\n#define\tSLIST_FOREACH(var, head, field)\t\t\t\t\t\\\n\tfor ((var) = SLIST_FIRST((head));\t\t\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = SLIST_NEXT((var), field))\n\n#define\tSLIST_FOREACH_FROM(var, head, field)\t\t\t\t\\\n\tfor ((var) = ((var) ? (var) : SLIST_FIRST((head)));\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = SLIST_NEXT((var), field))\n\n#define\tSLIST_FOREACH_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = SLIST_FIRST((head));\t\t\t\t\\\n\t    (var) && ((tvar) = SLIST_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tSLIST_FOREACH_FROM_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = ((var) ? (var) : SLIST_FIRST((head)));\t\t\\\n\t    (var) && ((tvar) = SLIST_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tSLIST_FOREACH_PREVPTR(var, varp, head, field)\t\t\t\\\n\tfor ((varp) = &SLIST_FIRST((head));\t\t\t\t\\\n\t    ((var) = *(varp)) != NULL;\t\t\t\t\t\\\n\t    (varp) = &SLIST_NEXT((var), field))\n\n#define\tSLIST_INIT(head) do {\t\t\t\t\t\t\\\n\tSLIST_FIRST((head)) = NULL;\t\t\t\t\t\\\n} while (0)\n\n#define\tSLIST_INSERT_AFTER(slistelm, elm, field) do {\t\t\t\\\n\tSLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field);\t\\\n\tSLIST_NEXT((slistelm), field) = (elm);\t\t\t\t\\\n} while (0)\n\n#define\tSLIST_INSERT_HEAD(head, elm, field) do {\t\t\t\\\n\tSLIST_NEXT((elm), field) = SLIST_FIRST((head));\t\t\t\\\n\tSLIST_FIRST((head)) = (elm);\t\t\t\t\t\\\n} while (0)\n\n#define\tSLIST_NEXT(elm, field)\t((elm)->field.sle_next)\n\n#define\tSLIST_REMOVE(head, elm, type, field) do {\t\t\t\\\n\tQMD_SAVELINK(oldnext, (elm)->field.sle_next);\t\t\t\\\n\tif (SLIST_FIRST((head)) == (elm)) {\t\t\t\t\\\n\t\tSLIST_REMOVE_HEAD((head), field);\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\telse {\t\t\t\t\t\t\t\t\\\n\t\tQUEUE_TYPEOF(type) *curelm = SLIST_FIRST(head);\t\t\\\n\t\twhile (SLIST_NEXT(curelm, field) != (elm))\t\t\\\n\t\t\tcurelm = SLIST_NEXT(curelm, field);\t\t\\\n\t\tSLIST_REMOVE_AFTER(curelm, field);\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tTRASHIT(*oldnext);\t\t\t\t\t\t\\\n} while (0)\n\n#define SLIST_REMOVE_AFTER(elm, field) do {\t\t\t\t\\\n\tSLIST_NEXT(elm, field) =\t\t\t\t\t\\\n\t    SLIST_NEXT(SLIST_NEXT(elm, field), field);\t\t\t\\\n} while (0)\n\n#define\tSLIST_REMOVE_HEAD(head, field) do {\t\t\t\t\\\n\tSLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field);\t\\\n} while (0)\n\n#define SLIST_SWAP(head1, head2, type) do {\t\t\t\t\\\n\tQUEUE_TYPEOF(type) *swap_first = SLIST_FIRST(head1);\t\t\\\n\tSLIST_FIRST(head1) = SLIST_FIRST(head2);\t\t\t\\\n\tSLIST_FIRST(head2) = swap_first;\t\t\t\t\\\n} while (0)\n\n/*\n * Singly-linked Tail queue declarations.\n */\n#define\tSTAILQ_HEAD(name, type)\t\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tstruct type *stqh_first;/* first element */\t\t\t\\\n\tstruct type **stqh_last;/* addr of last next element */\t\t\\\n}\n\n#define\tSTAILQ_CLASS_HEAD(name, type)\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tclass type *stqh_first;\t/* first element */\t\t\t\\\n\tclass type **stqh_last;\t/* addr of last next element */\t\t\\\n}\n\n#define\tSTAILQ_HEAD_INITIALIZER(head)\t\t\t\t\t\\\n\t{ NULL, &(head).stqh_first }\n\n#define\tSTAILQ_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tstruct type *stqe_next;\t/* next element */\t\t\t\\\n}\n\n#define\tSTAILQ_CLASS_ENTRY(type)\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tclass type *stqe_next;\t/* next element */\t\t\t\\\n}\n\n/*\n * Singly-linked Tail queue functions.\n */\n#define\tSTAILQ_CONCAT(head1, head2) do {\t\t\t\t\\\n\tif (!STAILQ_EMPTY((head2))) {\t\t\t\t\t\\\n\t\t*(head1)->stqh_last = (head2)->stqh_first;\t\t\\\n\t\t(head1)->stqh_last = (head2)->stqh_last;\t\t\\\n\t\tSTAILQ_INIT((head2));\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n} while (0)\n\n#define\tSTAILQ_EMPTY(head)\t((head)->stqh_first == NULL)\n\n#define\tSTAILQ_FIRST(head)\t((head)->stqh_first)\n\n#define\tSTAILQ_FOREACH(var, head, field)\t\t\t\t\\\n\tfor((var) = STAILQ_FIRST((head));\t\t\t\t\\\n\t   (var);\t\t\t\t\t\t\t\\\n\t   (var) = STAILQ_NEXT((var), field))\n\n#define\tSTAILQ_FOREACH_FROM(var, head, field)\t\t\t\t\\\n\tfor ((var) = ((var) ? (var) : STAILQ_FIRST((head)));\t\t\\\n\t   (var);\t\t\t\t\t\t\t\\\n\t   (var) = STAILQ_NEXT((var), field))\n\n#define\tSTAILQ_FOREACH_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = STAILQ_FIRST((head));\t\t\t\t\\\n\t    (var) && ((tvar) = STAILQ_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tSTAILQ_FOREACH_FROM_SAFE(var, head, field, tvar)\t\t\\\n\tfor ((var) = ((var) ? (var) : STAILQ_FIRST((head)));\t\t\\\n\t    (var) && ((tvar) = STAILQ_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tSTAILQ_INIT(head) do {\t\t\t\t\t\t\\\n\tSTAILQ_FIRST((head)) = NULL;\t\t\t\t\t\\\n\t(head)->stqh_last = &STAILQ_FIRST((head));\t\t\t\\\n} while (0)\n\n#define\tSTAILQ_INSERT_AFTER(head, tqelm, elm, field) do {\t\t\\\n\tif ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\\\n\t\t(head)->stqh_last = &STAILQ_NEXT((elm), field);\t\t\\\n\tSTAILQ_NEXT((tqelm), field) = (elm);\t\t\t\t\\\n} while (0)\n\n#define\tSTAILQ_INSERT_HEAD(head, elm, field) do {\t\t\t\\\n\tif ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL)\t\\\n\t\t(head)->stqh_last = &STAILQ_NEXT((elm), field);\t\t\\\n\tSTAILQ_FIRST((head)) = (elm);\t\t\t\t\t\\\n} while (0)\n\n#define\tSTAILQ_INSERT_TAIL(head, elm, field) do {\t\t\t\\\n\tSTAILQ_NEXT((elm), field) = NULL;\t\t\t\t\\\n\t*(head)->stqh_last = (elm);\t\t\t\t\t\\\n\t(head)->stqh_last = &STAILQ_NEXT((elm), field);\t\t\t\\\n} while (0)\n\n#define\tSTAILQ_LAST(head, type, field)\t\t\t\t\\\n\t(STAILQ_EMPTY((head)) ? NULL :\t\t\t\t\\\n\t    __containerof((head)->stqh_last,\t\t\t\\\n\t    QUEUE_TYPEOF(type), field.stqe_next))\n\n#define\tSTAILQ_NEXT(elm, field)\t((elm)->field.stqe_next)\n\n#define\tSTAILQ_REMOVE(head, elm, type, field) do {\t\t\t\\\n\tQMD_SAVELINK(oldnext, (elm)->field.stqe_next);\t\t\t\\\n\tif (STAILQ_FIRST((head)) == (elm)) {\t\t\t\t\\\n\t\tSTAILQ_REMOVE_HEAD((head), field);\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\telse {\t\t\t\t\t\t\t\t\\\n\t\tQUEUE_TYPEOF(type) *curelm = STAILQ_FIRST(head);\t\\\n\t\twhile (STAILQ_NEXT(curelm, field) != (elm))\t\t\\\n\t\t\tcurelm = STAILQ_NEXT(curelm, field);\t\t\\\n\t\tSTAILQ_REMOVE_AFTER(head, curelm, field);\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tTRASHIT(*oldnext);\t\t\t\t\t\t\\\n} while (0)\n\n#define STAILQ_REMOVE_AFTER(head, elm, field) do {\t\t\t\\\n\tif ((STAILQ_NEXT(elm, field) =\t\t\t\t\t\\\n\t     STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL)\t\\\n\t\t(head)->stqh_last = &STAILQ_NEXT((elm), field);\t\t\\\n} while (0)\n\n#define\tSTAILQ_REMOVE_HEAD(head, field) do {\t\t\t\t\\\n\tif ((STAILQ_FIRST((head)) =\t\t\t\t\t\\\n\t     STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL)\t\t\\\n\t\t(head)->stqh_last = &STAILQ_FIRST((head));\t\t\\\n} while (0)\n\n#define STAILQ_SWAP(head1, head2, type) do {\t\t\t\t\\\n\tQUEUE_TYPEOF(type) *swap_first = STAILQ_FIRST(head1);\t\t\\\n\tQUEUE_TYPEOF(type) **swap_last = (head1)->stqh_last;\t\t\\\n\tSTAILQ_FIRST(head1) = STAILQ_FIRST(head2);\t\t\t\\\n\t(head1)->stqh_last = (head2)->stqh_last;\t\t\t\\\n\tSTAILQ_FIRST(head2) = swap_first;\t\t\t\t\\\n\t(head2)->stqh_last = swap_last;\t\t\t\t\t\\\n\tif (STAILQ_EMPTY(head1))\t\t\t\t\t\\\n\t\t(head1)->stqh_last = &STAILQ_FIRST(head1);\t\t\\\n\tif (STAILQ_EMPTY(head2))\t\t\t\t\t\\\n\t\t(head2)->stqh_last = &STAILQ_FIRST(head2);\t\t\\\n} while (0)\n\n\n/*\n * List declarations.\n */\n#define\tLIST_HEAD(name, type)\t\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tstruct type *lh_first;\t/* first element */\t\t\t\\\n}\n\n#define\tLIST_CLASS_HEAD(name, type)\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tclass type *lh_first;\t/* first element */\t\t\t\\\n}\n\n#define\tLIST_HEAD_INITIALIZER(head)\t\t\t\t\t\\\n\t{ NULL }\n\n#define\tLIST_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tstruct type *le_next;\t/* next element */\t\t\t\\\n\tstruct type **le_prev;\t/* address of previous next element */\t\\\n}\n\n#define\tLIST_CLASS_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tclass type *le_next;\t/* next element */\t\t\t\\\n\tclass type **le_prev;\t/* address of previous next element */\t\\\n}\n\n/*\n * List functions.\n */\n\n#if (defined(_KERNEL) && defined(INVARIANTS))\n#define\tQMD_LIST_CHECK_HEAD(head, field) do {\t\t\t\t\\\n\tif (LIST_FIRST((head)) != NULL &&\t\t\t\t\\\n\t    LIST_FIRST((head))->field.le_prev !=\t\t\t\\\n\t     &LIST_FIRST((head)))\t\t\t\t\t\\\n\t\tpanic(\"Bad list head %p first->prev != head\", (head));\t\\\n} while (0)\n\n#define\tQMD_LIST_CHECK_NEXT(elm, field) do {\t\t\t\t\\\n\tif (LIST_NEXT((elm), field) != NULL &&\t\t\t\t\\\n\t    LIST_NEXT((elm), field)->field.le_prev !=\t\t\t\\\n\t     &((elm)->field.le_next))\t\t\t\t\t\\\n\t     \tpanic(\"Bad link elm %p next->prev != elm\", (elm));\t\\\n} while (0)\n\n#define\tQMD_LIST_CHECK_PREV(elm, field) do {\t\t\t\t\\\n\tif (*(elm)->field.le_prev != (elm))\t\t\t\t\\\n\t\tpanic(\"Bad link elm %p prev->next != elm\", (elm));\t\\\n} while (0)\n#else\n#define\tQMD_LIST_CHECK_HEAD(head, field)\n#define\tQMD_LIST_CHECK_NEXT(elm, field)\n#define\tQMD_LIST_CHECK_PREV(elm, field)\n#endif /* (_KERNEL && INVARIANTS) */\n\n#define\tLIST_EMPTY(head)\t((head)->lh_first == NULL)\n\n#define\tLIST_FIRST(head)\t((head)->lh_first)\n\n#define\tLIST_FOREACH(var, head, field)\t\t\t\t\t\\\n\tfor ((var) = LIST_FIRST((head));\t\t\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = LIST_NEXT((var), field))\n\n#define\tLIST_FOREACH_FROM(var, head, field)\t\t\t\t\\\n\tfor ((var) = ((var) ? (var) : LIST_FIRST((head)));\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = LIST_NEXT((var), field))\n\n#define\tLIST_FOREACH_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = LIST_FIRST((head));\t\t\t\t\\\n\t    (var) && ((tvar) = LIST_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tLIST_FOREACH_FROM_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = ((var) ? (var) : LIST_FIRST((head)));\t\t\\\n\t    (var) && ((tvar) = LIST_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tLIST_INIT(head) do {\t\t\t\t\t\t\\\n\tLIST_FIRST((head)) = NULL;\t\t\t\t\t\\\n} while (0)\n\n#define\tLIST_INSERT_AFTER(listelm, elm, field) do {\t\t\t\\\n\tQMD_LIST_CHECK_NEXT(listelm, field);\t\t\t\t\\\n\tif ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\\\n\t\tLIST_NEXT((listelm), field)->field.le_prev =\t\t\\\n\t\t    &LIST_NEXT((elm), field);\t\t\t\t\\\n\tLIST_NEXT((listelm), field) = (elm);\t\t\t\t\\\n\t(elm)->field.le_prev = &LIST_NEXT((listelm), field);\t\t\\\n} while (0)\n\n#define\tLIST_INSERT_BEFORE(listelm, elm, field) do {\t\t\t\\\n\tQMD_LIST_CHECK_PREV(listelm, field);\t\t\t\t\\\n\t(elm)->field.le_prev = (listelm)->field.le_prev;\t\t\\\n\tLIST_NEXT((elm), field) = (listelm);\t\t\t\t\\\n\t*(listelm)->field.le_prev = (elm);\t\t\t\t\\\n\t(listelm)->field.le_prev = &LIST_NEXT((elm), field);\t\t\\\n} while (0)\n\n#define\tLIST_INSERT_HEAD(head, elm, field) do {\t\t\t\t\\\n\tQMD_LIST_CHECK_HEAD((head), field);\t\t\t\t\\\n\tif ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL)\t\\\n\t\tLIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\\\n\tLIST_FIRST((head)) = (elm);\t\t\t\t\t\\\n\t(elm)->field.le_prev = &LIST_FIRST((head));\t\t\t\\\n} while (0)\n\n#define\tLIST_NEXT(elm, field)\t((elm)->field.le_next)\n\n#define\tLIST_PREV(elm, head, type, field)\t\t\t\\\n\t((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL :\t\\\n\t    __containerof((elm)->field.le_prev,\t\t\t\\\n\t    QUEUE_TYPEOF(type), field.le_next))\n\n#define\tLIST_REMOVE(elm, field) do {\t\t\t\t\t\\\n\tQMD_SAVELINK(oldnext, (elm)->field.le_next);\t\t\t\\\n\tQMD_SAVELINK(oldprev, (elm)->field.le_prev);\t\t\t\\\n\tQMD_LIST_CHECK_NEXT(elm, field);\t\t\t\t\\\n\tQMD_LIST_CHECK_PREV(elm, field);\t\t\t\t\\\n\tif (LIST_NEXT((elm), field) != NULL)\t\t\t\t\\\n\t\tLIST_NEXT((elm), field)->field.le_prev = \t\t\\\n\t\t    (elm)->field.le_prev;\t\t\t\t\\\n\t*(elm)->field.le_prev = LIST_NEXT((elm), field);\t\t\\\n\tTRASHIT(*oldnext);\t\t\t\t\t\t\\\n\tTRASHIT(*oldprev);\t\t\t\t\t\t\\\n} while (0)\n\n#define LIST_SWAP(head1, head2, type, field) do {\t\t\t\\\n\tQUEUE_TYPEOF(type) *swap_tmp = LIST_FIRST(head1);\t\t\\\n\tLIST_FIRST((head1)) = LIST_FIRST((head2));\t\t\t\\\n\tLIST_FIRST((head2)) = swap_tmp;\t\t\t\t\t\\\n\tif ((swap_tmp = LIST_FIRST((head1))) != NULL)\t\t\t\\\n\t\tswap_tmp->field.le_prev = &LIST_FIRST((head1));\t\t\\\n\tif ((swap_tmp = LIST_FIRST((head2))) != NULL)\t\t\t\\\n\t\tswap_tmp->field.le_prev = &LIST_FIRST((head2));\t\t\\\n} while (0)\n\n/*\n * Tail queue declarations.\n */\n#define\tTAILQ_HEAD(name, type)\t\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tstruct type *tqh_first;\t/* first element */\t\t\t\\\n\tstruct type **tqh_last;\t/* addr of last next element */\t\t\\\n\tTRACEBUF\t\t\t\t\t\t\t\\\n}\n\n#define\tTAILQ_CLASS_HEAD(name, type)\t\t\t\t\t\\\nstruct name {\t\t\t\t\t\t\t\t\\\n\tclass type *tqh_first;\t/* first element */\t\t\t\\\n\tclass type **tqh_last;\t/* addr of last next element */\t\t\\\n\tTRACEBUF\t\t\t\t\t\t\t\\\n}\n\n#define\tTAILQ_HEAD_INITIALIZER(head)\t\t\t\t\t\\\n\t{ NULL, &(head).tqh_first, TRACEBUF_INITIALIZER }\n\n#define\tTAILQ_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tstruct type *tqe_next;\t/* next element */\t\t\t\\\n\tstruct type **tqe_prev;\t/* address of previous next element */\t\\\n\tTRACEBUF\t\t\t\t\t\t\t\\\n}\n\n#define\tTAILQ_CLASS_ENTRY(type)\t\t\t\t\t\t\\\nstruct {\t\t\t\t\t\t\t\t\\\n\tclass type *tqe_next;\t/* next element */\t\t\t\\\n\tclass type **tqe_prev;\t/* address of previous next element */\t\\\n\tTRACEBUF\t\t\t\t\t\t\t\\\n}\n\n/*\n * Tail queue functions.\n */\n#if (defined(_KERNEL) && defined(INVARIANTS))\n#define\tQMD_TAILQ_CHECK_HEAD(head, field) do {\t\t\t\t\\\n\tif (!TAILQ_EMPTY(head) &&\t\t\t\t\t\\\n\t    TAILQ_FIRST((head))->field.tqe_prev !=\t\t\t\\\n\t     &TAILQ_FIRST((head)))\t\t\t\t\t\\\n\t\tpanic(\"Bad tailq head %p first->prev != head\", (head));\t\\\n} while (0)\n\n#define\tQMD_TAILQ_CHECK_TAIL(head, field) do {\t\t\t\t\\\n\tif (*(head)->tqh_last != NULL)\t\t\t\t\t\\\n\t    \tpanic(\"Bad tailq NEXT(%p->tqh_last) != NULL\", (head)); \t\\\n} while (0)\n\n#define\tQMD_TAILQ_CHECK_NEXT(elm, field) do {\t\t\t\t\\\n\tif (TAILQ_NEXT((elm), field) != NULL &&\t\t\t\t\\\n\t    TAILQ_NEXT((elm), field)->field.tqe_prev !=\t\t\t\\\n\t     &((elm)->field.tqe_next))\t\t\t\t\t\\\n\t\tpanic(\"Bad link elm %p next->prev != elm\", (elm));\t\\\n} while (0)\n\n#define\tQMD_TAILQ_CHECK_PREV(elm, field) do {\t\t\t\t\\\n\tif (*(elm)->field.tqe_prev != (elm))\t\t\t\t\\\n\t\tpanic(\"Bad link elm %p prev->next != elm\", (elm));\t\\\n} while (0)\n#else\n#define\tQMD_TAILQ_CHECK_HEAD(head, field)\n#define\tQMD_TAILQ_CHECK_TAIL(head, headname)\n#define\tQMD_TAILQ_CHECK_NEXT(elm, field)\n#define\tQMD_TAILQ_CHECK_PREV(elm, field)\n#endif /* (_KERNEL && INVARIANTS) */\n\n#define\tTAILQ_CONCAT(head1, head2, field) do {\t\t\t\t\\\n\tif (!TAILQ_EMPTY(head2)) {\t\t\t\t\t\\\n\t\t*(head1)->tqh_last = (head2)->tqh_first;\t\t\\\n\t\t(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last;\t\\\n\t\t(head1)->tqh_last = (head2)->tqh_last;\t\t\t\\\n\t\tTAILQ_INIT((head2));\t\t\t\t\t\\\n\t\tQMD_TRACE_HEAD(head1);\t\t\t\t\t\\\n\t\tQMD_TRACE_HEAD(head2);\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n} while (0)\n\n#define\tTAILQ_EMPTY(head)\t((head)->tqh_first == NULL)\n\n#define\tTAILQ_FIRST(head)\t((head)->tqh_first)\n\n#define\tTAILQ_FOREACH(var, head, field)\t\t\t\t\t\\\n\tfor ((var) = TAILQ_FIRST((head));\t\t\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = TAILQ_NEXT((var), field))\n\n#define\tTAILQ_FOREACH_FROM(var, head, field)\t\t\t\t\\\n\tfor ((var) = ((var) ? (var) : TAILQ_FIRST((head)));\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = TAILQ_NEXT((var), field))\n\n#define\tTAILQ_FOREACH_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = TAILQ_FIRST((head));\t\t\t\t\\\n\t    (var) && ((tvar) = TAILQ_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tTAILQ_FOREACH_FROM_SAFE(var, head, field, tvar)\t\t\t\\\n\tfor ((var) = ((var) ? (var) : TAILQ_FIRST((head)));\t\t\\\n\t    (var) && ((tvar) = TAILQ_NEXT((var), field), 1);\t\t\\\n\t    (var) = (tvar))\n\n#define\tTAILQ_FOREACH_REVERSE(var, head, headname, field)\t\t\\\n\tfor ((var) = TAILQ_LAST((head), headname);\t\t\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = TAILQ_PREV((var), headname, field))\n\n#define\tTAILQ_FOREACH_REVERSE_FROM(var, head, headname, field)\t\t\\\n\tfor ((var) = ((var) ? (var) : TAILQ_LAST((head), headname));\t\\\n\t    (var);\t\t\t\t\t\t\t\\\n\t    (var) = TAILQ_PREV((var), headname, field))\n\n#define\tTAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)\t\\\n\tfor ((var) = TAILQ_LAST((head), headname);\t\t\t\\\n\t    (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1);\t\\\n\t    (var) = (tvar))\n\n#define\tTAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \\\n\tfor ((var) = ((var) ? (var) : TAILQ_LAST((head), headname));\t\\\n\t    (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1);\t\\\n\t    (var) = (tvar))\n\n#define\tTAILQ_INIT(head) do {\t\t\t\t\t\t\\\n\tTAILQ_FIRST((head)) = NULL;\t\t\t\t\t\\\n\t(head)->tqh_last = &TAILQ_FIRST((head));\t\t\t\\\n\tQMD_TRACE_HEAD(head);\t\t\t\t\t\t\\\n} while (0)\n\n#define\tTAILQ_INSERT_AFTER(head, listelm, elm, field) do {\t\t\\\n\tQMD_TAILQ_CHECK_NEXT(listelm, field);\t\t\t\t\\\n\tif ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\\\n\t\tTAILQ_NEXT((elm), field)->field.tqe_prev = \t\t\\\n\t\t    &TAILQ_NEXT((elm), field);\t\t\t\t\\\n\telse {\t\t\t\t\t\t\t\t\\\n\t\t(head)->tqh_last = &TAILQ_NEXT((elm), field);\t\t\\\n\t\tQMD_TRACE_HEAD(head);\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\tTAILQ_NEXT((listelm), field) = (elm);\t\t\t\t\\\n\t(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field);\t\t\\\n\tQMD_TRACE_ELEM(&(elm)->field);\t\t\t\t\t\\\n\tQMD_TRACE_ELEM(&(listelm)->field);\t\t\t\t\\\n} while (0)\n\n#define\tTAILQ_INSERT_BEFORE(listelm, elm, field) do {\t\t\t\\\n\tQMD_TAILQ_CHECK_PREV(listelm, field);\t\t\t\t\\\n\t(elm)->field.tqe_prev = (listelm)->field.tqe_prev;\t\t\\\n\tTAILQ_NEXT((elm), field) = (listelm);\t\t\t\t\\\n\t*(listelm)->field.tqe_prev = (elm);\t\t\t\t\\\n\t(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field);\t\t\\\n\tQMD_TRACE_ELEM(&(elm)->field);\t\t\t\t\t\\\n\tQMD_TRACE_ELEM(&(listelm)->field);\t\t\t\t\\\n} while (0)\n\n#define\tTAILQ_INSERT_HEAD(head, elm, field) do {\t\t\t\\\n\tQMD_TAILQ_CHECK_HEAD(head, field);\t\t\t\t\\\n\tif ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL)\t\\\n\t\tTAILQ_FIRST((head))->field.tqe_prev =\t\t\t\\\n\t\t    &TAILQ_NEXT((elm), field);\t\t\t\t\\\n\telse\t\t\t\t\t\t\t\t\\\n\t\t(head)->tqh_last = &TAILQ_NEXT((elm), field);\t\t\\\n\tTAILQ_FIRST((head)) = (elm);\t\t\t\t\t\\\n\t(elm)->field.tqe_prev = &TAILQ_FIRST((head));\t\t\t\\\n\tQMD_TRACE_HEAD(head);\t\t\t\t\t\t\\\n\tQMD_TRACE_ELEM(&(elm)->field);\t\t\t\t\t\\\n} while (0)\n\n#define\tTAILQ_INSERT_TAIL(head, elm, field) do {\t\t\t\\\n\tQMD_TAILQ_CHECK_TAIL(head, field);\t\t\t\t\\\n\tTAILQ_NEXT((elm), field) = NULL;\t\t\t\t\\\n\t(elm)->field.tqe_prev = (head)->tqh_last;\t\t\t\\\n\t*(head)->tqh_last = (elm);\t\t\t\t\t\\\n\t(head)->tqh_last = &TAILQ_NEXT((elm), field);\t\t\t\\\n\tQMD_TRACE_HEAD(head);\t\t\t\t\t\t\\\n\tQMD_TRACE_ELEM(&(elm)->field);\t\t\t\t\t\\\n} while (0)\n\n#define\tTAILQ_LAST(head, headname)\t\t\t\t\t\\\n\t(*(((struct headname *)((head)->tqh_last))->tqh_last))\n\n#define\tTAILQ_NEXT(elm, field) ((elm)->field.tqe_next)\n\n#define\tTAILQ_PREV(elm, headname, field)\t\t\t\t\\\n\t(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))\n\n#define\tTAILQ_REMOVE(head, elm, field) do {\t\t\t\t\\\n\tQMD_SAVELINK(oldnext, (elm)->field.tqe_next);\t\t\t\\\n\tQMD_SAVELINK(oldprev, (elm)->field.tqe_prev);\t\t\t\\\n\tQMD_TAILQ_CHECK_NEXT(elm, field);\t\t\t\t\\\n\tQMD_TAILQ_CHECK_PREV(elm, field);\t\t\t\t\\\n\tif ((TAILQ_NEXT((elm), field)) != NULL)\t\t\t\t\\\n\t\tTAILQ_NEXT((elm), field)->field.tqe_prev = \t\t\\\n\t\t    (elm)->field.tqe_prev;\t\t\t\t\\\n\telse {\t\t\t\t\t\t\t\t\\\n\t\t(head)->tqh_last = (elm)->field.tqe_prev;\t\t\\\n\t\tQMD_TRACE_HEAD(head);\t\t\t\t\t\\\n\t}\t\t\t\t\t\t\t\t\\\n\t*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field);\t\t\\\n\tTRASHIT(*oldnext);\t\t\t\t\t\t\\\n\tTRASHIT(*oldprev);\t\t\t\t\t\t\\\n\tQMD_TRACE_ELEM(&(elm)->field);\t\t\t\t\t\\\n} while (0)\n\n#define TAILQ_SWAP(head1, head2, type, field) do {\t\t\t\\\n\tQUEUE_TYPEOF(type) *swap_first = (head1)->tqh_first;\t\t\\\n\tQUEUE_TYPEOF(type) **swap_last = (head1)->tqh_last;\t\t\\\n\t(head1)->tqh_first = (head2)->tqh_first;\t\t\t\\\n\t(head1)->tqh_last = (head2)->tqh_last;\t\t\t\t\\\n\t(head2)->tqh_first = swap_first;\t\t\t\t\\\n\t(head2)->tqh_last = swap_last;\t\t\t\t\t\\\n\tif ((swap_first = (head1)->tqh_first) != NULL)\t\t\t\\\n\t\tswap_first->field.tqe_prev = &(head1)->tqh_first;\t\\\n\telse\t\t\t\t\t\t\t\t\\\n\t\t(head1)->tqh_last = &(head1)->tqh_first;\t\t\\\n\tif ((swap_first = (head2)->tqh_first) != NULL)\t\t\t\\\n\t\tswap_first->field.tqe_prev = &(head2)->tqh_first;\t\\\n\telse\t\t\t\t\t\t\t\t\\\n\t\t(head2)->tqh_last = &(head2)->tqh_first;\t\t\\\n} while (0)\n\n#endif /* !_SYS_QUEUE_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_features.h\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_FEATURES_H_\n#define CS_MONGOOSE_SRC_FEATURES_H_\n\n#ifndef MG_DISABLE_HTTP_DIGEST_AUTH\n#define MG_DISABLE_HTTP_DIGEST_AUTH 0\n#endif\n\n#ifndef MG_DISABLE_HTTP_KEEP_ALIVE\n#define MG_DISABLE_HTTP_KEEP_ALIVE 0\n#endif\n\n#ifndef MG_DISABLE_PFS\n#define MG_DISABLE_PFS 0\n#endif\n\n#ifndef MG_DISABLE_WS_RANDOM_MASK\n#define MG_DISABLE_WS_RANDOM_MASK 0\n#endif\n\n#ifndef MG_ENABLE_ASYNC_RESOLVER\n#define MG_ENABLE_ASYNC_RESOLVER 1\n#endif\n\n#ifndef MG_ENABLE_BROADCAST\n#define MG_ENABLE_BROADCAST 0\n#endif\n\n#ifndef MG_ENABLE_COAP\n#define MG_ENABLE_COAP 0\n#endif\n\n#ifndef MG_ENABLE_DEBUG\n#define MG_ENABLE_DEBUG 0\n#endif\n\n#ifndef MG_ENABLE_DIRECTORY_LISTING\n#define MG_ENABLE_DIRECTORY_LISTING 0\n#endif\n\n#ifndef MG_ENABLE_DNS\n#define MG_ENABLE_DNS 1\n#endif\n\n#ifndef MG_ENABLE_DNS_SERVER\n#define MG_ENABLE_DNS_SERVER 0\n#endif\n\n#ifndef MG_ENABLE_FAKE_DAVLOCK\n#define MG_ENABLE_FAKE_DAVLOCK 0\n#endif\n\n#ifndef MG_ENABLE_FILESYSTEM\n#define MG_ENABLE_FILESYSTEM 0\n#endif\n\n#ifndef MG_ENABLE_GETADDRINFO\n#define MG_ENABLE_GETADDRINFO 0\n#endif\n\n#ifndef MG_ENABLE_HEXDUMP\n#define MG_ENABLE_HEXDUMP CS_ENABLE_STDIO\n#endif\n\n#ifndef MG_ENABLE_HTTP\n#define MG_ENABLE_HTTP 1\n#endif\n\n#ifndef MG_ENABLE_HTTP_CGI\n#define MG_ENABLE_HTTP_CGI 0\n#endif\n\n#ifndef MG_ENABLE_HTTP_SSI\n#define MG_ENABLE_HTTP_SSI MG_ENABLE_FILESYSTEM\n#endif\n\n#ifndef MG_ENABLE_HTTP_SSI_EXEC\n#define MG_ENABLE_HTTP_SSI_EXEC 0\n#endif\n\n#ifndef MG_ENABLE_HTTP_STREAMING_MULTIPART\n#define MG_ENABLE_HTTP_STREAMING_MULTIPART 0\n#endif\n\n#ifndef MG_ENABLE_HTTP_WEBDAV\n#define MG_ENABLE_HTTP_WEBDAV 0\n#endif\n\n#ifndef MG_ENABLE_HTTP_WEBSOCKET\n#define MG_ENABLE_HTTP_WEBSOCKET MG_ENABLE_HTTP\n#endif\n\n#ifndef MG_ENABLE_IPV6\n#define MG_ENABLE_IPV6 0\n#endif\n\n#ifndef MG_ENABLE_MQTT\n#define MG_ENABLE_MQTT 1\n#endif\n\n#ifndef MG_ENABLE_SOCKS\n#define MG_ENABLE_SOCKS 0\n#endif\n\n#ifndef MG_ENABLE_MQTT_BROKER\n#define MG_ENABLE_MQTT_BROKER 0\n#endif\n\n#ifndef MG_ENABLE_SSL\n#define MG_ENABLE_SSL 0\n#endif\n\n#ifndef MG_ENABLE_SYNC_RESOLVER\n#define MG_ENABLE_SYNC_RESOLVER 0\n#endif\n\n#ifndef MG_ENABLE_STDIO\n#define MG_ENABLE_STDIO CS_ENABLE_STDIO\n#endif\n\n#ifndef MG_NET_IF\n#define MG_NET_IF MG_NET_IF_SOCKET\n#endif\n\n#ifndef MG_SSL_IF\n#define MG_SSL_IF MG_SSL_IF_OPENSSL\n#endif\n\n#ifndef MG_ENABLE_THREADS /* ifdef-ok */\n#ifdef _WIN32\n#define MG_ENABLE_THREADS 1\n#else\n#define MG_ENABLE_THREADS 0\n#endif\n#endif\n\n#if MG_ENABLE_DEBUG && !defined(CS_ENABLE_DEBUG)\n#define CS_ENABLE_DEBUG 1\n#endif\n\n/* MQTT broker requires MQTT */\n#if MG_ENABLE_MQTT_BROKER && !MG_ENABLE_MQTT\n#undef MG_ENABLE_MQTT\n#define MG_ENABLE_MQTT 1\n#endif\n\n#ifndef MG_ENABLE_HTTP_URL_REWRITES\n#define MG_ENABLE_HTTP_URL_REWRITES \\\n  (CS_PLATFORM == CS_P_WINDOWS || CS_PLATFORM == CS_P_UNIX)\n#endif\n\n#ifndef MG_ENABLE_SNTP\n#define MG_ENABLE_SNTP 0\n#endif\n\n#ifndef MG_ENABLE_EXTRA_ERRORS_DESC\n#define MG_ENABLE_EXTRA_ERRORS_DESC 0\n#endif\n\n#ifndef MG_ENABLE_CALLBACK_USERDATA\n#define MG_ENABLE_CALLBACK_USERDATA 0\n#endif\n\n#if MG_ENABLE_CALLBACK_USERDATA\n#define MG_UD_ARG(ud) , ud\n#define MG_CB(cb, ud) cb, ud\n#else\n#define MG_UD_ARG(ud)\n#define MG_CB(cb, ud) cb\n#endif\n\n#endif /* CS_MONGOOSE_SRC_FEATURES_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if.h\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_NET_IF_H_\n#define CS_MONGOOSE_SRC_NET_IF_H_\n\n/* Amalgamated: #include \"common/platform.h\" */\n\n/*\n * Internal async networking core interface.\n * Consists of calls made by the core, which should not block,\n * and callbacks back into the core (\"..._cb\").\n * Callbacks may (will) cause methods to be invoked from within,\n * but methods are not allowed to invoke callbacks inline.\n *\n * Implementation must ensure that only one callback is invoked at any time.\n */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#define MG_MAIN_IFACE 0\n\nstruct mg_mgr;\nstruct mg_connection;\nunion socket_address;\n\nstruct mg_iface_vtable;\n\nstruct mg_iface {\n  struct mg_mgr *mgr;\n  void *data; /* Implementation-specific data */\n  const struct mg_iface_vtable *vtable;\n};\n\nstruct mg_iface_vtable {\n  void (*init)(struct mg_iface *iface);\n  void (*_free)(struct mg_iface *iface);\n  void (*add_conn)(struct mg_connection *nc);\n  void (*remove_conn)(struct mg_connection *nc);\n  time_t (*poll)(struct mg_iface *iface, int timeout_ms);\n\n  /* Set up a listening TCP socket on a given address. rv = 0 -> ok. */\n  int (*listen_tcp)(struct mg_connection *nc, union socket_address *sa);\n  /* Request that a \"listening\" UDP socket be created. */\n  int (*listen_udp)(struct mg_connection *nc, union socket_address *sa);\n\n  /* Request that a TCP connection is made to the specified address. */\n  void (*connect_tcp)(struct mg_connection *nc, const union socket_address *sa);\n  /* Open a UDP socket. Doesn't actually connect anything. */\n  void (*connect_udp)(struct mg_connection *nc);\n\n  /* Send functions for TCP and UDP. Sent data is copied before return. */\n  int (*tcp_send)(struct mg_connection *nc, const void *buf, size_t len);\n  int (*udp_send)(struct mg_connection *nc, const void *buf, size_t len);\n\n  int (*tcp_recv)(struct mg_connection *nc, void *buf, size_t len);\n  int (*udp_recv)(struct mg_connection *nc, void *buf, size_t len,\n                  union socket_address *sa, size_t *sa_len);\n\n  /* Perform interface-related connection initialization. Return 1 on ok. */\n  int (*create_conn)(struct mg_connection *nc);\n  /* Perform interface-related cleanup on connection before destruction. */\n  void (*destroy_conn)(struct mg_connection *nc);\n\n  /* Associate a socket to a connection. */\n  void (*sock_set)(struct mg_connection *nc, sock_t sock);\n\n  /* Put connection's address into *sa, local (remote = 0) or remote. */\n  void (*get_conn_addr)(struct mg_connection *nc, int remote,\n                        union socket_address *sa);\n};\n\nextern const struct mg_iface_vtable *mg_ifaces[];\nextern int mg_num_ifaces;\n\n/* Creates a new interface instance. */\nstruct mg_iface *mg_if_create_iface(const struct mg_iface_vtable *vtable,\n                                    struct mg_mgr *mgr);\n\n/*\n * Find an interface with a given implementation. The search is started from\n * interface `from`, exclusive. Returns NULL if none is found.\n */\nstruct mg_iface *mg_find_iface(struct mg_mgr *mgr,\n                               const struct mg_iface_vtable *vtable,\n                               struct mg_iface *from);\n/*\n * Deliver a new TCP connection. Returns NULL in case on error (unable to\n * create connection, in which case interface state should be discarded.\n * This is phase 1 of the two-phase process - MG_EV_ACCEPT will be delivered\n * when mg_if_accept_tcp_cb is invoked.\n */\nstruct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc);\nvoid mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,\n                         size_t sa_len);\n\n/* Callback invoked by connect methods. err = 0 -> ok, != 0 -> error. */\nvoid mg_if_connect_cb(struct mg_connection *nc, int err);\n/*\n * Callback that tells the core that data can be received.\n * Core will use tcp/udp_recv to retrieve the data.\n */\nvoid mg_if_can_recv_cb(struct mg_connection *nc);\nvoid mg_if_can_send_cb(struct mg_connection *nc);\n/*\n * Receive callback.\n * buf must be heap-allocated and ownership is transferred to the core.\n */\nvoid mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len,\n                       union socket_address *sa, size_t sa_len);\n\n/* void mg_if_close_conn(struct mg_connection *nc); */\n\n/* Deliver a POLL event to the connection. */\nint mg_if_poll(struct mg_connection *nc, double now);\n\n/*\n * Return minimal timer value amoung connections in the manager.\n * Returns 0 if there aren't any timers.\n */\ndouble mg_mgr_min_timer(const struct mg_mgr *mgr);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_MONGOOSE_SRC_NET_IF_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_ssl_if.h\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_SSL_IF_H_\n#define CS_MONGOOSE_SRC_SSL_IF_H_\n\n#if MG_ENABLE_SSL\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nstruct mg_ssl_if_ctx;\nstruct mg_connection;\n\nvoid mg_ssl_if_init(void);\n\nenum mg_ssl_if_result {\n  MG_SSL_OK = 0,\n  MG_SSL_WANT_READ = -1,\n  MG_SSL_WANT_WRITE = -2,\n  MG_SSL_ERROR = -3,\n};\n\nstruct mg_ssl_if_conn_params {\n  const char *cert;\n  const char *key;\n  const char *ca_cert;\n  const char *server_name;\n  const char *cipher_suites;\n  const char *psk_identity;\n  const char *psk_key;\n};\n\nenum mg_ssl_if_result mg_ssl_if_conn_init(\n    struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,\n    const char **err_msg);\nenum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,\n                                            struct mg_connection *lc);\nvoid mg_ssl_if_conn_close_notify(struct mg_connection *nc);\nvoid mg_ssl_if_conn_free(struct mg_connection *nc);\n\nenum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc);\nint mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size);\nint mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MG_ENABLE_SSL */\n\n#endif /* CS_MONGOOSE_SRC_SSL_IF_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n/*\n * === Core API: TCP/UDP/SSL\n *\n * NOTE: Mongoose manager is single threaded. It does not protect\n * its data structures by mutexes, therefore all functions that are dealing\n * with a particular event manager should be called from the same thread,\n * with exception of the `mg_broadcast()` function. It is fine to have different\n * event managers handled by different threads.\n */\n\n#ifndef CS_MONGOOSE_SRC_NET_H_\n#define CS_MONGOOSE_SRC_NET_H_\n\n/* Amalgamated: #include \"mg_common.h\" */\n/* Amalgamated: #include \"mg_net_if.h\" */\n/* Amalgamated: #include \"common/mbuf.h\" */\n\n#ifndef MG_VPRINTF_BUFFER_SIZE\n#define MG_VPRINTF_BUFFER_SIZE 100\n#endif\n\n#ifdef MG_USE_READ_WRITE\n#define MG_RECV_FUNC(s, b, l, f) read(s, b, l)\n#define MG_SEND_FUNC(s, b, l, f) write(s, b, l)\n#else\n#define MG_RECV_FUNC(s, b, l, f) recv(s, b, l, f)\n#define MG_SEND_FUNC(s, b, l, f) send(s, b, l, f)\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nunion socket_address {\n  struct sockaddr sa;\n  struct sockaddr_in sin;\n#if MG_ENABLE_IPV6\n  struct sockaddr_in6 sin6;\n#else\n  struct sockaddr sin6;\n#endif\n};\n\nstruct mg_connection;\n\n/*\n * Callback function (event handler) prototype. Must be defined by the user.\n * Mongoose calls the event handler, passing the events defined below.\n */\ntypedef void (*mg_event_handler_t)(struct mg_connection *nc, int ev,\n                                   void *ev_data MG_UD_ARG(void *user_data));\n\n/* Events. Meaning of event parameter (evp) is given in the comment. */\n#define MG_EV_POLL 0    /* Sent to each connection on each mg_mgr_poll() call */\n#define MG_EV_ACCEPT 1  /* New connection accepted. union socket_address * */\n#define MG_EV_CONNECT 2 /* connect() succeeded or failed. int *  */\n#define MG_EV_RECV 3    /* Data has been received. int *num_bytes */\n#define MG_EV_SEND 4    /* Data has been written to a socket. int *num_bytes */\n#define MG_EV_CLOSE 5   /* Connection is closed. NULL */\n#define MG_EV_TIMER 6   /* now >= conn->ev_timer_time. double * */\n\n/*\n * Mongoose event manager.\n */\nstruct mg_mgr {\n  struct mg_connection *active_connections;\n#if MG_ENABLE_HEXDUMP\n  const char *hexdump_file; /* Debug hexdump file path */\n#endif\n#if MG_ENABLE_BROADCAST\n  sock_t ctl[2]; /* Socketpair for mg_broadcast() */\n#endif\n  void *user_data; /* User data */\n  int num_ifaces;\n  int num_calls;\n  struct mg_iface **ifaces; /* network interfaces */\n  const char *nameserver;   /* DNS server to use */\n};\n\n/*\n * Mongoose connection.\n */\nstruct mg_connection {\n  struct mg_connection *next, *prev; /* mg_mgr::active_connections linkage */\n  struct mg_connection *listener;    /* Set only for accept()-ed connections */\n  struct mg_mgr *mgr;                /* Pointer to containing manager */\n\n  sock_t sock; /* Socket to the remote peer */\n  int err;\n  union socket_address sa; /* Remote peer address */\n  size_t recv_mbuf_limit;  /* Max size of recv buffer */\n  struct mbuf recv_mbuf;   /* Received data */\n  struct mbuf send_mbuf;   /* Data scheduled for sending */\n  time_t last_io_time;     /* Timestamp of the last socket IO */\n  double ev_timer_time;    /* Timestamp of the future MG_EV_TIMER */\n#if MG_ENABLE_SSL\n  void *ssl_if_data; /* SSL library data. */\n#endif\n  mg_event_handler_t proto_handler; /* Protocol-specific event handler */\n  void *proto_data;                 /* Protocol-specific data */\n  void (*proto_data_destructor)(void *proto_data);\n  mg_event_handler_t handler; /* Event handler function */\n  void *user_data;            /* User-specific data */\n  union {\n    void *v;\n    /*\n     * the C standard is fussy about fitting function pointers into\n     * void pointers, since some archs might have fat pointers for functions.\n     */\n    mg_event_handler_t f;\n  } priv_1;\n  void *priv_2;\n  void *mgr_data; /* Implementation-specific event manager's data. */\n  struct mg_iface *iface;\n  unsigned long flags;\n/* Flags set by Mongoose */\n#define MG_F_LISTENING (1 << 0)          /* This connection is listening */\n#define MG_F_UDP (1 << 1)                /* This connection is UDP */\n#define MG_F_RESOLVING (1 << 2)          /* Waiting for async resolver */\n#define MG_F_CONNECTING (1 << 3)         /* connect() call in progress */\n#define MG_F_SSL (1 << 4)                /* SSL is enabled on the connection */\n#define MG_F_SSL_HANDSHAKE_DONE (1 << 5) /* SSL hanshake has completed */\n#define MG_F_WANT_READ (1 << 6)          /* SSL specific */\n#define MG_F_WANT_WRITE (1 << 7)         /* SSL specific */\n#define MG_F_IS_WEBSOCKET (1 << 8)       /* Websocket specific */\n#define MG_F_RECV_AND_CLOSE (1 << 9) /* Drain rx and close the connection. */\n\n/* Flags that are settable by user */\n#define MG_F_SEND_AND_CLOSE (1 << 10)      /* Push remaining data and close  */\n#define MG_F_CLOSE_IMMEDIATELY (1 << 11)   /* Disconnect */\n#define MG_F_WEBSOCKET_NO_DEFRAG (1 << 12) /* Websocket specific */\n#define MG_F_DELETE_CHUNK (1 << 13)        /* HTTP specific */\n#define MG_F_ENABLE_BROADCAST (1 << 14)    /* Allow broadcast address usage */\n\n#define MG_F_USER_1 (1 << 20) /* Flags left for application */\n#define MG_F_USER_2 (1 << 21)\n#define MG_F_USER_3 (1 << 22)\n#define MG_F_USER_4 (1 << 23)\n#define MG_F_USER_5 (1 << 24)\n#define MG_F_USER_6 (1 << 25)\n};\n\n/*\n * Initialise Mongoose manager. Side effect: ignores SIGPIPE signal.\n * `mgr->user_data` field will be initialised with a `user_data` parameter.\n * That is an arbitrary pointer, where the user code can associate some data\n * with the particular Mongoose manager. For example, a C++ wrapper class\n * could be written in which case `user_data` can hold a pointer to the\n * class instance.\n */\nvoid mg_mgr_init(struct mg_mgr *mgr, void *user_data);\n\n/*\n * Optional parameters to `mg_mgr_init_opt()`.\n *\n * If `main_iface` is not NULL, it will be used as the main interface in the\n * default interface set. The pointer will be free'd by `mg_mgr_free`.\n * Otherwise, the main interface will be autodetected based on the current\n * platform.\n *\n * If `num_ifaces` is 0 and `ifaces` is NULL, the default interface set will be\n * used.\n * This is an advanced option, as it requires you to construct a full interface\n * set, including special networking interfaces required by some optional\n * features such as TCP tunneling. Memory backing `ifaces` and each of the\n * `num_ifaces` pointers it contains will be reclaimed by `mg_mgr_free`.\n */\nstruct mg_mgr_init_opts {\n  const struct mg_iface_vtable *main_iface;\n  int num_ifaces;\n  const struct mg_iface_vtable **ifaces;\n  const char *nameserver;\n};\n\n/*\n * Like `mg_mgr_init` but with more options.\n *\n * Notably, this allows you to create a manger and choose\n * dynamically which networking interface implementation to use.\n */\nvoid mg_mgr_init_opt(struct mg_mgr *mgr, void *user_data,\n                     struct mg_mgr_init_opts opts);\n\n/*\n * De-initialises Mongoose manager.\n *\n * Closes and deallocates all active connections.\n */\nvoid mg_mgr_free(struct mg_mgr *mgr);\n\n/*\n * This function performs the actual IO and must be called in a loop\n * (an event loop). It returns number of user events generated (except POLLs).\n * `milli` is the maximum number of milliseconds to sleep.\n * `mg_mgr_poll()` checks all connections for IO readiness. If at least one\n * of the connections is IO-ready, `mg_mgr_poll()` triggers the respective\n * event handlers and returns.\n */\nint mg_mgr_poll(struct mg_mgr *mgr, int milli);\n\n#if MG_ENABLE_BROADCAST\n/*\n * Passes a message of a given length to all connections.\n *\n * Must be called from a thread that does NOT call `mg_mgr_poll()`.\n * Note that `mg_broadcast()` is the only function\n * that can be, and must be, called from a different (non-IO) thread.\n *\n * `func` callback function will be called by the IO thread for each\n * connection. When called, the event will be `MG_EV_POLL`, and a message will\n * be passed as the `ev_data` pointer. Maximum message size is capped\n * by `MG_CTL_MSG_MESSAGE_SIZE` which is set to 8192 bytes by default.\n */\nvoid mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,\n                  size_t len);\n#endif\n\n/*\n * Iterates over all active connections.\n *\n * Returns the next connection from the list\n * of active connections or `NULL` if there are no more connections. Below\n * is the iteration idiom:\n *\n * ```c\n * for (c = mg_next(srv, NULL); c != NULL; c = mg_next(srv, c)) {\n *   // Do something with connection `c`\n * }\n * ```\n */\nstruct mg_connection *mg_next(struct mg_mgr *mgr, struct mg_connection *c);\n\n/*\n * Optional parameters to `mg_add_sock_opt()`.\n *\n * `flags` is an initial `struct mg_connection::flags` bitmask to set,\n * see `MG_F_*` flags definitions.\n */\nstruct mg_add_sock_opts {\n  void *user_data;           /* Initial value for connection's user_data */\n  unsigned int flags;        /* Initial connection flags */\n  const char **error_string; /* Placeholder for the error string */\n  struct mg_iface *iface;    /* Interface instance */\n};\n\n/*\n * Creates a connection, associates it with the given socket and event handler\n * and adds it to the manager.\n *\n * For more options see the `mg_add_sock_opt` variant.\n */\nstruct mg_connection *mg_add_sock(struct mg_mgr *mgr, sock_t sock,\n                                  MG_CB(mg_event_handler_t handler,\n                                        void *user_data));\n\n/*\n * Creates a connection, associates it with the given socket and event handler\n * and adds to the manager.\n *\n * See the `mg_add_sock_opts` structure for a description of the options.\n */\nstruct mg_connection *mg_add_sock_opt(struct mg_mgr *mgr, sock_t sock,\n                                      MG_CB(mg_event_handler_t handler,\n                                            void *user_data),\n                                      struct mg_add_sock_opts opts);\n\n/*\n * Optional parameters to `mg_bind_opt()`.\n *\n * `flags` is an initial `struct mg_connection::flags` bitmask to set,\n * see `MG_F_*` flags definitions.\n */\nstruct mg_bind_opts {\n  void *user_data;           /* Initial value for connection's user_data */\n  unsigned int flags;        /* Extra connection flags */\n  const char **error_string; /* Placeholder for the error string */\n  struct mg_iface *iface;    /* Interface instance */\n#if MG_ENABLE_SSL\n  /*\n   * SSL settings.\n   *\n   * Server certificate to present to clients or client certificate to\n   * present to tunnel dispatcher (for tunneled connections).\n   */\n  const char *ssl_cert;\n  /* Private key corresponding to the certificate. If ssl_cert is set but\n   * ssl_key is not, ssl_cert is used. */\n  const char *ssl_key;\n  /* CA bundle used to verify client certificates or tunnel dispatchers. */\n  const char *ssl_ca_cert;\n  /* Colon-delimited list of acceptable cipher suites.\n   * Names depend on the library used, for example:\n   *\n   * ECDH-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256 (OpenSSL)\n   * TLS-ECDH-ECDSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256\n   *   (mbedTLS)\n   *\n   * For OpenSSL the list can be obtained by running \"openssl ciphers\".\n   * For mbedTLS, names can be found in library/ssl_ciphersuites.c\n   * If NULL, a reasonable default is used.\n   */\n  const char *ssl_cipher_suites;\n#endif\n};\n\n/*\n * Creates a listening connection.\n *\n * See `mg_bind_opt` for full documentation.\n */\nstruct mg_connection *mg_bind(struct mg_mgr *mgr, const char *address,\n                              MG_CB(mg_event_handler_t handler,\n                                    void *user_data));\n/*\n * Creates a listening connection.\n *\n * The `address` parameter specifies which address to bind to. It's format is\n * the same as for the `mg_connect()` call, where `HOST` part is optional.\n * `address` can be just a port number, e.g. `:8000`. To bind to a specific\n * interface, an IP address can be specified, e.g. `1.2.3.4:8000`. By default,\n * a TCP connection is created. To create UDP connection, prepend `udp://`\n * prefix, e.g. `udp://:8000`. To summarize, `address` parameter has following\n * format: `[PROTO://][IP_ADDRESS]:PORT`, where `PROTO` could be `tcp` or\n * `udp`.\n *\n * See the `mg_bind_opts` structure for a description of the optional\n * parameters.\n *\n * Returns a new listening connection or `NULL` on error.\n * NOTE: The connection remains owned by the manager, do not free().\n */\nstruct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,\n                                  MG_CB(mg_event_handler_t handler,\n                                        void *user_data),\n                                  struct mg_bind_opts opts);\n\n/* Optional parameters to `mg_connect_opt()` */\nstruct mg_connect_opts {\n  void *user_data;           /* Initial value for connection's user_data */\n  unsigned int flags;        /* Extra connection flags */\n  const char **error_string; /* Placeholder for the error string */\n  struct mg_iface *iface;    /* Interface instance */\n  const char *nameserver;    /* DNS server to use, NULL for default */\n#if MG_ENABLE_SSL\n  /*\n   * SSL settings.\n   * Client certificate to present to the server.\n   */\n  const char *ssl_cert;\n  /*\n   * Private key corresponding to the certificate.\n   * If ssl_cert is set but ssl_key is not, ssl_cert is used.\n   */\n  const char *ssl_key;\n  /*\n   * Verify server certificate using this CA bundle. If set to \"*\", then SSL\n   * is enabled but no cert verification is performed.\n   */\n  const char *ssl_ca_cert;\n  /* Colon-delimited list of acceptable cipher suites.\n   * Names depend on the library used, for example:\n   *\n   * ECDH-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256 (OpenSSL)\n   * TLS-ECDH-ECDSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256\n   *   (mbedTLS)\n   *\n   * For OpenSSL the list can be obtained by running \"openssl ciphers\".\n   * For mbedTLS, names can be found in library/ssl_ciphersuites.c\n   * If NULL, a reasonable default is used.\n   */\n  const char *ssl_cipher_suites;\n  /*\n   * Server name verification. If ssl_ca_cert is set and the certificate has\n   * passed verification, its subject will be verified against this string.\n   * By default (if ssl_server_name is NULL) hostname part of the address will\n   * be used. Wildcard matching is supported. A special value of \"*\" disables\n   * name verification.\n   */\n  const char *ssl_server_name;\n  /*\n   * PSK identity and key. Identity is a NUL-terminated string and key is a hex\n   * string. Key must be either 16 or 32 bytes (32 or 64 hex digits) for AES-128\n   * or AES-256 respectively.\n   * Note: Default list of cipher suites does not include PSK suites, if you\n   * want to use PSK you will need to set ssl_cipher_suites as well.\n   */\n  const char *ssl_psk_identity;\n  const char *ssl_psk_key;\n#endif\n};\n\n/*\n * Connects to a remote host.\n *\n * See `mg_connect_opt()` for full documentation.\n */\nstruct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,\n                                 MG_CB(mg_event_handler_t handler,\n                                       void *user_data));\n\n/*\n * Connects to a remote host.\n *\n * The `address` format is `[PROTO://]HOST:PORT`. `PROTO` could be `tcp` or\n * `udp`. `HOST` could be an IP address,\n * IPv6 address (if Mongoose is compiled with `-DMG_ENABLE_IPV6`) or a host\n * name. If `HOST` is a name, Mongoose will resolve it asynchronously. Examples\n * of valid addresses: `google.com:80`, `udp://1.2.3.4:53`, `10.0.0.1:443`,\n * `[::1]:80`\n *\n * See the `mg_connect_opts` structure for a description of the optional\n * parameters.\n *\n * Returns a new outbound connection or `NULL` on error.\n *\n * NOTE: The connection remains owned by the manager, do not free().\n *\n * NOTE: To enable IPv6 addresses `-DMG_ENABLE_IPV6` should be specified\n * in the compilation flags.\n *\n * NOTE: The new connection will receive `MG_EV_CONNECT` as its first event\n * which will report the connect success status.\n * If the asynchronous resolution fails or the `connect()` syscall fails for\n * whatever reason (e.g. with `ECONNREFUSED` or `ENETUNREACH`), then\n * `MG_EV_CONNECT` event will report failure. Code example below:\n *\n * ```c\n * static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {\n *   int connect_status;\n *\n *   switch (ev) {\n *     case MG_EV_CONNECT:\n *       connect_status = * (int *) ev_data;\n *       if (connect_status == 0) {\n *         // Success\n *       } else  {\n *         // Error\n *         printf(\"connect() error: %s\\n\", strerror(connect_status));\n *       }\n *       break;\n *     ...\n *   }\n * }\n *\n *   ...\n *   mg_connect(mgr, \"my_site.com:80\", ev_handler);\n * ```\n */\nstruct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,\n                                     MG_CB(mg_event_handler_t handler,\n                                           void *user_data),\n                                     struct mg_connect_opts opts);\n\n#if MG_ENABLE_SSL && MG_NET_IF != MG_NET_IF_SIMPLELINK\n/*\n * Note: This function is deprecated. Please, use SSL options in\n * mg_connect_opt.\n *\n * Enables SSL for a given connection.\n * `cert` is a server certificate file name for a listening connection\n * or a client certificate file name for an outgoing connection.\n * The certificate files must be in PEM format. The server certificate file\n * must contain a certificate, concatenated with a private key, optionally\n * concatenated with DH parameters.\n * `ca_cert` is a CA certificate or NULL if peer verification is not\n * required.\n * Return: NULL on success or error message on error.\n */\nconst char *mg_set_ssl(struct mg_connection *nc, const char *cert,\n                       const char *ca_cert);\n#endif\n\n/*\n * Sends data to the connection.\n *\n * Note that sending functions do not actually push data to the socket.\n * They just append data to the output buffer. MG_EV_SEND will be delivered when\n * the data has actually been pushed out.\n */\nvoid mg_send(struct mg_connection *, const void *buf, int len);\n\n/* Enables format string warnings for mg_printf */\n#if defined(__GNUC__)\n__attribute__((format(printf, 2, 3)))\n#endif\n/* don't separate from mg_printf declaration */\n\n/*\n * Sends `printf`-style formatted data to the connection.\n *\n * See `mg_send` for more details on send semantics.\n */\nint mg_printf(struct mg_connection *, const char *fmt, ...);\n\n/* Same as `mg_printf()`, but takes `va_list ap` as an argument. */\nint mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);\n\n/*\n * Creates a socket pair.\n * `sock_type` can be either `SOCK_STREAM` or `SOCK_DGRAM`.\n * Returns 0 on failure and 1 on success.\n */\nint mg_socketpair(sock_t[2], int sock_type);\n\n#if MG_ENABLE_SYNC_RESOLVER\n/*\n * Convert domain name into IP address.\n *\n * This is a utility function. If compilation flags have\n * `-DMG_ENABLE_GETADDRINFO`, then `getaddrinfo()` call is used for name\n * resolution. Otherwise, `gethostbyname()` is used.\n *\n * CAUTION: this function can block.\n * Return 1 on success, 0 on failure.\n */\nint mg_resolve(const char *domain_name, char *ip_addr_buf, size_t buf_len);\n#endif\n\n/*\n * Verify given IP address against the ACL.\n *\n * `remote_ip` - an IPv4 address to check, in host byte order\n * `acl` - a comma separated list of IP subnets: `x.x.x.x/x` or `x.x.x.x`.\n * Each subnet is\n * prepended by either a - or a + sign. A plus sign means allow, where a\n * minus sign means deny. If a subnet mask is omitted, such as `-1.2.3.4`,\n * it means that only that single IP address is denied.\n * Subnet masks may vary from 0 to 32, inclusive. The default setting\n * is to allow all access. On each request the full list is traversed,\n * and the last match wins. Example:\n *\n * `-0.0.0.0/0,+192.168/16` - deny all accesses, only allow 192.168/16 subnet\n *\n * To learn more about subnet masks, see this\n * link:https://en.wikipedia.org/wiki/Subnetwork[Wikipedia page on Subnetwork].\n *\n * Returns -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.\n */\nint mg_check_ip_acl(const char *acl, uint32_t remote_ip);\n\n/*\n * Schedules an MG_EV_TIMER event to be delivered at `timestamp` time.\n * `timestamp` is UNIX time (the number of seconds since Epoch). It is\n * `double` instead of `time_t` to allow for sub-second precision.\n * Returns the old timer value.\n *\n * Example: set the connect timeout to 1.5 seconds:\n *\n * ```\n *  c = mg_connect(&mgr, \"cesanta.com\", ev_handler);\n *  mg_set_timer(c, mg_time() + 1.5);\n *  ...\n *\n *  void ev_handler(struct mg_connection *c, int ev, void *ev_data) {\n *  switch (ev) {\n *    case MG_EV_CONNECT:\n *      mg_set_timer(c, 0);  // Clear connect timer\n *      break;\n *    case MG_EV_TIMER:\n *      log(\"Connect timeout\");\n *      c->flags |= MG_F_CLOSE_IMMEDIATELY;\n *      break;\n * ```\n */\ndouble mg_set_timer(struct mg_connection *c, double timestamp);\n\n/*\n * A sub-second precision version of time().\n */\ndouble mg_time(void);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_MONGOOSE_SRC_NET_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_uri.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/*\n * === URI\n */\n\n#ifndef CS_MONGOOSE_SRC_URI_H_\n#define CS_MONGOOSE_SRC_URI_H_\n\n/* Amalgamated: #include \"mg_net.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Parses an URI and fills string chunks with locations of the respective\n * uri components within the input uri string. NULL pointers will be\n * ignored.\n *\n * General syntax:\n *\n *     [scheme://[user_info@]]host[:port][/path][?query][#fragment]\n *\n * Example:\n *\n *     foo.com:80\n *     tcp://foo.com:1234\n *     http://foo.com:80/bar?baz=1\n *     https://user:pw@foo.com:443/blah\n *\n * `path` will include the leading slash. `query` won't include the leading `?`.\n * `host` can contain embedded colons if surrounded by square brackets in order\n * to support IPv6 literal addresses.\n *\n *\n * Returns 0 on success, -1 on error.\n */\nint mg_parse_uri(const struct mg_str uri, struct mg_str *scheme,\n                 struct mg_str *user_info, struct mg_str *host,\n                 unsigned int *port, struct mg_str *path, struct mg_str *query,\n                 struct mg_str *fragment);\n\n/*\n * Assemble URI from parts. Any of the inputs can be NULL or zero-length mg_str.\n *\n * If normalize_path is true, path is normalized by resolving relative refs.\n *\n * Result is a heap-allocated string (uri->p must be free()d after use).\n *\n * Returns 0 on success, -1 on error.\n */\nint mg_assemble_uri(const struct mg_str *scheme, const struct mg_str *user_info,\n                    const struct mg_str *host, unsigned int port,\n                    const struct mg_str *path, const struct mg_str *query,\n                    const struct mg_str *fragment, int normalize_path,\n                    struct mg_str *uri);\n\nint mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* CS_MONGOOSE_SRC_URI_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_util.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/*\n * === Utility API\n */\n\n#ifndef CS_MONGOOSE_SRC_UTIL_H_\n#define CS_MONGOOSE_SRC_UTIL_H_\n\n#include <stdio.h>\n\n/* Amalgamated: #include \"mg_common.h\" */\n/* Amalgamated: #include \"mg_net_if.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifndef MG_MAX_PATH\n#ifdef PATH_MAX\n#define MG_MAX_PATH PATH_MAX\n#else\n#define MG_MAX_PATH 256\n#endif\n#endif\n\n/*\n * Fetches substring from input string `s`, `end` into `v`.\n * Skips initial delimiter characters. Records first non-delimiter character\n * at the beginning of substring `v`. Then scans the rest of the string\n * until a delimiter character or end-of-string is found.\n * `delimiters` is a 0-terminated string containing delimiter characters.\n * Either one of `delimiters` or `end_string` terminates the search.\n * Returns an `s` pointer, advanced forward where parsing has stopped.\n */\nconst char *mg_skip(const char *s, const char *end_string,\n                    const char *delimiters, struct mg_str *v);\n\n/*\n * Decodes base64-encoded string `s`, `len` into the destination `dst`.\n * The destination has to have enough space to hold the decoded buffer.\n * Decoding stops either when all strings have been decoded or invalid an\n * character appeared.\n * Destination is '\\0'-terminated.\n * Returns the number of decoded characters. On success, that should be equal\n * to `len`. On error (invalid character) the return value is smaller then\n * `len`.\n */\nint mg_base64_decode(const unsigned char *s, int len, char *dst);\n\n/*\n * Base64-encode chunk of memory `src`, `src_len` into the destination `dst`.\n * Destination has to have enough space to hold encoded buffer.\n * Destination is '\\0'-terminated.\n */\nvoid mg_base64_encode(const unsigned char *src, int src_len, char *dst);\n\n#if MG_ENABLE_FILESYSTEM\n/*\n * Performs a 64-bit `stat()` call against a given file.\n *\n * `path` should be UTF8 encoded.\n *\n * Return value is the same as for `stat()` syscall.\n */\nint mg_stat(const char *path, cs_stat_t *st);\n\n/*\n * Opens the given file and returns a file stream.\n *\n * `path` and `mode` should be UTF8 encoded.\n *\n * Return value is the same as for the `fopen()` call.\n */\nFILE *mg_fopen(const char *path, const char *mode);\n\n/*\n * Opens the given file and returns a file stream.\n *\n * `path` should be UTF8 encoded.\n *\n * Return value is the same as for the `open()` syscall.\n */\nint mg_open(const char *path, int flag, int mode);\n\n/*\n * Reads data from the given file stream.\n *\n * Return value is a number of bytes readen.\n */\nsize_t mg_fread(void *ptr, size_t size, size_t count, FILE *f);\n\n/*\n * Writes data to the given file stream.\n *\n * Return value is a number of bytes wtitten.\n */\nsize_t mg_fwrite(const void *ptr, size_t size, size_t count, FILE *f);\n\n#endif /* MG_ENABLE_FILESYSTEM */\n\n#if MG_ENABLE_THREADS\n#if CS_PLATFORM == CS_P_UNIX\n#include <pthread.h>\n#endif\n/*\n * Starts a new detached thread.\n * Arguments and semantics are the same as pthead's `pthread_create()`.\n * `thread_func` is a thread function, `thread_func_param` is a parameter\n * that is passed to the thread function.\n */\nvoid *mg_start_thread(void *(*thread_func)(void *), void *thread_func_param);\n#endif\n\nvoid mg_set_close_on_exec(sock_t);\n\n#define MG_SOCK_STRINGIFY_IP 1\n#define MG_SOCK_STRINGIFY_PORT 2\n#define MG_SOCK_STRINGIFY_REMOTE 4\n/*\n * Converts a connection's local or remote address into string.\n *\n * The `flags` parameter is a bit mask that controls the behaviour,\n * see `MG_SOCK_STRINGIFY_*` definitions.\n *\n * - MG_SOCK_STRINGIFY_IP - print IP address\n * - MG_SOCK_STRINGIFY_PORT - print port number\n * - MG_SOCK_STRINGIFY_REMOTE - print remote peer's IP/port, not local address\n *\n * If both port number and IP address are printed, they are separated by `:`.\n * If compiled with `-DMG_ENABLE_IPV6`, IPv6 addresses are supported.\n * Return length of the stringified address.\n */\nint mg_conn_addr_to_str(struct mg_connection *c, char *buf, size_t len,\n                        int flags);\n#if MG_NET_IF == MG_NET_IF_SOCKET\n/* Legacy interface. */\nvoid mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags);\n#endif\n\n/*\n * Convert the socket's address into string.\n *\n * `flags` is MG_SOCK_STRINGIFY_IP and/or MG_SOCK_STRINGIFY_PORT.\n */\nint mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,\n                        int flags);\n\n#if MG_ENABLE_HEXDUMP\n/*\n * Generates a human-readable hexdump of memory chunk.\n *\n * Takes a memory buffer `buf` of length `len` and creates a hex dump of that\n * buffer in `dst`. The generated output is a-la hexdump(1).\n * Returns the length of generated string, excluding terminating `\\0`. If\n * returned length is bigger than `dst_len`, the overflow bytes are discarded.\n */\nint mg_hexdump(const void *buf, int len, char *dst, int dst_len);\n\n/* Same as mg_hexdump, but with output going to file instead of a buffer. */\nvoid mg_hexdumpf(FILE *fp, const void *buf, int len);\n\n/*\n * Generates human-readable hexdump of the data sent or received by the\n * connection. `path` is a file name where hexdump should be written.\n * `num_bytes` is a number of bytes sent/received. `ev` is one of the `MG_*`\n * events sent to an event handler. This function is supposed to be called from\n * the event handler.\n */\nvoid mg_hexdump_connection(struct mg_connection *nc, const char *path,\n                           const void *buf, int num_bytes, int ev);\n#endif\n\n/*\n * Returns true if target platform is big endian.\n */\nint mg_is_big_endian(void);\n\n/*\n * Use with cs_base64_init/update/finish in order to write out base64 in chunks.\n */\nvoid mg_mbuf_append_base64_putc(char ch, void *user_data);\n\n/*\n * Encode `len` bytes starting at `data` as base64 and append them to an mbuf.\n */\nvoid mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len);\n\n/*\n * Generate a Basic Auth header and appends it to buf.\n * If pass is NULL, then user is expected to contain the credentials pair\n * already encoded as `user:pass`.\n */\nvoid mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,\n                          struct mbuf *buf);\n\n/*\n * URL-escape the specified string.\n * All characters acept letters, numbers and characters listed in\n * `safe` are escaped. If `hex_upper`is true, `A-F` are used for hex digits.\n * Input need not be NUL-terminated, but the returned string is.\n * Returned string is heap-allocated and must be free()'d.\n */\n#define MG_URL_ENCODE_F_SPACE_AS_PLUS (1 << 0)\n#define MG_URL_ENCODE_F_UPPERCASE_HEX (1 << 1)\nstruct mg_str mg_url_encode_opt(const struct mg_str src,\n                                const struct mg_str safe, unsigned int flags);\n\n/* Same as `mg_url_encode_opt(src, \"._-$,;~()/\", 0)`. */\nstruct mg_str mg_url_encode(const struct mg_str src);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* CS_MONGOOSE_SRC_UTIL_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/*\n * === Common API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_HTTP_H_\n#define CS_MONGOOSE_SRC_HTTP_H_\n\n#if MG_ENABLE_HTTP\n\n/* Amalgamated: #include \"mg_net.h\" */\n/* Amalgamated: #include \"common/mg_str.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifndef MG_MAX_HTTP_HEADERS\n#define MG_MAX_HTTP_HEADERS 20\n#endif\n\n#ifndef MG_MAX_HTTP_REQUEST_SIZE\n#define MG_MAX_HTTP_REQUEST_SIZE 1024\n#endif\n\n#ifndef MG_MAX_HTTP_SEND_MBUF\n#define MG_MAX_HTTP_SEND_MBUF 1024\n#endif\n\n#ifndef MG_CGI_ENVIRONMENT_SIZE\n#define MG_CGI_ENVIRONMENT_SIZE 8192\n#endif\n\n/* HTTP message */\nstruct http_message {\n  struct mg_str message; /* Whole message: request line + headers + body */\n  struct mg_str body;    /* Message body. 0-length for requests with no body */\n\n  /* HTTP Request line (or HTTP response line) */\n  struct mg_str method; /* \"GET\" */\n  struct mg_str uri;    /* \"/my_file.html\" */\n  struct mg_str proto;  /* \"HTTP/1.1\" -- for both request and response */\n\n  /* For responses, code and response status message are set */\n  int resp_code;\n  struct mg_str resp_status_msg;\n\n  /*\n   * Query-string part of the URI. For example, for HTTP request\n   *    GET /foo/bar?param1=val1&param2=val2\n   *    |    uri    |     query_string     |\n   *\n   * Note that question mark character doesn't belong neither to the uri,\n   * nor to the query_string\n   */\n  struct mg_str query_string;\n\n  /* Headers */\n  struct mg_str header_names[MG_MAX_HTTP_HEADERS];\n  struct mg_str header_values[MG_MAX_HTTP_HEADERS];\n};\n\n#if MG_ENABLE_HTTP_WEBSOCKET\n/* WebSocket message */\nstruct websocket_message {\n  unsigned char *data;\n  size_t size;\n  unsigned char flags;\n};\n#endif\n\n/* HTTP multipart part */\nstruct mg_http_multipart_part {\n  const char *file_name;\n  const char *var_name;\n  struct mg_str data;\n  int status; /* <0 on error */\n  void *user_data;\n  /*\n   * User handler can indicate how much of the data was consumed\n   * by setting this variable. By default, it is assumed that all\n   * data has been consumed by the handler.\n   * If not all data was consumed, user's handler will be invoked again later\n   * with the remainder.\n   */\n  size_t num_data_consumed;\n};\n\n/* SSI call context */\nstruct mg_ssi_call_ctx {\n  struct http_message *req; /* The request being processed. */\n  struct mg_str file;       /* Filesystem path of the file being processed. */\n  struct mg_str arg; /* The argument passed to the tag: <!-- call arg -->. */\n};\n\n/* HTTP and websocket events. void *ev_data is described in a comment. */\n#define MG_EV_HTTP_REQUEST 100 /* struct http_message * */\n#define MG_EV_HTTP_REPLY 101   /* struct http_message * */\n#define MG_EV_HTTP_CHUNK 102   /* struct http_message * */\n#define MG_EV_SSI_CALL 105     /* char * */\n#define MG_EV_SSI_CALL_CTX 106 /* struct mg_ssi_call_ctx * */\n\n#if MG_ENABLE_HTTP_WEBSOCKET\n#define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* struct http_message * */\n#define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112    /* struct http_message * */\n#define MG_EV_WEBSOCKET_FRAME 113             /* struct websocket_message * */\n#define MG_EV_WEBSOCKET_CONTROL_FRAME 114     /* struct websocket_message * */\n#endif\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n#define MG_EV_HTTP_MULTIPART_REQUEST 121 /* struct http_message */\n#define MG_EV_HTTP_PART_BEGIN 122        /* struct mg_http_multipart_part */\n#define MG_EV_HTTP_PART_DATA 123         /* struct mg_http_multipart_part */\n#define MG_EV_HTTP_PART_END 124          /* struct mg_http_multipart_part */\n/* struct mg_http_multipart_part */\n#define MG_EV_HTTP_MULTIPART_REQUEST_END 125\n#endif\n\n/*\n * Attaches a built-in HTTP event handler to the given connection.\n * The user-defined event handler will receive following extra events:\n *\n * - MG_EV_HTTP_REQUEST: HTTP request has arrived. Parsed HTTP request\n *  is passed as\n *   `struct http_message` through the handler's `void *ev_data` pointer.\n * - MG_EV_HTTP_REPLY: The HTTP reply has arrived. The parsed HTTP reply is\n *   passed as `struct http_message` through the handler's `void *ev_data`\n *   pointer.\n * - MG_EV_HTTP_CHUNK: The HTTP chunked-encoding chunk has arrived.\n *   The parsed HTTP reply is passed as `struct http_message` through the\n *   handler's `void *ev_data` pointer. `http_message::body` would contain\n *   incomplete, reassembled HTTP body.\n *   It will grow with every new chunk that arrives, and it can\n *   potentially consume a lot of memory. An event handler may process\n *   the body as chunks are coming, and signal Mongoose to delete processed\n *   body by setting `MG_F_DELETE_CHUNK` in `mg_connection::flags`. When\n *   the last zero chunk is received,\n *   Mongoose sends `MG_EV_HTTP_REPLY` event with\n *   full reassembled body (if handler did not signal to delete chunks) or\n *   with empty body (if handler did signal to delete chunks).\n * - MG_EV_WEBSOCKET_HANDSHAKE_REQUEST: server has received the WebSocket\n *   handshake request. `ev_data` contains parsed HTTP request.\n * - MG_EV_WEBSOCKET_HANDSHAKE_DONE: server has completed the WebSocket\n *   handshake. `ev_data` is a `struct http_message` containing the\n *   client's request (server mode) or server's response (client).\n *   In client mode handler can examine `resp_code`, which should be 101.\n * - MG_EV_WEBSOCKET_FRAME: new WebSocket frame has arrived. `ev_data` is\n *   `struct websocket_message *`\n *\n * When compiled with MG_ENABLE_HTTP_STREAMING_MULTIPART, Mongoose parses\n * multipart requests and splits them into separate events:\n * - MG_EV_HTTP_MULTIPART_REQUEST: Start of the request.\n *   This event is sent before body is parsed. After this, the user\n *   should expect a sequence of PART_BEGIN/DATA/END requests.\n *   This is also the last time when headers and other request fields are\n *   accessible.\n * - MG_EV_HTTP_PART_BEGIN: Start of a part of a multipart message.\n *   Argument: mg_http_multipart_part with var_name and file_name set\n *   (if present). No data is passed in this message.\n * - MG_EV_HTTP_PART_DATA: new portion of data from the multipart message.\n *   Argument: mg_http_multipart_part. var_name and file_name are preserved,\n *   data is available in mg_http_multipart_part.data.\n * - MG_EV_HTTP_PART_END: End of the current part. var_name, file_name are\n *   the same, no data in the message. If status is 0, then the part is\n *   properly terminated with a boundary, status < 0 means that connection\n *   was terminated.\n * - MG_EV_HTTP_MULTIPART_REQUEST_END: End of the multipart request.\n *   Argument: mg_http_multipart_part, var_name and file_name are NULL,\n *   status = 0 means request was properly closed, < 0 means connection\n *   was terminated (note: in this case both PART_END and REQUEST_END are\n *   delivered).\n */\nvoid mg_set_protocol_http_websocket(struct mg_connection *nc);\n\n#if MG_ENABLE_HTTP_WEBSOCKET\n/*\n * Send websocket handshake to the server.\n *\n * `nc` must be a valid connection, connected to a server. `uri` is an URI\n * to fetch, extra_headers` is extra HTTP headers to send or `NULL`.\n *\n * This function is intended to be used by websocket client.\n *\n * Note that the Host header is mandatory in HTTP/1.1 and must be\n * included in `extra_headers`. `mg_send_websocket_handshake2` offers\n * a better API for that.\n *\n * Deprecated in favour of `mg_send_websocket_handshake2`\n */\nvoid mg_send_websocket_handshake(struct mg_connection *nc, const char *uri,\n                                 const char *extra_headers);\n\n/*\n * Send websocket handshake to the server.\n *\n * `nc` must be a valid connection, connected to a server. `uri` is an URI\n * to fetch, `host` goes into the `Host` header, `protocol` goes into the\n * `Sec-WebSocket-Proto` header (NULL to omit), extra_headers` is extra HTTP\n * headers to send or `NULL`.\n *\n * This function is intended to be used by websocket client.\n */\nvoid mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,\n                                  const char *host, const char *protocol,\n                                  const char *extra_headers);\n\n/* Like mg_send_websocket_handshake2 but also passes basic auth header */\nvoid mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,\n                                  const char *host, const char *protocol,\n                                  const char *extra_headers, const char *user,\n                                  const char *pass);\n\n/* Same as mg_send_websocket_handshake3 but with strings not necessarily\n * NUL-temrinated */\nvoid mg_send_websocket_handshake3v(struct mg_connection *nc,\n                                   const struct mg_str path,\n                                   const struct mg_str host,\n                                   const struct mg_str protocol,\n                                   const struct mg_str extra_headers,\n                                   const struct mg_str user,\n                                   const struct mg_str pass);\n\n/*\n * Helper function that creates an outbound WebSocket connection.\n *\n * `url` is a URL to connect to. It must be properly URL-encoded, e.g. have\n * no spaces, etc. By default, `mg_connect_ws()` sends Connection and\n * Host headers. `extra_headers` is an extra HTTP header to send, e.g.\n * `\"User-Agent: my-app\\r\\n\"`.\n * If `protocol` is not NULL, then a `Sec-WebSocket-Protocol` header is sent.\n *\n * Examples:\n *\n * ```c\n *   nc1 = mg_connect_ws(mgr, ev_handler_1, \"ws://echo.websocket.org\", NULL,\n *                       NULL);\n *   nc2 = mg_connect_ws(mgr, ev_handler_1, \"wss://echo.websocket.org\", NULL,\n *                       NULL);\n *   nc3 = mg_connect_ws(mgr, ev_handler_1, \"ws://api.cesanta.com\",\n *                       \"clubby.cesanta.com\", NULL);\n * ```\n */\nstruct mg_connection *mg_connect_ws(struct mg_mgr *mgr,\n                                    MG_CB(mg_event_handler_t event_handler,\n                                          void *user_data),\n                                    const char *url, const char *protocol,\n                                    const char *extra_headers);\n\n/*\n * Helper function that creates an outbound WebSocket connection\n *\n * Mostly identical to `mg_connect_ws`, but allows to provide extra parameters\n * (for example, SSL parameters)\n */\nstruct mg_connection *mg_connect_ws_opt(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    struct mg_connect_opts opts, const char *url, const char *protocol,\n    const char *extra_headers);\n\n/*\n * Send WebSocket frame to the remote end.\n *\n * `op_and_flags` specifies the frame's type. It's one of:\n *\n * - WEBSOCKET_OP_CONTINUE\n * - WEBSOCKET_OP_TEXT\n * - WEBSOCKET_OP_BINARY\n * - WEBSOCKET_OP_CLOSE\n * - WEBSOCKET_OP_PING\n * - WEBSOCKET_OP_PONG\n *\n * Orred with one of the flags:\n *\n * - WEBSOCKET_DONT_FIN: Don't set the FIN flag on the frame to be sent.\n *\n * `data` and `data_len` contain frame data.\n */\nvoid mg_send_websocket_frame(struct mg_connection *nc, int op_and_flags,\n                             const void *data, size_t data_len);\n\n/*\n * Like `mg_send_websocket_frame()`, but composes a single frame from multiple\n * buffers.\n */\nvoid mg_send_websocket_framev(struct mg_connection *nc, int op_and_flags,\n                              const struct mg_str *strings, int num_strings);\n\n/*\n * Sends WebSocket frame to the remote end.\n *\n * Like `mg_send_websocket_frame()`, but allows to create formatted messages\n * with `printf()`-like semantics.\n */\nvoid mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags,\n                               const char *fmt, ...);\n\n/* Websocket opcodes, from http://tools.ietf.org/html/rfc6455 */\n#define WEBSOCKET_OP_CONTINUE 0\n#define WEBSOCKET_OP_TEXT 1\n#define WEBSOCKET_OP_BINARY 2\n#define WEBSOCKET_OP_CLOSE 8\n#define WEBSOCKET_OP_PING 9\n#define WEBSOCKET_OP_PONG 10\n\n/*\n * If set causes the FIN flag to not be set on outbound\n * frames. This enables sending multiple fragments of a single\n * logical message.\n *\n * The WebSocket protocol mandates that if the FIN flag of a data\n * frame is not set, the next frame must be a WEBSOCKET_OP_CONTINUE.\n * The last frame must have the FIN bit set.\n *\n * Note that mongoose will automatically defragment incoming messages,\n * so this flag is used only on outbound messages.\n */\n#define WEBSOCKET_DONT_FIN 0x100\n\n#endif /* MG_ENABLE_HTTP_WEBSOCKET */\n\n/*\n * Decodes a URL-encoded string.\n *\n * Source string is specified by (`src`, `src_len`), and destination is\n * (`dst`, `dst_len`). If `is_form_url_encoded` is non-zero, then\n * `+` character is decoded as a blank space character. This function\n * guarantees to NUL-terminate the destination. If destination is too small,\n * then the source string is partially decoded and `-1` is returned.\n *Otherwise,\n * a length of the decoded string is returned, not counting final NUL.\n */\nint mg_url_decode(const char *src, int src_len, char *dst, int dst_len,\n                  int is_form_url_encoded);\n\nextern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],\n                          const size_t *msg_lens, uint8_t *digest);\nextern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],\n                           const size_t *msg_lens, uint8_t *digest);\n\n/*\n * Flags for `mg_http_is_authorized()`.\n */\n#define MG_AUTH_FLAG_IS_DIRECTORY (1 << 0)\n#define MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE (1 << 1)\n#define MG_AUTH_FLAG_ALLOW_MISSING_FILE (1 << 2)\n\n/*\n * Checks whether an http request is authorized. `domain` is the authentication\n * realm, `passwords_file` is a htdigest file (can be created e.g. with\n * `htdigest` utility). If either `domain` or `passwords_file` is NULL, this\n * function always returns 1; otherwise checks the authentication in the\n * http request and returns 1 only if there is a match; 0 otherwise.\n */\nint mg_http_is_authorized(struct http_message *hm, struct mg_str path,\n                          const char *domain, const char *passwords_file,\n                          int flags);\n\n/*\n * Sends 401 Unauthorized response.\n */\nvoid mg_http_send_digest_auth_request(struct mg_connection *c,\n                                      const char *domain);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MG_ENABLE_HTTP */\n\n#endif /* CS_MONGOOSE_SRC_HTTP_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http_server.h\"\n#endif\n/*\n * === Server API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_HTTP_SERVER_H_\n#define CS_MONGOOSE_SRC_HTTP_SERVER_H_\n\n#if MG_ENABLE_HTTP\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Parses a HTTP message.\n *\n * `is_req` should be set to 1 if parsing a request, 0 if reply.\n *\n * Returns the number of bytes parsed. If HTTP message is\n * incomplete `0` is returned. On parse error, a negative number is returned.\n */\nint mg_parse_http(const char *s, int n, struct http_message *hm, int is_req);\n\n/*\n * Searches and returns the header `name` in parsed HTTP message `hm`.\n * If header is not found, NULL is returned. Example:\n *\n *     struct mg_str *host_hdr = mg_get_http_header(hm, \"Host\");\n */\nstruct mg_str *mg_get_http_header(struct http_message *hm, const char *name);\n\n/*\n * Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value\n * in the buffer `*buf`, `buf_size`. If the buffer size is not enough,\n * allocates a buffer of required size and writes it to `*buf`, similar to\n * asprintf(). The caller should always check whether the buffer was updated,\n * and free it if so.\n *\n * This function is supposed to parse cookies, authentication headers, etc.\n * Example (error handling omitted):\n *\n *     char user_buf[20];\n *     char *user = user_buf;\n *     struct mg_str *hdr = mg_get_http_header(hm, \"Authorization\");\n *     mg_http_parse_header2(hdr, \"username\", &user, sizeof(user_buf));\n *     // ... do something useful with user\n *     if (user != user_buf) {\n *       free(user);\n *     }\n *\n * Returns the length of the variable's value. If variable is not found, 0 is\n * returned.\n */\nint mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,\n                          size_t buf_size);\n\n/*\n * DEPRECATED: use mg_http_parse_header2() instead.\n *\n * Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of\n * `char **`), and thus it cannot allocate a new buffer if the provided one\n * is not enough, and just returns 0 in that case.\n */\nint mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,\n                         size_t buf_size)\n#ifdef __GNUC__\n    __attribute__((deprecated))\n#endif\n    ;\n\n/*\n * Gets and parses the Authorization: Basic header\n * Returns -1 if no Authorization header is found, or if\n * mg_parse_http_basic_auth\n * fails parsing the resulting header.\n */\nint mg_get_http_basic_auth(struct http_message *hm, char *user, size_t user_len,\n                           char *pass, size_t pass_len);\n\n/*\n * Parses the Authorization: Basic header\n * Returns -1 iif the authorization type is not \"Basic\" or any other error such\n * as incorrectly encoded base64 user password pair.\n */\nint mg_parse_http_basic_auth(struct mg_str *hdr, char *user, size_t user_len,\n                             char *pass, size_t pass_len);\n\n/*\n * Parses the buffer `buf`, `buf_len` that contains multipart form data chunks.\n * Stores the chunk name in a `var_name`, `var_name_len` buffer.\n * If a chunk is an uploaded file, then `file_name`, `file_name_len` is\n * filled with an uploaded file name. `chunk`, `chunk_len`\n * points to the chunk data.\n *\n * Return: number of bytes to skip to the next chunk or 0 if there are\n *         no more chunks.\n *\n * Usage example:\n *\n * ```c\n *    static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {\n *      switch(ev) {\n *        case MG_EV_HTTP_REQUEST: {\n *          struct http_message *hm = (struct http_message *) ev_data;\n *          char var_name[100], file_name[100];\n *          const char *chunk;\n *          size_t chunk_len, n1, n2;\n *\n *          n1 = n2 = 0;\n *          while ((n2 = mg_parse_multipart(hm->body.p + n1,\n *                                          hm->body.len - n1,\n *                                          var_name, sizeof(var_name),\n *                                          file_name, sizeof(file_name),\n *                                          &chunk, &chunk_len)) > 0) {\n *            printf(\"var: %s, file_name: %s, size: %d, chunk: [%.*s]\\n\",\n *                   var_name, file_name, (int) chunk_len,\n *                   (int) chunk_len, chunk);\n *            n1 += n2;\n *          }\n *        }\n *        break;\n * ```\n */\nsize_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,\n                          size_t var_name_len, char *file_name,\n                          size_t file_name_len, const char **chunk,\n                          size_t *chunk_len);\n\n/*\n * Fetches a HTTP form variable.\n *\n * Fetches a variable `name` from a `buf` into a buffer specified by `dst`,\n * `dst_len`. The destination is always zero-terminated. Returns the length of\n * a fetched variable. If not found, 0 is returned. `buf` must be valid\n * url-encoded buffer. If destination is too small or an error occured,\n * negative number is returned.\n */\nint mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,\n                    size_t dst_len);\n\n#if MG_ENABLE_FILESYSTEM\n/*\n * This structure defines how `mg_serve_http()` works.\n * Best practice is to set only required settings, and leave the rest as NULL.\n */\nstruct mg_serve_http_opts {\n  /* Path to web root directory */\n  const char *document_root;\n\n  /* List of index files. Default is \"\" */\n  const char *index_files;\n\n  /*\n   * Leave as NULL to disable authentication.\n   * To enable directory protection with authentication, set this to \".htpasswd\"\n   * Then, creating \".htpasswd\" file in any directory automatically protects\n   * it with digest authentication.\n   * Use `mongoose` web server binary, or `htdigest` Apache utility to\n   * create/manipulate passwords file.\n   * Make sure `auth_domain` is set to a valid domain name.\n   */\n  const char *per_directory_auth_file;\n\n  /* Authorization domain (domain name of this web server) */\n  const char *auth_domain;\n\n  /*\n   * Leave as NULL to disable authentication.\n   * Normally, only selected directories in the document root are protected.\n   * If absolutely every access to the web server needs to be authenticated,\n   * regardless of the URI, set this option to the path to the passwords file.\n   * Format of that file is the same as \".htpasswd\" file. Make sure that file\n   * is located outside document root to prevent people fetching it.\n   */\n  const char *global_auth_file;\n\n  /* Set to \"no\" to disable directory listing. Enabled by default. */\n  const char *enable_directory_listing;\n\n  /*\n   * SSI files pattern. If not set, \"**.shtml$|**.shtm$\" is used.\n   *\n   * All files that match ssi_pattern are treated as SSI.\n   *\n   * Server Side Includes (SSI) is a simple interpreted server-side scripting\n   * language which is most commonly used to include the contents of a file\n   * into a web page. It can be useful when it is desirable to include a common\n   * piece of code throughout a website, for example, headers and footers.\n   *\n   * In order for a webpage to recognize an SSI-enabled HTML file, the\n   * filename should end with a special extension, by default the extension\n   * should be either .shtml or .shtm\n   *\n   * Unknown SSI directives are silently ignored by Mongoose. Currently,\n   * the following SSI directives are supported:\n   *    &lt;!--#include FILE_TO_INCLUDE --&gt;\n   *    &lt;!--#exec \"COMMAND_TO_EXECUTE\" --&gt;\n   *    &lt;!--#call COMMAND --&gt;\n   *\n   * Note that &lt;!--#include ...> directive supports three path\n   *specifications:\n   *\n   * &lt;!--#include virtual=\"path\" --&gt;  Path is relative to web server root\n   * &lt;!--#include abspath=\"path\" --&gt;  Path is absolute or relative to the\n   *                                  web server working dir\n   * &lt;!--#include file=\"path\" --&gt;,    Path is relative to current document\n   * &lt;!--#include \"path\" --&gt;\n   *\n   * The include directive may be used to include the contents of a file or\n   * the result of running a CGI script.\n   *\n   * The exec directive is used to execute\n   * a command on a server, and show command's output. Example:\n   *\n   * &lt;!--#exec \"ls -l\" --&gt;\n   *\n   * The call directive is a way to invoke a C handler from the HTML page.\n   * On each occurence of &lt;!--#call COMMAND OPTIONAL_PARAMS> directive,\n   * Mongoose calls a registered event handler with MG_EV_SSI_CALL event,\n   * and event parameter will point to the COMMAND OPTIONAL_PARAMS string.\n   * An event handler can output any text, for example by calling\n   * `mg_printf()`. This is a flexible way of generating a web page on\n   * server side by calling a C event handler. Example:\n   *\n   * &lt;!--#call foo --&gt; ... &lt;!--#call bar --&gt;\n   *\n   * In the event handler:\n   *    case MG_EV_SSI_CALL: {\n   *      const char *param = (const char *) ev_data;\n   *      if (strcmp(param, \"foo\") == 0) {\n   *        mg_printf(c, \"hello from foo\");\n   *      } else if (strcmp(param, \"bar\") == 0) {\n   *        mg_printf(c, \"hello from bar\");\n   *      }\n   *      break;\n   *    }\n   */\n  const char *ssi_pattern;\n\n  /* IP ACL. By default, NULL, meaning all IPs are allowed to connect */\n  const char *ip_acl;\n\n#if MG_ENABLE_HTTP_URL_REWRITES\n  /* URL rewrites.\n   *\n   * Comma-separated list of `uri_pattern=url_file_or_directory_path` rewrites.\n   * When HTTP request is received, Mongoose constructs a file name from the\n   * requested URI by combining `document_root` and the URI. However, if the\n   * rewrite option is used and `uri_pattern` matches requested URI, then\n   * `document_root` is ignored. Instead, `url_file_or_directory_path` is used,\n   * which should be a full path name or a path relative to the web server's\n   * current working directory. It can also be an URI (http:// or https://)\n   * in which case mongoose will behave as a reverse proxy for that destination.\n   *\n   * Note that `uri_pattern`, as all Mongoose patterns, is a prefix pattern.\n   *\n   * If uri_pattern starts with `@` symbol, then Mongoose compares it with the\n   * HOST header of the request. If they are equal, Mongoose sets document root\n   * to `file_or_directory_path`, implementing virtual hosts support.\n   * Example: `@foo.com=/document/root/for/foo.com`\n   *\n   * If `uri_pattern` starts with `%` symbol, then Mongoose compares it with\n   * the listening port. If they match, then Mongoose issues a 301 redirect.\n   * For example, to redirect all HTTP requests to the\n   * HTTPS port, do `%80=https://my.site.com`. Note that the request URI is\n   * automatically appended to the redirect location.\n   */\n  const char *url_rewrites;\n#endif\n\n  /* DAV document root. If NULL, DAV requests are going to fail. */\n  const char *dav_document_root;\n\n  /*\n   * DAV passwords file. If NULL, DAV requests are going to fail.\n   * If passwords file is set to \"-\", then DAV auth is disabled.\n   */\n  const char *dav_auth_file;\n\n  /* Glob pattern for the files to hide. */\n  const char *hidden_file_pattern;\n\n  /* Set to non-NULL to enable CGI, e.g. **.cgi$|**.php$\" */\n  const char *cgi_file_pattern;\n\n  /* If not NULL, ignore CGI script hashbang and use this interpreter */\n  const char *cgi_interpreter;\n\n  /*\n   * Comma-separated list of Content-Type overrides for path suffixes, e.g.\n   * \".txt=text/plain; charset=utf-8,.c=text/plain\"\n   */\n  const char *custom_mime_types;\n\n  /*\n   * Extra HTTP headers to add to each server response.\n   * Example: to enable CORS, set this to \"Access-Control-Allow-Origin: *\".\n   */\n  const char *extra_headers;\n};\n\n/*\n * Serves given HTTP request according to the `options`.\n *\n * Example code snippet:\n *\n * ```c\n * static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {\n *   struct http_message *hm = (struct http_message *) ev_data;\n *   struct mg_serve_http_opts opts = { .document_root = \"/var/www\" };  // C99\n *\n *   switch (ev) {\n *     case MG_EV_HTTP_REQUEST:\n *       mg_serve_http(nc, hm, opts);\n *       break;\n *     default:\n *       break;\n *   }\n * }\n * ```\n */\nvoid mg_serve_http(struct mg_connection *nc, struct http_message *hm,\n                   struct mg_serve_http_opts opts);\n\n/*\n * Serves a specific file with a given MIME type and optional extra headers.\n *\n * Example code snippet:\n *\n * ```c\n * static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {\n *   switch (ev) {\n *     case MG_EV_HTTP_REQUEST: {\n *       struct http_message *hm = (struct http_message *) ev_data;\n *       mg_http_serve_file(nc, hm, \"file.txt\",\n *                          mg_mk_str(\"text/plain\"), mg_mk_str(\"\"));\n *       break;\n *     }\n *     ...\n *   }\n * }\n * ```\n */\nvoid mg_http_serve_file(struct mg_connection *nc, struct http_message *hm,\n                        const char *path, const struct mg_str mime_type,\n                        const struct mg_str extra_headers);\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n\n/* Callback prototype for `mg_file_upload_handler()`. */\ntypedef struct mg_str (*mg_fu_fname_fn)(struct mg_connection *nc,\n                                        struct mg_str fname);\n\n/*\n * File upload handler.\n * This handler can be used to implement file uploads with minimum code.\n * This handler will process MG_EV_HTTP_PART_* events and store file data into\n * a local file.\n * `local_name_fn` will be invoked with whatever name was provided by the client\n * and will expect the name of the local file to open. A return value of NULL\n * will abort file upload (client will get a \"403 Forbidden\" response). If\n * non-null, the returned string must be heap-allocated and will be freed by\n * the caller.\n * Exception: it is ok to return the same string verbatim.\n *\n * Example:\n *\n * ```c\n * struct mg_str upload_fname(struct mg_connection *nc, struct mg_str fname) {\n *   // Just return the same filename. Do not actually do this except in test!\n *   // fname is user-controlled and needs to be sanitized.\n *   return fname;\n * }\n * void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {\n *   switch (ev) {\n *     ...\n *     case MG_EV_HTTP_PART_BEGIN:\n *     case MG_EV_HTTP_PART_DATA:\n *     case MG_EV_HTTP_PART_END:\n *       mg_file_upload_handler(nc, ev, ev_data, upload_fname);\n *       break;\n *   }\n * }\n * ```\n */\nvoid mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,\n                            mg_fu_fname_fn local_name_fn\n                                MG_UD_ARG(void *user_data));\n#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */\n#endif /* MG_ENABLE_FILESYSTEM */\n\n/*\n * Registers a callback for a specified http endpoint\n * Note: if callback is registered it is called instead of the\n * callback provided in mg_bind\n *\n * Example code snippet:\n *\n * ```c\n * static void handle_hello1(struct mg_connection *nc, int ev, void *ev_data) {\n *   (void) ev; (void) ev_data;\n *   mg_printf(nc, \"HTTP/1.0 200 OK\\r\\n\\r\\n[I am Hello1]\");\n *  nc->flags |= MG_F_SEND_AND_CLOSE;\n * }\n *\n * static void handle_hello2(struct mg_connection *nc, int ev, void *ev_data) {\n *  (void) ev; (void) ev_data;\n *   mg_printf(nc, \"HTTP/1.0 200 OK\\r\\n\\r\\n[I am Hello2]\");\n *  nc->flags |= MG_F_SEND_AND_CLOSE;\n * }\n *\n * void init() {\n *   nc = mg_bind(&mgr, local_addr, cb1);\n *   mg_register_http_endpoint(nc, \"/hello1\", handle_hello1);\n *   mg_register_http_endpoint(nc, \"/hello1/hello2\", handle_hello2);\n * }\n * ```\n */\nvoid mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,\n                               MG_CB(mg_event_handler_t handler,\n                                     void *user_data));\n\nstruct mg_http_endpoint_opts {\n  void *user_data;\n  /* Authorization domain (realm) */\n  const char *auth_domain;\n  const char *auth_file;\n};\n\nvoid mg_register_http_endpoint_opt(struct mg_connection *nc,\n                                   const char *uri_path,\n                                   mg_event_handler_t handler,\n                                   struct mg_http_endpoint_opts opts);\n\n/*\n * Authenticates a HTTP request against an opened password file.\n * Returns 1 if authenticated, 0 otherwise.\n */\nint mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,\n                              FILE *fp);\n\n/*\n * Authenticates given response params against an opened password file.\n * Returns 1 if authenticated, 0 otherwise.\n *\n * It's used by mg_http_check_digest_auth().\n */\nint mg_check_digest_auth(struct mg_str method, struct mg_str uri,\n                         struct mg_str username, struct mg_str cnonce,\n                         struct mg_str response, struct mg_str qop,\n                         struct mg_str nc, struct mg_str nonce,\n                         struct mg_str auth_domain, FILE *fp);\n\n/*\n * Sends buffer `buf` of size `len` to the client using chunked HTTP encoding.\n * This function sends the buffer size as hex number + newline first, then\n * the buffer itself, then the newline. For example,\n * `mg_send_http_chunk(nc, \"foo\", 3)` will append the `3\\r\\nfoo\\r\\n` string\n * to the `nc->send_mbuf` output IO buffer.\n *\n * NOTE: The HTTP header \"Transfer-Encoding: chunked\" should be sent prior to\n * using this function.\n *\n * NOTE: do not forget to send an empty chunk at the end of the response,\n * to tell the client that everything was sent. Example:\n *\n * ```\n *   mg_printf_http_chunk(nc, \"%s\", \"my response!\");\n *   mg_send_http_chunk(nc, \"\", 0); // Tell the client we're finished\n * ```\n */\nvoid mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len);\n\n/*\n * Sends a printf-formatted HTTP chunk.\n * Functionality is similar to `mg_send_http_chunk()`.\n */\nvoid mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);\n\n/*\n * Sends the response status line.\n * If `extra_headers` is not NULL, then `extra_headers` are also sent\n * after the response line. `extra_headers` must NOT end end with new line.\n * Example:\n *\n *      mg_send_response_line(nc, 200, \"Access-Control-Allow-Origin: *\");\n *\n * Will result in:\n *\n *      HTTP/1.1 200 OK\\r\\n\n *      Access-Control-Allow-Origin: *\\r\\n\n */\nvoid mg_send_response_line(struct mg_connection *nc, int status_code,\n                           const char *extra_headers);\n\n/*\n * Sends an error response. If reason is NULL, the message will be inferred\n * from the error code (if supported).\n */\nvoid mg_http_send_error(struct mg_connection *nc, int code, const char *reason);\n\n/*\n * Sends a redirect response.\n * `status_code` should be either 301 or 302 and `location` point to the\n * new location.\n * If `extra_headers` is not empty, then `extra_headers` are also sent\n * after the response line. `extra_headers` must NOT end end with new line.\n *\n * Example:\n *\n *      mg_http_send_redirect(nc, 302, mg_mk_str(\"/login\"), mg_mk_str(NULL));\n */\nvoid mg_http_send_redirect(struct mg_connection *nc, int status_code,\n                           const struct mg_str location,\n                           const struct mg_str extra_headers);\n\n/*\n * Sends the response line and headers.\n * This function sends the response line with the `status_code`, and\n * automatically\n * sends one header: either \"Content-Length\" or \"Transfer-Encoding\".\n * If `content_length` is negative, then \"Transfer-Encoding: chunked\" header\n * is sent, otherwise, \"Content-Length\" header is sent.\n *\n * NOTE: If `Transfer-Encoding` is `chunked`, then message body must be sent\n * using `mg_send_http_chunk()` or `mg_printf_http_chunk()` functions.\n * Otherwise, `mg_send()` or `mg_printf()` must be used.\n * Extra headers could be set through `extra_headers`. Note `extra_headers`\n * must NOT be terminated by a new line.\n */\nvoid mg_send_head(struct mg_connection *n, int status_code,\n                  int64_t content_length, const char *extra_headers);\n\n/*\n * Sends a printf-formatted HTTP chunk, escaping HTML tags.\n */\nvoid mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...);\n\n#if MG_ENABLE_HTTP_URL_REWRITES\n/*\n * Proxies a given request to a given upstream http server. The path prefix\n * in `mount` will be stripped of the path requested to the upstream server,\n * e.g. if mount is /api and upstream is http://localhost:8001/foo\n * then an incoming request to /api/bar will cause a request to\n * http://localhost:8001/foo/bar\n *\n * EXPERIMENTAL API. Please use http_serve_http + url_rewrites if a static\n * mapping is good enough.\n */\nvoid mg_http_reverse_proxy(struct mg_connection *nc,\n                           const struct http_message *hm, struct mg_str mount,\n                           struct mg_str upstream);\n#endif\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MG_ENABLE_HTTP */\n\n#endif /* CS_MONGOOSE_SRC_HTTP_SERVER_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http_client.h\"\n#endif\n/*\n * === Client API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_HTTP_CLIENT_H_\n#define CS_MONGOOSE_SRC_HTTP_CLIENT_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Helper function that creates an outbound HTTP connection.\n *\n * `url` is the URL to fetch. It must be properly URL-encoded, e.g. have\n * no spaces, etc. By default, `mg_connect_http()` sends the Connection and\n * Host headers. `extra_headers` is an extra HTTP header to send, e.g.\n * `\"User-Agent: my-app\\r\\n\"`.\n * If `post_data` is NULL, then a GET request is created. Otherwise, a POST\n * request is created with the specified POST data. Note that if the data being\n * posted is a form submission, the `Content-Type` header should be set\n * accordingly (see example below).\n *\n * Examples:\n *\n * ```c\n *   nc1 = mg_connect_http(mgr, ev_handler_1, \"http://www.google.com\", NULL,\n *                         NULL);\n *   nc2 = mg_connect_http(mgr, ev_handler_1, \"https://github.com\", NULL, NULL);\n *   nc3 = mg_connect_http(\n *       mgr, ev_handler_1, \"my_server:8000/form_submit/\",\n *       \"Content-Type: application/x-www-form-urlencoded\\r\\n\",\n *       \"var_1=value_1&var_2=value_2\");\n * ```\n */\nstruct mg_connection *mg_connect_http(\n    struct mg_mgr *mgr,\n    MG_CB(mg_event_handler_t event_handler, void *user_data), const char *url,\n    const char *extra_headers, const char *post_data);\n\n/*\n * Helper function that creates an outbound HTTP connection.\n *\n * Mostly identical to mg_connect_http, but allows you to provide extra\n *parameters\n * (for example, SSL parameters)\n */\nstruct mg_connection *mg_connect_http_opt(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    struct mg_connect_opts opts, const char *url, const char *extra_headers,\n    const char *post_data);\n\n/* Creates digest authentication header for a client request. */\nint mg_http_create_digest_auth_header(char *buf, size_t buf_len,\n                                      const char *method, const char *uri,\n                                      const char *auth_domain, const char *user,\n                                      const char *passwd, const char *nonce);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* CS_MONGOOSE_SRC_HTTP_CLIENT_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_mqtt.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n/*\n * === MQTT API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_MQTT_H_\n#define CS_MONGOOSE_SRC_MQTT_H_\n\n/* Amalgamated: #include \"mg_net.h\" */\n\nstruct mg_mqtt_message {\n  int cmd;\n  int qos;\n  int len; /* message length in the IO buffer */\n  struct mg_str topic;\n  struct mg_str payload;\n\n  uint8_t connack_ret_code; /* connack */\n  uint16_t message_id;      /* puback */\n\n  /* connect */\n  uint8_t protocol_version;\n  uint8_t connect_flags;\n  uint16_t keep_alive_timer;\n  struct mg_str protocol_name;\n  struct mg_str client_id;\n  struct mg_str will_topic;\n  struct mg_str will_message;\n  struct mg_str user_name;\n  struct mg_str password;\n};\n\nstruct mg_mqtt_topic_expression {\n  const char *topic;\n  uint8_t qos;\n};\n\nstruct mg_send_mqtt_handshake_opts {\n  unsigned char flags; /* connection flags */\n  uint16_t keep_alive;\n  const char *will_topic;\n  const char *will_message;\n  const char *user_name;\n  const char *password;\n};\n\n/* mg_mqtt_proto_data should be in header to allow external access to it */\nstruct mg_mqtt_proto_data {\n  uint16_t keep_alive;\n  double last_control_time;\n};\n\n/* Message types */\n#define MG_MQTT_CMD_CONNECT 1\n#define MG_MQTT_CMD_CONNACK 2\n#define MG_MQTT_CMD_PUBLISH 3\n#define MG_MQTT_CMD_PUBACK 4\n#define MG_MQTT_CMD_PUBREC 5\n#define MG_MQTT_CMD_PUBREL 6\n#define MG_MQTT_CMD_PUBCOMP 7\n#define MG_MQTT_CMD_SUBSCRIBE 8\n#define MG_MQTT_CMD_SUBACK 9\n#define MG_MQTT_CMD_UNSUBSCRIBE 10\n#define MG_MQTT_CMD_UNSUBACK 11\n#define MG_MQTT_CMD_PINGREQ 12\n#define MG_MQTT_CMD_PINGRESP 13\n#define MG_MQTT_CMD_DISCONNECT 14\n\n/* MQTT event types */\n#define MG_MQTT_EVENT_BASE 200\n#define MG_EV_MQTT_CONNECT (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_CONNECT)\n#define MG_EV_MQTT_CONNACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_CONNACK)\n#define MG_EV_MQTT_PUBLISH (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBLISH)\n#define MG_EV_MQTT_PUBACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBACK)\n#define MG_EV_MQTT_PUBREC (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBREC)\n#define MG_EV_MQTT_PUBREL (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBREL)\n#define MG_EV_MQTT_PUBCOMP (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PUBCOMP)\n#define MG_EV_MQTT_SUBSCRIBE (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_SUBSCRIBE)\n#define MG_EV_MQTT_SUBACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_SUBACK)\n#define MG_EV_MQTT_UNSUBSCRIBE (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_UNSUBSCRIBE)\n#define MG_EV_MQTT_UNSUBACK (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_UNSUBACK)\n#define MG_EV_MQTT_PINGREQ (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PINGREQ)\n#define MG_EV_MQTT_PINGRESP (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_PINGRESP)\n#define MG_EV_MQTT_DISCONNECT (MG_MQTT_EVENT_BASE + MG_MQTT_CMD_DISCONNECT)\n\n/* Message flags */\n#define MG_MQTT_RETAIN 0x1\n#define MG_MQTT_DUP 0x4\n#define MG_MQTT_QOS(qos) ((qos) << 1)\n#define MG_MQTT_GET_QOS(flags) (((flags) &0x6) >> 1)\n#define MG_MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1)\n\n/* Connection flags */\n#define MG_MQTT_CLEAN_SESSION 0x02\n#define MG_MQTT_HAS_WILL 0x04\n#define MG_MQTT_WILL_RETAIN 0x20\n#define MG_MQTT_HAS_PASSWORD 0x40\n#define MG_MQTT_HAS_USER_NAME 0x80\n#define MG_MQTT_GET_WILL_QOS(flags) (((flags) &0x18) >> 3)\n#define MG_MQTT_SET_WILL_QOS(flags, qos) \\\n  (flags) = ((flags) & ~0x18) | ((qos) << 3)\n\n/* CONNACK return codes */\n#define MG_EV_MQTT_CONNACK_ACCEPTED 0\n#define MG_EV_MQTT_CONNACK_UNACCEPTABLE_VERSION 1\n#define MG_EV_MQTT_CONNACK_IDENTIFIER_REJECTED 2\n#define MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE 3\n#define MG_EV_MQTT_CONNACK_BAD_AUTH 4\n#define MG_EV_MQTT_CONNACK_NOT_AUTHORIZED 5\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Attaches a built-in MQTT event handler to the given connection.\n *\n * The user-defined event handler will receive following extra events:\n *\n * - MG_EV_MQTT_CONNACK\n * - MG_EV_MQTT_PUBLISH\n * - MG_EV_MQTT_PUBACK\n * - MG_EV_MQTT_PUBREC\n * - MG_EV_MQTT_PUBREL\n * - MG_EV_MQTT_PUBCOMP\n * - MG_EV_MQTT_SUBACK\n */\nvoid mg_set_protocol_mqtt(struct mg_connection *nc);\n\n/* Sends an MQTT handshake. */\nvoid mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id);\n\n/* Sends an MQTT handshake with optional parameters. */\nvoid mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,\n                                struct mg_send_mqtt_handshake_opts);\n\n/* Publishes a message to a given topic. */\nvoid mg_mqtt_publish(struct mg_connection *nc, const char *topic,\n                     uint16_t message_id, int flags, const void *data,\n                     size_t len);\n\n/* Subscribes to a bunch of topics. */\nvoid mg_mqtt_subscribe(struct mg_connection *nc,\n                       const struct mg_mqtt_topic_expression *topics,\n                       size_t topics_len, uint16_t message_id);\n\n/* Unsubscribes from a bunch of topics. */\nvoid mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,\n                         size_t topics_len, uint16_t message_id);\n\n/* Sends a DISCONNECT command. */\nvoid mg_mqtt_disconnect(struct mg_connection *nc);\n\n/* Sends a CONNACK command with a given `return_code`. */\nvoid mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code);\n\n/* Sends a PUBACK command with a given `message_id`. */\nvoid mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id);\n\n/* Sends a PUBREC command with a given `message_id`. */\nvoid mg_mqtt_pubrec(struct mg_connection *nc, uint16_t message_id);\n\n/* Sends a PUBREL command with a given `message_id`. */\nvoid mg_mqtt_pubrel(struct mg_connection *nc, uint16_t message_id);\n\n/* Sends a PUBCOMP command with a given `message_id`. */\nvoid mg_mqtt_pubcomp(struct mg_connection *nc, uint16_t message_id);\n\n/*\n * Sends a SUBACK command with a given `message_id`\n * and a sequence of granted QoSs.\n */\nvoid mg_mqtt_suback(struct mg_connection *nc, uint8_t *qoss, size_t qoss_len,\n                    uint16_t message_id);\n\n/* Sends a UNSUBACK command with a given `message_id`. */\nvoid mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id);\n\n/* Sends a PINGREQ command. */\nvoid mg_mqtt_ping(struct mg_connection *nc);\n\n/* Sends a PINGRESP command. */\nvoid mg_mqtt_pong(struct mg_connection *nc);\n\n/*\n * Extracts the next topic expression from a SUBSCRIBE command payload.\n *\n * The topic expression name will point to a string in the payload buffer.\n * Returns the pos of the next topic expression or -1 when the list\n * of topics is exhausted.\n */\nint mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,\n                                 struct mg_str *topic, uint8_t *qos, int pos);\n\n/*\n * Matches a topic against a topic expression\n *\n * Returns 1 if it matches; 0 otherwise.\n */\nint mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic);\n\n/*\n * Same as `mg_mqtt_match_topic_expression()`, but takes `exp` as a\n * NULL-terminated string.\n */\nint mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_MONGOOSE_SRC_MQTT_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_mqtt_server.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n/*\n * === MQTT Server API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_MQTT_BROKER_H_\n#define CS_MONGOOSE_SRC_MQTT_BROKER_H_\n\n#if MG_ENABLE_MQTT_BROKER\n\n/* Amalgamated: #include \"common/queue.h\" */\n/* Amalgamated: #include \"mg_mqtt.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifndef MG_MQTT_MAX_SESSION_SUBSCRIPTIONS\n#define MG_MQTT_MAX_SESSION_SUBSCRIPTIONS 512\n#endif\n\nstruct mg_mqtt_broker;\n\n/* MQTT session (Broker side). */\nstruct mg_mqtt_session {\n  struct mg_mqtt_broker *brk;       /* Broker */\n  LIST_ENTRY(mg_mqtt_session) link; /* mg_mqtt_broker::sessions linkage */\n  struct mg_connection *nc;         /* Connection with the client */\n  size_t num_subscriptions;         /* Size of `subscriptions` array */\n  void *user_data;                  /* User data */\n  struct mg_mqtt_topic_expression *subscriptions;\n};\n\n/* MQTT broker. */\nstruct mg_mqtt_broker {\n  LIST_HEAD(_mg_sesshead, mg_mqtt_session) sessions; /* Session list */\n  void *user_data;                                   /* User data */\n};\n\n/* Initialises a MQTT broker. */\nvoid mg_mqtt_broker_init(struct mg_mqtt_broker *brk, void *user_data);\n\n/*\n * Processes a MQTT broker message.\n *\n * The listening connection expects a pointer to an initialised\n * `mg_mqtt_broker` structure in the `user_data` field.\n *\n * Basic usage:\n *\n * ```c\n * mg_mqtt_broker_init(&brk, NULL);\n *\n * if ((nc = mg_bind(&mgr, address, mg_mqtt_broker)) == NULL) {\n *   // fail;\n * }\n * nc->user_data = &brk;\n * ```\n *\n * New incoming connections will receive a `mg_mqtt_session` structure\n * in the connection `user_data`. The original `user_data` will be stored\n * in the `user_data` field of the session structure. This allows the user\n * handler to store user data before `mg_mqtt_broker` creates the session.\n *\n * Since only the MG_EV_ACCEPT message is processed by the listening socket,\n * for most events the `user_data` will thus point to a `mg_mqtt_session`.\n */\nvoid mg_mqtt_broker(struct mg_connection *brk, int ev, void *data);\n\n/*\n * Iterates over all MQTT session connections. Example:\n *\n * ```c\n * struct mg_mqtt_session *s;\n * for (s = mg_mqtt_next(brk, NULL); s != NULL; s = mg_mqtt_next(brk, s)) {\n *   // Do something\n * }\n * ```\n */\nstruct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *brk,\n                                     struct mg_mqtt_session *s);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MG_ENABLE_MQTT_BROKER */\n#endif /* CS_MONGOOSE_SRC_MQTT_BROKER_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_dns.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/*\n * === DNS API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_DNS_H_\n#define CS_MONGOOSE_SRC_DNS_H_\n\n/* Amalgamated: #include \"mg_net.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#define MG_DNS_A_RECORD 0x01     /* Lookup IP address */\n#define MG_DNS_CNAME_RECORD 0x05 /* Lookup CNAME */\n#define MG_DNS_PTR_RECORD 0x0c   /* Lookup PTR */\n#define MG_DNS_TXT_RECORD 0x10   /* Lookup TXT */\n#define MG_DNS_AAAA_RECORD 0x1c  /* Lookup IPv6 address */\n#define MG_DNS_SRV_RECORD 0x21   /* Lookup SRV */\n#define MG_DNS_MX_RECORD 0x0f    /* Lookup mail server for domain */\n#define MG_DNS_ANY_RECORD 0xff\n#define MG_DNS_NSEC_RECORD 0x2f\n\n#define MG_MAX_DNS_QUESTIONS 32\n#define MG_MAX_DNS_ANSWERS 32\n\n#define MG_DNS_MESSAGE 100 /* High-level DNS message event */\n\nenum mg_dns_resource_record_kind {\n  MG_DNS_INVALID_RECORD = 0,\n  MG_DNS_QUESTION,\n  MG_DNS_ANSWER\n};\n\n/* DNS resource record. */\nstruct mg_dns_resource_record {\n  struct mg_str name; /* buffer with compressed name */\n  int rtype;\n  int rclass;\n  int ttl;\n  enum mg_dns_resource_record_kind kind;\n  struct mg_str rdata; /* protocol data (can be a compressed name) */\n};\n\n/* DNS message (request and response). */\nstruct mg_dns_message {\n  struct mg_str pkt; /* packet body */\n  uint16_t flags;\n  uint16_t transaction_id;\n  int num_questions;\n  int num_answers;\n  struct mg_dns_resource_record questions[MG_MAX_DNS_QUESTIONS];\n  struct mg_dns_resource_record answers[MG_MAX_DNS_ANSWERS];\n};\n\nstruct mg_dns_resource_record *mg_dns_next_record(\n    struct mg_dns_message *msg, int query, struct mg_dns_resource_record *prev);\n\n/*\n * Parses the record data from a DNS resource record.\n *\n *  - A:     struct in_addr *ina\n *  - AAAA:  struct in6_addr *ina\n *  - CNAME: char buffer\n *\n * Returns -1 on error.\n *\n * TODO(mkm): MX\n */\nint mg_dns_parse_record_data(struct mg_dns_message *msg,\n                             struct mg_dns_resource_record *rr, void *data,\n                             size_t data_len);\n\n/*\n * Sends a DNS query to the remote end.\n */\nvoid mg_send_dns_query(struct mg_connection *nc, const char *name,\n                       int query_type);\n\n/*\n * Inserts a DNS header to an IO buffer.\n *\n * Returns the number of bytes inserted.\n */\nint mg_dns_insert_header(struct mbuf *io, size_t pos,\n                         struct mg_dns_message *msg);\n\n/*\n * Appends already encoded questions from an existing message.\n *\n * This is useful when generating a DNS reply message which includes\n * all question records.\n *\n * Returns the number of appended bytes.\n */\nint mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg);\n\n/*\n * Encodes and appends a DNS resource record to an IO buffer.\n *\n * The record metadata is taken from the `rr` parameter, while the name and data\n * are taken from the parameters, encoded in the appropriate format depending on\n * record type and stored in the IO buffer. The encoded values might contain\n * offsets within the IO buffer. It's thus important that the IO buffer doesn't\n * get trimmed while a sequence of records are encoded while preparing a DNS\n * reply.\n *\n * This function doesn't update the `name` and `rdata` pointers in the `rr`\n * struct because they might be invalidated as soon as the IO buffer grows\n * again.\n *\n * Returns the number of bytes appended or -1 in case of error.\n */\nint mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr,\n                         const char *name, size_t nlen, const void *rdata,\n                         size_t rlen);\n\n/*\n * Encodes a DNS name.\n */\nint mg_dns_encode_name(struct mbuf *io, const char *name, size_t len);\n\n/* Low-level: parses a DNS response. */\nint mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg);\n\n/*\n * Uncompresses a DNS compressed name.\n *\n * The containing DNS message is required because of the compressed encoding\n * and reference suffixes present elsewhere in the packet.\n *\n * If the name is less than `dst_len` characters long, the remainder\n * of `dst` is terminated with `\\0` characters. Otherwise, `dst` is not\n * terminated.\n *\n * If `dst_len` is 0 `dst` can be NULL.\n * Returns the uncompressed name length.\n */\nsize_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,\n                              char *dst, int dst_len);\n\n/*\n * Attaches a built-in DNS event handler to the given listening connection.\n *\n * The DNS event handler parses the incoming UDP packets, treating them as DNS\n * requests. If an incoming packet gets successfully parsed by the DNS event\n * handler, a user event handler will receive an `MG_DNS_REQUEST` event, with\n * `ev_data` pointing to the parsed `struct mg_dns_message`.\n *\n * See\n * [captive_dns_server](https://github.com/cesanta/mongoose/tree/master/examples/captive_dns_server)\n * example on how to handle DNS request and send DNS reply.\n */\nvoid mg_set_protocol_dns(struct mg_connection *nc);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* CS_MONGOOSE_SRC_DNS_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_dns_server.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/*\n * === DNS server API reference\n *\n * Disabled by default; enable with `-DMG_ENABLE_DNS_SERVER`.\n */\n\n#ifndef CS_MONGOOSE_SRC_DNS_SERVER_H_\n#define CS_MONGOOSE_SRC_DNS_SERVER_H_\n\n#if MG_ENABLE_DNS_SERVER\n\n/* Amalgamated: #include \"mg_dns.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#define MG_DNS_SERVER_DEFAULT_TTL 3600\n\nstruct mg_dns_reply {\n  struct mg_dns_message *msg;\n  struct mbuf *io;\n  size_t start;\n};\n\n/*\n * Creates a DNS reply.\n *\n * The reply will be based on an existing query message `msg`.\n * The query body will be appended to the output buffer.\n * \"reply + recursion allowed\" will be added to the message flags and the\n * message's num_answers will be set to 0.\n *\n * Answer records can be appended with `mg_dns_send_reply` or by lower\n * level function defined in the DNS API.\n *\n * In order to send a reply use `mg_dns_send_reply`.\n * It's possible to use a connection's send buffer as reply buffer,\n * and it will work for both UDP and TCP connections.\n *\n * Example:\n *\n * ```c\n * reply = mg_dns_create_reply(&nc->send_mbuf, msg);\n * for (i = 0; i < msg->num_questions; i++) {\n *   rr = &msg->questions[i];\n *   if (rr->rtype == MG_DNS_A_RECORD) {\n *     mg_dns_reply_record(&reply, rr, 3600, &dummy_ip_addr, 4);\n *   }\n * }\n * mg_dns_send_reply(nc, &reply);\n * ```\n */\nstruct mg_dns_reply mg_dns_create_reply(struct mbuf *io,\n                                        struct mg_dns_message *msg);\n\n/*\n * Appends a DNS reply record to the IO buffer and to the DNS message.\n *\n * The message's num_answers field will be incremented. It's the caller's duty\n * to ensure num_answers is properly initialised.\n *\n * Returns -1 on error.\n */\nint mg_dns_reply_record(struct mg_dns_reply *reply,\n                        struct mg_dns_resource_record *question,\n                        const char *name, int rtype, int ttl, const void *rdata,\n                        size_t rdata_len);\n\n/*\n * Sends a DNS reply through a connection.\n *\n * The DNS data is stored in an IO buffer pointed by reply structure in `r`.\n * This function mutates the content of that buffer in order to ensure that\n * the DNS header reflects the size and flags of the message, that might have\n * been updated either with `mg_dns_reply_record` or by direct manipulation of\n * `r->message`.\n *\n * Once sent, the IO buffer will be trimmed unless the reply IO buffer\n * is the connection's send buffer and the connection is not in UDP mode.\n */\nvoid mg_dns_send_reply(struct mg_connection *nc, struct mg_dns_reply *r);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MG_ENABLE_DNS_SERVER */\n#endif /* CS_MONGOOSE_SRC_DNS_SERVER_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_resolv.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/*\n * === API reference\n */\n\n#ifndef CS_MONGOOSE_SRC_RESOLV_H_\n#define CS_MONGOOSE_SRC_RESOLV_H_\n\n/* Amalgamated: #include \"mg_dns.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nenum mg_resolve_err {\n  MG_RESOLVE_OK = 0,\n  MG_RESOLVE_NO_ANSWERS = 1,\n  MG_RESOLVE_EXCEEDED_RETRY_COUNT = 2,\n  MG_RESOLVE_TIMEOUT = 3\n};\n\ntypedef void (*mg_resolve_callback_t)(struct mg_dns_message *dns_message,\n                                      void *user_data, enum mg_resolve_err);\n\n/* Options for `mg_resolve_async_opt`. */\nstruct mg_resolve_async_opts {\n  const char *nameserver;\n  int max_retries;    /* defaults to 2 if zero */\n  int timeout;        /* in seconds; defaults to 5 if zero */\n  int accept_literal; /* pseudo-resolve literal ipv4 and ipv6 addrs */\n  int only_literal;   /* only resolves literal addrs; sync cb invocation */\n  struct mg_connection **dns_conn; /* return DNS connection */\n};\n\n/* See `mg_resolve_async_opt()` */\nint mg_resolve_async(struct mg_mgr *mgr, const char *name, int query,\n                     mg_resolve_callback_t cb, void *data);\n\n/* Set default DNS server */\nvoid mg_set_nameserver(struct mg_mgr *mgr, const char *nameserver);\n\n/*\n * Resolved a DNS name asynchronously.\n *\n * Upon successful resolution, the user callback will be invoked\n * with the full DNS response message and a pointer to the user's\n * context `data`.\n *\n * In case of timeout while performing the resolution the callback\n * will receive a NULL `msg`.\n *\n * The DNS answers can be extracted with `mg_next_record` and\n * `mg_dns_parse_record_data`:\n *\n * [source,c]\n * ----\n * struct in_addr ina;\n * struct mg_dns_resource_record *rr = mg_next_record(msg, MG_DNS_A_RECORD,\n *   NULL);\n * mg_dns_parse_record_data(msg, rr, &ina, sizeof(ina));\n * ----\n */\nint mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,\n                         mg_resolve_callback_t cb, void *data,\n                         struct mg_resolve_async_opts opts);\n\n/*\n * Resolve a name from `/etc/hosts`.\n *\n * Returns 0 on success, -1 on failure.\n */\nint mg_resolve_from_hosts_file(const char *host, union socket_address *usa);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* CS_MONGOOSE_SRC_RESOLV_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_coap.h\"\n#endif\n/*\n * Copyright (c) 2015 Cesanta Software Limited\n * All rights reserved\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n/*\n * === CoAP API reference\n *\n * CoAP message format:\n *\n * ```\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-\n * |Ver| T | TKL | Code | Message ID | Token (if any, TKL bytes) ...\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-\n * | Options (if any) ...            |1 1 1 1 1 1 1 1| Payload (if any) ...\n * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-\n * ```\n */\n\n#ifndef CS_MONGOOSE_SRC_COAP_H_\n#define CS_MONGOOSE_SRC_COAP_H_\n\n#if MG_ENABLE_COAP\n\n#define MG_COAP_MSG_TYPE_FIELD 0x2\n#define MG_COAP_CODE_CLASS_FIELD 0x4\n#define MG_COAP_CODE_DETAIL_FIELD 0x8\n#define MG_COAP_MSG_ID_FIELD 0x10\n#define MG_COAP_TOKEN_FIELD 0x20\n#define MG_COAP_OPTIOMG_FIELD 0x40\n#define MG_COAP_PAYLOAD_FIELD 0x80\n\n#define MG_COAP_ERROR 0x10000\n#define MG_COAP_FORMAT_ERROR (MG_COAP_ERROR | 0x20000)\n#define MG_COAP_IGNORE (MG_COAP_ERROR | 0x40000)\n#define MG_COAP_NOT_ENOUGH_DATA (MG_COAP_ERROR | 0x80000)\n#define MG_COAP_NETWORK_ERROR (MG_COAP_ERROR | 0x100000)\n\n#define MG_COAP_MSG_CON 0\n#define MG_COAP_MSG_NOC 1\n#define MG_COAP_MSG_ACK 2\n#define MG_COAP_MSG_RST 3\n#define MG_COAP_MSG_MAX 3\n\n#define MG_COAP_CODECLASS_REQUEST 0\n#define MG_COAP_CODECLASS_RESP_OK 2\n#define MG_COAP_CODECLASS_CLIENT_ERR 4\n#define MG_COAP_CODECLASS_SRV_ERR 5\n\n#define MG_COAP_EVENT_BASE 300\n#define MG_EV_COAP_CON (MG_COAP_EVENT_BASE + MG_COAP_MSG_CON)\n#define MG_EV_COAP_NOC (MG_COAP_EVENT_BASE + MG_COAP_MSG_NOC)\n#define MG_EV_COAP_ACK (MG_COAP_EVENT_BASE + MG_COAP_MSG_ACK)\n#define MG_EV_COAP_RST (MG_COAP_EVENT_BASE + MG_COAP_MSG_RST)\n\n/*\n * CoAP options.\n * Use mg_coap_add_option and mg_coap_free_options\n * for creation and destruction.\n */\nstruct mg_coap_option {\n  struct mg_coap_option *next;\n  uint32_t number;\n  struct mg_str value;\n};\n\n/* CoAP message. See RFC 7252 for details. */\nstruct mg_coap_message {\n  uint32_t flags;\n  uint8_t msg_type;\n  uint8_t code_class;\n  uint8_t code_detail;\n  uint16_t msg_id;\n  struct mg_str token;\n  struct mg_coap_option *options;\n  struct mg_str payload;\n  struct mg_coap_option *optiomg_tail;\n};\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/* Sets CoAP protocol handler - triggers CoAP specific events. */\nint mg_set_protocol_coap(struct mg_connection *nc);\n\n/*\n * Adds a new option to mg_coap_message structure.\n * Returns pointer to the newly created option.\n * Note: options must be freed by using mg_coap_free_options\n */\nstruct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,\n                                          uint32_t number, char *value,\n                                          size_t len);\n\n/*\n * Frees the memory allocated for options.\n * If the cm parameter doesn't contain any option it does nothing.\n */\nvoid mg_coap_free_options(struct mg_coap_message *cm);\n\n/*\n * Composes a CoAP message from `mg_coap_message`\n * and sends it into `nc` connection.\n * Returns 0 on success. On error, it is a bitmask:\n *\n * - `#define MG_COAP_ERROR 0x10000`\n * - `#define MG_COAP_FORMAT_ERROR (MG_COAP_ERROR | 0x20000)`\n * - `#define MG_COAP_IGNORE (MG_COAP_ERROR | 0x40000)`\n * - `#define MG_COAP_NOT_ENOUGH_DATA (MG_COAP_ERROR | 0x80000)`\n * - `#define MG_COAP_NETWORK_ERROR (MG_COAP_ERROR | 0x100000)`\n */\nuint32_t mg_coap_send_message(struct mg_connection *nc,\n                              struct mg_coap_message *cm);\n\n/*\n * Composes CoAP acknowledgement from `mg_coap_message`\n * and sends it into `nc` connection.\n * Return value: see `mg_coap_send_message()`\n */\nuint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id);\n\n/*\n * Parses CoAP message and fills mg_coap_message and returns cm->flags.\n * This is a helper function.\n *\n * NOTE: usually CoAP works over UDP, so lack of data means format error.\n * But, in theory, it is possible to use CoAP over TCP (according to RFC)\n *\n * The caller has to check results and treat COAP_NOT_ENOUGH_DATA according to\n * underlying protocol:\n *\n * - in case of UDP COAP_NOT_ENOUGH_DATA means COAP_FORMAT_ERROR,\n * - in case of TCP client can try to receive more data\n *\n * Return value: see `mg_coap_send_message()`\n */\nuint32_t mg_coap_parse(struct mbuf *io, struct mg_coap_message *cm);\n\n/*\n * Composes CoAP message from mg_coap_message structure.\n * This is a helper function.\n * Return value: see `mg_coap_send_message()`\n */\nuint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MG_ENABLE_COAP */\n\n#endif /* CS_MONGOOSE_SRC_COAP_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_sntp.h\"\n#endif\n/*\n * Copyright (c) 2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_SNTP_H_\n#define CS_MONGOOSE_SRC_SNTP_H_\n\n#if MG_ENABLE_SNTP\n\n#define MG_SNTP_EVENT_BASE 500\n\n/*\n * Received reply from time server. Event handler parameter contains\n * pointer to mg_sntp_message structure\n */\n#define MG_SNTP_REPLY (MG_SNTP_EVENT_BASE + 1)\n\n/* Received malformed SNTP packet */\n#define MG_SNTP_MALFORMED_REPLY (MG_SNTP_EVENT_BASE + 2)\n\n/* Failed to get time from server (timeout etc) */\n#define MG_SNTP_FAILED (MG_SNTP_EVENT_BASE + 3)\n\nstruct mg_sntp_message {\n  /* if server sends this flags, user should not send requests to it */\n  int kiss_of_death;\n  /* usual mg_time */\n  double time;\n};\n\n/* Establishes connection to given sntp server */\nstruct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,\n                                      MG_CB(mg_event_handler_t event_handler,\n                                            void *user_data),\n                                      const char *sntp_server_name);\n\n/* Sends time request to given connection */\nvoid mg_sntp_send_request(struct mg_connection *c);\n\n/*\n * Helper function\n * Establishes connection to time server, tries to send request\n * repeats sending SNTP_ATTEMPTS times every SNTP_TIMEOUT sec\n * (if needed)\n * See sntp_client example\n */\nstruct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr,\n                                       mg_event_handler_t event_handler,\n                                       const char *sntp_server_name);\n\n#endif\n\n#endif /* CS_MONGOOSE_SRC_SNTP_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_socks.h\"\n#endif\n/*\n * Copyright (c) 2017 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_SOCKS_H_\n#define CS_MONGOOSE_SRC_SOCKS_H_\n\n#if MG_ENABLE_SOCKS\n\n#define MG_SOCKS_VERSION 5\n\n#define MG_SOCKS_HANDSHAKE_DONE MG_F_USER_1\n#define MG_SOCKS_CONNECT_DONE MG_F_USER_2\n\n/* SOCKS5 handshake methods */\nenum mg_socks_handshake_method {\n  MG_SOCKS_HANDSHAKE_NOAUTH = 0,     /* Handshake method - no authentication */\n  MG_SOCKS_HANDSHAKE_GSSAPI = 1,     /* Handshake method - GSSAPI auth */\n  MG_SOCKS_HANDSHAKE_USERPASS = 2,   /* Handshake method - user/password auth */\n  MG_SOCKS_HANDSHAKE_FAILURE = 0xff, /* Handshake method - failure */\n};\n\n/* SOCKS5 commands */\nenum mg_socks_command {\n  MG_SOCKS_CMD_CONNECT = 1,       /* Command: CONNECT */\n  MG_SOCKS_CMD_BIND = 2,          /* Command: BIND */\n  MG_SOCKS_CMD_UDP_ASSOCIATE = 3, /* Command: UDP ASSOCIATE */\n};\n\n/* SOCKS5 address types */\nenum mg_socks_address_type {\n  MG_SOCKS_ADDR_IPV4 = 1,   /* Address type: IPv4 */\n  MG_SOCKS_ADDR_DOMAIN = 3, /* Address type: Domain name */\n  MG_SOCKS_ADDR_IPV6 = 4,   /* Address type: IPv6 */\n};\n\n/* SOCKS5 response codes */\nenum mg_socks_response {\n  MG_SOCKS_SUCCESS = 0,            /* Response: success */\n  MG_SOCKS_FAILURE = 1,            /* Response: failure */\n  MG_SOCKS_NOT_ALLOWED = 2,        /* Response: connection not allowed */\n  MG_SOCKS_NET_UNREACHABLE = 3,    /* Response: network unreachable */\n  MG_SOCKS_HOST_UNREACHABLE = 4,   /* Response: network unreachable */\n  MG_SOCKS_CONN_REFUSED = 5,       /* Response: network unreachable */\n  MG_SOCKS_TTL_EXPIRED = 6,        /* Response: network unreachable */\n  MG_SOCKS_CMD_NOT_SUPPORTED = 7,  /* Response: network unreachable */\n  MG_SOCKS_ADDR_NOT_SUPPORTED = 8, /* Response: network unreachable */\n};\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/* Turn the connection into the SOCKS server */\nvoid mg_set_protocol_socks(struct mg_connection *c);\n\n/* Create socks tunnel for the client connection */\nstruct mg_iface *mg_socks_mk_iface(struct mg_mgr *, const char *proxy_addr);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif\n#endif\n\n#endif /* INCLUDE_MONGOOSE_H_ */\n"
  },
  {
    "path": "include/optparse.h",
    "content": "/** @file\n    Option parsing functions to complement getopt.\n\n    Copyright (C) 2017 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OPTPARSE_H_\n#define INCLUDE_OPTPARSE_H_\n\n#include <stdint.h>\n\n// makes strcasecmp() and strncasecmp() available when including optparse.h\n#ifdef _MSC_VER\n    #include <string.h>\n    #define strcasecmp(s1,s2)     _stricmp(s1,s2)\n    #define strncasecmp(s1,s2,n)  _strnicmp(s1,s2,n)\n#else\n    #include <strings.h>\n#endif\n\n/// TLS settings.\ntypedef struct tls_opts {\n    /// Client certificate to present to the server.\n    char const *tls_cert;\n    /// Private key corresponding to the certificate.\n    /// If tls_cert is set but tls_key is not, tls_cert is used.\n    char const *tls_key;\n    /// Verify server certificate using this CA bundle. If set to \"*\", then TLS\n    /// is enabled but no cert verification is performed.\n    char const *tls_ca_cert;\n    /// Colon-delimited list of acceptable cipher suites.\n    /// Names depend on the library used, for example:\n    /// ECDH-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256 (OpenSSL)\n    /// For OpenSSL the list can be obtained by running \"openssl ciphers\".\n    /// If NULL, a reasonable default is used.\n    char const *tls_cipher_suites;\n    /// Server name verification. If tls_ca_cert is set and the certificate has\n    /// passed verification, its subject will be verified against this string.\n    /// By default (if tls_server_name is NULL) hostname part of the address will\n    /// be used. Wildcard matching is supported. A special value of \"*\" disables\n    /// name verification.\n    char const *tls_server_name;\n    /// PSK identity is a NUL-terminated string.\n    /// Note: Default list of cipher suites does not include PSK suites, if you\n    /// want to use PSK you will need to set tls_cipher_suites as well.\n    char const *tls_psk_identity;\n    /// PSK key hex string, must be either 16 or 32 bytes (32 or 64 hex digits)\n    /// for AES-128 or AES-256 respectively.\n    char const *tls_psk_key;\n} tls_opts_t;\n\n/// Parse a TLS option.\n///\n/// @sa tls_opts_t\n/// @return 0 if the option was valid, error code otherwise\nint tls_param(tls_opts_t *tls_opts, char const *key, char const *val);\n\n/// Convert string to bool with fallback default.\n/// Parses \"true\", \"yes\", \"on\", \"enable\" (not case-sensitive) to 1, atoi() otherwise.\nint atobv(char const *arg, int def);\n\n/// Convert string to int with fallback default.\nint atoiv(char const *arg, int def);\n\n/// Get the next colon or comma separated arg, NULL otherwise.\n/// Returns string including comma if a comma is found first,\n/// otherwise string after colon if found, NULL otherwise.\nchar *arg_param(char const *arg);\n\n/// Convert a string with optional leading equals char to a double.\n///\n/// Parse errors will fprintf(stderr, ...) and exit(1).\n///\n/// @param str character string to parse\n/// @param error_hint prepended to error output\n/// @return parsed number value\ndouble arg_float(char const *str, char const *error_hint);\n\n/// Parse param string to host and port.\n/// E.g. \":514\", \"localhost\", \"[::1]\", \"127.0.0.1:514\", \"[::1]:514\",\n/// also \"//localhost\", \"//localhost:514\", \"//:514\".\n/// Host or port are terminated at a comma, if found.\n/// @return the remaining options\nchar *hostport_param(char *param, char const **host, char const **port);\n\n/// Convert a string to an unsigned integer, uses strtod() and accepts\n/// metric suffixes of 'k', 'M', and 'G' (also 'K', 'm', and 'g').\n///\n/// Parse errors will fprintf(stderr, ...) and exit(1).\n///\n/// @param str character string to parse\n/// @param error_hint prepended to error output\n/// @return parsed number value\nuint32_t atouint32_metric(char const *str, char const *error_hint);\n\n/// Convert a string to an integer, uses strtod() and accepts\n/// time suffixes of 'd', 'h', 'm', and 's' (also 'D', 'H', 'M', and 'S'),\n/// or the form hours:minutes[:seconds].\n///\n/// Parse errors will fprintf(stderr, ...) and exit(1).\n///\n/// @param str character string to parse\n/// @param error_hint prepended to error output\n/// @return parsed number value in seconds\nint atoi_time(char const *str, char const *error_hint);\n\n/// Similar to strsep.\n///\n/// @param[in,out] stringp String to parse inplace\n/// @param delim the delimiter character\n/// @return the original value of *stringp\nchar *asepc(char **stringp, char delim);\n\n/// Similar to strsep but bounded by a stop character.\n///\n/// @param[in,out] stringp String to parse inplace\n/// @param delim the delimiter character\n/// @param stop the bounding character at which to stop\n/// @return the original value of *stringp\nchar *asepcb(char **stringp, char delim, char stop);\n\n/// Match the first key in a comma-separated list of key/value pairs.\n///\n/// @param s String of key=value pairs, separated by commas\n/// @param key keyword argument to match with\n/// @param[out] val value if found, NULL otherwise\n/// @return 1 if the key matches exactly, 0 otherwise\nint kwargs_match(char const *s, char const *key, char const **val);\n\n/// Skip the first key/value in a comma-separated list of key/value pairs.\n///\n/// @param s String of key=value pairs, separated by commas\n/// @return the next key in s, end of string or NULL otherwise\nchar const *kwargs_skip(char const *s);\n\n/// Parse a comma-separated list of key/value pairs into kwargs.\n///\n/// The input string will be modified and the pointer advanced.\n/// The key and val pointers will be into the original string.\n///\n/// @param[in,out] s String of key=value pairs, separated by commas\n/// @param[out] key keyword argument if found, NULL otherwise\n/// @param[out] val value if found, NULL otherwise\n/// @return the original value of *stringp (the keyword found)\nchar *getkwargs(char **s, char **key, char **val);\n\n/// Trim left and right whitespace in string.\n///\n/// @param[in,out] str String to change inplace\n/// @return the trimmed value of str\nchar *trim_ws(char *str);\n\n/// Remove all whitespace from string.\n///\n/// @param[in,out] str String to change inplace\n/// @return the stripped value of str\nchar *remove_ws(char *str);\n\n#endif /* INCLUDE_OPTPARSE_H_ */\n"
  },
  {
    "path": "include/output_file.h",
    "content": "/** @file\n    File outputs for rtl_433 events.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_FILE_H_\n#define INCLUDE_OUTPUT_FILE_H_\n\n#include \"data.h\"\n#include <stdio.h>\n\n/** Construct data output for CSV printer.\n\n    @param log_level the highest log level to process\n    @param file the output stream\n    @return The auxiliary data to pass along with data_csv_printer to data_print.\n            You must release this object with data_output_free once you're done with it.\n*/\nstruct data_output *data_output_csv_create(int log_level, FILE *file);\n\nstruct data_output *data_output_json_create(int log_level, FILE *file);\n\nstruct data_output *data_output_kv_create(int log_level, FILE *file);\n\n#endif /* INCLUDE_OUTPUT_FILE_H_ */\n"
  },
  {
    "path": "include/output_influx.h",
    "content": "/** @file\n    InfluxDB output for rtl_433 events\n\n    Copyright (C) 2019 Daniel Krueger\n    based on output_mqtt.c\n    Copyright (C) 2019 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_INFLUX_H_\n#define INCLUDE_OUTPUT_INFLUX_H_\n\n#include \"data.h\"\n\nstruct mg_mgr;\n\nstruct data_output *data_output_influx_create(struct mg_mgr *mgr, char *opts);\n\n#endif /* INCLUDE_OUTPUT_INFLUX_H_ */\n"
  },
  {
    "path": "include/output_log.h",
    "content": "/** @file\n    Log outputs for rtl_433 events.\n\n    Copyright (C) 2022 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_LOG_H_\n#define INCLUDE_OUTPUT_LOG_H_\n\n#include \"data.h\"\n#include <stdio.h>\n\n/** Construct data output for LOG printer.\n\n    @param log_level the highest log level to process\n    @param file the optional output stream, defaults to stderr\n    @return The auxiliary data to pass along with data_log_printer to data_print.\n            You must release this object with data_output_free once you're done with it.\n*/\nstruct data_output *data_output_log_create(int log_level, FILE *file);\n\n#endif /* INCLUDE_OUTPUT_LOG_H_ */\n"
  },
  {
    "path": "include/output_mqtt.h",
    "content": "/** @file\n    MQTT output for rtl_433 events\n\n    Copyright (C) 2019 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_MQTT_H_\n#define INCLUDE_OUTPUT_MQTT_H_\n\n#include \"data.h\"\n\nstruct mg_mgr;\n\nstruct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char *param, char const *dev_hint);\n\n#endif /* INCLUDE_OUTPUT_MQTT_H_ */\n"
  },
  {
    "path": "include/output_rtltcp.h",
    "content": "/** @file\n    rtl_tcp output for rtl_433 raw data.\n\n    Copyright (C) 2022 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_RTLTCP_H_\n#define INCLUDE_OUTPUT_RTLTCP_H_\n\n#include \"raw_output.h\"\n\n#include <stdint.h>\n\nstruct r_cfg;\n\n/** Construct rtl_tcp data output.\n\n    @param host the server host to bind\n    @param port the server port to bind\n    @param opts additional options, currently \"control\" enables write access\n    @param cfg the r_api config to use\n    @return The initialized rtltcp output instance.\n            You must release this object with raw_output_free once you're done with it.\n*/\nstruct raw_output *raw_output_rtltcp_create(char const *host, char const *port, char const *opts, struct r_cfg *cfg);\n\n#endif /* INCLUDE_OUTPUT_RTLTCP_H_ */\n"
  },
  {
    "path": "include/output_trigger.h",
    "content": "/** @file\n    Trigger output for rtl_433 events.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_TRIGGER_H_\n#define INCLUDE_OUTPUT_TRIGGER_H_\n\n#include \"data.h\"\n#include <stdio.h>\n\n/// Construct data output for a trigger stream.\n///\n/// This will print a `1` to the stream for every event.\n///\n/// Use e.g. on a Raspberry Pi to flash the LED:\n///\n///     $ sudo chmod a+w /sys/class/leds/led0/shot\n///     $ echo oneshot | sudo tee /sys/class/leds/led0/trigger\n///     $ rtl_433 ... -F trigger:/sys/class/leds/led0/shot\n///\n/// @param file a trigger output stream\n/// @return The initialized data output.\n///         You must release this object with data_output_free once you're done with it.\nstruct data_output *data_output_trigger_create(FILE *file);\n\n#endif /* INCLUDE_OUTPUT_TRIGGER_H_ */\n"
  },
  {
    "path": "include/output_udp.h",
    "content": "/** @file\n    UDP syslog output for rtl_433 events.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_OUTPUT_UDP_H_\n#define INCLUDE_OUTPUT_UDP_H_\n\n#include \"data.h\"\n\nstruct data_output *data_output_syslog_create(int log_level, const char *host, const char *port);\n\n#endif /* INCLUDE_OUTPUT_UDP_H_ */\n"
  },
  {
    "path": "include/pulse_analyzer.h",
    "content": "/** @file\n    Pulse analyzer functions.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_PULSE_ANALYZER_H_\n#define INCLUDE_PULSE_ANALYZER_H_\n\n#include \"pulse_detect.h\"\n\nstruct r_device;\n\n/// Analyze and print result.\nvoid pulse_analyzer(pulse_data_t *data, int package_type, struct r_device *device);\n\n#endif /* INCLUDE_PULSE_ANALYZER_H_ */\n"
  },
  {
    "path": "include/pulse_data.h",
    "content": "/** @file\n    Pulse data structure and functions.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_PULSE_DATA_H_\n#define INCLUDE_PULSE_DATA_H_\n\n#include <stdint.h>\n#include <stdio.h>\n#include \"data.h\"\n#include \"compat_time.h\"\n\n#define PD_MAX_PULSES        1200 // Maximum number of pulses before forcing End Of Package\n#define PD_MIN_PULSES        16   // Minimum number of pulses before declaring a proper package\n#define PD_MIN_PULSE_SAMPLES 10   // Minimum number of samples in a pulse for proper detection\n#define PD_MIN_GAP_MS        10   // Minimum gap size in milliseconds to exceed to declare End Of Package\n#define PD_MAX_GAP_MS        100  // Maximum gap size in milliseconds to exceed to declare End Of Package\n#define PD_MAX_GAP_RATIO     10   // Ratio gap/pulse width to exceed to declare End Of Package (heuristic)\n#define PD_MAX_PULSE_MS      100  // Pulse width in ms to exceed to declare End Of Package (e.g. for non OOK packages)\n\n/// Data for a compact representation of generic pulse train.\ntypedef struct pulse_data {\n    uint64_t offset;      ///< Offset to first pulse in number of samples from start of stream.\n    uint32_t sample_rate; ///< Sample rate the pulses are recorded with.\n    unsigned depth_bits;  ///< Sample depth in bits.\n    unsigned start_ago;   ///< Start of first pulse in number of samples ago.\n    unsigned end_ago;     ///< End of last pulse in number of samples ago.\n    unsigned int num_pulses;\n    int pulse[PD_MAX_PULSES]; ///< Width of pulses (high) in number of samples.\n    int gap[PD_MAX_PULSES];   ///< Width of gaps between pulses (low) in number of samples.\n    int ook_low_estimate;     ///< Estimate for the OOK low level (base noise level) at beginning of package.\n    int ook_high_estimate;    ///< Estimate for the OOK high level at end of package.\n    int fsk_f1_est;           ///< Estimate for the F1 frequency for FSK.\n    int fsk_f2_est;           ///< Estimate for the F2 frequency for FSK.\n    float freq1_hz;\n    float freq2_hz;\n    float centerfreq_hz;\n    float range_db;\n    float rssi_db;\n    float snr_db;\n    float noise_db;\n} pulse_data_t;\n\n/// Clear the content of a pulse_data_t structure.\nvoid pulse_data_clear(pulse_data_t *data);\n\n/// Shift out part of the data to make room for more.\nvoid pulse_data_shift(pulse_data_t *data);\n\n/// Print the content of a pulse_data_t structure (for debug).\nvoid pulse_data_print(pulse_data_t const *data);\n\n/// Dump the content of a pulse_data_t structure as raw binary.\nvoid pulse_data_dump_raw(uint8_t *buf, unsigned len, uint64_t buf_offset, pulse_data_t const *data, uint8_t bits);\n\n/// Print a header for the VCD format.\nvoid pulse_data_print_vcd_header(FILE *file, uint32_t sample_rate);\n\n/// Print the content of a pulse_data_t structure in VCD format.\nvoid pulse_data_print_vcd(FILE *file, pulse_data_t const *data, int ch_id);\n\n/// Read the next pulse_data_t structure from OOK text.\nvoid pulse_data_load(FILE *file, struct timeval *now, pulse_data_t *data, uint32_t sample_rate);\n\n/// Print a header for the OOK text format.\nvoid pulse_data_print_pulse_header(FILE *file);\n\n/// Print the content of a pulse_data_t structure as OOK text.\nvoid pulse_data_dump(FILE *file, pulse_data_t const *data);\n\n/// Print the content of a pulse_data_t structure as OOK json.\ndata_t *pulse_data_print_data(pulse_data_t const *data);\n\n#endif /* INCLUDE_PULSE_DATA_H_ */\n"
  },
  {
    "path": "include/pulse_detect.h",
    "content": "/** @file\n    Pulse detection functions.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_PULSE_DETECT_H_\n#define INCLUDE_PULSE_DETECT_H_\n\n#include <stdint.h>\n#include <stdio.h>\n#include \"pulse_data.h\"\n#include \"data.h\"\n\n/// Package types.\nenum package_types {\n    PULSE_DATA_OOK = 1,\n    PULSE_DATA_FSK = 2,\n};\n\n/// FSK pulse detector to use.\nenum {\n    FSK_PULSE_DETECT_OLD,\n    FSK_PULSE_DETECT_NEW,\n    FSK_PULSE_DETECT_AUTO,\n    FSK_PULSE_DETECT_END,\n};\n\ntypedef struct pulse_detect pulse_detect_t;\n\npulse_detect_t *pulse_detect_create(void);\n\nvoid pulse_detect_free(pulse_detect_t *pulse_detect);\n\n/// Reset pulse detector to initial values.\nvoid pulse_detect_reset(pulse_detect_t *pulse_detect);\n\n/// Set pulse detector level values.\n///\n/// @param pulse_detect The pulse_detect instance\n/// @param use_mag_est Use magnitude instead of amplitude\n/// @param fixed_high_level Manual high level override, default is 0 (auto)\n/// @param min_high_level Minimum high level, default is -12 dB\n/// @param high_low_ratio Minimum signal noise ratio, default is 9 dB\n/// @param verbosity Debug output verbosity, 0=None, 1=Levels, 2=Histograms\nvoid pulse_detect_set_levels(pulse_detect_t *pulse_detect, int use_mag_est, float fixed_high_level, float min_high_level, float high_low_ratio, int verbosity);\n\n/// Demodulate On/Off Keying (OOK) and Frequency Shift Keying (FSK) from an envelope signal.\n///\n/// Function is stateful and can be called with chunks of input data.\n///\n/// @param pulse_detect The pulse_detect instance\n/// @param envelope_data Samples with amplitude envelope of carrier\n/// @param fm_data Samples with frequency offset from center frequency\n/// @param len Number of samples in input buffers\n/// @param samp_rate Sample rate in samples per second\n/// @param sample_offset Offset tracking for ringbuffer\n/// @param[in,out] pulses Will return a pulse_data_t structure\n/// @param[in,out] fsk_pulses Will return a pulse_data_t structure for FSK demodulated data\n/// @param fpdm Index of filter setting to use\n/// @return if a package is detected\n/// @retval 0 all input sample data is processed\n/// @retval 1 OOK package is detected (but all sample data is still not completely processed)\n/// @retval 2 FSK package is detected (but all sample data is still not completely processed)\nint pulse_detect_package(pulse_detect_t *pulse_detect, int16_t const *envelope_data, int16_t const *fm_data, int len, uint32_t samp_rate, uint64_t sample_offset, pulse_data_t *pulses, pulse_data_t *fsk_pulses, unsigned fpdm);\n\n#endif /* INCLUDE_PULSE_DETECT_H_ */\n"
  },
  {
    "path": "include/pulse_detect_fsk.h",
    "content": "/** @file\n    Pulse detect functions, FSK pulse detector.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2019 Benjamin Larsson.\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_PULSE_DETECT_FSK_H_\n#define INCLUDE_PULSE_DETECT_FSK_H_\n\n#include \"pulse_data.h\"\n#include <stdint.h>\n\n/// State data for pulse_detect_fsk_ functions.\n///\n/// This should be private/opaque but the OOK pulse_detect uses this.\ntypedef struct {\n    unsigned int fsk_pulse_length; ///< Counter for internal FSK pulse detection\n    enum {\n        PD_FSK_STATE_INIT  = 0, ///< Initial frequency estimation\n        PD_FSK_STATE_FH    = 1, ///< High frequency (pulse)\n        PD_FSK_STATE_FL    = 2, ///< Low frequency (gap)\n        PD_FSK_STATE_ERROR = 3  ///< Error - stay here until cleared\n    } fsk_state;\n\n    int fm_f1_est; ///< Estimate for the F1 frequency for FSK\n    int fm_f2_est; ///< Estimate for the F2 frequency for FSK\n\n    int16_t var_test_max;\n    int16_t var_test_min;\n    int16_t maxx;\n    int16_t minn;\n    int16_t midd;\n    int skip_samples;\n} pulse_detect_fsk_t;\n\n/// Init/clear Demodulate Frequency Shift Keying (FSK) state.\n///\n/// @param s Internal state\nvoid pulse_detect_fsk_init(pulse_detect_fsk_t *s);\n\n/// Demodulate Frequency Shift Keying (FSK) sample by sample.\n///\n/// Function is stateful between calls\n/// Builds estimate for initial frequency. When frequency deviates more than a\n/// threshold value it will determine whether the deviation is positive or negative\n/// to classify it as a pulse or gap. It will then transition to other state (F1 or F2)\n/// and build an estimate of the other frequency. It will then transition back and forth when current\n/// frequency is closer to other frequency estimate.\n/// Includes spurious suppression by coalescing pulses when pulse/gap widths are too short.\n/// Pulses equal higher frequency (F1) and Gaps equal lower frequency (F2)\n/// @param s Internal state\n/// @param fm_n One single sample of FM data\n/// @param fsk_pulses Will return a pulse_data_t structure for FSK demodulated data\nvoid pulse_detect_fsk_classic(pulse_detect_fsk_t *s, int16_t fm_n, pulse_data_t *fsk_pulses);\n\n/// Wrap up FSK modulation and store last data at End Of Package.\n///\n/// @param s Internal state\n/// @param fsk_pulses Pulse_data_t structure for FSK demodulated data\nvoid pulse_detect_fsk_wrap_up(pulse_detect_fsk_t *s, pulse_data_t *fsk_pulses);\n\n/// Demodulate Frequency Shift Keying (FSK) sample by sample.\n///\n/// Function is stateful between calls\n/// @param s Internal state\n/// @param fm_n One single sample of FM data\n/// @param fsk_pulses Will return a pulse_data_t structure for FSK demodulated data\nvoid pulse_detect_fsk_minmax(pulse_detect_fsk_t *s, int16_t fm_n, pulse_data_t *fsk_pulses);\n\n#endif /* INCLUDE_PULSE_DETECT_FSK_H_ */\n"
  },
  {
    "path": "include/pulse_slicer.h",
    "content": "/** @file\n    Pulse slicer functions.\n\n    Binary slicers (PWM/PPM/Manchester/...) using a pulse data structure as input\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_PULSE_SLICER_H_\n#define INCLUDE_PULSE_SLICER_H_\n\n#include \"pulse_detect.h\"\n#include \"r_device.h\"\n\n/// Demodulate a Pulse Code Modulation signal.\n///\n/// Demodulate a Pulse Code Modulation (PCM) signal where bit width\n/// is fixed and each bit starts with a pulse or not. It may be either\n/// Return-to-Zero (RZ) encoding, where pulses are shorter than bit width\n/// or Non-Return-to-Zero (NRZ) encoding, where pulses are equal to bit width\n/// The presence of a pulse is:\n/// - Presence of a pulse equals 1\n/// - Absence of a pulse equals 0\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Nominal width of pulse [us]\n/// - long_width:  Nominal width of bit period [us]\n/// - gap_limit:   Maximum gap size before new row of bits (optional) [us]\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// - tolerance:   Maximum deviation from nominal widths (optional, default 25%) [us]\n/// @return number of events processed\nint pulse_slicer_pcm(pulse_data_t const *pulses, r_device *device);\n\n/// Demodulate a Pulse Position Modulation signal.\n///\n/// Demodulate a Pulse Position Modulation (PPM) signal consisting of pulses with variable gap.\n/// Pulse width may be fixed or variable.\n/// Gap between pulses determine the encoding:\n/// - Short gap will add a 0 bit\n/// - Long  gap will add a 1 bit\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Nominal width of '0' [us]\n/// - long_width:  Nominal width of '1' [us]\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// - gap_limit:   Maximum gap size before new row of bits [us]\n/// - tolerance:   Maximum deviation from nominal widths (optional, raw if 0) [us]\n/// @return number of events processed\nint pulse_slicer_ppm(pulse_data_t const *pulses, r_device *device);\n\n/// Demodulate a Pulse Width Modulation signal.\n///\n/// Demodulate a Pulse Width Modulation (PWM) signal consisting of short, long, and optional sync pulses.\n/// Gap between pulses may be of fixed size or variable (e.g. fixed period)\n/// - Short pulse will add a 1 bit\n/// - Long pulse will add a 0 bit\n/// - Sync pulse (optional) will add a new row to bitbuffer\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Nominal width of '1' [us]\n/// - long_width:  Nominal width of '0' [us]\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// - gap_limit:   Maximum gap size before new row of bits [us]\n/// - sync_width:  Nominal width of sync pulse (optional) [us]\n/// - tolerance:   Maximum deviation from nominal widths (optional, raw if 0) [us]\n/// @return number of events processed\nint pulse_slicer_pwm(pulse_data_t const *pulses, r_device *device);\n\n/// Demodulate a Manchester encoded signal with a hardcoded zerobit in front.\n///\n/// Demodulate a Manchester encoded signal where first rising edge is counted as a databit\n/// and therefore always will be zero (Most likely a hardcoded Oregon Scientific peculiarity)\n///\n/// Clock is recovered from the data based on pulse width. When time since last bit is more\n/// than 1.5 times the clock half period (short_width) it is declared a data edge where:\n/// - Rising edge means bit = 0\n/// - Falling edge means bit = 1\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Nominal width of clock half period [us]\n/// - long_width:  Not used\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// @return number of events processed\nint pulse_slicer_manchester_zerobit(pulse_data_t const *pulses, r_device *device);\n\n/// Demodulate a Differential Manchester Coded signal.\n///\n/// No level shift within the clock cycle translates to a logic 0\n/// One level shift within the clock cycle translates to a logic 1\n/// Each clock cycle begins with a level shift\n///\n///     +---+   +---+   +-------+       +  high\n///     |   |   |   |   |       |       |\n///     |   |   |   |   |       |       |\n///     +   +---+   +---+       +-------+  low\n///\n///     ^       ^       ^       ^       ^  clock cycle\n///     |   1   |   1   |   0   |   0   |  translates as\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Width in samples of '1' [us]\n/// - long_width:  Width in samples of '0' [us]\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// - tolerance:   Maximum deviation from nominal widths [us]\n/// @return number of events processed\nint pulse_slicer_dmc(pulse_data_t const *pulses, r_device *device);\n\n/// Demodulate a raw Pulse Interval and Width Modulation signal.\n///\n/// Each level shift is a new bit.\n/// A short interval is a logic 1, a long interval a logic 0\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Nominal width of a bit [us]\n/// - long_width:  Maximum width of a run of bits [us]\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// - tolerance:   Maximum deviation from nominal widths [us]\n/// @return number of events processed\nint pulse_slicer_piwm_raw(pulse_data_t const *pulses, r_device *device);\n\n/// Demodulate a differential Pulse Interval and Width Modulation signal.\n///\n/// Each level shift is a new bit.\n/// A short interval is a logic 1, a long interval a logic 0\n///\n/// @param pulses The pulse sequence to demodulate\n/// @param device Modulation parameters of\n/// - short_width: Nominal width of '1' [us]\n/// - long_width:  Nominal width of '0' [us]\n/// - reset_limit: Maximum gap size before End Of Message [us].\n/// - tolerance:   Maximum deviation from nominal widths [us]\n/// @return number of events processed\nint pulse_slicer_piwm_dc(pulse_data_t const *pulses, r_device *device);\n\nint pulse_slicer_nrzs(pulse_data_t const *pulses, r_device *device);\n\nint pulse_slicer_osv1(pulse_data_t const *pulses, r_device *device);\n\n/// Simulate demodulation using a given signal code string.\n///\n/// The (optionally \"0x\" prefixed) hex code is processed into a bitbuffer_t.\n/// Each row is optionally prefixed with a length enclosed in braces \"{}\" or\n/// separated with a slash \"/\" character. Whitespace is ignored.\n///\n/// @param code The pulse sequence to demodulate in text format\n/// @param device Device params are disregarded.\n/// @return number of events processed\nint pulse_slicer_string(const char *code, r_device *device);\n\n#endif /* INCLUDE_PULSE_SLICER_H_ */\n"
  },
  {
    "path": "include/r_api.h",
    "content": "/** @file\n    Generic RF data receiver and decoder for ISM band devices using RTL-SDR and SoapySDR.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_R_API_H_\n#define INCLUDE_R_API_H_\n\n#include <stdint.h>\n\nstruct r_cfg;\nstruct r_device;\nstruct data;\nstruct pulse_data;\nstruct list;\nstruct mg_mgr;\n\n/* general */\n\nchar const *version_string(void);\n\nstruct r_cfg *r_create_cfg(void);\n\nvoid r_init_cfg(struct r_cfg *cfg);\n\nvoid r_free_cfg(struct r_cfg *cfg);\n\n/* device decoder protocols */\n\nvoid register_protocol(struct r_cfg *cfg, struct r_device *r_dev, char *arg);\n\nvoid free_protocol(struct r_device *r_dev);\n\nvoid unregister_protocol(struct r_cfg *cfg, struct r_device *r_dev);\n\nvoid register_all_protocols(struct r_cfg *cfg, unsigned disabled);\n\n/* output helper */\n\nvoid calc_rssi_snr(struct r_cfg *cfg, struct pulse_data *pulse_data);\n\nchar *time_pos_str(struct r_cfg *cfg, unsigned samples_ago, char *buf);\n\nchar const **well_known_output_fields(struct r_cfg *cfg);\n\nchar const **determine_csv_fields(struct r_cfg *cfg, char const *const *well_known, int *num_fields);\n\nint run_ook_demods(struct list *r_devs, struct pulse_data *pulse_data);\n\nint run_fsk_demods(struct list *r_devs, struct pulse_data *fsk_pulse_data);\n\n/* handlers */\n\nvoid r_redirect_logging(struct r_cfg *cfg);\n\nvoid event_occurred_handler(struct r_cfg *cfg, struct data *data);\n\nvoid log_device_handler(struct r_device *r_dev, int level, struct data *data);\n\nvoid data_acquired_handler(struct r_device *r_dev, struct data *data);\n\nstruct data *create_report_data(struct r_cfg *cfg, int level);\n\nvoid flush_report_data(struct r_cfg *cfg);\n\n/* setup */\n\nvoid add_json_output(struct r_cfg *cfg, char *param);\n\nvoid add_csv_output(struct r_cfg *cfg, char *param);\n\nvoid add_log_output(struct r_cfg *cfg, char *param);\n\nvoid add_kv_output(struct r_cfg *cfg, char *param);\n\nvoid add_mqtt_output(struct r_cfg *cfg, char *param);\n\nvoid add_influx_output(struct r_cfg *cfg, char *param);\n\nvoid add_syslog_output(struct r_cfg *cfg, char *param);\n\nvoid add_http_output(struct r_cfg *cfg, char *param);\n\nvoid add_trigger_output(struct r_cfg *cfg, char *param);\n\nvoid add_null_output(struct r_cfg *cfg, char *param);\n\nvoid add_rtltcp_output(struct r_cfg *cfg, char *param);\n\nvoid start_outputs(struct r_cfg *cfg, char const *const *well_known);\n\nvoid add_sr_dumper(struct r_cfg *cfg, char const *spec, int overwrite);\n\nvoid reopen_dumpers(struct r_cfg *cfg);\n\nvoid close_dumpers(struct r_cfg *cfg);\n\nvoid add_dumper(struct r_cfg *cfg, char const *spec, int overwrite);\n\nvoid add_infile(struct r_cfg *cfg, char *in_file);\n\nvoid add_data_tag(struct r_cfg *cfg, char *param);\n\n/* runtime */\n\nstruct mg_mgr *get_mgr(struct r_cfg *cfg);\n\nvoid set_center_freq(struct r_cfg *cfg, uint32_t center_freq);\n\nvoid set_freq_correction(struct r_cfg *cfg, int freq_correction);\n\nvoid set_sample_rate(struct r_cfg *cfg, uint32_t sample_rate);\n\nvoid set_gain_str(struct r_cfg *cfg, char const *gain_str);\n\n#endif /* INCLUDE_R_API_H_ */\n"
  },
  {
    "path": "include/r_device.h",
    "content": "/** @file\n    Definition of r_device struct.\n*/\n\n#ifndef INCLUDE_R_DEVICE_H_\n#define INCLUDE_R_DEVICE_H_\n\n/**\n    Supported Modulation and Coding types.\n\n    Note that Modulation is a term used usually to refer to the analog domain.\n    We refer to Modulation for the process of (de-)modulating a digital line code,\n    represented as pulses and gaps (OOK) or mark and space (FSK) onto a RF carrier signal.\n    The line code is a coding of the bitstream data and referred to as the Coding of the data.\n\n    We however use the well known terms to refer to the combinations of this.\n    E.g. the term PWM is well known as analog or discrete range modulation, but here used\n    to refer to a binary Coding of bits to on and off states (or mark and space) of the carrier.\n    It should be thought of as Pulse-Width-Coding, then modulated on OOK or FSK.\n    I.e. it is not truly Pulse-Width-Modulation but Pulse-Width-Coding then OOK or FSK modulation.\n    This might be especially confusing with PCM, where there is no true Pulse-Code-Modulation,\n    but rather NRZ (or RZ) pulse code with then OOK or FSK modulation.\n*/\nenum modulation_types {\n    OOK_PULSE_MANCHESTER_ZEROBIT = 3,  ///< OOK Modulation, Manchester Coding. Hardcoded zerobit. Rising Edge = 0, Falling edge = 1.\n    OOK_PULSE_PCM                = 4,  ///< OOK Modulation, Non-Return-to-Zero coding, Pulse = 1, No pulse = 0.\n    OOK_PULSE_RZ                 = 4,  ///< OOK Modulation, Return-to-Zero coding, Pulse = 1, No pulse = 0.\n    OOK_PULSE_PPM                = 5,  ///< OOK Modulation, Pulse Position Coding. Short gap = 0, Long = 1.\n    OOK_PULSE_PWM                = 6,  ///< OOK Modulation, Pulse Width Coding. Short interval = 1, Long = 0.\n    OOK_PULSE_PIWM_RAW           = 8,  ///< OOK Modulation, Level shift for each bit. Short interval = 1, Long = 0.\n    OOK_PULSE_PIWM_DC            = 11, ///< OOK Modulation, Level shift for each bit. Short interval = 1, Long = 0.\n    OOK_PULSE_DMC                = 9,  ///< OOK Modulation, Differential Manchester, Level shift within the clock cycle.\n    OOK_PULSE_PWM_OSV1           = 10, ///< OOK Modulation, Pulse Width Coding. Oregon Scientific v1.\n    OOK_PULSE_NRZS               = 12, ///< OOK Modulation, NRZS Coding\n    FSK_DEMOD_MIN_VAL            = 16, ///< Dummy. FSK demodulation must start at this value.\n    FSK_PULSE_PCM                = 16, ///< FSK Modulation, Non-Return-to-Zero coding, Pulse = 1, No pulse = 0.\n    FSK_PULSE_PWM                = 17, ///< FSK Modulation, Pulse Width Coding. Short pulses = 1, Long = 0.\n    FSK_PULSE_MANCHESTER_ZEROBIT = 18, ///< FSK Modulation, Manchester coding.\n};\n\n/** Decoders should return n>0 for n packets successfully decoded,\n    an ABORT code if the bitbuffer is no applicable,\n    or a FAIL code if the message is malformed. */\nenum decode_return_codes {\n    DECODE_FAIL_OTHER   = 0, ///< legacy, do not use\n    /** Bitbuffer row count or row length is wrong for this sensor. */\n    DECODE_ABORT_LENGTH = -1,\n    DECODE_ABORT_EARLY  = -2,\n    /** Message Integrity Check failed: e.g. checksum/CRC doesn't validate. */\n    DECODE_FAIL_MIC     = -3,\n    DECODE_FAIL_SANITY  = -4,\n};\n\nstruct bitbuffer;\nstruct data;\n\n/** Device protocol decoder struct. */\ntypedef struct r_device {\n    unsigned protocol_num; ///< fixed sequence number, assigned in main().\n\n    /* information provided by each decoder */\n    char const *name;\n    unsigned modulation;\n    float short_width;\n    float long_width;\n    float reset_limit;\n    float gap_limit;\n    float sync_width;\n    float tolerance;\n    int (*decode_fn)(struct r_device *decoder, struct bitbuffer *bitbuffer);\n    struct r_device *(*create_fn)(char *args);\n    unsigned priority; ///< Run later and only if no previous events were produced\n    unsigned disabled; ///< 0: default enabled, 1: default disabled, 2: disabled, 3: disabled and hidden\n    char const *const *fields; ///< List of fields this decoder produces; required for CSV output. NULL-terminated.\n\n    /* public for each decoder */\n    int verbose;\n    int verbose_bits;\n    void (*log_fn)(struct r_device *decoder, int level, struct data *data);\n    void (*output_fn)(struct r_device *decoder, struct data *data);\n\n    /* Decoder results / statistics */\n    unsigned decode_events;\n    unsigned decode_ok;\n    unsigned decode_messages;\n    unsigned decode_fails[5];\n\n    /* private for flex decoder and output callback */\n    void *decode_ctx;\n    void *output_ctx;\n} r_device;\n\n#endif /* INCLUDE_R_DEVICE_H_ */\n"
  },
  {
    "path": "include/r_private.h",
    "content": "/** @file\n    Definition of r_private state structure.\n*/\n\n#ifndef INCLUDE_R_PRIVATE_H_\n#define INCLUDE_R_PRIVATE_H_\n\n#include <stdint.h>\n#include <time.h>\n#include \"list.h\"\n#include \"baseband.h\"\n#include \"pulse_detect.h\"\n#include \"fileformat.h\"\n#include \"samp_grab.h\"\n#include \"am_analyze.h\"\n#include \"rtl_433.h\"\n#include \"compat_time.h\"\n\nstruct dm_state {\n    float auto_level;\n    float squelch_offset;\n    float level_limit;\n    float noise_level;\n    float min_level_auto;\n    float min_level;\n    float min_snr;\n    float low_pass;\n    int use_mag_est;\n    int detect_verbosity;\n\n    int16_t am_buf[MAXIMAL_BUF_LENGTH];  // AM demodulated signal (for OOK decoding)\n    union {\n        // These buffers aren't used at the same time, so let's use a union to save some memory\n        int16_t fm[MAXIMAL_BUF_LENGTH];  // FM demodulated signal (for FSK decoding)\n        uint16_t temp[MAXIMAL_BUF_LENGTH];  // Temporary buffer (to be optimized out..)\n    } buf;\n    uint8_t u8_buf[MAXIMAL_BUF_LENGTH]; // format conversion buffer\n    float f32_buf[MAXIMAL_BUF_LENGTH]; // format conversion buffer\n    int sample_size; // CU8: 2, CS16: 4\n    pulse_detect_t *pulse_detect;\n    filter_state_t lowpass_filter_state;\n    demodfm_state_t demod_FM_state;\n    int enable_FM_demod;\n    unsigned fsk_pulse_detect_mode;\n    unsigned frequency;\n    samp_grab_t *samp_grab;\n    am_analyze_t *am_analyze;\n    int analyze_pulses;\n    file_info_t load_info;\n    list_t dumper;\n\n    /* Protocol states */\n    list_t r_devs;\n\n    pulse_data_t    pulse_data;\n    pulse_data_t    fsk_pulse_data;\n    unsigned frame_event_count;\n    unsigned frame_start_ago;\n    unsigned frame_end_ago;\n    struct timeval now;\n    float sample_file_pos;\n};\n\n#endif /* INCLUDE_R_PRIVATE_H_ */\n"
  },
  {
    "path": "include/r_util.h",
    "content": "/** @file\n    Various utility functions for use by applications.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_R_UTIL_H_\n#define INCLUDE_R_UTIL_H_\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <time.h>\n#include \"compat_time.h\"\n\n#if defined _MSC_VER || defined __cplusplus // Microsoft Visual Studio or C++ compilers (G++ is used by ESP32, ESP8266...)\n    // MSC and C++ compilers have something like C99 restrict as __restrict\n    #ifndef restrict\n    #define restrict  __restrict\n    #endif\n#endif\n\n// buffer to hold localized timestamp \"YYYY-MM-DD HH:MM:SS.000000+0000\"\n#define LOCAL_TIME_BUFLEN 32\n\n// Macro to prevent unused variables (passed into a function)\n// from generating a warning.\n#define UNUSED(x) (void)(x)\n// Macro to signal that that variable might not be used.\n// Useful when a variable is used only in an ifdef block that may or\n// may not be enabled.\n#define POSSIBLY_UNUSED(x) (void)(x)\n\n/** Get current time with usec precision.\n\n    @param tv output for current time\n*/\nvoid get_time_now(struct timeval *tv);\n\n/** Printable timestamp in local time.\n\n    @param[out] buf output buffer, long enough for \"YYYY-MM-DD HH:MM:SS+0000\"\n    @param format time format string, uses \"%Y-%m-%d %H:%M:%S\" if NULL\n    @param with_tz 1 to add a time offset, 0 otherwise\n    @param time_secs 0 for now, or seconds since the epoch\n    @return buf pointer (for short hand use as operator)\n*/\nchar *format_time_str(char *buf, char const *format, int with_tz, time_t time_secs);\n\n/** Printable timestamp in local time with microseconds.\n\n    @param[out] buf output buffer, long enough for \"YYYY-MM-DD HH:MM:SS.uuuuuu+0000\"\n    @param format time format string without usec, uses \"%Y-%m-%d %H:%M:%S\" if NULL\n    @param with_tz 1 to add a time offset, 0 otherwise\n    @param tv NULL for now, or seconds and microseconds since the epoch\n    @return buf pointer (for short hand use as operator)\n*/\nchar *usecs_time_str(char *buf, char const *format, int with_tz, struct timeval *tv);\n\n/** Parse a timestamp in the format \"YYYY-MM-DD HH:MM:SS.uuuuuu\" as produced by `usecs_time_str`.\n\n    @param buf input buffer containing the timestamp\n    @param out output timeval structure\n    @return pointer to the first character after the parsed time string, or null on error\n*/\nconst char *parse_time_str(const char *buf, struct timeval *out);\n\n/** Printable sample position.\n\n    @param sample_file_pos sample position\n    @param buf output buffer, long enough for \"@0.000000s\"\n    @return buf pointer (for short hand use as operator)\n*/\nchar *sample_pos_str(float sample_file_pos, char *buf);\n\n/** Convert Celsius to Fahrenheit.\n\n    @param celsius temperature in Celsius\n    @return temperature value in Fahrenheit\n*/\nfloat celsius2fahrenheit(float celsius);\n\n/** Convert Fahrenheit to Celsius.\n\n    @param fahrenheit temperature in Fahrenheit\n    @return temperature value in Celsius\n*/\nfloat fahrenheit2celsius(float fahrenheit);\n\n/** Convert Kilometers per hour (kph) to Miles per hour (mph).\n\n    @param kph speed in Kilometers per hour\n    @return speed in miles per hour\n*/\nfloat kmph2mph(float kph);\n\n/** Convert Miles per hour (mph) to Kilometers per hour (kmph).\n\n    @param mph speed in Kilometers per hour\n    @return speed in kilometers per hour\n*/\nfloat mph2kmph(float mph);\n\n/** Convert millimeters (mm) to inches (inch).\n\n    @param mm measurement in millimeters\n    @return measurement in inches\n*/\nfloat mm2inch(float mm);\n\n/** Convert inches (inch) to millimeters (mm).\n\n    @param inch measurement in inches\n    @return measurement in millimeters\n*/\nfloat inch2mm(float inch);\n\n/** Convert kilo Pascal (kPa) to pounds per square inch (PSI).\n\n    @param kpa pressure in kPa\n    @return pressure in PSI\n*/\nfloat kpa2psi(float kpa);\n\n/** Convert pounds per square inch (PSI) to kilo Pascal (kPa).\n\n    @param psi pressure in PSI\n    @return pressure in kPa\n*/\nfloat psi2kpa(float psi);\n\n/** Convert hecto Pascal (hPa) to inches of mercury (inHg).\n\n    @param hpa pressure in kPa\n    @return pressure in inHg\n*/\nfloat hpa2inhg(float hpa);\n\n/** Convert inches of mercury (inHg) to hecto Pascal (hPa).\n\n    @param inhg pressure in inHg\n    @return pressure in hPa\n*/\nfloat inhg2hpa(float inhg);\n\n/** Return true if the string ends with the specified suffix, otherwise return false.\n\n    @param str string to search for patterns\n    @param suffix the pattern to search\n    @return true if the string ends with the specified suffix, false otherwise.\n*/\nbool str_endswith(char const *restrict str, char const *restrict suffix);\n\n/** Replace a pattern in a string.\n\n    This utility function is useful when converting native units to si or customary.\n\n    @param orig string to search for patterns\n    @param rep the pattern to replace\n    @param with the replacement pattern\n    @return a new string that has rep replaced with with\n*/\nchar *str_replace(char const *orig, char const *rep, char const *with);\n\n/** Make a nice printable string for a frequency.\n\n    @param freq the frequency to convert to a string.\n*/\nchar const *nice_freq (double freq);\n\n#endif /* INCLUDE_R_UTIL_H_ */\n"
  },
  {
    "path": "include/raw_output.h",
    "content": "/** @file\n    Raw I/Q data output handler.\n\n    Copyright (C) 2022 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_RAW_OUTPUT_H_\n#define INCLUDE_RAW_OUTPUT_H_\n\n#include <stdint.h>\n\nstruct raw_output;\n\ntypedef struct raw_output {\n    void (*output_frame)(struct raw_output *output, uint8_t const *data, uint32_t len);\n    void (*output_free)(struct raw_output *output);\n} raw_output_t;\n\nvoid raw_output_frame(struct raw_output *output, uint8_t const *data, uint32_t len);\n\nvoid raw_output_free(struct raw_output *output);\n\n#endif /* INCLUDE_RAW_OUTPUT_H_ */\n"
  },
  {
    "path": "include/rfraw.h",
    "content": "/** @file\n    RfRaw format functions.\n\n    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_RFRAW_H_\n#define INCLUDE_RFRAW_H_\n\n#include \"pulse_detect.h\"\n#include <stdbool.h>\n\n/// Check if a given string is in RfRaw format.\nbool rfraw_check(char const *p);\n\n/// Decode RfRaw string to pulse data.\nbool rfraw_parse(pulse_data_t *data, char const *p);\n\n#endif /* INCLUDE_RFRAW_H_ */\n"
  },
  {
    "path": "include/rtl_433.h",
    "content": "/** @file\n    Definition of r_cfg application structure.\n*/\n\n#ifndef INCLUDE_RTL_433_H_\n#define INCLUDE_RTL_433_H_\n\n#include <stdint.h>\n#include \"list.h\"\n#include <time.h>\n#include <signal.h>\n\n#define DEFAULT_SAMPLE_RATE     250000\n#define DEFAULT_FREQUENCY       433920000\n#define DEFAULT_HOP_TIME        (60*10)\n#define DEFAULT_ASYNC_BUF_NUMBER    0 // Force use of default value (librtlsdr default: 15)\n#define DEFAULT_BUF_LENGTH      (16 * 32 * 512) // librtlsdr default\n#define FSK_PULSE_DETECTOR_LIMIT 800000000\n\n#define MINIMAL_BUF_LENGTH      512\n#define MAXIMAL_BUF_LENGTH      (256 * 16384)\n#define SIGNAL_GRABBER_BUFFER   (12 * DEFAULT_BUF_LENGTH)\n#define MAX_FREQS               32\n\n#define INPUT_LINE_MAX 8192 /**< enough for a complete textual bitbuffer (25*256) */\n\nstruct sdr_dev;\nstruct r_device;\nstruct mg_mgr;\n\ntypedef enum {\n    CONVERT_NATIVE,\n    CONVERT_SI,\n    CONVERT_CUSTOMARY,\n} conversion_mode_t;\n\ntypedef enum {\n    REPORT_TIME_DEFAULT,\n    REPORT_TIME_DATE,\n    REPORT_TIME_SAMPLES,\n    REPORT_TIME_UNIX,\n    REPORT_TIME_ISO,\n    REPORT_TIME_OFF,\n} time_mode_t;\n\ntypedef enum {\n    DEVICE_MODE_QUIT,\n    DEVICE_MODE_RESTART,\n    DEVICE_MODE_PAUSE,\n    DEVICE_MODE_MANUAL,\n} device_mode_t;\n\ntypedef enum {\n    DEVICE_STATE_STOPPED,\n    DEVICE_STATE_STARTING,\n    DEVICE_STATE_GRACE,\n    DEVICE_STATE_STARTED,\n} device_state_t;\n\ntypedef struct r_cfg {\n    device_mode_t dev_mode; ///< Input device run mode\n    device_state_t dev_state; ///< Input device run state\n    char *dev_query;\n    char const *dev_info;\n    char *gain_str;\n    char *settings_str;\n    int ppm_error;\n    uint32_t out_block_size;\n    char const *test_data;\n    list_t in_files;\n    char const *in_filename;\n    int in_replay;\n    volatile sig_atomic_t hop_now;\n    volatile sig_atomic_t exit_async;\n    volatile sig_atomic_t exit_code; ///< 0=no err, 1=params or cmd line err, 2=sdr device read error, 3=usb init error, 5=USB error (reset), other=other error\n    int frequencies;\n    int frequency_index;\n    uint32_t frequency[MAX_FREQS];\n    uint32_t center_frequency;\n    int fsk_pulse_detect_mode;\n    int hop_times;\n    int hop_time[MAX_FREQS];\n    time_t hop_start_time;\n    int duration;\n    time_t stop_time;\n    int after_successful_events_flag;\n    uint32_t samp_rate;\n    uint64_t input_pos;\n    uint32_t bytes_to_read;\n    struct sdr_dev *dev;\n    int grab_mode; ///< Signal grabber mode: 0=off, 1=all, 2=unknown, 3=known\n    int raw_mode; ///< Raw pulses printing mode: 0=off, 1=all, 2=unknown, 3=known\n    int verbosity; ///< 0=normal, 1=verbose, 2=verbose decoders, 3=debug decoders, 4=trace decoding.\n    int verbose_bits;\n    conversion_mode_t conversion_mode;\n    int report_meta;\n    int report_noise;\n    int report_protocol;\n    time_mode_t report_time;\n    int report_time_hires;\n    int report_time_tz;\n    int report_time_utc;\n    int report_description;\n    int report_stats;\n    int stats_interval;\n    volatile sig_atomic_t stats_now;\n    time_t stats_time;\n    int no_default_devices;\n    struct r_device *devices;\n    uint16_t num_r_devices;\n    list_t data_tags;\n    list_t output_handler;\n    list_t raw_handler;\n    int has_logout;\n    struct dm_state *demod;\n    char const *sr_filename;\n    int sr_execopen;\n    int watchdog; ///< SDR acquire stall watchdog\n    /* global stats */\n    time_t running_since;           ///< program start time statistic\n    unsigned total_frames_count;    ///< total frames recieved statistic\n    unsigned total_frames_squelch;  ///< total frames with noise only statistic\n    unsigned total_frames_ook;      ///< total frames with ook demod statistic\n    unsigned total_frames_fsk;      ///< total frames with fsk demod statistic\n    unsigned total_frames_events;   ///< total frames with decoder events statistic\n    /* sdr stats */\n    time_t sdr_since; ///< time of last SDR connect statistic\n    /* per report interval stats */\n    time_t frames_since;    ///< time at start of report interval statistic\n    unsigned frames_ook;    ///< counter of ook demods for report interval statistic\n    unsigned frames_fsk;    ///< counter of fsk demods for report interval statistic\n    unsigned frames_events; ///< counter of decoder events for report interval statistic\n    struct mg_mgr *mgr;\n} r_cfg_t;\n\n#endif /* INCLUDE_RTL_433_H_ */\n"
  },
  {
    "path": "include/rtl_433_devices.h",
    "content": "/** @file\n    Declaration of all available decoders.\n*/\n\n#ifndef INCLUDE_RTL_433_DEVICES_H_\n#define INCLUDE_RTL_433_DEVICES_H_\n\n#include \"r_device.h\"\n\n#define DEVICES \\\n    DECL(silvercrest) \\\n    DECL(rubicson) \\\n    DECL(prologue) \\\n    DECL(waveman) \\\n    DECL(new_template) \\\n    DECL(elv_em1000) \\\n    DECL(elv_ws2000) \\\n    DECL(lacrossetx) \\\n    DECL(new_template) \\\n    DECL(acurite_rain_896) \\\n    DECL(acurite_th) \\\n    DECL(oregon_scientific) \\\n    DECL(mebus433) \\\n    DECL(intertechno) \\\n    DECL(newkaku) \\\n    DECL(alectov1) \\\n    DECL(cardin) \\\n    DECL(fineoffset_WH2) \\\n    DECL(nexus) \\\n    DECL(ambient_weather) \\\n    DECL(calibeur_RF104) \\\n    DECL(X10_RF) \\\n    DECL(dsc_security) \\\n    DECL(brennenstuhl_rcs_2044) \\\n    DECL(gt_wt_02) \\\n    DECL(danfoss_CFR) \\\n    DECL(new_template) \\\n    DECL(new_template) \\\n    DECL(chuango) \\\n    DECL(generic_remote) \\\n    DECL(tfa_twin_plus_303049) \\\n    DECL(fineoffset_wh1080) \\\n    DECL(wt450) \\\n    DECL(lacrossews) \\\n    DECL(esperanza_ews) \\\n    DECL(efergy_e2_classic) \\\n    DECL(kw9015b) \\\n    DECL(generic_temperature_sensor) \\\n    DECL(wg_pb12v1) \\\n    DECL(acurite_txr) \\\n    DECL(acurite_986) \\\n    DECL(hideki_ts04) \\\n    DECL(oil_watchman) \\\n    DECL(current_cost) \\\n    DECL(emontx) \\\n    DECL(ht680) \\\n    DECL(s3318p) \\\n    DECL(akhan_100F14) \\\n    DECL(quhwa) \\\n    DECL(oregon_scientific_v1) \\\n    DECL(proove) \\\n    DECL(bresser_3ch) \\\n    DECL(springfield) \\\n    DECL(oregon_scientific_sl109h) \\\n    DECL(acurite_606) \\\n    DECL(tfa_pool_thermometer) \\\n    DECL(kedsum) \\\n    DECL(blyss) \\\n    DECL(steelmate) \\\n    DECL(schraeder) \\\n    DECL(lightwave_rf) \\\n    DECL(elro_db286a) \\\n    DECL(efergy_optical) \\\n    DECL(hondaremote) \\\n    DECL(new_template) \\\n    DECL(new_template) \\\n    DECL(radiohead_ask) \\\n    DECL(kerui) \\\n    DECL(fineoffset_wh1050) \\\n    DECL(honeywell) \\\n    DECL(maverick_et73x) \\\n    DECL(rftech) \\\n    DECL(lacrosse_tx141x) \\\n    DECL(acurite_00275rm) \\\n    DECL(lacrosse_tx35) \\\n    DECL(lacrosse_tx29) \\\n    DECL(vaillant_vrt340f) \\\n    DECL(fineoffset_WH25) \\\n    DECL(fineoffset_WH0530) \\\n    DECL(ibis_beacon) \\\n    DECL(oil_standard) \\\n    DECL(tpms_citroen) \\\n    DECL(oil_standard_ask) \\\n    DECL(thermopro_tp11) \\\n    DECL(solight_te44) \\\n    DECL(smoke_gs558) \\\n    DECL(generic_motion) \\\n    DECL(tpms_toyota) \\\n    DECL(tpms_ford) \\\n    DECL(tpms_renault) \\\n    DECL(infactory) \\\n    DECL(ft004b) \\\n    DECL(fordremote) \\\n    DECL(philips_aj3650) \\\n    DECL(schrader_EG53MA4) \\\n    DECL(nexa) \\\n    DECL(thermopro_tp12) \\\n    DECL(ge_coloreffects) \\\n    DECL(x10_sec) \\\n    DECL(interlogix) \\\n    DECL(dish_remote_6_3) \\\n    DECL(ss_sensor) \\\n    DECL(sensible_living) \\\n    DECL(m_bus_mode_c_t) \\\n    DECL(m_bus_mode_s) \\\n    DECL(m_bus_mode_r) \\\n    DECL(m_bus_mode_f) \\\n    DECL(wssensor) \\\n    DECL(wt1024) \\\n    DECL(tpms_pmv107j) \\\n    DECL(ttx201) \\\n    DECL(ambientweather_tx8300) \\\n    DECL(ambientweather_wh31e) \\\n    DECL(maverick_et73) \\\n    DECL(honeywell_wdb) \\\n    DECL(honeywell_wdb_fsk) \\\n    DECL(esa_energy) \\\n    DECL(bt_rain) \\\n    DECL(bresser_5in1) \\\n    DECL(digitech_xc0324) \\\n    DECL(opus_xt300) \\\n    DECL(fs20) \\\n    DECL(tpms_jansite) \\\n    DECL(lacrosse_ws7000) \\\n    DECL(ts_ft002) \\\n    DECL(companion_wtr001) \\\n    DECL(ecowitt) \\\n    DECL(directv) \\\n    DECL(eurochron) \\\n    DECL(ikea_sparsnas) \\\n    DECL(hcs200) \\\n    DECL(tfa_303196) \\\n    DECL(rubicson_48659) \\\n    DECL(holman_ws5029pcm) \\\n    DECL(philips_aj7010) \\\n    DECL(esic_emt7110) \\\n    DECL(gt_tmbbq05) \\\n    DECL(gt_wt_03) \\\n    DECL(norgo) \\\n    DECL(tpms_elantra2012) \\\n    DECL(auriol_hg02832) \\\n    DECL(fineoffset_WH51) \\\n    DECL(holman_ws5029pwm) \\\n    DECL(archos_tbh) \\\n    DECL(ws2032) \\\n    DECL(auriol_afw2a1) \\\n    DECL(tfa_drop_303233) \\\n    DECL(dsc_security_ws4945) \\\n    DECL(ert_scm) \\\n    DECL(klimalogg) \\\n    DECL(visonic_powercode) \\\n    DECL(eurochron_efth800) \\\n    DECL(cotech_36_7959) \\\n    DECL(scmplus) \\\n    DECL(fineoffset_wh1080_fsk) \\\n    DECL(tpms_abarth124) \\\n    DECL(missil_ml0757) \\\n    DECL(sharp_spc775) \\\n    DECL(insteon) \\\n    DECL(ert_idm) \\\n    DECL(ert_netidm) \\\n    DECL(thermopro_tx2) \\\n    DECL(acurite_590tx) \\\n    DECL(secplus_v2) \\\n    DECL(tfa_30_3221) \\\n    DECL(lacrosse_breezepro) \\\n    DECL(somfy_rts) \\\n    DECL(schrader_SMD3MA4) \\\n    DECL(nice_flor_s) \\\n    DECL(lacrosse_wr1) \\\n    DECL(lacrosse_th3) \\\n    DECL(bresser_6in1) \\\n    DECL(bresser_7in1) \\\n    DECL(ecodhome) \\\n    DECL(lacrosse_r1) \\\n    DECL(blueline) \\\n    DECL(burnhardbbq) \\\n    DECL(secplus_v1) \\\n    DECL(cavius) \\\n    DECL(tpms_jansite_solar) \\\n    DECL(abmt) \\\n    DECL(tfa_marbella) \\\n    DECL(auriol_ahfl) \\\n    DECL(auriol_aft77b2) \\\n    DECL(honeywell_cm921) \\\n    DECL(tpms_hyundai_vdo) \\\n    DECL(rojaflex) \\\n    DECL(marlec_solar) \\\n    DECL(somfy_iohc) \\\n    DECL(fineoffset_wh31l) \\\n    DECL(markisol) \\\n    DECL(govee) \\\n    DECL(cmr113) \\\n    DECL(inkbird_ith20r) \\\n    DECL(rainpoint) \\\n    DECL(atech_ws308) \\\n    DECL(acurite_01185m) \\\n    DECL(enocean_erp1) \\\n    DECL(megacode) \\\n    DECL(auriol_4ld5661) \\\n    DECL(tpms_truck) \\\n    DECL(funkbus_remote) \\\n    DECL(tpms_porsche) \\\n    DECL(jasco) \\\n    DECL(telldus_ft0385r) \\\n    DECL(lacrosse_tx34) \\\n    DECL(proflame2) \\\n    DECL(tpms_ave) \\\n    DECL(simplisafe_gen3) \\\n    DECL(yale_hsa) \\\n    DECL(regency_fan) \\\n    DECL(tpms_renault_0435r) \\\n    DECL(fineoffset_ws80) \\\n    DECL(emos_e6016) \\\n    DECL(emax) \\\n    DECL(ant_antplus) \\\n    DECL(emos_e6016_rain) \\\n    DECL(hcs200_fsk) \\\n    DECL(fineoffset_wh45) \\\n    DECL(maverick_xr30) \\\n    DECL(fineoffset_wn34) \\\n    DECL(rubicson_pool_48942) \\\n    DECL(badger_orion) \\\n    DECL(geo_minim) \\\n    DECL(tpms_tyreguard400) \\\n    DECL(tpms_kia) \\\n    DECL(srsmith_pool_srs_2c_tx) \\\n    DECL(neptune_r900) \\\n    DECL(wec2103) \\\n    DECL(vauno_en8822c) \\\n    DECL(govee_h5054) \\\n    DECL(tfa_14_1504_v2) \\\n    DECL(ced7000) \\\n    DECL(oil_watchman_advanced) \\\n    DECL(oil_smart) \\\n    DECL(gasmate_ba1008) \\\n    DECL(flowis) \\\n    DECL(m_bus_mode_c_t_downlink) \\\n    DECL(revolt_nc5462) \\\n    DECL(lacrosse_tx31u) \\\n    DECL(tpms_eezrv) \\\n    DECL(baldr_rain) \\\n    DECL(celsia_czc1) \\\n    DECL(fineoffset_ws90) \\\n    DECL(thermopro_tx2c) \\\n    DECL(tfa_303151) \\\n    DECL(bresser_leakage) \\\n    DECL(tpms_nissan) \\\n    DECL(bresser_lightning) \\\n    DECL(schou_72543_rain) \\\n    DECL(fineoffset_wh55) \\\n    DECL(tpms_bmw) \\\n    DECL(watts_thermostat) \\\n    DECL(thermor) \\\n    DECL(mueller_hotrod) \\\n    DECL(thermopro_tp28b) \\\n    DECL(tpms_bmwg3) \\\n    DECL(chamberlain_cwpirc) \\\n    DECL(thermopro_tp829b) \\\n    DECL(arad_ms_meter) \\\n    DECL(geevon_tx16) \\\n    DECL(fineoffset_wh46) \\\n    DECL(vevor_7in1) \\\n    DECL(arexx_ml) \\\n    DECL(rosstech_dcu706) \\\n    DECL(risco_agility) \\\n    DECL(thermopro_tp828b) \\\n    DECL(bresser_st1005h) \\\n    DECL(deltadore_x3d) \\\n    DECL(quinetic) \\\n    DECL(gridstream96) \\\n    DECL(gridstream192) \\\n    DECL(gridstream384) \\\n    DECL(revolt_zx7717) \\\n    DECL(tpms_gm) \\\n    DECL(rainpoint_hcs012arf) \\\n    DECL(apator_metra_erm30) \\\n    DECL(thermopro_tx7b) \\\n    DECL(nexus_sauna) \\\n    DECL(homelead_hg9901) \\\n    DECL(maverick_xr50) \\\n    DECL(orion_endpoint) \\\n    DECL(fineoffset_wh43) \\\n    DECL(baldr_therm) \\\n    DECL(bm5) \\\n    DECL(universalfanctrl) \\\n    DECL(fineoffset_ws85) \\\n    DECL(oria_wa150km) \\\n    DECL(ec3k) \\\n    DECL(orion_endpoint_2020) \\\n    DECL(geevon_tx19) \\\n    DECL(wallarge_cltx001) \\\n    DECL(sainlogic_sa8) \\\n    DECL(thermopro_tp862b) \\\n    DECL(tpms_airpuxem) \\\n    DECL(apator_metra_eitn30) \\\n    DECL(thermopro_tp211b) \\\n    DECL(tpms_trw_ook) \\\n    DECL(tpms_trw_fsk) \\\n\n    /* Add new decoders here. */\n\n#define DECL(name) extern r_device name;\nDEVICES\n#undef DECL\n\n#endif /* INCLUDE_RTL_433_DEVICES_H_ */\n"
  },
  {
    "path": "include/samp_grab.h",
    "content": "/** @file\n    IQ sample grabber (ring buffer and dumper).\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_SAMP_GRAB_H_\n#define INCLUDE_SAMP_GRAB_H_\n\n#include <stdint.h>\n\ntypedef struct samp_grab {\n    uint32_t *frequency;\n    uint32_t *samp_rate;\n    int *sample_size;\n\n    unsigned sg_counter;\n    char *sg_buf;\n    unsigned sg_size;\n    unsigned sg_index;\n    unsigned sg_len;\n} samp_grab_t;\n\nsamp_grab_t *samp_grab_create(unsigned size);\n\nvoid samp_grab_free(samp_grab_t *g);\n\nvoid samp_grab_push(samp_grab_t *g, unsigned char *iq_buf, uint32_t len);\n\nvoid samp_grab_reset(samp_grab_t *g);\n\n/// grab_end is counted in samples from end of buf.\nvoid samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end);\n\n#endif /* INCLUDE_SAMP_GRAB_H_ */\n"
  },
  {
    "path": "include/sdr.h",
    "content": "/** @file\n    SDR input from RTLSDR or SoapySDR.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_SDR_H_\n#define INCLUDE_SDR_H_\n\n#include <stdint.h>\n\n#define SDR_DEFAULT_BUF_NUMBER 15\n#define SDR_DEFAULT_BUF_LENGTH 0x40000\n\ntypedef struct sdr_dev sdr_dev_t;\n\ntypedef enum sdr_event_flags {\n    SDR_EV_EMPTY = 0,\n    SDR_EV_DATA = 1 << 0,\n    SDR_EV_RATE = 1 << 1,\n    SDR_EV_CORR = 1 << 2,\n    SDR_EV_FREQ = 1 << 3,\n    SDR_EV_GAIN = 1 << 4,\n} sdr_event_flags_t;\n\ntypedef struct sdr_event {\n    sdr_event_flags_t ev;\n    uint32_t sample_rate;\n    int freq_correction;\n    uint32_t center_frequency;\n    char const *gain_str;\n    void *buf;\n    int len;\n} sdr_event_t;\n\ntypedef void (*sdr_event_cb_t)(sdr_event_t *ev, void *ctx);\n\n/** Find the closest matching device, optionally report status.\n\n    @param out_dev device output returned\n    @param dev_query a string to be parsed as device spec\n    @param verbose the verbosity level for reports to stderr\n    @return dev 0 if successful\n*/\nint sdr_open(sdr_dev_t **out_dev, char const *dev_query, int verbose);\n\n/** Close the device.\n\n    @note\n    All previous sdr_event_t buffers will be invalid after calling sdr_close().\n    Make sure none are in use anymore.\n\n    @param dev the device handle\n    @return 0 on success\n*/\nint sdr_close(sdr_dev_t *dev);\n\n/** Get device info.\n\n    @param dev the device handle\n    @return JSON device info string\n*/\nchar const *sdr_get_dev_info(sdr_dev_t *dev);\n\n/** Get sample size.\n\n    @param dev the device handle\n    @return Sample size of I/Q elements in bytes (CU8: 2, CS16: 4, ...)\n*/\nint sdr_get_sample_size(sdr_dev_t *dev);\n\n/** Get sample signedness.\n\n    @param dev the device handle\n    @return 1 if the samples are signed (CS8, CS16, ...), 0 otherwise (CU8, ...)\n*/\nint sdr_get_sample_signed(sdr_dev_t *dev);\n\n/** Set device frequency, optionally report status.\n\n    @param dev the device handle\n    @param freq in Hz\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_set_center_freq(sdr_dev_t *dev, uint32_t freq, int verbose);\n\n/** Get device frequency.\n\n    @param dev the device handle\n    @return frequency in Hz on success, 0 otherwise\n*/\nuint32_t sdr_get_center_freq(sdr_dev_t *dev);\n\n/** Set the frequency correction value for the device, optionally report status.\n\n    @param dev the device handle\n    @param ppm correction value in parts per million (ppm)\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_set_freq_correction(sdr_dev_t *dev, int ppm, int verbose);\n\n/** Enable auto gain, optionally report status.\n\n    @param dev the device handle\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_set_auto_gain(sdr_dev_t *dev, int verbose);\n\n/** Set tuner gain or gain elements, optionally report status.\n\n    @param dev the device handle\n    @param gain_str in tenths of a dB for RTL-SDR, string of gain element pairs (example LNA=40,VGA=20,AMP=0), or string of overall gain, in dB\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_set_tuner_gain(sdr_dev_t *dev, char const *gain_str, int verbose);\n\n/** Set device sample rate, optionally report status.\n\n    @param dev the device handle\n    @param rate in samples/second\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_set_sample_rate(sdr_dev_t *dev, uint32_t rate, int verbose);\n\n/** Set device antenna.\n\n    @param dev the device handle\n    @param antenna_str name of the antenna (example 'Tuner 2 50 ohm')\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_set_antenna(sdr_dev_t *dev, char const *antenna_str, int verbose);\n\n/** Get device sample rate.\n\n    @param dev the device handle\n    @return sample rate in samples/second on success, 0 otherwise\n*/\nuint32_t sdr_get_sample_rate(sdr_dev_t *dev);\n\n/** Apply a list of sdr settings.\n\n    @param dev the device handle\n    @param sdr_settings keyword list of settings\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_apply_settings(sdr_dev_t *dev, char const *sdr_settings, int verbose);\n\n/** Activate stream (only needed for SoapySDR).\n\n    @param dev the device handle\n    @return 0 on success\n*/\nint sdr_activate(sdr_dev_t *dev);\n\n/** Deactivate stream (only needed for SoapySDR).\n\n    @param dev the device handle\n    @return 0 on success\n*/\nint sdr_deactivate(sdr_dev_t *dev);\n\n/** Reset buffer (only needed for RTL-SDR), optionally report status.\n\n    @param dev the device handle\n    @param verbose the verbosity level for reports to stderr\n    @return 0 on success\n*/\nint sdr_reset(sdr_dev_t *dev, int verbose);\n\n/** Start the SDR data acquisition.\n\n    @note\n    All previous sdr_event_t buffers will be invalid if @p buf_num or @p buf_len changed.\n    Make sure none are in use anymore.\n\n    @param dev the device handle\n    @param async_cb a callback for sdr_event_t messages\n    @param async_ctx a user context to be passed to @p async_cb\n    @param buf_num the number of buffers to keep\n    @param buf_len the size in bytes of each buffer\n    @return 0 on success\n*/\nint sdr_start(sdr_dev_t *dev, sdr_event_cb_t async_cb, void *async_ctx, uint32_t buf_num, uint32_t buf_len);\nint sdr_start_sync(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len);\n\n/** Stop the SDR data acquisition.\n\n    @note\n    All previous sdr_event_t buffers will remain valid until sdr_close().\n\n    @param dev the device handle\n    @return 0 on success\n*/\nint sdr_stop(sdr_dev_t *dev);\nint sdr_stop_sync(sdr_dev_t *dev);\n\n/** Redirect SoapySDR library logging.\n*/\nvoid sdr_redirect_logging(void);\n\n#endif /* INCLUDE_SDR_H_ */\n"
  },
  {
    "path": "include/term_ctl.h",
    "content": "/** @file\n    Terminal control utility functions.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_TERM_CTL_H_\n#define INCLUDE_TERM_CTL_H_\n\n#include <stdio.h>\n\nvoid *term_init(FILE *fp);\n\nvoid term_free(void *ctx);\n\nint term_get_columns(void *ctx);\n\nint term_has_color(void *ctx);\n\nvoid term_ring_bell(void *ctx);\n\ntypedef enum term_color {\n    TERM_COLOR_RESET          = 0,\n    TERM_COLOR_BLACK          = 30,\n    TERM_COLOR_RED            = 31,\n    TERM_COLOR_GREEN          = 32,\n    TERM_COLOR_YELLOW         = 33,\n    TERM_COLOR_BLUE           = 34,\n    TERM_COLOR_MAGENTA        = 35,\n    TERM_COLOR_CYAN           = 36,\n    TERM_COLOR_WHITE          = 37,\n    TERM_COLOR_BRIGHT_BLACK   = 90,\n    TERM_COLOR_BRIGHT_RED     = 91,\n    TERM_COLOR_BRIGHT_GREEN   = 92,\n    TERM_COLOR_BRIGHT_YELLOW  = 93,\n    TERM_COLOR_BRIGHT_BLUE    = 94,\n    TERM_COLOR_BRIGHT_MAGENTA = 95,\n    TERM_COLOR_BRIGHT_CYAN    = 96,\n    TERM_COLOR_BRIGHT_WHITE   = 97,\n} term_color_t;\n\n/**\n * Sets the terminal text foreground color.\n * Always sets the bold font attribute, except for TERM_COLOR_RESET.\n */\nvoid term_set_fg(void *ctx, term_color_t color);\n\n/**\n * Sets the terminal background and foreground color.\n * Both are optional, use `0` to omit a color.\n * Must not be used for TERM_COLOR_RESET.\n */\nvoid term_set_bg(void *ctx, term_color_t bg, term_color_t fg);\n\n/*\n * Defined in newer <sal.h> for MSVC.\n */\n#ifndef _Printf_format_string_\n#define _Printf_format_string_\n#endif\n\n/**\n * Print to terminal with color-codes inline turned into above colors.\n * Takes a var-arg format.\n *\n * E.g.:\n *\n *     void *term = term_init(stdout);\n *     term_printf (term, \"~4Hello ~2world~0.\\n\");\n *\n *   will print to stdout with 'Hello' mapped to colour 4\n *   and 'world' mapped to colour 2. See 'term_set_color_map()' below.\n *\n * And a 'term_printf (NULL, \"~4Hello ~2world~0.\\n\");'\n * will print \"Hello world\" to stderr' with no colors.\n */\nint term_printf(void *ctx, _Printf_format_string_ const char *format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 2, 3)))\n#endif\n        ;\n\n/**\n * Print to terminal with markup turned into colors.\n * Like 'term_printf()', but with automatic coloring for markup.\n *\n * Markup:\n *   = Heading =\n *   [option argument]\n *   \"quoted\"\n *   'quoted'\n */\nint term_help_fprintf(FILE *fp, _Printf_format_string_ char const *format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 2, 3)))\n#endif\n        ;\n\n/**\n * Like 'term_printf()', but no var-arg format.\n * Simply takes a 0-terminated buffer.\n */\nint term_puts(void *ctx, const char *buf);\n\n/**\n * Like 'term_help_fprintf()', but no var-arg format.\n * Simply takes a 0-terminated buffer.\n */\nint term_help_fputs(void *ctx, const char *buf, FILE *fp);\n\n/**\n * Change the default color map.\n * By default, the color-codes maps to these foreground colour:\n *   \"~0\": always restores terminal-colors; TERM_COLOR_RESET.\n *   \"~1\": print using TERM_COLOR_GREEN.\n *   \"~2\": print using TERM_COLOR_WHITE.\n *   \"~3\": print using TERM_COLOR_BLUE.\n *   \"~4\": print using TERM_COLOR_CYAN.\n *   \"~5\": print using TERM_COLOR_MAGENTA.\n *   \"~6\": print using TERM_COLOR_YELLOW.\n *   \"~7\": print using TERM_COLOR_BLACK.\n *   \"~8\": print using TERM_COLOR_RED.\n */\nint term_set_color_map(int idx, term_color_t color);\n\n/**\n * Returns the current color-value ('enum term_color') for color-index.\n * 'idx'. This index goes from ASCII '0' to 'X'.\n * 'X' = '0' + the dimension of the internal 'color_map[]'.\n */\nint term_get_color_map(int idx);\n\n#endif /* INCLUDE_TERM_CTL_H_ */\n"
  },
  {
    "path": "include/write_sigrok.h",
    "content": "/** @file\n    Sigrok Pulseview format writer.\n\n    Copyright (C) 2020 by Christian Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#ifndef INCLUDE_WRITE_SIGROK_\n#define INCLUDE_WRITE_SIGROK_\n\n/** Write a Sigrok file from data dump files.\n\n    @param filename file to write\n    @param samplerate sample rate for the channels\n    @param probes number of binary channels, needs \"logic-1-1\" file\n    @param analogs number of analog channels, needs \"analog-1-N-1\" with N starting at probes+1\n    @param labels channel labels, probes+analog strings or NULL for generic labels\n*/\nvoid write_sigrok(char const *filename, unsigned samplerate, unsigned probes, unsigned analogs, char const *labels[]);\n\n/** Open a file in a forked Pulseview.\n\n    @param filename file to open in Pulseview\n*/\nvoid open_pulseview(char const *filename);\n\n#endif /* INCLUDE_WRITE_SIGROK_ */\n"
  },
  {
    "path": "maintainer_update.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"rtl_433 maintainer updates to build files and docs.\"\"\"\n\nimport sys\nimport os\nimport subprocess\nimport glob\nimport re\n\ndef require_clean_work_tree():\n    \"\"\"Check if the working tree is clean, exit otherwise.\"\"\"\n    clean = not len(subprocess.check_output([\"git\", \"diff\", \"--stat\"]))\n    if not clean:\n        print(\"Please commit or stash your changes.\")\n        exit(1)\n\n\ndef grep_lines(pattern, filepath):\n    with open(filepath, 'r') as file:\n        filedata = file.read()\n    regex = re.compile(pattern)\n    return regex.findall(filedata)\n\n\ndef replace_text(pattern, repl, filepath):\n    with open(filepath, 'r') as file:\n        filedata = file.read()\n    regex = re.compile(pattern)\n    filedata = regex.sub(repl, filedata)\n    with open(filepath, 'w') as file:\n        file.write(filedata)\n\n\ndef replace_block(from_pattern, to_pattern, repl, filepath):\n    with open(filepath, 'r') as file:\n        filedata = file.read()\n    pattern = '(?ms)(' + from_pattern + ').*?(' + to_pattern + ')'\n    repl = r'\\g<1>%s\\g<2>' % repl\n    regex = re.compile(pattern)\n    filedata = regex.sub(repl, filedata)\n    with open(filepath, 'w') as file:\n        file.write(filedata)\n\n\ndef get_help_text(option):\n    try:\n        help_text = subprocess.check_output(\n            [\"./build/src/rtl_433\", \"-c\", \"0\", option], stderr=subprocess.STDOUT).decode('utf-8')\n    except subprocess.CalledProcessError as e:\n        help_text = e.output.decode('utf-8')\n\n    # trim help text\n    help_text = re.sub(r'(?s).*Usage:', '', help_text)\n    help_text = re.sub(r'(?s).*option requires an argument -- \\'?.\\'?', '', help_text)\n    # help_text = re.sub(r'(?m)^\\s*=\\s+(.*)\\s+=\\s*$', r'### \\1', help_text)\n    return help_text\n\n\ndef markup_man_text(help_text):\n    # sub section headings\n    help_text = re.sub(r'(?m)^\\s*=\\s+(.*)\\s+=\\s*$', r'.SS \"\\1\"', help_text)\n    # indented lines\n    help_text = re.sub(r'(?m)^\\t(.*)$', r'.RS\\n\\1\\n.RE', help_text)\n    # options\n    help_text = re.sub(r'(?m)^\\s*\\[(\\S*)(.*)\\]\\s*(.*)$',\n                       r'.TP\\n[ \\\\\\\\fB\\1\\\\\\\\fI\\2\\\\\\\\fP ]\\n\\3', help_text)\n    # fix hyphens\n    help_text = re.sub(r'-', '\\\\-', help_text)\n    # fix quotes\n    help_text = re.sub(r'(?m)^\\'', ' \\'', help_text)\n    return help_text\n\n\ndef parse_devices(devices_text):\n    devices = []\n    for line in devices_text.splitlines():\n        # match the [123] device number\n        device_info = re.search(r'\\[(\\d{1,5})\\](.) (.*)', line)\n        if not device_info:\n            continue\n        device_number = int(device_info.group(1).strip(), base=10)\n        is_disabled = device_info.group(2).strip() == \"*\"\n        device_text = device_info.group(3).strip()\n\n        devices.append((device_number, device_text, is_disabled))\n    return devices\n\n\nverbose = '-v' in sys.argv\n\n# Make sure we run from the top dir\ntopdir = os.path.dirname(os.path.abspath(__file__))\nos.chdir(topdir)\n\n# Only ever run on a clean working tree\nrequire_clean_work_tree()\n\n# glob all src and device files\nos.chdir(\"src\")\nsrc_files = sorted(glob.glob('*.c'))\nif (verbose):\n    print(\"src_files =\", src_files)\ndevice_files = sorted(glob.glob('devices/*.c'))\nif (verbose):\n    print(\"device_files =\", device_files)\nos.chdir(\"..\")\n\n# glob all includes\nos.chdir(\"include\")\ninclude_files = sorted(glob.glob('*.h'))\nif (verbose):\n    print(\"include_files =\", include_files)\nos.chdir(\"..\")\n\n# grep all r_devices\nr_devices = [grep_lines(r'(?m)^r_device\\s*(.*?)\\s*=.*',\n                        os.path.join(\"src\", p)) for p in device_files]\nr_devices = [item for sublist in r_devices for item in sublist]\nif (verbose):\n    print(\"r_devices =\", r_devices)\n\n# count r_devices, correct for 'new_template' being used six times\nr_devices_used = len(r_devices) + 5\n\n# src/CMakeLists.txt\nrepl = src_files + device_files\nrepl.remove('rtl_433.c') # exclude apps from lib sources\nrepl = '\\n    ' + ('\\n    '.join(repl)) + '\\n'\nreplace_block(r'add_library\\(r_433 STATIC$',\n              r'^\\)', repl, 'src/CMakeLists.txt')\n\n# include/rtl_433.h\n# update '#define MAX_PROTOCOLS ?' with actual count\n#replace_text(r'(?m)(#define\\s+MAX_PROTOCOLS\\s+)\\d+',\n#             r'\\g<1>%d' % r_devices_used, 'include/rtl_433.h')\n\n# include/rtl_433_devices.h\n# check that everything between '#define DEVICES' and \\n\\n with DECL(device_name) matches r_devices\n# TODO: implement the check...\n\nif (not os.path.isfile(\"./build/src/rtl_433\")):\n    print(\"\\nWARNING: rtl_433 binary not found: skipping README/man generation!\\n\")\n    exit(0)\n\n# README.md\n# Replace everything between ``` with help output.\nrepl = '\\n' + get_help_text('-h') + '\\n'\ndevices = get_help_text('-R') + '\\n'\nrepl2 = get_help_text('-d') + '\\n'\nrepl2 += get_help_text('-g') + '\\n'\nrepl2 += get_help_text('-X') + '\\n'\nrepl2 += get_help_text('-F') + '\\n'\nrepl2 += get_help_text('-M') + '\\n'\nrepl2 += get_help_text('-r') + '\\n'\nrepl2 += get_help_text('-w') + '\\n'\nreplace_block(r'```',\n              r'```', repl + devices + repl2, 'README.md')\n\n# conf/rtl_433.example.conf\nparsed_devices = parse_devices(devices)\nconf_text = \"\"\nfor dev_num, dev_descr, disabled in parsed_devices:\n    comment = \"# \" if disabled else \"  \"\n    spaces = (4 - len(str(dev_num))) * \" \"\n    text = f\"{comment}protocol {dev_num}{spaces}# {dev_descr}\\n\"\n    conf_text += text\n    #print(dev_num, \"-\" if disabled else \"+\", dev_descr)\nprint(conf_text)\nreplace_block(r'## Protocols to enable \\(command line option \\\"-R\\\"\\)\\n',\n        \"## Flex devices\", \"\\n\" + conf_text + \"\\n\", \"conf/rtl_433.example.conf\")\n\n# MAN pages\nrepl = markup_man_text(repl + repl2)\nreplace_block(r'\\.\\\\\" body',\n              r'\\.\\\\\" end', '\\n'+repl, 'man/man1/rtl_433.1')\n"
  },
  {
    "path": "man/man1/rtl_433.1",
    "content": ".TH \"RTL_433\" \"1\" \"2019-08-21\" \"rtl_433\" \"rtl_433 Commands\"\n.ie \\n(.g .ds Aq \\(aq\n.el       .ds Aq '\n.ss \\n[.ss] 0\n.nh\n.ad l\n.de URL\n\\fI\\\\$2\\fP <\\\\$1>\\\\$3\n..\n.als MTO URL\n.if \\n[.g] \\{\\\n.  mso www.tmac\n.  am URL\n.    ad l\n.  .\n.  am MTO\n.    ad l\n.  .\n.  LINKSTYLE blue R < >\n.\\}\n.SH \"NAME\"\nrtl_433 \\- Generic RF data receiver and decoder for ISM band devices using RTL-SDR and SoapySDR.\n.SH \"DESCRIPTION\"\n.sp\nThis manual page documents briefly the \\fBrtl_433\\fP command.\n.sp\n\\fBrtl_433\\fP is a generic data receiver, mainly for the 433.92 MHz, 868 MHz (SRD),\n315 MHz, and 915 MHz ISM bands. It works with RTL\\-SDR and/or SoapySDR. Actively tested\nand supported are Realtek RTL2832 based DVB dongles (using RTL\\-SDR) and LimeSDR\n(LimeSDR USB and LimeSDR mini engineering samples kindly provided by MyriadRf),\nPlutoSDR, HackRF One (using SoapySDR drivers), as well as SoapyRemote.\n.SH \"SYNOPSIS\"\n.sp\n\\fBrtl_433\\fP [\\fIOPTION\\fP]... \\fIFILE\\fP...\n.SH \"OPTIONS\"\n.sp\nA summary of options is included below.\nDetailed information on some options follows.\n.\\\" body\n\n\n  A \"rtl_433.conf\" file is searched in \"./\", XDG_CONFIG_HOME e.g. \"$HOME/.config/rtl_433/\",\n  SYSCONFDIR e.g. \"/usr/local/etc/rtl_433/\", then command line args will be parsed in order.\n.SS \"General options\"\n.TP\n[ \\fB\\-V\\fI\\fP ]\nOutput the version string and exit\n.TP\n[ \\fB\\-v\\fI\\fP ]\nIncrease verbosity (can be used multiple times).\n       \\-v : verbose notice, \\-vv : verbose info, \\-vvv : debug, \\-vvvv : trace.\n.TP\n[ \\fB\\-c\\fI <path>\\fP ]\nRead config options from a file\n.SS \"Tuner options\"\n.TP\n[ \\fB\\-d\\fI <RTL\\-SDR USB device index> | :<RTL\\-SDR USB device serial> | <SoapySDR device query> | rtl_tcp | help\\fP ]\n[\\-g <gain> | help] (default: auto)\n.TP\n[ \\fB\\-t\\fI <settings>\\fP ]\napply a list of keyword=value settings to the SDR device\n       e.g. for SoapySDR \\-t \"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\"\n       for RTL\\-SDR use \"direct_samp[=1]\", \"offset_tune[=1]\", \"digital_agc[=1]\", \"biastee[=1]\"\n.TP\n[ \\fB\\-f\\fI <frequency>\\fP ]\nReceive frequency(s) (default: 433920000 Hz)\n.TP\n[ \\fB\\-H\\fI <seconds>\\fP ]\nHop interval for polling of multiple frequencies (default: 600 seconds)\n.TP\n[ \\fB\\-p\\fI <ppm_error>\\fP ]\nCorrect rtl\\-sdr tuner frequency offset error (default: 0)\n.TP\n[ \\fB\\-s\\fI <sample rate>\\fP ]\nSet sample rate (default: 250000 Hz)\n.TP\n[ \\fB\\-D\\fI quit | restart | pause | manual\\fP ]\nInput device run mode options (default: quit).\n.SS \"Demodulator options\"\n.TP\n[ \\fB\\-R\\fI <device> | help\\fP ]\nEnable only the specified device decoding protocol (can be used multiple times)\n       Specify a negative number to disable a device decoding protocol (can be used multiple times)\n.TP\n[ \\fB\\-X\\fI <spec> | help\\fP ]\nAdd a general purpose decoder (prepend \\-R 0 to disable all decoders)\n.TP\n[ \\fB\\-Y\\fI auto | classic | minmax\\fP ]\nFSK pulse detector mode.\n.TP\n[ \\fB\\-Y\\fI level=<dB level>\\fP ]\nManual detection level used to determine pulses (\\-1.0 to \\-30.0) (0=auto).\n.TP\n[ \\fB\\-Y\\fI minlevel=<dB level>\\fP ]\nManual minimum detection level used to determine pulses (\\-1.0 to \\-99.0).\n.TP\n[ \\fB\\-Y\\fI minsnr=<dB level>\\fP ]\nMinimum SNR to determine pulses (1.0 to 99.0).\n.TP\n[ \\fB\\-Y\\fI autolevel\\fP ]\nSet minlevel automatically based on average estimated noise.\n.TP\n[ \\fB\\-Y\\fI squelch\\fP ]\nSkip frames below estimated noise level to reduce cpu load.\n.TP\n[ \\fB\\-Y\\fI ampest | magest\\fP ]\nChoose amplitude or magnitude level estimator.\n.SS \"Analyze/Debug options\"\n.TP\n[ \\fB\\-A\\fI\\fP ]\nPulse Analyzer. Enable pulse analysis and decode attempt.\n       Disable all decoders with \\-R 0 if you want analyzer output only.\n.TP\n[ \\fB\\-y\\fI <code>\\fP ]\nVerify decoding of demodulated test data (e.g. \"{25}fb2dd58\") with enabled devices\n.SS \"File I/O options\"\n.TP\n[ \\fB\\-S\\fI none | all | unknown | known\\fP ]\nSignal auto save. Creates one file per signal.\n       Note: Saves raw I/Q samples (uint8 pcm, 2 channel). Preferred mode for generating test files.\n.TP\n[ \\fB\\-r\\fI <filename> | help\\fP ]\nRead data from input file instead of a receiver\n.TP\n[ \\fB\\-w\\fI <filename> | help\\fP ]\nSave data stream to output file (a '\\-' dumps samples to stdout)\n.TP\n[ \\fB\\-W\\fI <filename> | help\\fP ]\nSave data stream to output file, overwrite existing file\n.SS \"Data output options\"\n.TP\n[ \\fB\\-F\\fI log | kv | json | csv | mqtt | influx | syslog | trigger | rtl_tcp | http | null | help\\fP ]\nProduce decoded output in given format.\n       Append output to file with :<filename> (e.g. \\-F csv:log.csv), defaults to stdout.\n       Specify host/port for syslog with e.g. \\-F syslog:127.0.0.1:1514\n.TP\n[ \\fB\\-M\\fI time[:<options>] | protocol | level | noise[:<secs>] | stats | bits | help\\fP ]\nAdd various meta data to each output.\n.TP\n[ \\fB\\-K\\fI FILE | PATH | <tag> | <key>=<tag>\\fP ]\nAdd an expanded token or fixed tag to every output line.\n.TP\n[ \\fB\\-C\\fI native | si | customary\\fP ]\nConvert units in decoded output.\n.TP\n[ \\fB\\-n\\fI <value>\\fP ]\nSpecify number of samples to take (each sample is an I/Q pair)\n.TP\n[ \\fB\\-T\\fI <seconds>\\fP ]\nSpecify number of seconds to run, also 12:34 or 1h23m45s\n.TP\n[ \\fB\\-E\\fI hop | quit\\fP ]\nHop/Quit after outputting successful event(s)\n.TP\n[ \\fB\\-h\\fI\\fP ]\nOutput this usage help and exit\n       Use \\-d, \\-g, \\-R, \\-X, \\-F, \\-M, \\-r, \\-w, or \\-W without argument for more help\n.SS \"Input device selection\"\n.RS\nRTL\\-SDR device driver is available.\n.RE\n.TP\n[ \\fB\\-d\\fI <RTL\\-SDR USB device index>\\fP ]\n(default: 0)\n.TP\n[ \\fB\\-d\\fI :<RTL\\-SDR USB device serial (can be set with rtl_eeprom \\-s)>\\fP ]\n.RS\nTo set gain for RTL\\-SDR use \\-g <gain> to set an overall gain in dB.\n.RE\n.RS\nSoapySDR device driver is available.\n.RE\n.TP\n[ \\fB\\-d\\fI \"\"\\fP ]\nOpen default SoapySDR device\n.TP\n[ \\fB\\-d\\fI driver=rtlsdr\\fP ]\nOpen e.g. specific SoapySDR device\n.RS\nTo set gain for SoapySDR use \\-g ELEM=val,ELEM=val,... e.g. \\-g LNA=20,TIA=8,PGA=2 (for LimeSDR).\n.RE\n.TP\n[ \\fB\\-d\\fI rtl_tcp[:[//]host[:port]\\fP ]\n(default: localhost:1234)\n.RS\nSpecify host/port to connect to with e.g. \\-d rtl_tcp:127.0.0.1:1234\n.RE\n.SS \"Gain option\"\n.TP\n[ \\fB\\-g\\fI <gain>\\fP ]\n(default: auto)\n.RS\nFor RTL\\-SDR: gain in dB (\"0\" is auto).\n.RE\n.RS\nFor SoapySDR: gain in dB for automatic distribution (\"\" is auto), or string of gain elements.\n.RE\n.RS\nE.g. \"LNA=20,TIA=8,PGA=2\" for LimeSDR.\n.RE\n.SS \"Flex decoder spec\"\nUse \\-X <spec> to add a flexible general purpose decoder.\n\n<spec> is \"key=value[,key=value...]\"\nCommon keys are:\n.RS\nname=<name> (or: n=<name>)\n.RE\n.RS\nmodulation=<modulation> (or: m=<modulation>)\n.RE\n.RS\nshort=<short> (or: s=<short>)\n.RE\n.RS\nlong=<long> (or: l=<long>)\n.RE\n.RS\nsync=<sync> (or: y=<sync>)\n.RE\n.RS\nreset=<reset> (or: r=<reset>)\n.RE\n.RS\ngap=<gap> (or: g=<gap>)\n.RE\n.RS\ntolerance=<tolerance> (or: t=<tolerance>)\n.RE\n.RS\npriority=<n> : run decoder only as fallback\n.RE\nwhere:\n<name> can be any descriptive name tag you need in the output\n<modulation> is one of:\n.RS\nOOK_MC_ZEROBIT :  Manchester Code with fixed leading zero bit\n.RE\n.RS\nOOK_PCM :         Non Return to Zero coding (Pulse Code)\n.RE\n.RS\nOOK_RZ :          Return to Zero coding (Pulse Code)\n.RE\n.RS\nOOK_PPM :         Pulse Position Modulation\n.RE\n.RS\nOOK_PWM :         Pulse Width Modulation\n.RE\n.RS\nOOK_DMC :         Differential Manchester Code\n.RE\n.RS\nOOK_PIWM_RAW :    Raw Pulse Interval and Width Modulation\n.RE\n.RS\nOOK_PIWM_DC :     Differential Pulse Interval and Width Modulation\n.RE\n.RS\nOOK_MC_OSV1 :     Manchester Code for OSv1 devices\n.RE\n.RS\nFSK_PCM :         FSK Pulse Code Modulation\n.RE\n.RS\nFSK_PWM :         FSK Pulse Width Modulation\n.RE\n.RS\nFSK_MC_ZEROBIT :  Manchester Code with fixed leading zero bit\n.RE\n<short>, <long>, <sync> are nominal modulation timings in us,\n<reset>, <gap>, <tolerance> are maximum modulation timings in us:\nPCM/RZ  short: Nominal width of pulse [us]\n         long: Nominal width of bit period [us]\nPPM     short: Nominal width of '0' gap [us]\n         long: Nominal width of '1' gap [us]\nPWM     short: Nominal width of '1' pulse [us]\n         long: Nominal width of '0' pulse [us]\n         sync: Nominal width of sync pulse [us] (optional)\ncommon    gap: Maximum gap size before new row of bits [us]\n        reset: Maximum gap size before End Of Message [us]\n    tolerance: Maximum pulse deviation [us] (optional).\nAvailable options are:\n.RS\nbits=<n> : only match if at least one row has <n> bits\n.RE\n.RS\nrows=<n> : only match if there are <n> rows\n.RE\n.RS\nrepeats=<n> : only match if some row is repeated <n> times\n.RE\n.RS\n\tuse opt>=n to match at least <n> and opt<=n to match at most <n>\n.RE\n.RS\ninvert : invert all bits\n.RE\n.RS\nreflect : reflect each byte (MSB first to MSB last)\n.RE\n.RS\ndecode_uart : UART 8n1 (10\\-to\\-8) decode\n.RE\n.RS\ndecode_dm : Differential Manchester decode\n.RE\n.RS\ndecode_mc : Manchester decode\n.RE\n.RS\nmatch=<bits> : only match if the <bits> are found\n.RE\n.RS\npreamble=<bits> : match and align at the <bits> preamble\n.RE\n.RS\n\t<bits> is a row spec of {<bit count>}<bits as hex number>\n.RE\n.RS\nunique : suppress duplicate row output\n.RE\n\n.RS\ncountonly : suppress detailed row output\n.RE\n\nE.g. \\-X \"n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repeats>=3\"\n.SS \"Output format option\"\n.TP\n[ \\fB\\-F\\fI log|kv|json|csv|mqtt|influx|syslog|trigger|rtl_tcp|http|null\\fP ]\nProduce decoded output in given format.\n.RS\nWithout this option the default is LOG and KV output. Use \"\\-F null\" to remove the default.\n.RE\n.RS\nAppend output to file with :<filename> (e.g. \\-F csv:log.csv), defaults to stdout.\n.RE\n.TP\n[ \\fB\\-F\\fI mqtt[s][:[//]host[:port][,<options>]\\fP ]\n(default: localhost:1883)\n.RS\nSpecify MQTT server with e.g. \\-F mqtt://localhost:1883\n.RE\n.RS\nDefault user and password are read from MQTT_USERNAME and MQTT_PASSWORD env vars.\n.RE\n.RS\nAdd MQTT options with e.g. \\-F \"mqtt://host:1883,opt=arg\"\n.RE\n.RS\nMQTT options are: user=foo, pass=bar, retain[=0|1], <format>[=topic]\n.RE\n.RS\nSupported MQTT formats: (default is all)\n.RE\n.RS\n  availability: posts availability (online/offline)\n.RE\n.RS\n  events: posts JSON event data, default \"<base>/events\"\n.RE\n.RS\n  states: posts JSON state data, default \"<base>/states\"\n.RE\n.RS\n  devices: posts device and sensor info in nested topics,\n.RE\n.RS\n           default \"<base>/devices[/type][/model][/subtype][/channel][/id]\"\n.RE\n.RS\nA base topic can be set with base=<topic>, default is \"rtl_433/HOSTNAME\".\n.RE\n.RS\nAny topic string overrides the base topic and will expand keys like [/model]\n.RE\n.RS\nE.g. \\-F \"mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]\"\n.RE\n.RS\nFor TLS use e.g. \\-F \"mqtts://host,tls_cert=<path>,tls_key=<path>,tls_ca_cert=<path>\"\n.RE\n.RS\nWith MQTT each rtl_433 instance needs a distinct driver selection. The MQTT Client\\-ID is computed from the driver string.\n.RE\n.RS\nIf you use multiple RTL\\-SDR, perhaps set a serial and select by that (helps not to get the wrong antenna).\n.RE\n.TP\n[ \\fB\\-F\\fI influx[:[//]host[:port][/<path and options>]\\fP ]\n.RS\nSpecify InfluxDB 2.0 server with e.g. \\-F \"influx://localhost:9999/api/v2/write?org=<org>&bucket=<bucket>,token=<authtoken>\"\n.RE\n.RS\nSpecify InfluxDB 1.x server with e.g. \\-F \"influx://localhost:8086/write?db=<db>&p=<password>&u=<user>\"\n.RE\n.RS\n  Additional parameter \\-M time:unix:usec:utc for correct timestamps in InfluxDB recommended\n.RE\n.TP\n[ \\fB\\-F\\fI syslog[:[//]host[:port\\fP ]\n(default: localhost:514)\n.RS\nSpecify host/port for syslog with e.g. \\-F syslog:127.0.0.1:1514\n.RE\n.TP\n[ \\fB\\-F\\fI trigger:/path/to/file\\fP ]\n.RS\nAdd an output that writes a \"1\" to the path for each event, use with a e.g. a GPIO\n.RE\n.TP\n[ \\fB\\-F\\fI rtl_tcp[:[//]bind[:port]\\fP ]\n(default: localhost:1234)\n.RS\nAdd a rtl_tcp pass\\-through server\n.RE\n.TP\n[ \\fB\\-F\\fI http[:[//]bind[:port]\\fP ]\n(default: 0.0.0.0:8433)\n.RS\nAdd a HTTP API server, a UI is at e.g. http://localhost:8433/\n.RE\n.SS \"Meta information option\"\n.TP\n[ \\fB\\-M\\fI time[:<options>]|protocol|level|noise[:<secs>]|stats|bits\\fP ]\nAdd various metadata to every output line.\n.RS\nUse \"time\" to add current date and time meta data (preset for live inputs).\n.RE\n.RS\nUse \"time:rel\" to add sample position meta data (preset for read\\-file and stdin).\n.RE\n.RS\nUse \"time:unix\" to show the seconds since unix epoch as time meta data. This is always UTC.\n.RE\n.RS\nUse \"time:iso\" to show the time with ISO\\-8601 format (YYYY\\-MM\\-DD\"T\"hh:mm:ss).\n.RE\n.RS\nUse \"time:off\" to remove time meta data.\n.RE\n.RS\nUse \"time:usec\" to add microseconds to date time meta data.\n.RE\n.RS\nUse \"time:tz\" to output time with timezone offset.\n.RE\n.RS\nUse \"time:utc\" to output time in UTC.\n.RE\n.RS\n\t(this may also be accomplished by invocation with TZ environment variable set).\n.RE\n.RS\n\t\"usec\" and \"utc\" can be combined with other options, eg. \"time:iso:utc\" or \"time:unix:usec\".\n.RE\n.RS\nUse \"replay[:N]\" to replay file inputs at (N\\-times) realtime.\n.RE\n.RS\nUse \"protocol\" / \"noprotocol\" to output the decoder protocol number meta data.\n.RE\n.RS\nUse \"level\" to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\n.RE\n.RS\nUse \"noise[:<secs>]\" to report estimated noise level at intervals (default: 10 seconds).\n.RE\n.RS\nUse \"stats[:[<level>][:<interval>]]\" to report statistics (default: 600 seconds).\n.RE\n.RS\n  level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\n.RE\n.RS\nUse \"bits\" to add bit representation to code outputs (for debug).\n.RE\n.SS \"Read file option\"\n.TP\n[ \\fB\\-r\\fI <filename>\\fP ]\nRead data from input file instead of a receiver\n.RS\nParameters are detected from the full path, file name, and extension.\n.RE\n\n.RS\nA center frequency is detected as (fractional) number suffixed with 'M',\n.RE\n.RS\n 'Hz', 'kHz', 'MHz', or 'GHz'.\n.RE\n\n.RS\nA sample rate is detected as (fractional) number suffixed with 'k',\n.RE\n.RS\n 'sps', 'ksps', 'Msps', or 'Gsps'.\n.RE\n\n.RS\nFile content and format are detected as parameters, possible options are:\n.RE\n.RS\n 'cu8', 'cs16', 'cf32' ('IQ' implied), and 'am.s16'.\n.RE\n\n.RS\nParameters must be separated by non\\-alphanumeric chars and are case\\-insensitive.\n.RE\n.RS\nOverrides can be prefixed, separated by colon (':')\n.RE\n\n.RS\nE.g. default detection by extension: path/filename.am.s16\n.RE\n.RS\nforced overrides: am:s16:path/filename.ext\n.RE\n\n.RS\nReading from pipes also support format options.\n.RE\n.RS\nE.g reading complex 32\\-bit float: CU32:\\-\n.RE\n.SS \"Write file option\"\n.TP\n[ \\fB\\-w\\fI <filename>\\fP ]\nSave data stream to output file (a '\\-' dumps samples to stdout)\n.TP\n[ \\fB\\-W\\fI <filename>\\fP ]\nSave data stream to output file, overwrite existing file\n.RS\nParameters are detected from the full path, file name, and extension.\n.RE\n\n.RS\nFile content and format are detected as parameters, possible options are:\n.RE\n.RS\n 'cu8', 'cs8', 'cs16', 'cf32' ('IQ' implied),\n.RE\n.RS\n 'am.s16', 'am.f32', 'fm.s16', 'fm.f32',\n.RE\n.RS\n 'i.f32', 'q.f32', 'logic.u8', 'ook', and 'vcd'.\n.RE\n\n.RS\nParameters must be separated by non\\-alphanumeric chars and are case\\-insensitive.\n.RE\n.RS\nOverrides can be prefixed, separated by colon (':')\n.RE\n\n.RS\nE.g. default detection by extension: path/filename.am.s16\n.RE\n.RS\nforced overrides: am:s16:path/filename.ext\n.RE\n\n.\\\" end\n.SH \"RESOURCES\"\n.sp\n\\fBProject web site:\\fP \\c\n.URL \"https://github.com/merbanan/rtl_433\" \"\" \"\"\n.SH \"COPYING\"\n.sp\nCopyright \\(co 2012\\-2019 Benjamin Larsson, Christian W. Zuckschwerdt, and many contributors.\n.br\nFree use of this software is granted under the terms of the GPL\\-2+ License.\n"
  },
  {
    "path": "rtl433.pc.in",
    "content": "prefix=@prefix@\nexec_prefix=@exec_prefix@\nlibdir=@libdir@\nincludedir=@includedir@\n\nName: RTL-433 Utility\nDescription: C Utility\nVersion: @VERSION@\nCflags: -I${includedir}/ @RTL433_PC_CFLAGS@\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "########################################################################\n# Build libraries and executables\n########################################################################\n# commodity libraries\n# consider -fvisibility=hidden\n# Proper object library type was only introduced with CMake 2.8.8\nadd_library(r_433 STATIC\n    abuf.c\n    am_analyze.c\n    baseband.c\n    bit_util.c\n    bitbuffer.c\n    compat_paths.c\n    compat_time.c\n    confparse.c\n    data.c\n    data_tag.c\n    decoder_util.c\n    fileformat.c\n    http_server.c\n    jsmn.c\n    list.c\n    logger.c\n    mongoose.c\n    optparse.c\n    output_file.c\n    output_influx.c\n    output_log.c\n    output_mqtt.c\n    output_rtltcp.c\n    output_trigger.c\n    output_udp.c\n    pulse_analyzer.c\n    pulse_data.c\n    pulse_detect.c\n    pulse_detect_fsk.c\n    pulse_slicer.c\n    r_api.c\n    r_util.c\n    raw_output.c\n    rfraw.c\n    samp_grab.c\n    sdr.c\n    term_ctl.c\n    write_sigrok.c\n    devices/abmt.c\n    devices/acurite.c\n    devices/acurite_01185m.c\n    devices/akhan_100F14.c\n    devices/alecto.c\n    devices/ambient_weather.c\n    devices/ambientweather_tx8300.c\n    devices/ambientweather_wh31e.c\n    devices/ant_antplus.c\n    devices/apator_metra_eitn30.c\n    devices/apator_metra_erm30.c\n    devices/arad_ms_meter.c\n    devices/archos_tbh.c\n    devices/arexx_ml.c\n    devices/atech_ws308.c\n    devices/auriol_4ld5661.c\n    devices/auriol_aft77b2.c\n    devices/auriol_afw2a1.c\n    devices/auriol_ahfl.c\n    devices/auriol_hg02832.c\n    devices/badger_orion_endpoint.c\n    devices/badger_water.c\n    devices/baldr_rain.c\n    devices/baldr_therm.c\n    devices/blueline.c\n    devices/blyss.c\n    devices/bm5.c\n    devices/brennenstuhl_rcs_2044.c\n    devices/bresser_3ch.c\n    devices/bresser_5in1.c\n    devices/bresser_6in1.c\n    devices/bresser_7in1.c\n    devices/bresser_leakage.c\n    devices/bresser_lightning.c\n    devices/bresser_st1005h.c\n    devices/bt_rain.c\n    devices/burnhardbbq.c\n    devices/calibeur.c\n    devices/cardin.c\n    devices/cavius.c\n    devices/ced7000.c\n    devices/celsia_czc1.c\n    devices/chamberlain_cwpirc.c\n    devices/chuango.c\n    devices/cmr113.c\n    devices/companion_wtr001.c\n    devices/cotech_36_7959.c\n    devices/current_cost.c\n    devices/danfoss.c\n    devices/deltadore_x3d.c\n    devices/digitech_xc0324.c\n    devices/directv.c\n    devices/dish_remote_6_3.c\n    devices/dsc.c\n    devices/ec3k.c\n    devices/ecodhome.c\n    devices/ecowitt.c\n    devices/efergy_e2_classic.c\n    devices/efergy_optical.c\n    devices/efth800.c\n    devices/elro_db286a.c\n    devices/elv.c\n    devices/emax.c\n    devices/emontx.c\n    devices/emos_e6016.c\n    devices/emos_e6016_rain.c\n    devices/enocean_erp1.c\n    devices/ert_idm.c\n    devices/ert_scm.c\n    devices/esa.c\n    devices/esic_emt7110.c\n    devices/esperanza_ews.c\n    devices/eurochron.c\n    devices/fineoffset.c\n    devices/fineoffset_wh1050.c\n    devices/fineoffset_wh1080.c\n    devices/fineoffset_wh31l.c\n    devices/fineoffset_wh43.c\n    devices/fineoffset_wh45.c\n    devices/fineoffset_wh46.c\n    devices/fineoffset_wh55.c\n    devices/fineoffset_wn34.c\n    devices/fineoffset_ws80.c\n    devices/fineoffset_ws85.c\n    devices/fineoffset_ws90.c\n    devices/flex.c\n    devices/flowis.c\n    devices/fordremote.c\n    devices/fs20.c\n    devices/ft004b.c\n    devices/funkbus.c\n    devices/gasmate_ba1008.c\n    devices/ge_coloreffects.c\n    devices/geevon.c\n    devices/geevon_tx19.c\n    devices/generic_motion.c\n    devices/generic_remote.c\n    devices/generic_temperature_sensor.c\n    devices/geo_minim.c\n    devices/govee.c\n    devices/gridstream.c\n    devices/gt_tmbbq05.c\n    devices/gt_wt_02.c\n    devices/gt_wt_03.c\n    devices/hcs200.c\n    devices/hideki.c\n    devices/holman_ws5029.c\n    devices/homelead_hg9901.c\n    devices/hondaremote.c\n    devices/honeywell.c\n    devices/honeywell_cm921.c\n    devices/honeywell_wdb.c\n    devices/ht680.c\n    devices/ibis_beacon.c\n    devices/ikea_sparsnas.c\n    devices/infactory.c\n    devices/inkbird_ith20r.c\n    devices/inovalley-kw9015b.c\n    devices/insteon.c\n    devices/interlogix.c\n    devices/intertechno.c\n    devices/jasco.c\n    devices/kedsum.c\n    devices/kerui.c\n    devices/klimalogg.c\n    devices/lacrosse.c\n    devices/lacrosse_breezepro.c\n    devices/lacrosse_r1.c\n    devices/lacrosse_th3.c\n    devices/lacrosse_tx141x.c\n    devices/lacrosse_tx31u.c\n    devices/lacrosse_tx34.c\n    devices/lacrosse_tx35.c\n    devices/lacrosse_wr1.c\n    devices/lacrosse_ws7000.c\n    devices/lacrossews.c\n    devices/lightwave_rf.c\n    devices/m_bus.c\n    devices/markisol.c\n    devices/marlec_solar.c\n    devices/maverick_et73.c\n    devices/maverick_et73x.c\n    devices/maverick_xr30.c\n    devices/maverick_xr50.c\n    devices/mebus.c\n    devices/megacode.c\n    devices/missil_ml0757.c\n    devices/mueller_hotrod.c\n    devices/neptune_r900.c\n    devices/new_template.c\n    devices/newkaku.c\n    devices/nexa.c\n    devices/nexus.c\n    devices/nice_flor_s.c\n    devices/norgo.c\n    devices/oil_smart.c\n    devices/oil_standard.c\n    devices/oil_watchman.c\n    devices/oil_watchman_advanced.c\n    devices/opus_xt300.c\n    devices/oregon_scientific.c\n    devices/oregon_scientific_sl109h.c\n    devices/oregon_scientific_v1.c\n    devices/oria_wa150km.c\n    devices/philips_aj3650.c\n    devices/philips_aj7010.c\n    devices/proflame2.c\n    devices/prologue.c\n    devices/proove.c\n    devices/quhwa.c\n    devices/quinetic.c\n    devices/radiohead_ask.c\n    devices/rainpoint.c\n    devices/rainpoint_hcs012arf.c\n    devices/regency_fan.c\n    devices/revolt_nc5462.c\n    devices/revolt_zx7717.c\n    devices/rftech.c\n    devices/risco_agility.c\n    devices/rojaflex.c\n    devices/rosstech_dcu706.c\n    devices/rubicson.c\n    devices/rubicson_48659.c\n    devices/rubicson_pool_48942.c\n    devices/s3318p.c\n    devices/sainlogic_sa8.c\n    devices/schou_72543_rain.c\n    devices/schraeder.c\n    devices/scmplus.c\n    devices/secplus_v1.c\n    devices/secplus_v2.c\n    devices/sharp_spc775.c\n    devices/silvercrest.c\n    devices/simplisafe.c\n    devices/simplisafe_gen3.c\n    devices/smoke_gs558.c\n    devices/solight_te44.c\n    devices/somfy_iohc.c\n    devices/somfy_rts.c\n    devices/springfield.c\n    devices/srsmith_pool_srs_2c_tx.c\n    devices/steelmate.c\n    devices/telldus_ft0385r.c\n    devices/tfa_14_1504_v2.c\n    devices/tfa_30_3196.c\n    devices/tfa_30_3221.c\n    devices/tfa_drop_30.3233.c\n    devices/tfa_marbella.c\n    devices/tfa_pool_thermometer.c\n    devices/tfa_twin_plus_30.3049.c\n    devices/thermopro_tp11.c\n    devices/thermopro_tp12.c\n    devices/thermopro_tp211b.c\n    devices/thermopro_tp28b.c\n    devices/thermopro_tp82xb.c\n    devices/thermopro_tp862b.c\n    devices/thermopro_tx2.c\n    devices/thermopro_tx2c.c\n    devices/thermopro_tx7b.c\n    devices/thermor.c\n    devices/tpms_abarth124.c\n    devices/tpms_airpuxem.c\n    devices/tpms_ave.c\n    devices/tpms_bmw.c\n    devices/tpms_bmw_g3.c\n    devices/tpms_citroen.c\n    devices/tpms_eezrv.c\n    devices/tpms_elantra2012.c\n    devices/tpms_ford.c\n    devices/tpms_gm.c\n    devices/tpms_hyundai_vdo.c\n    devices/tpms_jansite.c\n    devices/tpms_jansite_solar.c\n    devices/tpms_kia.c\n    devices/tpms_nissan.c\n    devices/tpms_pmv107j.c\n    devices/tpms_porsche.c\n    devices/tpms_renault.c\n    devices/tpms_renault_0435r.c\n    devices/tpms_toyota.c\n    devices/tpms_truck.c\n    devices/tpms_trw.c\n    devices/tpms_tyreguard400.c\n    devices/ts_ft002.c\n    devices/ttx201.c\n    devices/universalfanctrl.c\n    devices/vaillant_vrt340f.c\n    devices/vauno_en8822c.c\n    devices/vevor_7in1.c\n    devices/visonic_powercode.c\n    devices/wallarge_cltx001.c\n    devices/watts_thermostat.c\n    devices/waveman.c\n    devices/wec2103.c\n    devices/wg_pb12v1.c\n    devices/ws2032.c\n    devices/wssensor.c\n    devices/wt0124.c\n    devices/wt450.c\n    devices/x10_rf.c\n    devices/x10_sec.c\n    devices/yale_hsa.c\n)\n\nif(\"${CMAKE_C_COMPILER_ID}\" STREQUAL \"GNU\" OR \"${CMAKE_C_COMPILER_ID}\" MATCHES \"Clang\")\n    # untouched upstream code, disable all warnings\n    set_source_files_properties(mongoose.c PROPERTIES COMPILE_FLAGS \"-w\")\nendif()\n\nadd_executable(rtl_433 rtl_433.c)\ntarget_link_libraries(rtl_433 r_433)\n\n# target_compile_definitions was only added with CMake 2.8.11\nif(THREADS_HAVE_PTHREAD_ARG)\n    set_target_properties(r_433 PROPERTIES COMPILE_OPTIONS \"-pthread\")\n    set_target_properties(r_433 PROPERTIES INTERFACE_COMPILE_OPTIONS \"-pthread\")\nendif()\nif(CMAKE_THREAD_LIBS_INIT)\n    target_link_libraries(rtl_433 \"${CMAKE_THREAD_LIBS_INIT}\")\nendif()\n\nif(MSVC)\n    # needs CMake 3.1 but Windows builds should have that\n    target_sources(rtl_433 PRIVATE getopt/getopt.c)\nendif()\n\nadd_library(data data.c abuf.c)\ntarget_link_libraries(data ${NET_LIBRARIES})\n\ntarget_link_libraries(rtl_433\n    ${SDR_LIBRARIES}\n    ${NET_LIBRARIES}\n)\n\nset(INSTALL_TARGETS rtl_433)\nif(UNIX)\ntarget_link_libraries(rtl_433 m)\nendif()\n\n# Explicitly say that we want C99\nset_target_properties(rtl_433 r_433 PROPERTIES C_STANDARD 99)\n\n########################################################################\n# Install built library files & utilities\n########################################################################\ninstall(TARGETS ${INSTALL_TARGETS}\n    RUNTIME DESTINATION bin              # .dll file\n)\n"
  },
  {
    "path": "src/abuf.c",
    "content": "/** @file\n    array buffer (string builder).\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <assert.h>\n#include <string.h>\n\n#include \"abuf.h\"\n\nvoid abuf_init(abuf_t *buf, char *dst, size_t len)\n{\n    buf->head = dst;\n    buf->tail = dst;\n    buf->left = len;\n}\n\nvoid abuf_setnull(abuf_t *buf)\n{\n    buf->head = NULL;\n    buf->tail = NULL;\n    buf->left = 0;\n}\n\nchar *abuf_push(abuf_t *buf)\n{\n    return buf->tail;\n}\n\nvoid abuf_pop(abuf_t *buf, char *end)\n{\n    buf->left += buf->tail - end;\n    buf->tail = end;\n}\n\nvoid abuf_cat(abuf_t *buf, char const *str)\n{\n    size_t len = strlen(str);\n    if (buf->left >= len + 1) {\n        memcpy(buf->tail, str, len + 1);\n        buf->tail += len;\n        buf->left -= len;\n    }\n}\n\nint abuf_printf(abuf_t *buf, _Printf_format_string_ char const *restrict format, ...)\n{\n    va_list ap;\n    va_start(ap, format);\n\n    int n = vsnprintf(buf->tail, buf->left, format, ap);\n\n    if (n > 0) {\n        size_t len = (size_t)n < buf->left ? (size_t)n : buf->left;\n        buf->tail += len;\n        buf->left -= len;\n    }\n\n    va_end(ap);\n    return n;\n}\n"
  },
  {
    "path": "src/am_analyze.c",
    "content": "/** @file\n    AM signal analyzer.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"bitbuffer.h\"\n#include \"samp_grab.h\"\n#include \"fatal.h\"\n\n#include \"am_analyze.h\"\n\n#define FRAME_END_MIN 50000 /* minimum sample count to detect frame end */\n#define FRAME_PAD 10000 /* number of samples to pad both frame start and end */\n\nam_analyze_t *am_analyze_create(void)\n{\n    am_analyze_t *a;\n    a = calloc(1, sizeof(am_analyze_t));\n    if (!a)\n        WARN_CALLOC(\"am_analyze_create()\");\n    return a; // NOTE: returns NULL on alloc failure.\n}\n\nvoid am_analyze_free(am_analyze_t *a)\n{\n    free(a);\n}\n\nvoid am_analyze_skip(am_analyze_t *a, unsigned n_samples)\n{\n    a->counter += n_samples;\n    a->signal_start = 0;\n}\n\nvoid am_analyze(am_analyze_t *a, int16_t *am_buf, unsigned n_samples, int debug_output, samp_grab_t *g)\n{\n    unsigned int i;\n    int threshold = (a->level_limit ? a->level_limit : 8000);  // Does not support auto level. Use old default instead.\n\n    for (i = 0; i < n_samples; i++) {\n        if (am_buf[i] > threshold) {\n            if (!a->signal_start)\n                a->signal_start = a->counter;\n            if (a->print) {\n                a->pulses_found++;\n                a->pulse_start = a->counter;\n                a->signal_pulse_data[a->signal_pulse_counter][0] = a->counter;\n                a->signal_pulse_data[a->signal_pulse_counter][1] = -1;\n                a->signal_pulse_data[a->signal_pulse_counter][2] = -1;\n                if (debug_output) {\n                    fprintf(stderr, \"pulse_distance %u\\n\", a->counter - a->pulse_end);\n                    fprintf(stderr, \"pulse_start distance %u\\n\", a->pulse_start - a->prev_pulse_start);\n                    fprintf(stderr, \"pulse_start[%u] found at sample %u, value = %d\\n\", a->pulses_found, a->counter, am_buf[i]);\n                }\n                a->prev_pulse_start = a->pulse_start;\n                a->print = 0;\n                a->print2 = 1;\n            }\n        }\n        a->counter++;\n        if (am_buf[i] < threshold) {\n            if (a->print2) {\n                a->pulse_avg += a->counter - a->pulse_start;\n                if (debug_output) {\n                    fprintf(stderr, \"pulse_end  [%u] found at sample %u, pulse length = %u, pulse avg length = %u\\n\",\n                            a->pulses_found, a->counter, a->counter - a->pulse_start, (a->pulses_found) ? (a->pulse_avg / a->pulses_found) : 0);\n                }\n                a->pulse_end = a->counter;\n                a->print2 = 0;\n                a->signal_pulse_data[a->signal_pulse_counter][1] = a->counter;\n                a->signal_pulse_data[a->signal_pulse_counter][2] = a->counter - a->pulse_start;\n                a->signal_pulse_counter++;\n                if (a->signal_pulse_counter >= PULSE_DATA_SIZE) {\n                    a->signal_pulse_counter = 0;\n                    fprintf(stderr, \"Too many pulses detected, probably bad input data or input parameters\\n\");\n                    return;\n                }\n            }\n            a->print = 1;\n            if (a->signal_start && (a->pulse_end + FRAME_END_MIN < a->counter)) {\n                unsigned padded_start = a->signal_start - FRAME_PAD;\n                unsigned padded_end   = a->counter - FRAME_END_MIN + FRAME_PAD;\n                unsigned padded_len   = padded_end - padded_start;\n                fprintf(stderr, \"*** signal_start = %u, signal_end = %u, signal_len = %u, pulses_found = %u\\n\",\n                        padded_start, padded_end, padded_len, a->pulses_found);\n\n                am_analyze_classify(a); // clears signal_pulse_data\n                a->pulses_found = 0;\n\n                if (g) {\n                    samp_grab_write(g, padded_len, n_samples - i - 1);\n                }\n                a->signal_start = 0;\n            }\n        }\n    }\n}\n\n\nvoid am_analyze_classify(am_analyze_t *aa)\n{\n    unsigned int i, k, max = 0, min = 1000000, t;\n    unsigned int delta, p_limit;\n    unsigned int a[3], b[2], a_cnt[3], a_new[3];\n    unsigned int signal_distance_data[PULSE_DATA_SIZE] = {0};\n    bitbuffer_t bits = {0};\n    unsigned int signal_type;\n\n    if (!aa->signal_pulse_data[0][0])\n        return;\n\n    for (i = 0; i < aa->signal_pulse_counter; i++) {\n        if (aa->signal_pulse_data[i][0] > 0) {\n            //fprintf(stderr, \"[%03d] s: %d\\t  e:\\t %d\\t l:%d\\n\",\n            //i, aa->signal_pulse_data[i][0], aa->signal_pulse_data[i][1],\n            //aa->signal_pulse_data[i][2]);\n            if (aa->signal_pulse_data[i][2] > max)\n                max = aa->signal_pulse_data[i][2];\n            if (aa->signal_pulse_data[i][2] <= min)\n                min = aa->signal_pulse_data[i][2];\n        }\n    }\n    t = (max + min) / 2;\n    //fprintf(stderr, \"\\n\\nMax: %d, Min: %d  t:%d\\n\", max, min, t);\n\n    delta = (max - min)*(max - min);\n\n    //TODO use Lloyd-Max quantizer instead\n    k = 1;\n    while ((k < 10) && (delta > 0)) {\n        unsigned min_new = 0;\n        unsigned count_min = 0;\n        unsigned max_new = 0;\n        unsigned count_max = 0;\n\n        for (i = 0; i < aa->signal_pulse_counter; i++) {\n            if (aa->signal_pulse_data[i][0] > 0) {\n                if (aa->signal_pulse_data[i][2] < t) {\n                    min_new = min_new + aa->signal_pulse_data[i][2];\n                    count_min++;\n                } else {\n                    max_new = max_new + aa->signal_pulse_data[i][2];\n                    count_max++;\n                }\n            }\n        }\n        if (count_min != 0 && count_max != 0) {\n            min_new = min_new / count_min;\n            max_new = max_new / count_max;\n        }\n\n        delta = (min - min_new)*(min - min_new) + (max - max_new)*(max - max_new);\n        min = min_new;\n        max = max_new;\n        t = (min + max) / 2;\n\n        fprintf(stderr, \"Iteration %u. t: %u    min: %u (%u)    max: %u (%u)    delta %u\\n\", k, t, min, count_min, max, count_max, delta);\n        k++;\n    }\n\n    for (i = 0; i < aa->signal_pulse_counter; i++) {\n        if (aa->signal_pulse_data[i][0] > 0) {\n            //fprintf(stderr, \"%d\\n\", aa->signal_pulse_data[i][1]);\n        }\n    }\n    /* 50% decision limit */\n    if (min != 0 && max / min > 1) {\n        fprintf(stderr, \"Pulse coding: Short pulse length %u - Long pulse length %u\\n\", min, max);\n        signal_type = 2;\n    } else {\n        fprintf(stderr, \"Distance coding: Pulse length %u\\n\", (min + max) / 2);\n        signal_type = 1;\n    }\n    p_limit = (max + min) / 2;\n\n    /* Initial guesses */\n    a[0] = 1000000;\n    a[2] = 0;\n    for (i = 1; i < aa->signal_pulse_counter; i++) {\n        if (aa->signal_pulse_data[i][0] > 0) {\n            //               fprintf(stderr, \"[%03d] s: %d\\t  e:\\t %d\\t l:%d\\t  d:%d\\n\",\n            //               i, aa->signal_pulse_data[i][0], aa->signal_pulse_data[i][1],\n            //               aa->signal_pulse_data[i][2], aa->signal_pulse_data[i][0]-aa->signal_pulse_data[i-1][1]);\n            signal_distance_data[i - 1] = aa->signal_pulse_data[i][0] - aa->signal_pulse_data[i - 1][1];\n            if (signal_distance_data[i - 1] > a[2])\n                a[2] = signal_distance_data[i - 1];\n            if (signal_distance_data[i - 1] <= a[0])\n                a[0] = signal_distance_data[i - 1];\n        }\n    }\n    min = a[0];\n    max = a[2];\n    a[1] = (a[0] + a[2]) / 2;\n    //    for (i=0 ; i<1 ; i++) {\n    //        b[i] = (a[i]+a[i+1])/2;\n    //    }\n    b[0] = (a[0] + a[1]) / 2;\n    b[1] = (a[1] + a[2]) / 2;\n    //     fprintf(stderr, \"a[0]: %d\\t a[1]: %d\\t a[2]: %d\\t\\n\",a[0],a[1],a[2]);\n    //     fprintf(stderr, \"b[0]: %d\\t b[1]: %d\\n\",b[0],b[1]);\n\n    k = 1;\n    delta = 10000000;\n    while ((k < 10) && (delta > 0)) {\n        for (i = 0; i < 3; i++) {\n            a_new[i] = 0;\n            a_cnt[i] = 0;\n        }\n\n        for (i = 0; i < aa->signal_pulse_counter; i++) {\n            if (signal_distance_data[i] > 0) {\n                if (signal_distance_data[i] < b[0]) {\n                    a_new[0] += signal_distance_data[i];\n                    a_cnt[0]++;\n                } else if (signal_distance_data[i] < b[1] && signal_distance_data[i] >= b[0]) {\n                    a_new[1] += signal_distance_data[i];\n                    a_cnt[1]++;\n                } else if (signal_distance_data[i] >= b[1]) {\n                    a_new[2] += signal_distance_data[i];\n                    a_cnt[2]++;\n                }\n            }\n        }\n\n        //         fprintf(stderr, \"Iteration %d.\", k);\n        delta = 0;\n        for (i = 0; i < 3; i++) {\n            if (a_cnt[i])\n                a_new[i] /= a_cnt[i];\n            delta += (a[i] - a_new[i])*(a[i] - a_new[i]);\n            //             fprintf(stderr, \"\\ta[%d]: %d (%d)\", i, a_new[i], a[i]);\n            a[i] = a_new[i];\n        }\n        //         fprintf(stderr, \" delta %d\\n\", delta);\n\n        if (a[0] < min) {\n            a[0] = min;\n            //             fprintf(stderr, \"Fixing a[0] = %d\\n\", min);\n        }\n        if (a[2] > max) {\n            a[0] = max;\n            //             fprintf(stderr, \"Fixing a[2] = %d\\n\", max);\n        }\n        //         if (a[1] == 0) {\n        //             a[1] = (a[2]+a[0])/2;\n        //             fprintf(stderr, \"Fixing a[1] = %d\\n\", a[1]);\n        //         }\n\n        //         fprintf(stderr, \"Iteration %d.\", k);\n        for (i = 0; i < 2; i++) {\n            //             fprintf(stderr, \"\\tb[%d]: (%d) \", i, b[i]);\n            b[i] = (a[i] + a[i + 1]) / 2;\n            //             fprintf(stderr, \"%d  \", b[i]);\n        }\n        //         fprintf(stderr, \"\\n\");\n        k++;\n    }\n\n    if (aa->override_short) {\n        p_limit = aa->override_short;\n        a[0] = aa->override_short;\n    }\n\n    if (aa->override_long) {\n        a[1] = aa->override_long;\n    }\n\n    fprintf(stderr, \"\\nShort distance: %u, long distance: %u, packet distance: %u\\n\", a[0], a[1], a[2]);\n    fprintf(stderr, \"\\np_limit: %u\\n\", p_limit);\n\n    bitbuffer_clear(&bits);\n    if (signal_type == 1) {\n        for (i = 0; i < aa->signal_pulse_counter; i++) {\n            if (signal_distance_data[i] > 0) {\n                if (signal_distance_data[i] < (a[0] + a[1]) / 2) {\n                    //                     fprintf(stderr, \"0 [%d] %d < %d\\n\",i, signal_distance_data[i], (a[0]+a[1])/2);\n                    bitbuffer_add_bit(&bits, 0);\n                } else if ((signal_distance_data[i] > (a[0] + a[1]) / 2) && (signal_distance_data[i] < (a[1] + a[2]) / 2)) {\n                    //                     fprintf(stderr, \"0 [%d] %d > %d\\n\",i, signal_distance_data[i], (a[0]+a[1])/2);\n                    bitbuffer_add_bit(&bits, 1);\n                } else if (signal_distance_data[i] > (a[1] + a[2]) / 2) {\n                    //                     fprintf(stderr, \"0 [%d] %d > %d\\n\",i, signal_distance_data[i], (a[1]+a[2])/2);\n                    bitbuffer_add_row(&bits);\n                }\n\n            }\n\n        }\n        bitbuffer_print(&bits);\n    }\n    if (signal_type == 2) {\n        for (i = 0; i < aa->signal_pulse_counter; i++) {\n            if (aa->signal_pulse_data[i][2] > 0) {\n                if (aa->signal_pulse_data[i][2] < p_limit) {\n                    //                     fprintf(stderr, \"0 [%d] %d < %d\\n\",i, aa->signal_pulse_data[i][2], p_limit);\n                    bitbuffer_add_bit(&bits, 0);\n                } else {\n                    //                     fprintf(stderr, \"1 [%d] %d > %d\\n\",i, aa->signal_pulse_data[i][2], p_limit);\n                    bitbuffer_add_bit(&bits, 1);\n                }\n                if ((signal_distance_data[i] >= (a[1] + a[2]) / 2)) {\n                    //                     fprintf(stderr, \"\\\\n [%d] %d > %d\\n\",i, signal_distance_data[i], (a[1]+a[2])/2);\n                    bitbuffer_add_row(&bits);\n                }\n\n\n            }\n        }\n        bitbuffer_print(&bits);\n    }\n\n    // clear signal_pulse_data\n    aa->signal_pulse_counter = 0;\n}\n"
  },
  {
    "path": "src/baseband.c",
    "content": "/** @file\n    Various functions for baseband sample processing.\n\n    Copyright (C) 2012 by Benjamin Larsson <benjamin@southpole.se>\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"baseband.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\n#include \"logger.h\"\n#include \"r_util.h\"\n\nstatic uint16_t scaled_squares[256];\n\n/// precalculate lookup table for envelope detection.\nstatic void calc_squares(void)\n{\n    if (scaled_squares[0])\n        return; // already initialized\n    int i;\n    for (i = 0; i < 256; i++)\n        scaled_squares[i] = (127 - i) * (127 - i);\n}\n\n// This will give a noisy envelope of OOK/ASK signals.\n// Subtract the bias (-128) and get an envelope estimation.\nfloat envelope_detect(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len)\n{\n    unsigned long i;\n    uint32_t sum = 0;\n    for (i = 0; i < len; i++) {\n        y_buf[i] = scaled_squares[iq_buf[2 * i ]] + scaled_squares[iq_buf[2 * i + 1]];\n        sum += y_buf[i];\n    }\n    return len > 0 && sum >= len ? AMP_TO_DB((float)sum / len) : AMP_TO_DB(1);\n}\n\n/// This will give a noisy envelope of OOK/ASK signals.\n/// Subtracts the bias (-128) and calculates the norm (scaled by 16384).\n/// Using a LUT is slower for O1 and above.\nfloat envelope_detect_nolut(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len)\n{\n    unsigned long i;\n    uint32_t sum = 0;\n    for (i = 0; i < len; i++) {\n        int16_t x = 127 - iq_buf[2 * i];\n        int16_t y = 127 - iq_buf[2 * i + 1];\n        y_buf[i]  = x * x + y * y; // max 32768, fs 16384\n        sum += y_buf[i];\n    }\n    return len > 0 && sum >= len ? AMP_TO_DB((float)sum / len) : AMP_TO_DB(1);\n}\n\n/// 122/128, 51/128 Magnitude Estimator for CU8 (SIMD has min/max).\n/// Note that magnitude emphasizes quiet signals / deemphasizes loud signals.\nfloat magnitude_est_cu8(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len)\n{\n    unsigned long i;\n    uint32_t sum = 0;\n    for (i = 0; i < len; i++) {\n        uint16_t x = abs(iq_buf[2 * i] - 128);\n        uint16_t y = abs(iq_buf[2 * i + 1] - 128);\n        uint16_t mi = x < y ? x : y;\n        uint16_t mx = x > y ? x : y;\n        uint16_t mag_est = 122 * mx + 51 * mi;\n        y_buf[i] = mag_est; // max 22144, fs 16384\n        sum += y_buf[i];\n    }\n    return len > 0 && sum >= len ? MAG_TO_DB((float)sum / len) : MAG_TO_DB(1);\n}\n\n/// True Magnitude for CU8 (sqrt can SIMD but float is slow).\nfloat magnitude_true_cu8(uint8_t const *iq_buf, uint16_t *y_buf, uint32_t len)\n{\n    unsigned long i;\n    uint32_t sum = 0;\n    for (i = 0; i < len; i++) {\n        int16_t x = iq_buf[2 * i] - 128;\n        int16_t y = iq_buf[2 * i + 1] - 128;\n        y_buf[i]  = (uint16_t)(sqrt(x * x + y * y) * 128.0); // max 181, scaled 23170, fs 16384\n        sum += y_buf[i];\n    }\n    return len > 0 && sum >= len ? MAG_TO_DB((float)sum / len) : MAG_TO_DB(1);\n}\n\n/// 122/128, 51/128 Magnitude Estimator for CS16 (SIMD has min/max).\nfloat magnitude_est_cs16(int16_t const *iq_buf, uint16_t *y_buf, uint32_t len)\n{\n    unsigned long i;\n    uint32_t sum = 0;\n    for (i = 0; i < len; i++) {\n        uint32_t x = abs(iq_buf[2 * i]);\n        uint32_t y = abs(iq_buf[2 * i + 1]);\n        uint32_t mi = x < y ? x : y;\n        uint32_t mx = x > y ? x : y;\n        uint32_t mag_est = 122 * mx + 51 * mi;\n        y_buf[i] = mag_est >> 8; // max 5668864, scaled 22144, fs 16384\n        sum += y_buf[i];\n    }\n    return len > 0 && sum >= len ? MAG_TO_DB((float)sum / len) : MAG_TO_DB(1);\n}\n\n/// True Magnitude for CS16 (sqrt can SIMD but float is slow).\nfloat magnitude_true_cs16(int16_t const *iq_buf, uint16_t *y_buf, uint32_t len)\n{\n    unsigned long i;\n    uint32_t sum = 0;\n    for (i = 0; i < len; i++) {\n        int32_t x = iq_buf[2 * i];\n        int32_t y = iq_buf[2 * i + 1];\n        y_buf[i]  = (int)sqrt(x * x + y * y) >> 1; // max 46341, scaled 23170, fs 16384\n        sum += y_buf[i];\n    }\n    return len > 0 && sum >= len ? MAG_TO_DB((float)sum / len) : MAG_TO_DB(1);\n}\n\nvoid baseband_low_pass_filter_reset(filter_state_t *lowpass_filter)\n{\n    *lowpass_filter = (filter_state_t){0};\n}\n\n// Fixed-point arithmetic on Q0.15\n#define F_SCALE 15\n#define S_CONST (1 << F_SCALE)\n#define FIX(x) ((int)(x * S_CONST))\n\n/** Something that might look like a IIR lowpass filter.\n\n    [b,a] = butter(1, Wc) # low pass filter with cutoff pi*Wc radians\n    - Q1.15*Q15.0 = Q16.15\n    - Q16.15>>1 = Q15.14\n    - Q15.14 + Q15.14 + Q15.14 could possibly overflow to 17.14\n    - but the b coeffs are small so it won't happen\n    - Q15.14>>14 = Q15.0\n*/\nvoid baseband_low_pass_filter(filter_state_t *state, uint16_t const *x_buf, int16_t *y_buf, uint32_t len)\n{\n    ///  [b,a] = butter(1, 0.01) -> 3x tau (95%) ~100 samples\n    //static int const a[FILTER_ORDER + 1] = {FIX(1.00000) >> 1, FIX(0.96907) >> 1};\n    //static int const b[FILTER_ORDER + 1] = {FIX(0.015466) >> 1, FIX(0.015466) >> 1};\n    ///  [b,a] = butter(1, 0.05) -> 3x tau (95%) ~20 samples\n    static int const a[FILTER_ORDER + 1] = {FIX(1.00000) >> 1, FIX(0.85408) >> 1};\n    static int const b[FILTER_ORDER + 1] = {FIX(0.07296) >> 1, FIX(0.07296) >> 1};\n    // note that coeffs are prescaled by div 2\n\n    // Prevent out of bounds access\n    if (len < FILTER_ORDER) {\n        return;\n    }\n\n    // Calculate first sample\n    y_buf[0] = (a[1] * state->y[0] + b[0] * (x_buf[0] + state->x[0])) >> (F_SCALE - 1); // note: prescaled, b[0]==b[1]\n    for (unsigned long i = 1; i < len; i++) {\n        y_buf[i] = (a[1] * y_buf[i - 1] + b[0] * (x_buf[i] + x_buf[i - 1])) >> (F_SCALE - 1); // note: prescaled, b[0]==b[1]\n    }\n\n    // Save last samples\n    memcpy(state->x, &x_buf[len - FILTER_ORDER], FILTER_ORDER * sizeof (int16_t));\n    memcpy(state->y, &y_buf[len - FILTER_ORDER], FILTER_ORDER * sizeof (int16_t));\n}\n\n\n/** Integer implementation of atan2() with int16_t normalized output.\n\n    Returns arc tangent of y/x across all quadrants in radians.\n    Error max 0.07 radians.\n    Reference: http://dspguru.com/dsp/tricks/fixed-point-atan2-with-self-normalization\n    @param y Numerator (imaginary value of complex vector)\n    @param x Denominator (real value of complex vector)\n    @return angle in radians (Pi equals INT16_MAX)\n*/\nstatic int16_t atan2_int16(int32_t y, int32_t x)\n{\n    static int32_t const I_PI_4 = INT16_MAX/4;      // M_PI/4\n    static int32_t const I_3_PI_4 = 3*INT16_MAX/4;  // 3*M_PI/4\n\n    int32_t const abs_y = abs(y);\n    int32_t angle;\n\n    if (!x && !y) return 0; // We would get 8191 with the code below\n\n    if (x >= 0) {    // Quadrant I and IV\n        int32_t denom = (abs_y + x);\n        if (denom == 0) denom = 1;  // Prevent divide by zero\n        angle = I_PI_4 - I_PI_4 * (x - abs_y) / denom;\n    } else {        // Quadrant II and III\n        int32_t denom = (abs_y - x);\n        if (denom == 0) denom = 1;  // Prevent divide by zero\n        angle = I_3_PI_4 - I_PI_4 * (x + abs_y) / denom;\n    }\n    if (y < 0) angle = -angle;    // Negate if in III or IV\n    return angle;\n}\n\nvoid baseband_demod_FM_reset(demodfm_state_t *demod_fm)\n{\n    *demod_fm = (demodfm_state_t){0};\n}\n\n/// Fast Instantaneous frequency and Low Pass filter, CU8 samples\nvoid baseband_demod_FM(demodfm_state_t *state, uint8_t const *x_buf, int16_t *y_buf, unsigned long num_samples, uint32_t samp_rate, float low_pass)\n{\n    // Select filter coeffs, [b,a] = butter(1, cutoff)\n    // e.g [b,a] = butter(1, 0.1) -> 3x tau (95%) ~10 samples, 250k -> 40us, 1024k -> 10us\n    // a = 1.00000, 0.72654; b = 0.13673, 0.13673;\n    // e.g. [b,a] = butter(1, 0.2) -> 3x tau (95%) ~5 samples, 250k -> 20us, 1024k -> 5us\n    // a = 1.00000, 0.50953; b = 0.24524, 0.24524;\n    if (state->rate != samp_rate) {\n        if (low_pass > 1e4f) {\n            low_pass = low_pass / samp_rate;\n        } else if (low_pass >= 1.0f) {\n            low_pass = 1e6f / low_pass / samp_rate;\n        }\n        print_logf(LOG_NOTICE, \"Baseband\", \"low pass filter for %u Hz at cutoff %.0f Hz, %.1f us\",\n                samp_rate, samp_rate * low_pass, 1e6 / (samp_rate * low_pass));\n        double ita  = 1.0 / tan(M_PI_2 * low_pass);\n        double gain = 1.0 / (1.0 + ita) / 2; // prescaled by div 2\n        state->alp_16[0] = FIX(1.0);\n        state->alp_16[1] = FIX((ita - 1.0) * gain); // scaled by -1\n        state->blp_16[0] = FIX(gain);\n        state->blp_16[1] = FIX(gain);\n        state->rate      = samp_rate;\n    }\n    int32_t const *alp = state->alp_16;\n    int32_t const *blp = state->blp_16;\n\n    // Pre-feed old sample\n    int16_t x0r = state->xr; // IQ sample: x[n], real\n    int16_t x0i = state->xi; // IQ sample: x[n], imag\n    int16_t x0f = state->xf; // Instantaneous frequency\n    int16_t y0f = state->yf; // Instantaneous frequency, low pass filtered\n\n    for (unsigned n = 0; n < num_samples; n++) {\n        int16_t x1r, x1i; // Old IQ sample: x[n-1]\n        int16_t x1f, y1f; // Instantaneous frequency, old sample\n        int32_t pr, pi;   // Phase difference vector\n\n        // delay old sample\n        x1r = x0r;\n        x1i = x0i;\n        y1f = y0f;\n        x1f = x0f;\n        // get new sample\n        x0r = *x_buf++ - 128;\n        x0i = *x_buf++ - 128;\n        // Calculate phase difference vector: x[n] * conj(x[n-1])\n        pr = x0r * x1r + x0i * x1i; // May exactly overflow an int16_t (-128*-128 + -128*-128)\n        pi = x0i * x1r - x0r * x1i;\n        // xlp = (int16_t)((atan2f(pi, pr) / M_PI) * INT16_MAX); // Floating point implementation\n        x0f = atan2_int16(pi, pr); // Integer implementation\n        // xlp = pi; // Cheat and use only imaginary part (works OK, but is amplitude sensitive)\n        // Low pass filter\n        // y0f      = ((alp[1] * y1f >> 1) + (blp[0] * x0f >> 1) + (blp[1] * x1f >> 1)) >> (F_SCALE - 1);\n        y0f      = (alp[1] * y1f + blp[0] * (x0f + x1f)) >> (F_SCALE - 1); // note: prescaled, blp[0]==blp[1]\n        *y_buf++ = y0f;\n    }\n\n    // Store newest sample for next run\n    state->xr = x0r;\n    state->xi = x0i;\n    state->xf = x0f;\n    state->yf = y0f;\n}\n\n\n// Fixed-point arithmetic on Q0.31 (actually Q0.30 to counter 64 signed trouble)\n#define F_SCALE32 30\n#define S_CONST32 (1 << F_SCALE32)\n#define FIX32(x) ((int)(x * S_CONST32))\n\n/// for evaluation.\nstatic int32_t atan2_int32(int32_t y, int32_t x)\n{\n    static int64_t const I_PI_4 = INT32_MAX / 4;          // M_PI/4\n    static int64_t const I_3_PI_4 = 3ll * INT32_MAX / 4;  // 3*M_PI/4\n\n    int64_t const abs_y = abs(y);\n    int64_t angle;\n\n    if (x >= 0) { // Quadrant I and IV\n        int64_t denom = (abs_y + x);\n        if (denom == 0) denom = 1; // Prevent divide by zero\n        angle = I_PI_4 - I_PI_4 * (x - abs_y) / denom;\n    } else { // Quadrant II and III\n        int64_t denom = (abs_y - x);\n        if (denom == 0) denom = 1; // Prevent divide by zero\n        angle = I_3_PI_4 - I_PI_4 * (x + abs_y) / denom;\n    }\n    if (y < 0) angle = -angle; // Negate if in III or IV\n    return angle;\n}\n\n/// Fast Instantaneous frequency and Low Pass filter, CS16 samples.\nvoid baseband_demod_FM_cs16(demodfm_state_t *state, int16_t const *x_buf, int16_t *y_buf, unsigned long num_samples, uint32_t samp_rate, float low_pass)\n{\n    // Select filter coeffs, [b,a] = butter(1, cutoff)\n    // e.g [b,a] = butter(1, 0.1) -> 3x tau (95%) ~10 samples, 250k -> 40us, 1024k -> 10us\n    // a = 1.00000, 0.72654; b = 0.13673, 0.13673;\n    // e.g. [b,a] = butter(1, 0.2) -> 3x tau (95%) ~5 samples, 250k -> 20us, 1024k -> 5us\n    // a = 1.00000, 0.50953; b = 0.24524, 0.24524;\n    if (state->rate != samp_rate) {\n        if (low_pass > 1e4f) {\n            low_pass = low_pass / samp_rate;\n        } else if (low_pass >= 1.0f) {\n            low_pass = 1e6f / low_pass / samp_rate;\n        }\n        print_logf(LOG_NOTICE, \"Baseband\", \"low pass filter for %u Hz at cutoff %.0f Hz, %.1f us\",\n                samp_rate, samp_rate * low_pass, 1e6 / (samp_rate * low_pass));\n        double ita  = 1.0 / tan(M_PI_2 * low_pass);\n        double gain = 1.0 / (1.0 + ita);\n        state->alp_32[0] = FIX32(1.0);\n        state->alp_32[1] = FIX32((ita - 1.0) * gain); // scaled by -1\n        state->blp_32[0] = FIX32(gain);\n        state->blp_32[1] = FIX32(gain);\n        state->rate      = samp_rate;\n    }\n    int64_t const *alp = state->alp_32;\n    int64_t const *blp = state->blp_32;\n\n    // Pre-feed old sample\n    int32_t x0r = state->xr; // IQ sample: x[n], real\n    int32_t x0i = state->xi; // IQ sample: x[n], imag\n    int32_t x0f = state->xf; // Instantaneous frequency\n    int32_t y0f = state->yf; // Instantaneous frequency, low pass filtered\n\n    for (unsigned n = 0; n < num_samples; n++) {\n        int32_t x1r, x1i; // Old IQ sample: x[n-1]\n        int32_t x1f, y1f; // Instantaneous frequency, old sample\n        int64_t pr, pi;   // Phase difference vector\n\n        // delay old sample\n        x1r = x0r;\n        x1i = x0i;\n        y1f = y0f;\n        x1f = x0f;\n        // get new sample\n        x0r = *x_buf++;\n        x0i = *x_buf++;\n        // Calculate phase difference vector: x[n] * conj(x[n-1])\n        pr = (int64_t)x0r * x1r + (int64_t)x0i * x1i; // May exactly overflow an int32_t (-32768*-32768 + -32768*-32768)\n        pi = (int64_t)x0i * x1r - (int64_t)x0r * x1i;\n        // xlp = (int32_t)((atan2f(pi, pr) / M_PI) * INT32_MAX); // Floating point implementation\n        x0f = atan2_int32(pi, pr); // Integer implementation\n        // xlp = atan2_int16(pi >> 16, pr >> 16) << 16; // Integer implementation, truncated\n        // xlp = pi; // Cheat and use only imaginary part (works OK, but is amplitude sensitive)\n        // Low pass filter\n        // y0f      = (alp[1] * y1f + blp[0] * x0f + blp[1] * x1f) >> F_SCALE32;\n        y0f      = (alp[1] * y1f + blp[0] * ((int64_t)x0f + x1f)) >> F_SCALE32; // note: blp[0]==blp[1]\n        *y_buf++ = y0f >> 16; // not really losing info here, maybe optimize earlier\n    }\n\n    // Store newest sample for next run\n    state->xr = x0r;\n    state->xi = x0i;\n    state->xf = x0f;\n    state->yf = y0f;\n}\n\nvoid baseband_init(void)\n{\n    calc_squares();\n}\n"
  },
  {
    "path": "src/bit_util.c",
    "content": "/** @file\n    Various utility functions for use by device drivers.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"bit_util.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\nuint8_t reverse8(uint8_t x)\n{\n    x = (x & 0xF0) >> 4 | (x & 0x0F) << 4;\n    x = (x & 0xCC) >> 2 | (x & 0x33) << 2;\n    x = (x & 0xAA) >> 1 | (x & 0x55) << 1;\n    return x;\n}\n\nuint32_t reverse32(uint32_t x)\n{\n    uint32_t ret;\n    uint8_t const* xp = (uint8_t*)&x;\n    ret = (uint32_t) reverse8(xp[0]) << 24 | reverse8(xp[1]) << 16 | reverse8(xp[2]) << 8 | reverse8(xp[3]);\n    return ret;\n}\n\nvoid reflect_bytes(uint8_t message[], unsigned num_bytes)\n{\n    for (unsigned i = 0; i < num_bytes; ++i) {\n        message[i] = reverse8(message[i]);\n    }\n}\n\nuint8_t reflect4(uint8_t x)\n{\n    x = (x & 0xCC) >> 2 | (x & 0x33) << 2;\n    x = (x & 0xAA) >> 1 | (x & 0x55) << 1;\n    return x;\n}\n\nvoid reflect_nibbles(uint8_t message[], unsigned num_bytes)\n{\n    for (unsigned i = 0; i < num_bytes; ++i) {\n        message[i] = reflect4(message[i]);\n    }\n}\n\nunsigned extract_nibbles_4b1s(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst)\n{\n    unsigned ret = 0;\n\n    while (num_bits >= 5) {\n        uint16_t bits = (message[offset_bits / 8] << 8) | message[(offset_bits / 8) + 1];\n        bits >>= 11 - (offset_bits % 8); // align 5 bits to LSB\n        if ((bits & 1) != 1)\n            break; // stuff-bit error\n        *dst++ = (bits >> 1) & 0xf;\n        ret += 1;\n        offset_bits += 5;\n        num_bits -= 5;\n    }\n\n    return ret;\n}\n\nunsigned extract_bytes_uart(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst)\n{\n    unsigned ret = 0;\n\n    while (num_bits >= 10) {\n        int startb = message[offset_bits / 8] >> (7 - (offset_bits % 8));\n        offset_bits += 1;\n        int datab = message[offset_bits / 8];\n        if (offset_bits % 8) {\n            datab = (message[offset_bits / 8] << 8) | message[offset_bits / 8 + 1];\n            datab >>= 8 - (offset_bits % 8);\n        }\n        offset_bits += 8;\n        int stopb = message[offset_bits / 8] >> (7 - (offset_bits % 8));\n        offset_bits += 1;\n        if ((startb & 1) != 0)\n            break; // start-bit error\n        if ((stopb & 1) != 1)\n            break; // stop-bit error\n        *dst++ = reverse8(datab & 0xff);\n        ret += 1;\n        num_bits -= 10;\n    }\n\n    return ret;\n}\n\nunsigned extract_bytes_uart_parity(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint8_t *dst)\n{\n    unsigned ret = 0;\n\n    while (num_bits >= 11) {\n        int startb = message[offset_bits / 8] >> (7 - (offset_bits % 8));\n        offset_bits += 1;\n        int datab = message[offset_bits / 8];\n        if (offset_bits % 8) {\n            datab = (message[offset_bits / 8] << 8) | message[offset_bits / 8 + 1];\n            datab >>= 8 - (offset_bits % 8);\n        }\n        offset_bits += 8;\n        int parityb = message[offset_bits / 8] >> (7 - (offset_bits % 8));\n        offset_bits += 1;\n        int stopb = message[offset_bits / 8] >> (7 - (offset_bits % 8));\n        offset_bits += 1;\n        int data_parity = parity8(datab);\n        if ((startb & 1) != 1)\n            break; // start-bit error\n        if ((parityb & 1) != data_parity)\n            break; // parity-bit error\n        if ((stopb & 1) != 0)\n            break; // stop-bit error\n        *dst++ = (datab & 0xff);\n        ret += 1;\n        num_bits -= 11;\n    }\n\n    return ret;\n}\n\nstatic unsigned symbol_match(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint32_t symbol)\n{\n    unsigned symbol_len = symbol & 0x1f;\n\n    // check required len\n    if (num_bits < symbol_len) {\n        return 0;\n    }\n\n    // match each bit otherwise abort\n    for (unsigned pos = 0; pos < symbol_len; ++pos) {\n        unsigned m_pos = offset_bits + pos;\n        unsigned m_bit = message[m_pos / 8] >> (7 - (m_pos % 8));\n        unsigned s_bit = symbol >> (31 - pos);\n        if ((m_bit & 1) != (s_bit & 1)) {\n            return 0;\n        }\n    }\n\n    return symbol_len;\n}\n\nunsigned extract_bits_symbols(uint8_t const *message, unsigned offset_bits, unsigned num_bits, uint32_t zero, uint32_t one, uint32_t sync, uint8_t *dst)\n{\n    unsigned zero_len = zero & 0x1f;\n    unsigned one_len  = one & 0x1f;\n    unsigned sync_len = sync & 0x1f;\n\n    unsigned dst_len = 0;\n\n    while (num_bits >= 1) {\n        // TODO: match the longest symbol first\n        if (symbol_match(message, offset_bits, num_bits, sync)) {\n            offset_bits += sync_len;\n            num_bits -= sync_len;\n            // just skip\n        }\n        else if (symbol_match(message, offset_bits, num_bits, zero)) {\n            offset_bits += zero_len;\n            num_bits -= zero_len;\n            // no need to set a zero\n            dst_len += 1;\n        }\n        else if (symbol_match(message, offset_bits, num_bits, one)) {\n            offset_bits += one_len;\n            num_bits -= one_len;\n            dst[dst_len / 8] |= 0x80 >> (dst_len % 8);\n            dst_len += 1;\n        }\n        else {\n            break;\n        }\n    }\n\n    // fprintf(stderr, \"extract_bits_symbols: %x %x %x : %u (%u)\\n\", zero, one, sync, dst_len, num_bits);\n    return dst_len;\n}\n\nuint8_t crc4(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init)\n{\n    unsigned remainder = init << 4; // LSBs are unused\n    unsigned poly = polynomial << 4;\n    unsigned bit;\n\n    while (nBytes--) {\n        remainder ^= *message++;\n        for (bit = 0; bit < 8; bit++) {\n            if (remainder & 0x80) {\n                remainder = (remainder << 1) ^ poly;\n            } else {\n                remainder = (remainder << 1);\n            }\n        }\n    }\n    return remainder >> 4 & 0x0f; // discard the LSBs\n}\n\nuint8_t crc7(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init)\n{\n    unsigned remainder = init << 1; // LSB is unused\n    unsigned poly = polynomial << 1;\n    unsigned byte, bit;\n\n    for (byte = 0; byte < nBytes; ++byte) {\n        remainder ^= message[byte];\n        for (bit = 0; bit < 8; ++bit) {\n            if (remainder & 0x80) {\n                remainder = (remainder << 1) ^ poly;\n            } else {\n                remainder = (remainder << 1);\n            }\n        }\n    }\n    return remainder >> 1 & 0x7f; // discard the LSB\n}\n\nuint8_t crc8(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init)\n{\n    uint8_t remainder = init;\n    unsigned byte, bit;\n\n    for (byte = 0; byte < nBytes; ++byte) {\n        remainder ^= message[byte];\n        for (bit = 0; bit < 8; ++bit) {\n            if (remainder & 0x80) {\n                remainder = (remainder << 1) ^ polynomial;\n            } else {\n                remainder = (remainder << 1);\n            }\n        }\n    }\n    return remainder;\n}\n\nuint8_t crc8le(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t init)\n{\n    uint8_t remainder = reverse8(init);\n    unsigned byte, bit;\n    polynomial = reverse8(polynomial);\n\n    for (byte = 0; byte < nBytes; ++byte) {\n        remainder ^= message[byte];\n        for (bit = 0; bit < 8; ++bit) {\n            if (remainder & 1) {\n                remainder = (remainder >> 1) ^ polynomial;\n            } else {\n                remainder = (remainder >> 1);\n            }\n        }\n    }\n    return remainder;\n}\n\nuint16_t crc16lsb(uint8_t const message[], unsigned nBytes, uint16_t polynomial, uint16_t init)\n{\n    uint16_t remainder = init;\n    unsigned byte, bit;\n\n    for (byte = 0; byte < nBytes; ++byte) {\n        remainder ^= message[byte];\n        for (bit = 0; bit < 8; ++bit) {\n            if (remainder & 1) {\n                remainder = (remainder >> 1) ^ polynomial;\n            }\n            else {\n                remainder = (remainder >> 1);\n            }\n        }\n    }\n    return remainder;\n}\n\nuint16_t crc16(uint8_t const message[], unsigned nBytes, uint16_t polynomial, uint16_t init)\n{\n    uint16_t remainder = init;\n    unsigned byte, bit;\n\n    for (byte = 0; byte < nBytes; ++byte) {\n        remainder ^= message[byte] << 8;\n        for (bit = 0; bit < 8; ++bit) {\n            if (remainder & 0x8000) {\n                remainder = (remainder << 1) ^ polynomial;\n            }\n            else {\n                remainder = (remainder << 1);\n            }\n        }\n    }\n    return remainder;\n}\n\nuint8_t lfsr_digest8(uint8_t const message[], unsigned bytes, uint8_t gen, uint8_t key)\n{\n    uint8_t sum = 0;\n    // Process message from first byte to last byte\n    for (unsigned k = 0; k < bytes; ++k) {\n        uint8_t data = message[k];\n        // Process individual bits of each byte (MSB to LSB)\n        for (int i = 7; i >= 0; --i) {\n            // fprintf(stderr, \"key at %d.%d : %02x\\n\", k, i, key);\n            // XOR key into sum if data bit is set\n            if ((data >> i) & 1)\n                sum ^= key;\n\n            // roll the key right (actually the lsb is dropped here)\n            // and apply the gen (needs to include the dropped lsb as msb)\n            if (key & 1)\n                key = (key >> 1) ^ gen;\n            else\n                key = (key >> 1);\n        }\n    }\n    return sum;\n}\n\nuint8_t lfsr_digest8_reverse(uint8_t const *message, int bytes, uint8_t gen, uint8_t key)\n{\n    uint8_t sum = 0;\n    // Process message from last byte to first byte (reflected)\n    for (int k = bytes - 1; k >= 0; --k) {\n        uint8_t data = message[k];\n        // Process individual bits of each byte (MSB to LSB)\n        for (int i = 7; i >= 0; --i) {\n            // fprintf(stderr, \"key at %d.%d : %02x\\n\", k, i, key);\n            // XOR key into sum if data bit is set\n            if ((data >> i) & 1) {\n                sum ^= key;\n            }\n\n            // roll the key right (actually the lsb is dropped here)\n            // and apply the gen (needs to include the dropped lsb as msb)\n            if (key & 1)\n                key = (key >> 1) ^ gen;\n            else\n                key = (key >> 1);\n        }\n    }\n    return sum;\n}\n\nuint8_t lfsr_digest8_reflect(uint8_t const message[], int bytes, uint8_t gen, uint8_t key)\n{\n    uint8_t sum = 0;\n    // Process message from last byte to first byte (reflected)\n    for (int k = bytes - 1; k >= 0; --k) {\n        uint8_t data = message[k];\n        // Process individual bits of each byte (reflected)\n        for (int i = 0; i < 8; ++i) {\n            // fprintf(stderr, \"key at %d.%d : %02x\\n\", k, i, key);\n            // XOR key into sum if data bit is set\n            if ((data >> i) & 1) {\n                sum ^= key;\n            }\n\n            // roll the key left (actually the msb is dropped here)\n            // and apply the gen (needs to include the dropped msb as lsb)\n            if (key & 0x80)\n                key = (key << 1) ^ gen;\n            else\n                key = (key << 1);\n        }\n    }\n    return sum;\n}\n\nuint16_t lfsr_digest16(uint8_t const message[], unsigned bytes, uint16_t gen, uint16_t key)\n{\n    uint16_t sum = 0;\n    for (unsigned k = 0; k < bytes; ++k) {\n        uint8_t data = message[k];\n        for (int i = 7; i >= 0; --i) {\n            // fprintf(stderr, \"key at bit %d : %04x\\n\", i, key);\n            // if data bit is set then xor with key\n            if ((data >> i) & 1)\n                sum ^= key;\n\n            // roll the key right (actually the lsb is dropped here)\n            // and apply the gen (needs to include the dropped lsb as msb)\n            if (key & 1)\n                key = (key >> 1) ^ gen;\n            else\n                key = (key >> 1);\n        }\n    }\n    return sum;\n}\n\n// The CCITT data whitening process is built around a 9-bit Linear Feedback Shift Register (LFSR).\n// The LFSR polynomial is the same polynomial as for IBM data whitening (x9 + x5 + 1).\n// The initial value of the data whitening key is set to all ones, 0x1FF.\n// s.a. https://www.nxp.com/docs/en/application-note/AN5070.pdf s.5.2\nvoid ccitt_whitening(uint8_t *buffer, unsigned buffer_size)\n{\n    uint8_t key_msb = 0x01;\n    uint8_t key_lsb = 0xff;\n\n    for (unsigned buffer_pos = 0; buffer_pos < buffer_size; buffer_pos++) {\n        uint8_t reflected_key_lsb;\n        reflected_key_lsb = (key_lsb & 0xf0) >> 4 | (key_lsb & 0x0f) << 4;\n        reflected_key_lsb = (reflected_key_lsb & 0xcc) >> 2 | (reflected_key_lsb & 0x33) << 2;\n        reflected_key_lsb = (reflected_key_lsb & 0xaa) >> 1 | (reflected_key_lsb & 0x55) << 1;\n\n        buffer[buffer_pos] ^= reflected_key_lsb;\n\n        for (uint8_t rol_counter = 0; rol_counter < 8; rol_counter++) {\n            uint8_t key_msb_previous;\n            key_msb_previous = key_msb;\n            key_msb          = (key_lsb & 0x01) ^ ((key_lsb >> 5) & 0x01);\n            key_lsb          = ((key_msb_previous << 7) & 0x80) | ((key_lsb >> 1) & 0xff);\n        }\n    }\n}\n\n// The IBM data whitening process is built around a 9-bit Linear Feedback Shift Register (LFSR).\n// CCITT data whitening processes data packets byte-per-byte, whereas IBM data\n// whitening processes the data packet bit-per-bit\n// Same, the initial value of the data whitening key is set to all ones, 0x1FF.\n// s.a. https://www.nxp.com/docs/en/application-note/AN5070.pdf s.5.1\n\nvoid ibm_whitening(uint8_t *buffer, unsigned buffer_size)\n{\n    uint8_t key_msb = 0x01;\n    uint8_t key_lsb = 0xff;\n    uint8_t key_msb_previous = 0;\n\n    for (unsigned buffer_pos = 0; buffer_pos < buffer_size; buffer_pos++) {\n        buffer[buffer_pos] ^= key_lsb;\n        for (uint8_t rol_counter = 0; rol_counter < 8; rol_counter++) {\n            key_msb_previous = key_msb;\n            key_msb          = (key_lsb & 0x01) ^ ((key_lsb >> 5) & 0x01);\n            key_lsb          = ((key_lsb >> 1) & 0xff) | ((key_msb_previous << 7) & 0x80);\n        }\n    }\n}\n\n/*\nvoid lfsr_keys_fwd16(int rounds, uint16_t gen, uint16_t key)\n{\n    for (int i = 0; i <= rounds; ++i) {\n        fprintf(stderr, \"key at bit %d : %04x\\n\", i, key);\n\n        // roll the key right (actually the lsb is dropped here)\n        // and apply the gen (needs to include the dropped lsb as msb)\n        if (key & 1)\n            key = (key >> 1) ^ gen;\n        else\n            key = (key >> 1);\n    }\n}\n\nvoid lfsr_keys_rwd16(int rounds, uint16_t gen, uint16_t key)\n{\n    for (int i = 0; i <= rounds; ++i) {\n        fprintf(stderr, \"key at bit -%d : %04x\\n\", i, key);\n\n        // roll the key left (actually the msb is dropped here)\n        // and apply the gen (needs to include the dropped msb as lsb)\n        if (key & (1 << 15))\n            key = (key << 1) ^ gen;\n        else\n            key = (key << 1);\n    }\n}\n*/\n\n// we could use popcount intrinsic, but don't actually need the performance\nint parity8(uint8_t byte)\n{\n    byte ^= byte >> 4;\n    byte &= 0xf;\n    return (0x6996 >> byte) & 1;\n}\n\nint parity_bytes(uint8_t const message[], unsigned num_bytes)\n{\n    int result = 0;\n    for (unsigned i = 0; i < num_bytes; ++i) {\n        result ^= parity8(message[i]);\n    }\n    return result;\n}\n\nuint8_t xor_bytes(uint8_t const message[], unsigned num_bytes)\n{\n    uint8_t result = 0;\n    for (unsigned i = 0; i < num_bytes; ++i) {\n        result ^= message[i];\n    }\n    return result;\n}\n\nint add_bytes(uint8_t const message[], unsigned num_bytes)\n{\n    int result = 0;\n    for (unsigned i = 0; i < num_bytes; ++i) {\n        result += message[i];\n    }\n    return result;\n}\n\nint add_nibbles(uint8_t const message[], unsigned num_bytes)\n{\n    int result = 0;\n    for (unsigned i = 0; i < num_bytes; ++i) {\n        result += (message[i] >> 4) + (message[i] & 0x0f);\n    }\n    return result;\n}\n\n// Unit testing\n#ifdef _TEST\n#define ASSERT_EQUALS(a, b) \\\n    do { \\\n        if ((a) == (b)) \\\n            ++passed; \\\n        else { \\\n            ++failed; \\\n            fprintf(stderr, \"FAIL: %d <> %d\\n\", (a), (b)); \\\n        } \\\n    } while (0)\n#define ASSERT_MATCH(a, b, n) \\\n    do { \\\n        if (memcmp(a, b, n) == 0) \\\n            ++passed; \\\n        else { \\\n            ++failed; \\\n            fprintf(stderr, \"FAIL:\"); \\\n            for (size_t i = 0; i < n; i++) { \\\n                fprintf(stderr, \" %02x\", a[i]); \\\n            } \\\n            fprintf(stderr, \"\\n   <>\"); \\\n            for (size_t i = 0; i < n; i++) { \\\n                fprintf(stderr, \" %02x\", b[i]); \\\n            } \\\n            fprintf(stderr, \"\\n\"); \\\n        } \\\n    } while (0)\n\nint main(void) {\n    unsigned passed = 0;\n    unsigned failed = 0;\n\n    fprintf(stderr, \"util:: test\\n\");\n\n    uint8_t msg[] = {0x08, 0x0a, 0xe8, 0x80};\n\n    fprintf(stderr, \"util::crc8(): odd parity\\n\");\n    ASSERT_EQUALS(crc8(msg, 3, 0x80, 0x00), 0x80);\n\n    fprintf(stderr, \"util::crc8(): even parity\\n\");\n    ASSERT_EQUALS(crc8(msg, 4, 0x80, 0x00), 0x00);\n\n    // sync-word 0b0 0xff 0b1 0b0 0x33 0b1 (i.e. 0x7fd99, note that 0x33 is 0xcc \"on the wire\")\n    uint8_t uart[]   = {0x7f, 0xd9, 0x90};\n    uint8_t bytes[6] = {0};\n\n    // y0 xff y1 y0 xcc y1 y0 x80 y1 y0 x40 y1 y0 xc0 y1\n    uint8_t uart123[] = {0x07, 0xfd, 0x99, 0x40, 0x48, 0x16, 0x04, 0x00};\n\n    fprintf(stderr, \"util::extract_bytes_uart():\\n\");\n    ASSERT_EQUALS(extract_bytes_uart(uart, 0, 24, bytes), 2);\n    ASSERT_EQUALS(bytes[0], 0xff);\n    ASSERT_EQUALS(bytes[1], 0x33);\n\n    ASSERT_EQUALS(extract_bytes_uart(uart123, 4, 60, bytes), 5);\n    ASSERT_EQUALS(bytes[0], 0xff);\n    ASSERT_EQUALS(bytes[1], 0x33);\n    ASSERT_EQUALS(bytes[2], 0x01);\n    ASSERT_EQUALS(bytes[3], 0x02);\n    ASSERT_EQUALS(bytes[4], 0x03);\n\n    fprintf(stderr, \"util::ccitt_whitening():\\n\");\n    uint8_t buf1[16] = {0};\n    uint8_t chk1[16] = {0xff, 0x87, 0xb8, 0x59, 0xb7, 0xa1, 0xcc, 0x24, 0x57, 0x5e, 0x4b, 0x9c, 0x0e, 0xe9, 0xea, 0x50};\n    ccitt_whitening(buf1, sizeof(buf1)) ;\n    ASSERT_MATCH(buf1, chk1, sizeof(buf1));\n\n    fprintf(stderr, \"util::ibm_whitening():\\n\");\n    uint8_t buf2[16] = {0};\n    uint8_t chk2[16] = {0xff, 0xe1, 0x1d, 0x9a, 0xed, 0x85, 0x33, 0x24, 0xea, 0x7a, 0xd2, 0x39, 0x70, 0x97, 0x57, 0x0a};\n    ibm_whitening(buf2, sizeof(buf2)) ;\n    ASSERT_MATCH(buf2, chk2, sizeof(buf2));\n\n    // ------------- add test above this line -----------------------------------------------------\n    // Show result of the tests, this line must stay the last line before return failed;\n    fprintf(stderr, \"util:: test (%u/%u) passed, (%u) failed.\\n\", passed, passed + failed, failed);\n\n    return failed;\n}\n#endif /* _TEST */\n"
  },
  {
    "path": "src/bitbuffer.c",
    "content": "/** @file\n    A two-dimensional bit buffer consisting of bytes.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"bitbuffer.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid bitbuffer_clear(bitbuffer_t *bits)\n{\n    memset(bits, 0, sizeof(*bits));\n}\n\nvoid bitbuffer_add_bit(bitbuffer_t *bits, int bit)\n{\n    if (bits->num_rows == 0)\n        bits->free_row = bits->num_rows = 1; // Add first row automatically\n\n    if (bits->bits_per_row[bits->num_rows - 1] == UINT16_MAX) {\n        // fprintf(stderr, \"%s: Could not add more bits\\n\", __func__);\n        return;\n    }\n    if (bits->bits_per_row[bits->num_rows - 1] == UINT16_MAX - 1) {\n        //print_logf(LOG_WARNING, __func__, \"Warning: row length limit (%u bits) reached\", UINT16_MAX);\n        fprintf(stderr, \"%s: Warning: row length limit (%u bits) reached\\n\", __func__, UINT16_MAX);\n    }\n\n    uint16_t col_index = bits->bits_per_row[bits->num_rows - 1] / 8;\n    uint16_t bit_index = bits->bits_per_row[bits->num_rows - 1] % 8;\n    if (bits->bits_per_row[bits->num_rows - 1] > 0\n            && bits->bits_per_row[bits->num_rows - 1] % (BITBUF_COLS * 8) == 0) {\n        // spill into next row\n        // fprintf(stderr, \"%s: row spill [%d] to %d (%d)\\n\", __func__, bits->num_rows - 1, col_index, bits->free_row);\n        if (bits->free_row == BITBUF_ROWS - 1) {\n            //print_logf(LOG_WARNING, __func__, \"Warning: row count limit (%d rows) reached\", BITBUF_ROWS);\n            fprintf(stderr, \"%s: Warning: row count limit (%d rows) reached\\n\", __func__, BITBUF_ROWS);\n        }\n        if (bits->free_row < BITBUF_ROWS) {\n            bits->free_row++;\n        }\n        else {\n            // fprintf(stderr, \"%s: Could not add more rows\\n\", __func__);\n            return;\n        }\n    }\n    uint8_t *b = bits->bb[bits->num_rows - 1];\n    b[col_index] |= (bit << (7 - bit_index));\n    bits->bits_per_row[bits->num_rows - 1]++;\n\n/*\n    // preamble compression\n    if (bits->bits_per_row[bits->num_rows - 1] == 60 * 8) {\n        uint8_t *b = bits->bb[bits->num_rows - 1];\n        for (int i = 21; i < 60; ++i) {\n            if (b[20] != b[i]) {\n                return;\n            }\n        }\n        // fprintf(stderr, \"%s: preamble compression\\n\", __func__);\n        memset(&b[30], 0, 30);\n        bits->bits_per_row[bits->num_rows - 1] = 30 * 8;\n    }\n*/\n}\n\n/// Set the width of the current (last) row by expanding or truncating as needed.\nstatic void bitbuffer_set_width(bitbuffer_t *bits, uint16_t width)\n{\n    if (bits->num_rows == 0)\n        bits->free_row = bits->num_rows = 1; // Add first row automatically\n\n    unsigned remaining_rows = BITBUF_ROWS - bits->num_rows + 1;\n    unsigned remaining_bits = remaining_rows * BITBUF_COLS * 8;\n    if (width > remaining_bits) {\n        // fprintf(stderr, \"%s: Could not add more bits\\n\", __func__);\n        width = remaining_bits;\n    }\n\n    // clear bits when truncating\n    if (bits->bits_per_row[bits->num_rows - 1] > width) {\n        uint8_t *b = bits->bb[bits->num_rows - 1];\n        unsigned clr_from = (width + 7) / 8;\n        unsigned clr_end  = (bits->bits_per_row[bits->num_rows - 1] + 7) / 8;\n        memset(&b[clr_from], 0, clr_end - clr_from);\n\n        // note that width became strictly smaller, that way we don't overflow\n        b[width / 8] &= 0xff00 >> (width % 8);\n    }\n\n    bits->bits_per_row[bits->num_rows - 1] = width;\n\n    unsigned extra_rows = width == 0 ? 0 : (width - 1) / (BITBUF_COLS * 8);\n    bits->free_row = bits->num_rows + extra_rows;\n}\n\nvoid bitbuffer_add_row(bitbuffer_t *bits)\n{\n    if (bits->num_rows == 0)\n        bits->free_row = bits->num_rows = 1; // Add first row automatically\n    if (bits->free_row == BITBUF_ROWS - 1) {\n        // fprintf(stderr, \"%s: Warning: row count limit (%d rows) reached\\n\", __func__, BITBUF_ROWS);\n    }\n    if (bits->free_row < BITBUF_ROWS) {\n        bits->free_row++;\n        bits->num_rows = bits->free_row;\n    }\n    else {\n        bits->bits_per_row[bits->num_rows - 1] = 0; // Clear last row to handle overflow somewhat gracefully\n        // fprintf(stderr, \"ERROR: bitbuffer:: Could not add more rows\\n\");    // Some decoders may add many rows...\n    }\n}\n\nvoid bitbuffer_add_sync(bitbuffer_t *bits)\n{\n    if (bits->num_rows == 0)\n        bits->free_row = bits->num_rows = 1; // Add first row automatically\n    if (bits->bits_per_row[bits->num_rows - 1]) {\n        bitbuffer_add_row(bits);\n    }\n    bits->syncs_before_row[bits->num_rows - 1]++;\n}\n\nvoid bitbuffer_invert(bitbuffer_t *bits)\n{\n    for (unsigned row = 0; row < bits->num_rows; ++row) {\n        if (bits->bits_per_row[row] > 0) {\n            uint8_t *b = bits->bb[row];\n\n            const unsigned last_col  = (bits->bits_per_row[row] - 1) / 8;\n            const unsigned last_bits = ((bits->bits_per_row[row] - 1) % 8) + 1;\n            for (unsigned col = 0; col <= last_col; ++col) {\n                b[col] = ~b[col]; // Invert\n            }\n            b[last_col] ^= 0xFF >> last_bits; // Re-invert unused bits in last byte\n        }\n    }\n}\n\nvoid bitbuffer_nrzs_decode(bitbuffer_t *bits)\n{\n    for (unsigned row = 0; row < bits->num_rows; ++row) {\n        if (bits->bits_per_row[row] > 0) {\n            uint8_t *b = bits->bb[row];\n\n            const unsigned last_col  = (bits->bits_per_row[row] - 1) / 8;\n            const unsigned last_bits = ((bits->bits_per_row[row] - 1) % 8) + 1;\n\n            int prev = 0;\n            for (unsigned col = 0; col <= last_col; ++col) {\n                int mask = (prev << 7) | b[col] >> 1;\n                prev     = b[col];\n                b[col]   = b[col] ^ ~mask;\n            }\n            b[last_col] &= 0xFF << (8 - last_bits); // Clear unused bits in last byte\n        }\n    }\n}\n\nvoid bitbuffer_nrzm_decode(bitbuffer_t *bits)\n{\n    for (unsigned row = 0; row < bits->num_rows; ++row) {\n        if (bits->bits_per_row[row] > 0) {\n            uint8_t *b = bits->bb[row];\n\n            const unsigned last_col  = (bits->bits_per_row[row] - 1) / 8;\n            const unsigned last_bits = ((bits->bits_per_row[row] - 1) % 8) + 1;\n\n            int prev = 0;\n            for (unsigned col = 0; col <= last_col; ++col) {\n                int mask = (prev << 7) | b[col] >> 1;\n                prev     = b[col];\n                b[col]   = b[col] ^ mask;\n            }\n            b[last_col] &= 0xFF << (8 - last_bits); // Clear unused bits in last byte\n        }\n    }\n}\n\nvoid bitbuffer_extract_bytes(bitbuffer_t *bitbuffer, unsigned row,\n        unsigned pos, uint8_t *out, unsigned len)\n{\n    uint8_t *bits = bitbuffer->bb[row];\n    if (len == 0)\n        return;\n    if ((pos & 7) == 0) {\n        memcpy(out, bits + (pos / 8), (len + 7) / 8);\n    }\n    else {\n        unsigned shift = 8 - (pos & 7);\n        unsigned bytes = (len + 7) >> 3;\n        uint8_t *p = out;\n        uint16_t word;\n        pos = pos >> 3; // Convert to bytes\n\n        word = bits[pos];\n\n        while (bytes--) {\n            word <<= 8;\n            word |= bits[++pos];\n            *(p++) = word >> shift;\n        }\n    }\n    if (len & 7)\n        out[(len - 1) / 8] &= 0xff00 >> (len & 7); // mask off bottom bits\n}\n\n// If we make this an inline function instead of a macro, it means we don't\n// have to worry about using bit numbers with side-effects (bit++).\nstatic inline uint8_t bit_at(const uint8_t *bytes, unsigned bit)\n{\n    return (uint8_t)(bytes[bit >> 3] >> (7 - (bit & 7)) & 1);\n}\n\nunsigned bitbuffer_search(bitbuffer_t *bitbuffer, unsigned row, unsigned start,\n        const uint8_t *pattern, unsigned pattern_bits_len)\n{\n    uint8_t *bits = bitbuffer->bb[row];\n    unsigned len  = bitbuffer->bits_per_row[row];\n    unsigned ipos = start;\n    unsigned ppos = 0; // cursor on init pattern\n\n    while (ipos < len && ppos < pattern_bits_len) {\n        if (bit_at(bits, ipos) == bit_at(pattern, ppos)) {\n            ppos++;\n            ipos++;\n            if (ppos == pattern_bits_len)\n                return ipos - pattern_bits_len;\n        }\n        else {\n            ipos -= ppos;\n            ipos++;\n            ppos = 0;\n        }\n    }\n\n    // Not found\n    return len;\n}\n\nunsigned bitbuffer_manchester_decode(bitbuffer_t *inbuf, unsigned row, unsigned start,\n        bitbuffer_t *outbuf, unsigned max)\n{\n    uint8_t *bits     = inbuf->bb[row];\n    unsigned int len  = inbuf->bits_per_row[row];\n    unsigned int ipos = start;\n\n    if (max && len > start + (max * 2))\n        len = start + (max * 2);\n\n    while (ipos < len) {\n        uint8_t bit1, bit2;\n\n        bit1 = bit_at(bits, ipos++);\n        bit2 = bit_at(bits, ipos++);\n\n        if (bit1 == bit2)\n            break;\n\n        bitbuffer_add_bit(outbuf, bit2);\n    }\n\n    return ipos;\n}\n\nunsigned bitbuffer_differential_manchester_decode(bitbuffer_t *inbuf, unsigned row, unsigned start,\n        bitbuffer_t *outbuf, unsigned max)\n{\n    uint8_t *bits     = inbuf->bb[row];\n    unsigned int len  = inbuf->bits_per_row[row];\n    unsigned int ipos = start;\n    uint8_t bit1, bit2 = 0;\n\n    if (max && len > start + (max * 2))\n        len = start + (max * 2);\n\n    // the first long pulse will determine the clock\n    // if needed skip one short pulse to get in synch\n    while (ipos < len) {\n        bit1 = bit_at(bits, ipos++);\n        bit2 = bit_at(bits, ipos++);\n        uint8_t bit3 = bit_at(bits, ipos);\n\n        if (bit1 != bit2) {\n            if (bit2 != bit3) {\n                bitbuffer_add_bit(outbuf, 0);\n            }\n            else {\n                bit2 = bit1;\n                ipos -= 1;\n                break;\n            }\n        }\n        else {\n            bit2 = 1 - bit1;\n            ipos -= 2;\n            break;\n        }\n    }\n\n    while (ipos < len) {\n        bit1 = bit_at(bits, ipos++);\n        if (bit1 == bit2)\n            break; // clock missing, abort\n        bit2 = bit_at(bits, ipos++);\n\n        if (bit1 == bit2)\n            bitbuffer_add_bit(outbuf, 1);\n        else\n            bitbuffer_add_bit(outbuf, 0);\n    }\n\n    return ipos;\n}\n\nstatic void print_bitrow(uint8_t const *bitrow, unsigned bit_len, unsigned highest_indent, int always_binary)\n{\n    unsigned row_len = 0;\n\n    fprintf(stderr, \"{%2u} \", bit_len);\n    for (unsigned col = 0; col < (bit_len + 7) / 8; ++col) {\n        row_len += fprintf(stderr, \"%02x \", bitrow[col]);\n    }\n    // Print binary values also?\n    if (always_binary || bit_len <= BITBUF_MAX_PRINT_BITS) {\n        fprintf(stderr, \"%-*s: \", highest_indent > row_len ? highest_indent - row_len : 0, \"\");\n        for (unsigned bit = 0; bit < bit_len; ++bit) {\n            if (bitrow[bit / 8] & (0x80 >> (bit % 8))) {\n                fprintf(stderr, \"1\");\n            }\n            else {\n                fprintf(stderr, \"0\");\n            }\n            if ((bit % 8) == 7) // Add byte separators\n                fprintf(stderr, \" \");\n        }\n    }\n    fprintf(stderr, \"\\n\");\n}\n\nstatic void print_bitbuffer(const bitbuffer_t *bits, int always_binary)\n{\n    // Figure out the longest row of bits to get the highest_indent\n    unsigned highest_indent = sizeof(\"[dd] {dd} \") - 1;\n    for (unsigned row = 0; row < bits->num_rows; ++row) {\n        unsigned hex_bytes = (bits->bits_per_row[row] + 7) / 8;\n        unsigned indent_this_row = (2 + 1) * hex_bytes;\n        if (indent_this_row > highest_indent) {\n            highest_indent = indent_this_row;\n        }\n    }\n\n    fprintf(stderr, \"bitbuffer:: Number of rows: %u \\n\", bits->num_rows);\n    for (unsigned row = 0; row < bits->num_rows; ++row) {\n        fprintf(stderr, \"[%02u] \", row);\n        print_bitrow(bits->bb[row], bits->bits_per_row[row], highest_indent, always_binary);\n    }\n    if (bits->num_rows >= BITBUF_ROWS) {\n        fprintf(stderr, \"... Maximum number of rows reached. Message is likely truncated.\\n\");\n    }\n}\n\nvoid bitbuffer_print(const bitbuffer_t *bits)\n{\n    print_bitbuffer(bits, 0);\n}\n\nvoid bitbuffer_debug(const bitbuffer_t *bits)\n{\n    print_bitbuffer(bits, 1);\n}\n\nvoid bitrow_print(uint8_t const *bitrow, unsigned bit_len)\n{\n    print_bitrow(bitrow, bit_len, 0, 0);\n}\n\nvoid bitrow_debug(uint8_t const *bitrow, unsigned bit_len)\n{\n    print_bitrow(bitrow, bit_len, 0, 1);\n}\n\nint bitrow_snprint(uint8_t const *bitrow, unsigned bit_len, char *str, unsigned size)\n{\n    if (bit_len == 0 && size > 0) {\n        str[0] = '\\0';\n    }\n    int len = 0;\n    for (unsigned i = 0; size > (unsigned)len && i < (bit_len + 7) / 8; ++i) {\n        len += snprintf(str + len, size - len, \"%02x\", bitrow[i]);\n    }\n    return len;\n}\n\nvoid bitbuffer_parse(bitbuffer_t *bits, const char *code)\n{\n    const char *c;\n    int data  = 0;\n    int width = -1;\n\n    bitbuffer_clear(bits);\n\n    for (c = code; *c; ++c) {\n\n        if (*c == ' ') {\n            continue;\n        }\n        else if (*c == '0' && (*(c + 1) == 'x' || *(c + 1) == 'X')) {\n            ++c;\n            continue;\n        }\n        else if (*c == '{') {\n            if (width >= 0) {\n                bitbuffer_set_width(bits, width);\n            }\n            if (bits->num_rows > 0) {\n                bitbuffer_add_row(bits);\n            }\n\n            char const *p = c;\n            width = strtol(c + 1, (char **)&c, 0);\n            while (*c == ' ' || *c == '\\t' || *c == '\\r' || *c == '\\n')\n                c++;\n            if (*c != '}')\n                fprintf(stderr, \"Bad length indication: %.10s\\n\", p);\n            if (width > BITBUF_MAX_ROW_BITS)\n                width = BITBUF_MAX_ROW_BITS;\n            if (!*c)\n                break; // no closing brace and end of string\n            continue;\n        }\n        else if (*c == '/') {\n            if (width >= 0) {\n                bitbuffer_set_width(bits, width);\n                width = -1;\n            }\n            bitbuffer_add_row(bits);\n            continue;\n        }\n        else if (*c >= '0' && *c <= '9') {\n            data = *c - '0';\n        }\n        else if (*c >= 'A' && *c <= 'F') {\n            data = *c - 'A' + 10;\n        }\n        else if (*c >= 'a' && *c <= 'f') {\n            data = *c - 'a' + 10;\n        }\n        bitbuffer_add_bit(bits, data >> 3 & 0x01);\n        bitbuffer_add_bit(bits, data >> 2 & 0x01);\n        bitbuffer_add_bit(bits, data >> 1 & 0x01);\n        bitbuffer_add_bit(bits, data >> 0 & 0x01);\n    }\n    if (width >= 0) {\n        bitbuffer_set_width(bits, width);\n    }\n}\n\nint bitbuffer_compare_rows(bitbuffer_t *bits, unsigned row_a, unsigned row_b, unsigned max_bits)\n{\n    if (max_bits == 0 || bits->bits_per_row[row_a] < max_bits || bits->bits_per_row[row_b] < max_bits) {\n        // full compare, no max_bits or rows too short\n        return (bits->bits_per_row[row_a] == bits->bits_per_row[row_b]\n                && !memcmp(bits->bb[row_a], bits->bb[row_b],\n                        (bits->bits_per_row[row_a] + 7) / 8));\n    }\n    else {\n        // prefix-only compare, both rows are at least max_bits long\n        uint8_t *a = bits->bb[row_a];\n        uint8_t *b = bits->bb[row_b];\n        unsigned last = (max_bits - 1) / 8; // max_bits is at least 1\n        unsigned mask = 0xff00 >> (max_bits & 7); // mask off bottom bits\n        return (!memcmp(bits->bb[row_a], bits->bb[row_b], max_bits / 8)\n                && (a[last] & mask) == (b[last] & mask));\n    }\n}\n\nunsigned bitbuffer_count_repeats(bitbuffer_t *bits, unsigned row, unsigned max_bits)\n{\n    unsigned cnt = 0;\n    for (int i = 0; i < bits->num_rows; ++i) {\n        if (bitbuffer_compare_rows(bits, row, i, max_bits)) {\n            ++cnt;\n        }\n    }\n    return cnt;\n}\n\nint bitbuffer_find_repeated_row(bitbuffer_t *bits, unsigned min_repeats, unsigned min_bits)\n{\n    for (int i = 0; i < bits->num_rows; ++i) {\n        if (bits->bits_per_row[i] >= min_bits &&\n                bitbuffer_count_repeats(bits, i, 0) >= min_repeats) {\n            return i;\n        }\n    }\n    return -1;\n}\n\nint bitbuffer_find_repeated_prefix(bitbuffer_t *bits, unsigned min_repeats, unsigned min_bits)\n{\n    for (int i = 0; i < bits->num_rows; ++i) {\n        if (bits->bits_per_row[i] >= min_bits &&\n                bitbuffer_count_repeats(bits, i, min_bits) >= min_repeats) {\n            return i;\n        }\n    }\n    return -1;\n}\n\n// Unit testing\n#ifdef _TEST\n\n#define ASSERT(expr) \\\n    do { \\\n        if (expr) { \\\n            ++passed; \\\n        } else { \\\n            ++failed; \\\n            fprintf(stderr, \"FAIL: line %d: %s\\n\", __LINE__, #expr); \\\n        } \\\n    } while (0)\n\nint main(void)\n{\n    unsigned passed = 0;\n    unsigned failed = 0;\n\n    fprintf(stderr, \"bitbuffer:: test\\n\");\n\n    bitbuffer_t bits = {0};\n\n    fprintf(stderr, \"TEST: bitbuffer:: The empty buffer\\n\");\n    bitbuffer_print(&bits);\n    ASSERT(bits.num_rows == 0);\n\n    fprintf(stderr, \"TEST: bitbuffer:: Add 1 bit\\n\");\n    bitbuffer_add_bit(&bits, 1);\n    bitbuffer_print(&bits);\n    ASSERT(bits.num_rows == 1);\n\n    fprintf(stderr, \"TEST: bitbuffer:: Add 1 new row\\n\");\n    bitbuffer_add_row(&bits);\n    bitbuffer_print(&bits);\n    ASSERT(bits.num_rows == 2);\n\n    fprintf(stderr, \"TEST: bitbuffer:: Fill row\\n\");\n    for (int i = 0; i < BITBUF_COLS * 8; ++i) {\n        bitbuffer_add_bit(&bits, i % 2);\n    }\n    bitbuffer_print(&bits);\n    ASSERT(bits.num_rows == 2);\n\n    fprintf(stderr, \"TEST: bitbuffer:: Add row and fill 1 column too many\\n\");\n    bitbuffer_add_row(&bits);\n    for (int i = 0; i <= BITBUF_COLS * 8; ++i) {\n        bitbuffer_add_bit(&bits, i % 2);\n    }\n    bitbuffer_print(&bits);\n    ASSERT(bits.num_rows == 3);\n\n    fprintf(stderr, \"TEST: bitbuffer:: invert\\n\");\n    bitbuffer_invert(&bits);\n    bitbuffer_print(&bits);\n\n    fprintf(stderr, \"TEST: bitbuffer:: nrzs_decode\\n\");\n    bits.num_rows = 1;\n    bits.bb[0][0] = 0x74;\n    bits.bb[0][1] = 0x60;\n    bits.bits_per_row[0] = 12;\n    bitbuffer_nrzs_decode(&bits);\n    bitbuffer_print(&bits);\n    ASSERT(bits.bb[0][0] == 0xB1);\n    ASSERT(bits.bb[0][1] == 0xA0);\n\n    fprintf(stderr, \"TEST: bitbuffer:: Clear\\n\");\n    bitbuffer_clear(&bits);\n    ASSERT(bits.num_rows == 0);\n    bitbuffer_print(&bits);\n\n    fprintf(stderr, \"TEST: bitbuffer:: Add 1 row too many\\n\");\n    for (int i = 0; i <= BITBUF_ROWS; ++i) {\n        bitbuffer_add_row(&bits);\n    }\n    bitbuffer_add_bit(&bits, 1);\n    bitbuffer_print(&bits);\n\n    fprintf(stderr, \"bitbuffer:: test (%u/%u) passed, (%u) failed.\\n\", passed, passed + failed, failed);\n\n    return failed > 0 ? 1 : 0;\n}\n#endif /* _TEST */\n"
  },
  {
    "path": "src/compat_paths.c",
    "content": "// compat_paths addresses following compatibility issue:\n// topic: default search paths for config file\n// issue: Linux and Windows use different common paths for config files\n// solution: provide specific default paths for each system\n\n#ifndef _WIN32\n// Linux variant\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"compat_paths.h\"\n\nchar **compat_get_default_conf_paths(void)\n{\n    static char *paths[4] = { NULL };\n    static char buf[256] = \"\";\n    char *env_config_home = getenv(\"XDG_CONFIG_HOME\");\n    if (!paths[0]) {\n        paths[0] = \"rtl_433.conf\";\n        if (env_config_home && *env_config_home)\n            snprintf(buf, sizeof(buf), \"%s%s\", env_config_home, \"/rtl_433/rtl_433.conf\");\n        else\n            snprintf(buf, sizeof(buf), \"%s%s\", getenv(\"HOME\"), \"/.config/rtl_433/rtl_433.conf\");\n        paths[1] = buf;\n#define STR_VALUE(arg) #arg\n#define STR_EXPAND(s)  STR_VALUE(s)\n        paths[2] = STR_EXPAND(INSTALL_SYSCONFDIR) \"/rtl_433/rtl_433.conf\";\n        paths[3] = NULL;\n    };\n    return paths;\n}\n\n#else\n// Windows variant\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <shlobj.h>\n\n#include \"compat_paths.h\"\n\nchar **compat_get_default_conf_paths(void)\n{\n    static char bufs[3][256];\n    static char *paths[4] = { NULL };\n    if (paths[0]) return paths;\n    // Working directory, i.e. where the binary is located\n    if (GetModuleFileName(NULL, bufs[0], sizeof(bufs[0]))) {\n        char *last_backslash = strrchr(bufs[0], '\\\\');\n        if (last_backslash)\n            *last_backslash = '\\0';\n        strcat_s(bufs[0], sizeof(bufs[0]), \"\\\\rtl_433.conf\");\n        paths[0] = bufs[0];\n    }\n    else {\n        paths[0] = NULL;\n    }\n    // Local per user configuration files %LocalAppData% (e.g. Win7: C:\\Users\\myusername\\AppData\\Local\\rtl_433\\rtl_433.conf)\n    if (SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, bufs[1]) == S_OK) {\n        strcat_s(bufs[1], sizeof(bufs[1]), \"\\\\rtl_433\\\\rtl_433.conf\");\n        paths[1] = bufs[1];\n    }\n    else {\n        paths[1] = NULL;\n    }\n    // Per machine configuration data %ProgramData% (e.g. Win7: C:\\ProgramData\\rtl_433\\rtl_433.conf)\n    if (SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, bufs[2]) == S_OK) {\n        strcat_s(bufs[2], sizeof(bufs[2]), \"\\\\rtl_433\\\\rtl_433.conf\");\n        paths[2] = bufs[2];\n    }\n    else {\n        paths[2] = NULL;\n    }\n    paths[3] = NULL;\n    return paths;\n}\n#endif // _WIN32 / !_WIN32\n"
  },
  {
    "path": "src/compat_time.c",
    "content": "// compat_time addresses following compatibility issue:\n// topic: high-resolution timestamps\n// issue: <sys/time.h> is not available on Windows systems\n// solution: provide a compatible version for Windows systems\n\n#include \"compat_time.h\"\n\n#ifdef _WIN32\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)\n#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64\n#else\n#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL\n#endif\n\nint gettimeofday(struct timeval *tv, void *tz)\n{\n    if (tz)\n        return -1; // we don't support TZ\n\n    FILETIME ft;\n    unsigned __int64 t64;\n    GetSystemTimeAsFileTime(&ft);\n    t64 = (((unsigned __int64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;\n    t64 /= 10; // convert to microseconds\n    t64 -= DELTA_EPOCH_IN_MICROSECS; // convert file time to unix epoch\n    tv->tv_sec = (long)(t64 / 1000000UL);\n    tv->tv_usec = (long)(t64 % 1000000UL);\n\n    return 0;\n}\n\n#endif // _WIN32\n\nint timeval_subtract(struct timeval *result, struct timeval const *x, struct timeval const *y)\n{\n    // Copy one input\n    struct timeval yy = *y;\n\n    // Perform the carry for the later subtraction by updating y\n    if (x->tv_usec < yy.tv_usec) {\n        int nsec = (yy.tv_usec - x->tv_usec) / 1000000 + 1;\n        yy.tv_usec -= 1000000 * nsec;\n        yy.tv_sec += nsec;\n    }\n    if (x->tv_usec - yy.tv_usec > 1000000) {\n        int nsec = (x->tv_usec - yy.tv_usec) / 1000000;\n        yy.tv_usec += 1000000 * nsec;\n        yy.tv_sec -= nsec;\n    }\n\n    // Compute the time difference, tv_usec is certainly positive\n    result->tv_sec  = x->tv_sec - yy.tv_sec;\n    result->tv_usec = x->tv_usec - yy.tv_usec;\n\n    // Return 1 if result is negative\n    return x->tv_sec < yy.tv_sec;\n}\n"
  },
  {
    "path": "src/confparse.c",
    "content": "/** @file\n    Light-weight (i.e. dumb) config-file parser.\n\n    - a valid config line is a keyword followed by an argument to the end of line\n    - whitespace around the keyword is ignored\n    - comments start with a hash sign, no inline comments, empty lines are ok.\n    - whitespace is space and tab\n\n    Copyright (C) 2018 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include \"confparse.h\"\n#include \"fatal.h\"\n\n#ifdef _WIN32\n#include <io.h>\n#include <fcntl.h>\n#ifdef _MSC_VER\n#define F_OK 0\n#define R_OK (1<<2)\n#endif\n#endif\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\nstatic off_t fsize(const char *path)\n{\n    struct stat st;\n    if (stat(path, &st) == 0)\n        return st.st_size;\n\n    return -1;\n}\n\nint hasconf(char const *path)\n{\n    return !access(path, R_OK);\n}\n\nchar *readconf(char const *path)\n{\n    FILE *fp;\n    char *conf;\n    off_t file_size = fsize(path);\n\n    if (file_size < 0) {\n        fprintf(stderr, \"Failed to stat \\\"%s\\\"\\n\", path);\n        return NULL;\n    }\n\n    fp = fopen(path, \"rb\");\n    if (fp == NULL) {\n        fprintf(stderr, \"Failed to open \\\"%s\\\"\\n\", path);\n        return NULL;\n    }\n\n    conf = malloc(file_size + 1);\n    if (!conf) {\n        WARN_MALLOC(\"readconf()\");\n        fprintf(stderr, \"Failed to allocate memory for \\\"%s\\\"\\n\", path);\n        fclose(fp);\n        return NULL;\n    }\n\n    off_t n_read = fread(conf, sizeof(char), file_size, fp);\n    fclose(fp);\n    if (n_read != file_size) {\n        fprintf(stderr, \"Failed to read \\\"%s\\\"\\n\", path);\n        free(conf);\n        return NULL;\n    }\n    conf[file_size] = '\\0';\n\n    return conf;\n}\n\nint getconf(char **conf, struct conf_keywords const keywords[], char **arg)\n{\n    // abort if no conf or EOF\n    if (!conf || !*conf || !**conf)\n        return -1;\n\n    char *p = *conf;\n\n    // skip whitespace and comments\n    while (*p == ' ' || *p == '\\t' || *p == '\\r' || *p == '\\n' || *p == '#')\n        if (*p++ == '#') {\n            while (*p && *p != '\\r' && *p != '\\n') p++;\n        }\n\n    // abort if EOF\n    if (!*p)\n        return -1;\n\n    // parse keyword\n    char *kw = p;\n    while (*p && *p != ' ' && *p != '\\t' && *p != '\\r' && *p != '\\n')\n        p++;\n    if (*p)\n        *p++ = '\\0';\n\n    // parse arg\n    while (*p == ' ' || *p == '\\t')\n        p++;\n    char *ka = p;\n    if (*p == '{') { // quoted\n        ka = ++p;\n        while (*p) { // skip to end-quote\n            while (*p && *p != '}')\n                p++;\n            char *e = p; // possible end-quote\n            if (*p)\n                p++;\n            // skip ws\n            while (*p == ' ' || *p == '\\t')\n                p++;\n            // check if proper end-quote\n            if (!*p || *p == '\\r' || *p == '\\n' || *p == '#') {\n                *e = '\\0';\n                break;\n            }\n        }\n\n    } else { // not quoted\n        // find end of arg/eol\n        while (*p && *p != '\\r' && *p != '\\n' && *p != '#')\n            p++;\n        // skip eol comments\n        if (*p == '#') {\n            *p++ = '\\0';\n            while (*p && *p != '\\r' && *p != '\\n')\n                p++;\n        }\n        if (*p)\n            *p++ = '\\0';\n    }\n\n    // set OUT vars\n    if (arg)\n        *arg = ka;\n    *conf = p;\n\n    // decode keyword\n    for (; keywords->keyword; keywords++) {\n        if (!strcmp(keywords->keyword, kw)) {\n            return keywords->key;\n        }\n    }\n\n    fprintf(stderr, \"Unknown keyword \\\"%s\\\"\\n\", kw);\n    return '?';\n}\n"
  },
  {
    "path": "src/data.c",
    "content": "/** @file\n    A general structure for extracting hierarchical data from the devices;\n    typically key-value pairs, but allows for more rich data as well.\n\n    Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"data.h\"\n\n#include \"abuf.h\"\n#include \"fatal.h\"\n\n#include <stdarg.h>\n#include <assert.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n\n// Macro to prevent unused variables (passed into a function)\n// from generating a warning.\n#define UNUSED(x) (void)(x)\n\ntypedef void* (*array_elementwise_import_fn)(void*);\ntypedef void (*array_element_release_fn)(void*);\ntypedef void (*value_release_fn)(void*);\n\ntypedef struct {\n    /* what is the element size when put inside an array? */\n    int array_element_size;\n\n    /* is the element boxed (ie. behind a pointer) when inside an array?\n       if it's not boxed (\"unboxed\"), json dumping function needs to make\n       a copy of the value beforehand, because the dumping function only\n       deals with boxed values.\n     */\n    bool array_is_boxed;\n\n    /* function for importing arrays. strings are specially handled (as they\n       are copied deeply), whereas other arrays are just copied shallowly\n       (but copied nevertheless) */\n    array_elementwise_import_fn array_elementwise_import;\n\n    /* a function for releasing an element when put in an array; integers\n     * don't need to be released, while ie. strings and arrays do. */\n    array_element_release_fn array_element_release;\n\n    /* a function for releasing a value. everything needs to be released. */\n    value_release_fn value_release;\n} data_meta_type_t;\n\nstatic data_meta_type_t dmt[DATA_COUNT] = {\n    //  DATA_DATA\n    { .array_element_size       = sizeof(data_t*),\n      .array_is_boxed           = true,\n      .array_elementwise_import = NULL,\n      .array_element_release    = (array_element_release_fn) data_free,\n      .value_release            = (value_release_fn) data_free },\n\n    //  DATA_INT\n    { .array_element_size       = sizeof(int),\n      .array_is_boxed           = false,\n      .array_elementwise_import = NULL,\n      .array_element_release    = NULL,\n      .value_release            = NULL },\n\n    //  DATA_DOUBLE\n    { .array_element_size       = sizeof(double),\n      .array_is_boxed           = false,\n      .array_elementwise_import = NULL,\n      .array_element_release    = NULL,\n      .value_release            = NULL },\n\n    //  DATA_STRING\n    { .array_element_size       = sizeof(char*),\n      .array_is_boxed           = true,\n      .array_elementwise_import = (array_elementwise_import_fn) strdup,\n      .array_element_release    = (array_element_release_fn) free,\n      .value_release            = (value_release_fn) free },\n\n    //  DATA_ARRAY\n    { .array_element_size       = sizeof(data_array_t*),\n      .array_is_boxed           = true,\n      .array_elementwise_import = NULL,\n      .array_element_release    = (array_element_release_fn) data_array_free ,\n      .value_release            = (value_release_fn) data_array_free },\n};\n\nstatic bool import_values(void *dst, void const *src, int num_values, data_type_t type)\n{\n    int element_size = dmt[type].array_element_size;\n    array_elementwise_import_fn import = dmt[type].array_elementwise_import;\n    if (import) {\n        for (int i = 0; i < num_values; ++i) {\n            void *copy = import(*(void **)((char *)src + element_size * i));\n            if (!copy) {\n                --i;\n                while (i >= 0) {\n                    free(*(void **)((char *)dst + element_size * i));\n                    --i;\n                }\n                return false;\n            } else {\n                *((char **)dst + i) = copy;\n            }\n        }\n    } else {\n        memcpy(dst, src, (size_t)element_size * num_values);\n    }\n    return true; // error is returned early\n}\n\n/* data */\n\nR_API data_array_t *data_array(int num_values, data_type_t type, void const *values)\n{\n    if (num_values < 0) {\n      return NULL;\n    }\n    data_array_t *array = calloc(1, sizeof(data_array_t));\n    if (!array) {\n        WARN_CALLOC(\"data_array()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    int element_size = dmt[type].array_element_size;\n    if (num_values > 0) { // don't alloc empty arrays\n        array->values = calloc(num_values, element_size);\n        if (!array->values) {\n            WARN_CALLOC(\"data_array()\");\n            goto alloc_error;\n        }\n        if (!import_values(array->values, values, num_values, type))\n            goto alloc_error;\n    }\n\n    array->num_values = num_values;\n    array->type       = type;\n\n    return array;\n\nalloc_error:\n    if (array)\n        free(array->values);\n    free(array);\n    return NULL;\n}\n\n// the static analyzer can't prove the allocs to be correct\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wunknown-warning-option\"\n#pragma GCC diagnostic ignored \"-Wanalyzer-malloc-leak\"\n\nstatic data_t *vdata_make(data_t *first, const char *key, const char *pretty_key, va_list ap)\n{\n    data_type_t type;\n    data_t *prev = first;\n    while (prev && prev->next)\n        prev = prev->next;\n    char *format = NULL;\n    int skip = 0; // skip the data item if this is set\n    type = va_arg(ap, data_type_t);\n    do {\n        data_t *current;\n        data_value_t value = {0};\n        // store explicit release function, CSA checker gets confused without this\n        value_release_fn value_release = NULL; // appease CSA checker\n\n        switch (type) {\n        case DATA_COND:\n            skip |= !va_arg(ap, int);\n            type = va_arg(ap, data_type_t);\n            continue;\n        case DATA_FORMAT:\n            if (format) {\n                fprintf(stderr, \"vdata_make() format type used twice\\n\");\n                goto alloc_error;\n            }\n            format = va_arg(ap, char *);\n            if (format) {\n                format = strdup(format);\n                if (!format) {\n                    WARN_STRDUP(\"vdata_make()\");\n                    goto alloc_error;\n                }\n            }\n            type = va_arg(ap, data_type_t);\n            continue;\n        case DATA_COUNT:\n            assert(0);\n            break;\n        case DATA_DATA:\n            value_release = (value_release_fn)data_free; // appease CSA checker\n            value.v_ptr = va_arg(ap, data_t *);\n            break;\n        case DATA_INT:\n            value.v_int = va_arg(ap, int);\n            break;\n        case DATA_DOUBLE:\n            value.v_dbl = va_arg(ap, double);\n            break;\n        case DATA_STRING:\n            value_release = (value_release_fn)free; // appease CSA checker\n            value.v_ptr = strdup(va_arg(ap, char const *));\n            if (!value.v_ptr)\n                WARN_STRDUP(\"vdata_make()\");\n            break;\n        case DATA_ARRAY:\n            value_release = (value_release_fn)data_array_free; // appease CSA checker\n            value.v_ptr = va_arg(ap, data_array_t *);\n            break;\n        default:\n            fprintf(stderr, \"vdata_make() bad data type (%d)\\n\", type);\n            goto alloc_error;\n        }\n\n        if (skip) {\n            if (value_release) // could use dmt[type].value_release\n                value_release(value.v_ptr);\n            free(format);\n            format = NULL;\n            skip = 0;\n        }\n        else {\n            current = calloc(1, sizeof(*current));\n            if (!current) {\n                WARN_CALLOC(\"vdata_make()\");\n                if (value_release) // could use dmt[type].value_release\n                    value_release(value.v_ptr);\n                goto alloc_error;\n            }\n            current->type   = type;\n            current->format = format;\n            format          = NULL; // consumed\n            current->value  = value;\n            current->next   = NULL;\n\n            if (prev)\n                prev->next = current;\n            prev = current;\n            if (!first)\n                first = current;\n\n            current->key = strdup(key);\n            if (!current->key) {\n                WARN_STRDUP(\"vdata_make()\");\n                goto alloc_error;\n            }\n            current->pretty_key = strdup(pretty_key ? pretty_key : key);\n            if (!current->pretty_key) {\n                WARN_STRDUP(\"vdata_make()\");\n                goto alloc_error;\n            }\n        }\n\n        // next args\n        key = va_arg(ap, const char *);\n        if (key) {\n            pretty_key = va_arg(ap, const char *);\n            type = va_arg(ap, data_type_t);\n        }\n    } while (key);\n    if (format) {\n        fprintf(stderr, \"vdata_make() format type without data\\n\");\n        goto alloc_error;\n    }\n\n    return first;\n\nalloc_error:\n    free(format); // if not consumed\n    data_free(first);\n    return NULL;\n}\n\nR_API data_t *data_make(const char *key, const char *pretty_key, ...)\n{\n    va_list ap;\n    va_start(ap, pretty_key);\n    data_t *result = vdata_make(NULL, key, pretty_key, ap);\n    va_end(ap);\n    return result;\n}\n\nstatic data_t *data_append(data_t *first, const char *key, const char *pretty_key, ...)\n{\n    va_list ap;\n    va_start(ap, pretty_key);\n    data_t *result = vdata_make(first, key, pretty_key, ap);\n    va_end(ap);\n    return result;\n}\n\nR_API data_t *data_prepend(data_t *tail, data_t *head)\n{\n    if (!head) {\n        return tail;\n    }\n\n    data_t *prev = head;\n    while (prev->next) {\n        prev = prev->next;\n    }\n    prev->next = tail;\n\n    return head;\n}\n\n// Wrappers for now, should be refactored.\nR_API data_t *data_int(data_t *first, char const *key, char const *pretty_key, char const *format, int val)\n{\n    return data_append(first, key, pretty_key, DATA_FORMAT, format, DATA_INT, val, NULL);\n}\nR_API data_t *data_dbl(data_t *first, char const *key, char const *pretty_key, char const *format, double val)\n{\n    return data_append(first, key, pretty_key, DATA_FORMAT, format, DATA_DOUBLE, val, NULL);\n}\nR_API data_t *data_str(data_t *first, char const *key, char const *pretty_key, char const *format, char const *val)\n{\n    return data_append(first, key, pretty_key, DATA_FORMAT, format, DATA_STRING, val, NULL);\n}\nR_API data_t *data_ary(data_t *first, char const *key, char const *pretty_key, char const *format, data_array_t *val)\n{\n    return data_append(first, key, pretty_key, DATA_FORMAT, format, DATA_ARRAY, val, NULL);\n}\nR_API data_t *data_dat(data_t *first, char const *key, char const *pretty_key, char const *format, data_t *val)\n{\n    return data_append(first, key, pretty_key, DATA_FORMAT, format, DATA_DATA, val, NULL);\n}\nR_API data_t *data_hex(data_t *first, char const *key, char const *pretty_key, char const *format, uint8_t const *val, unsigned len, char *buf)\n{\n    if (!format || !*format) {\n        format = \"%02x\";\n    }\n\n    char *p = buf;\n    for (unsigned i = 0; i < len; i += 1) {\n        p += sprintf(p, format, val[i]);\n    }\n    *p = '\\0';\n\n    return data_append(first, key, pretty_key, DATA_FORMAT, NULL, DATA_STRING, buf, NULL);\n}\n\nR_API void data_array_free(data_array_t *array)\n{\n    array_element_release_fn release = dmt[array->type].array_element_release;\n    if (release) {\n        int element_size = dmt[array->type].array_element_size;\n        for (int i = 0; i < array->num_values; ++i)\n            release(*(void **)((char *)array->values + element_size * i));\n    }\n    free(array->values);\n    free(array);\n}\n\nR_API data_t *data_retain(data_t *data)\n{\n    if (data)\n        ++data->retain;\n    return data;\n}\n\n#if defined(__clang__)\n    // ignore \"call to function _free through pointer to incorrect function type\"\n    __attribute__((no_sanitize(\"undefined\")))\n#endif\nR_API void data_free(data_t *data)\n{\n    if (data && data->retain) {\n        --data->retain;\n        return;\n    }\n    while (data) {\n        data_t *prev_data = data;\n        if (dmt[data->type].value_release)\n            dmt[data->type].value_release(data->value.v_ptr);\n        free(data->format);\n        free(data->pretty_key);\n        free(data->key);\n        data = data->next;\n        free(prev_data);\n    }\n}\n\n#pragma GCC diagnostic pop\n\n/* data output */\n\nR_API void data_output_print(data_output_t *output, data_t *data)\n{\n    if (!output)\n        return;\n    if (output->output_print) {\n        output->output_print(output, data);\n    }\n    else {\n        output->print_data(output, data, NULL);\n    }\n}\n\nR_API void data_output_start(struct data_output *output, char const *const *fields, int num_fields)\n{\n    if (!output || !output->output_start)\n        return;\n    output->output_start(output, fields, num_fields);\n}\n\nR_API void data_output_free(data_output_t *output)\n{\n    if (!output)\n        return;\n    output->output_free(output);\n}\n\n/* output helpers */\n\nR_API void print_value(data_output_t *output, data_type_t type, data_value_t value, char const *format)\n{\n    switch (type) {\n    case DATA_FORMAT:\n    case DATA_COUNT:\n    case DATA_COND:\n        assert(0);\n        break;\n    case DATA_DATA:\n        output->print_data(output, value.v_ptr, format);\n        break;\n    case DATA_INT:\n        output->print_int(output, value.v_int, format);\n        break;\n    case DATA_DOUBLE:\n        output->print_double(output, value.v_dbl, format);\n        break;\n    case DATA_STRING:\n        output->print_string(output, value.v_ptr, format);\n        break;\n    case DATA_ARRAY:\n        output->print_array(output, value.v_ptr, format);\n        break;\n    }\n}\n\nR_API void print_array_value(data_output_t *output, data_array_t *array, char const *format, int idx)\n{\n    int element_size = dmt[array->type].array_element_size;\n    data_value_t value = {0};\n\n    if (!dmt[array->type].array_is_boxed) {\n        memcpy(&value, (char *)array->values + element_size * idx, element_size);\n        print_value(output, array->type, value, format);\n    } else {\n        // Note: on 32-bit data_value_t has different size/alignment than a pointer!\n        value.v_ptr = *(void **)((char *)array->values + element_size * idx);\n        print_value(output, array->type, value, format);\n    }\n}\n\n/* JSON string printer */\n\ntypedef struct {\n    struct data_output output;\n    abuf_t msg;\n} data_print_jsons_t;\n\nstatic void R_API_CALLCONV format_jsons_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    data_print_jsons_t *jsons = (data_print_jsons_t *)output;\n\n    abuf_cat(&jsons->msg, \"[\");\n    for (int c = 0; c < array->num_values; ++c) {\n        if (c)\n            abuf_cat(&jsons->msg, \",\");\n        print_array_value(output, array, format, c);\n    }\n    abuf_cat(&jsons->msg, \"]\");\n}\n\nstatic void R_API_CALLCONV format_jsons_object(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_print_jsons_t *jsons = (data_print_jsons_t *)output;\n\n    bool separator = false;\n    abuf_cat(&jsons->msg, \"{\");\n    while (data) {\n        if (separator)\n            abuf_cat(&jsons->msg, \",\");\n        output->print_string(output, data->key, NULL);\n        abuf_cat(&jsons->msg, \":\");\n        print_value(output, data->type, data->value, data->format);\n        separator = true;\n        data      = data->next;\n    }\n    abuf_cat(&jsons->msg, \"}\");\n}\n\nstatic void R_API_CALLCONV format_jsons_string(data_output_t *output, const char *str, char const *format)\n{\n    UNUSED(format);\n    data_print_jsons_t *jsons = (data_print_jsons_t *)output;\n\n    char *buf   = jsons->msg.tail;\n    size_t size = jsons->msg.left;\n\n    size_t str_len = strlen(str);\n    if (size < str_len + 3) {\n        return;\n    }\n\n    if (str[0] == '{' && str[str_len - 1] == '}') {\n        // Print embedded JSON object verbatim\n        abuf_cat(&jsons->msg, str);\n        return;\n    }\n\n    *buf++ = '\"';\n    size--;\n    for (; *str && size >= 3; ++str) {\n        if (*str == '\\r') {\n            *buf++ = '\\\\';\n            size--;\n            *buf++ = 'r';\n            size--;\n            continue;\n        }\n        if (*str == '\\n') {\n            *buf++ = '\\\\';\n            size--;\n            *buf++ = 'n';\n            size--;\n            continue;\n        }\n        if (*str == '\\t') {\n            *buf++ = '\\\\';\n            size--;\n            *buf++ = 't';\n            size--;\n            continue;\n        }\n        if (*str == '\"' || *str == '\\\\') {\n            *buf++ = '\\\\';\n            size--;\n        }\n        *buf++ = *str;\n        size--;\n    }\n    if (size >= 2) {\n        *buf++ = '\"';\n        size--;\n    }\n    *buf = '\\0';\n\n    jsons->msg.tail = buf;\n    jsons->msg.left = size;\n}\n\nstatic void R_API_CALLCONV format_jsons_double(data_output_t *output, double data, char const *format)\n{\n    UNUSED(format);\n    data_print_jsons_t *jsons = (data_print_jsons_t *)output;\n    // use scientific notation for very big/small values\n    if (data > 1e7 || data < 1e-4) {\n        abuf_printf(&jsons->msg, \"%g\", data);\n    }\n    else {\n        abuf_printf(&jsons->msg, \"%.5f\", data);\n        // remove trailing zeros, always keep one digit after the decimal point\n        while (jsons->msg.left > 0 && *(jsons->msg.tail - 1) == '0' && *(jsons->msg.tail - 2) != '.') {\n            jsons->msg.tail--;\n            jsons->msg.left++;\n            *jsons->msg.tail = '\\0';\n        }\n    }\n}\n\nstatic void R_API_CALLCONV format_jsons_int(data_output_t *output, int data, char const *format)\n{\n    UNUSED(format);\n    data_print_jsons_t *jsons = (data_print_jsons_t *)output;\n    abuf_printf(&jsons->msg, \"%d\", data);\n}\n\nR_API size_t data_print_jsons(data_t *data, char *dst, size_t len)\n{\n    data_print_jsons_t jsons = {\n            .output = {\n                    .print_data   = format_jsons_object,\n                    .print_array  = format_jsons_array,\n                    .print_string = format_jsons_string,\n                    .print_double = format_jsons_double,\n                    .print_int    = format_jsons_int,\n            },\n    };\n\n    abuf_init(&jsons.msg, dst, len);\n\n    format_jsons_object(&jsons.output, data, NULL);\n\n    return len - jsons.msg.left;\n}\n"
  },
  {
    "path": "src/data_tag.c",
    "content": "/** @file\n    Custom data tags for data struct.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"data_tag.h\"\n#include \"mongoose.h\"\n#include \"jsmn.h\"\n#include \"data.h\"\n#include \"list.h\"\n#include \"optparse.h\"\n#include \"c_util.h\" // for MIN()\n#include \"fileformat.h\"\n#include \"fatal.h\"\n\ntypedef struct gpsd_client {\n    struct mg_connect_opts connect_opts;\n    struct mg_connection *conn;\n    int prev_status;\n    char address[253 + 6 + 1]; // dns max + port\n    char const *init_str;\n    char const *filter_str;\n    char msg[1024]; // GPSd TPV should about 600 bytes\n} gpsd_client_t;\n\n// GPSd JSON mode\nchar const watch_json[] = \"?WATCH={\\\"enable\\\":true,\\\"json\\\":true}\\n\";\nchar const filter_json[] = \"{\\\"class\\\":\\\"TPV\\\",\";\n// GPSd NMEA mode\nchar const watch_nmea[] = \"?WATCH={\\\"enable\\\":true,\\\"nmea\\\":true}\\n\";\nchar const filter_nmea[] = \"$GPGGA,\";\n\nstatic void gpsd_client_line(gpsd_client_t *ctx, char *line)\n{\n    if (!ctx->filter_str || strncmp(line, ctx->filter_str, strlen(ctx->filter_str)) == 0) {\n        snprintf(ctx->msg, sizeof(ctx->msg), \"%s\", line);\n    }\n}\n\nstatic struct mg_connection *gpsd_client_connect(gpsd_client_t *ctx, struct mg_mgr *mgr);\n\nstatic void gpsd_client_event(struct mg_connection *nc, int ev, void *ev_data)\n{\n    // note that while shutting down the ctx is NULL\n    gpsd_client_t *ctx = (gpsd_client_t *)nc->user_data;\n\n    //if (ev != MG_EV_POLL)\n    //    fprintf(stderr, \"GPSd user handler got event %d\\n\", ev);\n\n    switch (ev) {\n    case MG_EV_CONNECT: {\n        int connect_status = *(int *)ev_data;\n        if (connect_status == 0) {\n            // Success\n            fprintf(stderr, \"GPSd Connected...\\n\");\n            if (ctx->init_str && *ctx->init_str) {\n                mg_send(nc, ctx->init_str, strlen(ctx->init_str));\n            }\n        }\n        else {\n            // Error, print only once\n            if (ctx && ctx->prev_status != connect_status)\n                fprintf(stderr, \"GPSd connect error: %s\\n\", strerror(connect_status));\n        }\n        if (ctx)\n            ctx->prev_status = connect_status;\n        break;\n    }\n    case MG_EV_RECV: {\n        // Require a newline\n        struct mbuf *io = &nc->recv_mbuf;\n        // note: we could scan only the last *(int *)ev_data bytes...\n        char *eol = memchr(io->buf, '\\n', io->len);\n        if (eol) {\n            size_t len = eol - io->buf + 1;\n            // strip [\\r]\\n\n            io->buf[len - 1] = '\\0';\n            if (len >= 2 && io->buf[len - 2] == '\\r') {\n                io->buf[len - 2] = '\\0';\n            }\n            gpsd_client_line(ctx, io->buf);\n            mbuf_remove(io, len); // Discard line from recv buffer\n        }\n        break;\n    }\n    case MG_EV_CLOSE:\n        if (!ctx)\n            break; // shutting down\n        if (ctx->prev_status == 0)\n            fprintf(stderr, \"GPSd Connection failed...\\n\");\n        // reconnect\n        gpsd_client_connect(ctx, nc->mgr);\n        break;\n    }\n}\n\nstatic struct mg_connection *gpsd_client_connect(gpsd_client_t *ctx, struct mg_mgr *mgr)\n{\n    char const *error_string       = NULL;\n    ctx->connect_opts.error_string = &error_string;\n    ctx->conn                      = mg_connect_opt(mgr, ctx->address, gpsd_client_event, ctx->connect_opts);\n    ctx->connect_opts.error_string = NULL;\n    if (!ctx->conn) {\n        fprintf(stderr, \"GPSd connect (%s) failed%s%s\\n\", ctx->address,\n                error_string ? \": \" : \"\", error_string ? error_string : \"\");\n    }\n    return ctx->conn;\n}\n\nstatic gpsd_client_t *gpsd_client_init(char const *host, char const *port, char const *init_str, char const *filter_str, struct mg_mgr *mgr)\n{\n    gpsd_client_t *ctx;\n    ctx = calloc(1, sizeof(gpsd_client_t));\n    if (!ctx) {\n        WARN_CALLOC(\"gpsd_client_init()\");\n        return NULL;\n    }\n\n    // if the host is an IPv6 address it needs quoting\n    if (strchr(host, ':'))\n        snprintf(ctx->address, sizeof(ctx->address), \"[%s]:%s\", host, port);\n    else\n        snprintf(ctx->address, sizeof(ctx->address), \"%s:%s\", host, port);\n\n    ctx->init_str = init_str;\n    ctx->filter_str = filter_str;\n    ctx->connect_opts.user_data = ctx;\n\n    if (!gpsd_client_connect(ctx, mgr)) {\n        exit(1);\n    }\n\n    return ctx;\n}\n\nstatic void gpsd_client_free(gpsd_client_t *ctx)\n{\n    if (ctx && ctx->conn) {\n        ctx->conn->user_data = NULL;\n        ctx->conn->flags |= MG_F_CLOSE_IMMEDIATELY;\n    }\n    free(ctx);\n}\n\ndata_tag_t *data_tag_create(char *param, struct mg_mgr *mgr)\n{\n    data_tag_t *tag;\n    tag = calloc(1, sizeof(data_tag_t));\n    if (!tag) {\n        WARN_CALLOC(\"data_tag_create()\");\n        return NULL;\n    }\n\n    char *p = param;\n    asepcb(&p, '=', ','); // look for '=' but stop at ','\n    if (p) {\n        tag->key = param;\n        tag->val = p;\n    } else {\n        tag->val = param;\n    }\n\n    int gpsd_mode = strncmp(tag->val, \"gpsd\", 4) == 0;\n    if (gpsd_mode || strncmp(tag->val, \"tcp:\", 4) == 0) {\n        p          = arg_param(tag->val); // strip scheme\n        char const *host = gpsd_mode ? \"localhost\" : NULL;\n        char const *port = gpsd_mode ? \"2947\" : NULL;\n        char *opts = hostport_param(p, &host, &port);\n        list_t includes = {0};\n\n        // default to GPSd JSON\n        char const *mode = gpsd_mode ? \"GPSd JSON\" : \"TCP custom\";\n        char const *init_str = gpsd_mode ? watch_json : NULL;\n        char const *filter_str = gpsd_mode ? filter_json : NULL;\n        // parse format options\n        char *key, *val;\n        while (getkwargs(&opts, &key, &val)) {\n            key = remove_ws(key);\n            val = trim_ws(val);\n            if (!key || !*key)\n                continue;\n            else if (!strcasecmp(key, \"nmea\")) {\n                mode       = \"GPSd NMEA\";\n                init_str   = watch_nmea;\n                filter_str = filter_nmea;\n            }\n            else if (!strcasecmp(key, \"init\")) {\n                init_str = val;\n            }\n            else if (!strcasecmp(key, \"filter\")) {\n                filter_str = val;\n            }\n            else if (val) {\n                fprintf(stderr, \"Invalid key \\\"%s\\\" option.\\n\", key);\n                exit(1);\n            }\n            else {\n                list_push(&includes, key);\n            }\n        }\n\n        tag->includes = (char const **)includes.elems;\n        if (!tag->key && !tag->includes)\n            tag->key = gpsd_mode ? \"gps\" : \"tag\";\n\n        if (!host || !port) {\n            fprintf(stderr, \"Host or port for tag client missing!\\n\");\n            exit(1);\n        }\n\n        fprintf(stderr, \"Getting %s data from %s port %s\\n\", mode, host, port);\n\n        tag->gpsd_client = gpsd_client_init(host, port, init_str, filter_str, mgr);\n    }\n    else {\n        if (!tag->key)\n            tag->key = \"tag\";\n    }\n\n    return tag; // NOTE: returns NULL on alloc failure.\n}\n\nvoid data_tag_free(data_tag_t *tag)\n{\n    free((void *)tag->includes);\n    gpsd_client_free(tag->gpsd_client);\n\n    free(tag);\n}\n\nstatic char const *find_list_strncmp(char const **list, char const *key, size_t len)\n{\n    for (; list && *list; ++list) {\n        char const *elem = *list;\n        if (elem && strncmp(elem, key, len) == 0) {\n            return elem;\n        }\n    }\n    return NULL;\n}\n\n#define MAX_JSON_TOKENS 128\n\nstatic data_t *append_filtered_json(data_t *data, char const *json, char const **includes)\n{\n    jsmn_parser parser = {0};\n    jsmn_init(&parser);\n    jsmntok_t tok[MAX_JSON_TOKENS] = {{0}}; // make the compiler happy, should be {0}\n\n    int toks = jsmn_parse(&parser, json, strlen(json), tok, MAX_JSON_TOKENS);\n    if (toks < 1 || tok[0].type != JSMN_OBJECT) {\n        fprintf(stderr, \"invalid json (%d): %s\\n\", toks, json);\n        return data; // invalid json\n    }\n\n    // check all tokens\n    for (int i = 1; i < toks - 1; ++i) {\n        jsmntok_t *k = tok + i;\n        jsmntok_t *v = tok + i + 1;\n        i += k->size + v->size;\n        //fprintf(stderr, \"TOK (%d %d) %.*s : (%d %d) %.*s\\n\",\n        //        k->type, k->size, k->end - k->start, json + k->start,\n        //        v->type, v->size, v->end - v->start, json + v->start);\n\n        // check all includes\n        char const *key = find_list_strncmp(includes, json + k->start, k->end - k->start);\n        if (key) {\n            // append json tag\n            char buf[1024];\n            int len = MIN(v->end - v->start, (int)sizeof(buf) - 1);\n            memcpy(buf, json + v->start, len);\n            buf[len] = '\\0';\n            data = data_str(data, key, \"\", NULL, buf);\n        }\n    }\n\n    return data;\n}\n\ndata_t *data_tag_apply(data_tag_t *tag, data_t *data, char const *filename)\n{\n    char const *val = tag->val;\n    if (tag->gpsd_client) {\n        val = tag->gpsd_client->msg;\n        if (tag->includes) {\n            if (tag->key) {\n                // wrap tag includes\n                data_t *obj = append_filtered_json(NULL, val, tag->includes);\n                // append tag wrapper\n                data = data_dat(data, tag->key, \"\", NULL, obj);\n            }\n            else {\n                // append tag includes\n                data = append_filtered_json(data, val, tag->includes);\n            }\n        }\n        else {\n            // append tag string\n            data = data_str(data, tag->key, \"\", NULL, val);\n        }\n        return data;\n    }\n    else if (filename && !strcmp(\"PATH\", tag->val)) {\n        val = filename;\n    }\n    else if (filename && !strcmp(\"FILE\", tag->val)) {\n        val = file_basename(filename);\n    }\n\n    // prepend simple tags\n    data = data_prepend(data,\n            data_str(NULL, tag->key, \"\", NULL, val));\n\n    return data;\n}\n"
  },
  {
    "path": "src/decoder_util.c",
    "content": "/** @file\n    High-level utility functions for decoders.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder_util.h\"\n#include <stdlib.h>\n#include <stdio.h>\n#include \"fatal.h\"\n\n// create decoder functions\n\nr_device *decoder_create(r_device const *dev_template, unsigned user_data_size)\n{\n    r_device *r_dev = calloc(1, sizeof (*r_dev));\n    if (!r_dev) {\n        WARN_MALLOC(\"decoder_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n    if (dev_template)\n        *r_dev = *dev_template; // copy\n\n    if (user_data_size) {\n        r_dev->decode_ctx = calloc(1, user_data_size);\n        if (!r_dev->decode_ctx) {\n            WARN_MALLOC(\"decoder_create()\");\n            free(r_dev);\n            return NULL; // NOTE: returns NULL on alloc failure.\n        }\n    }\n\n    return r_dev;\n}\n\nvoid *decoder_user_data(r_device *decoder)\n{\n    return decoder->decode_ctx;\n}\n\n// output functions\n\nvoid decoder_output_log(r_device *decoder, int level, data_t *data)\n{\n    decoder->log_fn(decoder, level, data);\n}\n\nvoid decoder_output_data(r_device *decoder, data_t *data)\n{\n    decoder->output_fn(decoder, data);\n}\n\n// helper\n\nstatic char *bitrow_asprint_code(uint8_t const *bitrow, unsigned bit_len)\n{\n    char *row_code;\n    char row_bytes[BITBUF_ROWS * BITBUF_COLS * 2 + 1]; // TODO: this is a lot of stack\n\n    row_bytes[0] = '\\0';\n    // print byte-wide\n    for (unsigned col = 0; col < (unsigned)(bit_len + 7) / 8; ++col) {\n        sprintf(&row_bytes[2 * col], \"%02x\", bitrow[col]);\n    }\n    // remove last nibble if needed\n    row_bytes[2 * (bit_len + 3) / 8] = '\\0';\n\n    // print at least one '0'\n    if (bit_len == 0) {\n        snprintf(row_bytes, sizeof(row_bytes), \"0\");\n    }\n\n    // a simple bitrow representation\n    row_code = malloc(8 + bit_len / 4 + 1); // \"{nnnn}..\\0\"\n    if (!row_code) {\n        WARN_MALLOC(\"decoder_output_bitbuffer()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n    sprintf(row_code, \"{%u}%s\", bit_len, row_bytes);\n\n    return row_code;\n}\n\nstatic char *bitrow_asprint_bits(uint8_t const *bitrow, unsigned bit_len)\n{\n    char *row_bits, *p;\n\n    p = row_bits = malloc(bit_len + bit_len / 4 + 1); // \"1..\\0\" (1 space per nibble)\n    if (!row_bits) {\n        WARN_MALLOC(\"bitrow_asprint_bits()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    // print bit-wide with a space every nibble\n    for (unsigned i = 0; i < bit_len; ++i) {\n        if (i > 0 && i % 4 == 0) {\n            *p++ = ' ';\n        }\n        if (bitrow[i / 8] & (0x80 >> (i % 8))) {\n            *p++ = '1';\n        }\n        else {\n            *p++ = '0';\n        }\n    }\n    *p++ = '\\0';\n\n    return row_bits;\n}\n\n// variadic output functions\n\nint decoder_verbose(r_device *decoder)\n{\n    return decoder->verbose;\n}\n\nvoid decoder_log(r_device *decoder, int level, char const *func, char const *msg)\n{\n    if (decoder->verbose >= level) {\n        // note that decoder levels start at LOG_WARNING\n        level += 4;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"src\",     \"\",     DATA_STRING, func,\n                \"lvl\",      \"\",     DATA_INT,    level,\n                \"msg\",      \"\",     DATA_STRING, msg,\n                NULL);\n        /* clang-format on */\n        decoder_output_log(decoder, level, data);\n    }\n}\n\nvoid decoder_logf(r_device *decoder, int level, char const *func, _Printf_format_string_ const char *format, ...)\n{\n    if (decoder->verbose >= level) {\n        char msg[60]; // fixed length limit\n        va_list ap;\n        va_start(ap, format);\n        vsnprintf(msg, sizeof(msg), format, ap);\n        va_end(ap);\n\n        decoder_log(decoder, level, func, msg);\n    }\n}\n\nvoid decoder_log_bitbuffer(r_device *decoder, int level, char const *func, const bitbuffer_t *bitbuffer, char const *msg)\n{\n    if (decoder->verbose >= level) {\n        // note that decoder levels start at LOG_WARNING\n        level += 4;\n\n        char *row_codes[BITBUF_ROWS] = {0};\n        char *row_bits[BITBUF_ROWS] = {0};\n\n        unsigned num_rows = bitbuffer->num_rows;\n        for (unsigned i = 0; i < num_rows; i++) {\n            row_codes[i] = bitrow_asprint_code(bitbuffer->bb[i], bitbuffer->bits_per_row[i]);\n\n            if (decoder->verbose_bits) {\n                row_bits[i] = bitrow_asprint_bits(bitbuffer->bb[i], bitbuffer->bits_per_row[i]);\n            }\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"src\",     \"\",     DATA_STRING, func,\n                \"lvl\",      \"\",     DATA_INT,    level,\n                \"msg\",      \"\",     DATA_STRING, msg,\n                \"num_rows\", \"\",     DATA_INT, num_rows,\n                \"codes\",    \"\",     DATA_ARRAY, data_array(num_rows, DATA_STRING, row_codes),\n                NULL);\n        /* clang-format on */\n\n        if (decoder->verbose_bits) {\n            data = data_ary(data, \"bits\", \"\", NULL, data_array(num_rows, DATA_STRING, row_bits));\n        }\n\n        decoder_output_log(decoder, level, data);\n\n        for (unsigned i = 0; i < num_rows; i++) {\n            free(row_codes[i]);\n            free(row_bits[i]);\n        }\n    }\n}\n\nvoid decoder_logf_bitbuffer(r_device *decoder, int level, char const *func, const bitbuffer_t *bitbuffer, _Printf_format_string_ const char *format, ...)\n{\n    // TODO: pass to interested outputs\n    if (decoder->verbose >= level) {\n        char msg[60]; // fixed length limit\n        va_list ap;\n        va_start(ap, format);\n        vsnprintf(msg, sizeof(msg), format, ap);\n        va_end(ap);\n\n        decoder_log_bitbuffer(decoder, level, func, bitbuffer, msg);\n    }\n}\n\nvoid decoder_log_bitrow(r_device *decoder, int level, char const *func, uint8_t const *bitrow, unsigned bit_len, char const *msg)\n{\n    if (decoder->verbose >= level) {\n        // note that decoder levels start at LOG_WARNING\n        level += 4;\n\n        char *row_code;\n        char *row_bits = NULL;\n\n        row_code = bitrow_asprint_code(bitrow, bit_len);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"src\",     \"\",     DATA_STRING, func,\n                \"lvl\",      \"\",     DATA_INT,    level,\n                \"msg\",      \"\",     DATA_STRING, msg,\n                \"codes\",    \"\",     DATA_STRING, row_code,\n                NULL);\n        /* clang-format on */\n\n        if (decoder->verbose_bits) {\n            row_bits = bitrow_asprint_bits(bitrow, bit_len);\n            data = data_str(data, \"bits\", \"\", NULL, row_bits);\n        }\n\n        decoder_output_log(decoder, level, data);\n\n        free(row_code);\n        free(row_bits);\n    }\n}\n\nvoid decoder_logf_bitrow(r_device *decoder, int level, char const *func, uint8_t const *bitrow, unsigned bit_len, _Printf_format_string_ const char *format, ...)\n{\n    if (decoder->verbose >= level) {\n        char msg[60]; // fixed length limit\n        va_list ap;\n        va_start(ap, format);\n        vsnprintf(msg, sizeof(msg), format, ap);\n        va_end(ap);\n\n        decoder_log_bitrow(decoder, level, func, bitrow, bit_len, msg);\n    }\n}\n\n/* TODO: maybe use as decoder_log function\nvoid decoder_output_bitbuffer_array(r_device *decoder, bitbuffer_t const *bitbuffer, char const *msg)\n{\n    data_t *data;\n    data_t *row_data[BITBUF_ROWS];\n    char *row_codes[BITBUF_ROWS];\n    char row_bytes[BITBUF_ROWS * BITBUF_COLS * 2 + 1]; // TODO: this is a lot of stack\n    unsigned i;\n\n    for (i = 0; i < bitbuffer->num_rows; i++) {\n        row_bytes[0] = '\\0';\n        // print byte-wide\n        for (unsigned col = 0; col < (unsigned)(bitbuffer->bits_per_row[i] + 7) / 8; ++col) {\n            sprintf(&row_bytes[2 * col], \"%02x\", bitbuffer->bb[i][col]);\n        }\n        // remove last nibble if needed\n        row_bytes[2 * (bitbuffer->bits_per_row[i] + 3) / 8] = '\\0';\n\n        row_data[i] = data_make(\n                \"len\", \"\", DATA_INT, bitbuffer->bits_per_row[i],\n                \"data\", \"\", DATA_STRING, row_bytes,\n                NULL);\n\n        // a simpler representation for csv output\n        row_codes[i] = bitrow_asprint_code(bitbuffer->bb[i], bitbuffer->bits_per_row[i]);\n    }\n\n    data = data_make(\n            \"msg\", \"\", DATA_STRING, msg,\n            \"num_rows\", \"\", DATA_INT, bitbuffer->num_rows,\n            \"rows\", \"\", DATA_ARRAY, data_array(bitbuffer->num_rows, DATA_DATA, row_data),\n            \"codes\", \"\", DATA_ARRAY, data_array(bitbuffer->num_rows, DATA_STRING, row_codes),\n            NULL);\n    decoder_output_data(decoder, data);\n\n    for (i = 0; i < bitbuffer->num_rows; i++) {\n        free(row_codes[i]);\n    }\n}\n*/\n"
  },
  {
    "path": "src/devices/abmt.c",
    "content": "/** @file\n    Amazon Basics Meat Thermometer\n\n    Copyright (C) 2021 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nAmazon Basics Meat Thermometer\n\nManchester encoded PCM signal.\n\n[00] {48} e4 00 a3 01 40 ff\n\nII 00 UU TT T0 FF\n\nI - power on random id\n0 - zeros\nU - Unknown\nT - bcd coded temperature\nF - ones\n\n\n*/\n#include \"decoder.h\"\n\n#define SYNC_PATTERN_START_OFF 72\n\n// Convert two BCD encoded nibbles to an integer\nstatic unsigned bcd2int(uint8_t bcd)\n{\n    return 10 * (bcd >> 4) + (bcd & 0xF);\n}\n\nstatic int abmt_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row;\n    float temp_c;\n    bitbuffer_t packet_bits = {0};\n    unsigned int id;\n    unsigned bitpos = 0;\n    uint8_t *b;\n    int16_t temp;\n    uint8_t const sync_pattern[3] = {0x55, 0xAA, 0xAA};\n\n    // Find repeats\n    row = bitbuffer_find_repeated_row(bitbuffer, 4, 90);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 120)\n        return DECODE_ABORT_LENGTH;\n\n    // search for 24 bit sync pattern\n    bitpos = bitbuffer_search(bitbuffer, row, bitpos, sync_pattern, 24);\n    // if sync is not found or sync is found with to little bits available, abort\n    if ((bitpos == bitbuffer->bits_per_row[row]) || (bitpos < SYNC_PATTERN_START_OFF))\n        return DECODE_FAIL_SANITY;\n\n    // sync bitstream\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos - SYNC_PATTERN_START_OFF, &packet_bits, 48);\n    bitbuffer_invert(&packet_bits);\n\n    b      = packet_bits.bb[0];\n    id     = b[0];\n    temp   = bcd2int(b[3]) * 10 + bcd2int(b[4] >> 4);\n    temp_c = (float)temp;\n\n    /* clang-format off */\n    data_t *data = data_make(\n             \"model\",         \"\",            DATA_STRING, \"Basics-Meat\",\n             \"id\",            \"Id\",          DATA_INT,    id,\n             \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n             NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        NULL,\n};\n\nr_device const abmt = {\n        .name        = \"Amazon Basics Meat Thermometer\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 550,\n        .long_width  = 550,\n        .gap_limit   = 2000,\n        .reset_limit = 5000,\n        .decode_fn   = &abmt_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/acurite.c",
    "content": "/** @file\n    Acurite weather stations and temperature / humidity sensors.\n\n    Copyright (c) 2015, Jens Jenson, Helge Weissig, David Ray Thompson, Robert Terzi\n    Enhanced Acurite-606TX by Boing <dhs.mobil@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\nAcurite weather stations and temperature / humidity sensors.\n\nDevices decoded:\n- Acurite Iris (5-n-1) weather station, Model; VN1TXC, 06004RM\n- Acurite 5-n-1 pro weather sensor, Model: 06014RM\n- Acurite Atlas (7-n-1) weather station\n- Acurite Notos (3-n-1) weather station\n- Acurite 896 Rain gauge, Model: 00896\n- Acurite 592TXR / 06002RM / 6044m Tower sensor (temperature and humidity)\n  (Note: Some newer sensors share the 592TXR coding for compatibility.\n- Acurite 609TXC \"TH\" temperature and humidity sensor (609A1TX)\n- Acurite 986 Refrigerator / Freezer Thermometer\n- Acurite 515 Refrigerator / Freezer Thermometer\n- Acurite 606TX / Technoline TX960 temperature sensor, optional with channels and [TX]Button\n- Acurite 6045M Lightning Detector\n- Acurite 00275rm and 00276rm temp. and humidity with optional probe.\n- Acurite 1190/1192 leak/water detector\n*/\n\n#include \"decoder.h\"\n\n#define ACURITE_515_BITLEN        50\n#define ACURITE_TXR_BITLEN        56\n#define ACURITE_5N1_BITLEN        64\n#define ACURITE_6045_BITLEN       72\n#define ACURITE_ATLAS_BITLEN      80\n\n#define ACURITE_515_BYTELEN             6\n#define ACURITE_TXR_BYTELEN             7\n#define ACURITE_1190_BYTELEN            7\n#define ACURITE_3N1_BYTELEN             8\n#define ACURITE_5N1_BYTELEN             8\n#define ACURITE_899_BYTELEN             8\n#define ACURITE_ATLAS_BYTELEN           8\n#define ACURITE_6045_BYTELEN            9\n#define ACURITE_ATLAS_LTNG_BYTELEN      10\n\n\n// ** Acurite known message types\n#define ACURITE_MSGTYPE_1190_DETECTOR                   0x01\n\n#define ACURITE_MSGTYPE_TOWER_SENSOR                    0x04\n\n#define ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM           0x05\n#define ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN               0x06\n#define ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX             0x07\n\n#define ACURITE_MSGTYPE_515_REFRIGERATOR                0x08\n#define ACURITE_MSGTYPE_515_FREEZER                     0x09\n\n#define ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY     0x20\n\n#define ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG      0x25\n#define ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG          0x26\n#define ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG        0x27\n\n#define ACURITE_MSGTYPE_6045M                           0x2f\n#define ACURITE_MSGTYPE_899_RAINFALL                    0x30\n#define ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL  0x31\n#define ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY     0x38\n\n\n\n// Acurite 5n1 Wind direction values.\n// There are seem to be conflicting decodings.\n// It is possible there there are different versions\n// of the 5n1 station that report differently.\n//\n// The original implementation used by the 5n1 device type\n// here seems to have a straight linear/circular mapping.\n//\n// The newer 5n1 mapping seems to just jump around with no clear\n// meaning, but does map to the values sent by Acurite's\n// only Acu-Link Internet Bridge and physical console 1512.\n// This is may be a modified/non-standard Gray Code.\n\n// Mapping 5n1 raw RF wind direction values to aculink's values\n//    RF, AcuLink\n//     0,  6,   NW,  315.0\n//     1,  8,  WSW,  247.5\n//     2,  2,  WNW,  292.5\n//     3,  0,    W,  270.0\n//     4,  4,  NNW,  337.5\n//     5,  A,   SW,  225.0\n//     6,  5,    N,    0.0\n//     7,  E,  SSW,  202.5\n//     8,  1,  ENE,   67.5\n//     9,  F,   SE,  135.0\n//     A,  9,    E,   90.0\n//     B,  B,  ESE,  112.5\n//     C,  3,   NE,   45.0\n//     D,  D,  SSE,  157.0\n//     E,  7,  NNE,   22.5\n//     F,  C,    S,  180.0\n\n// From draythomp/Desert-home-rtl_433\n// matches acu-link internet bridge values\n// The mapping isn't circular, it jumps around.\n// units are 22.5 deg\nint const acurite_5n1_winddirections[] = {\n    14, // 0 - NW\n    11, // 1 - WSW\n    13, // 2 - WNW\n    12, // 3 - W\n    15, // 4 - NNW\n    10, // 5 - SW\n    0,   // 6 - N\n    9, // 7 - SSW\n    3,  // 8 - ENE\n    6, // 9 - SE\n    4,  // a - E\n    5, // b - ESE\n    2,  // c - NE\n    7, // d - SSE\n    1,  // e - NNE\n    8, // f - S\n};\n\n// The high 2 bits of byte zero are the channel (bits 7,6)\n//  00 = C\n//  10 = B\n//  11 = A\nstatic char const *acurite_getChannel(uint8_t byte)\n{\n    static char const *const channel_strs[] = {\"C\", \"E\", \"B\", \"A\"}; // 'E' stands for error\n\n    int channel = (byte & 0xC0) >> 6;\n    return channel_strs[channel];\n}\n\n/**\nAcurite 896 rain gauge\n\n*/\nstatic int acurite_rain_896_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const *b = bitbuffer->bb[0];\n    int id;\n    float total_rain;\n\n    // This needs more validation to positively identify correct sensor type, but it basically works if message is really from acurite raingauge and it doesn't have any errors\n    if (bitbuffer->bits_per_row[0] < 24)\n        return DECODE_ABORT_LENGTH;\n\n    // The nominal repeat count is 16, require a minimum of 12 rows\n    if (bitbuffer->num_rows < 12)\n        return DECODE_ABORT_EARLY; // likely Oregon V1, not AcuRite\n\n    if ((b[0] == 0) || (b[1] == 0) || (b[2] == 0) || (b[3] != 0) || (b[4] != 0))\n        return DECODE_ABORT_EARLY;\n\n    id = b[0];\n    total_rain = ((b[1] & 0xf) << 8) | b[2];\n    total_rain *= 0.5; // Sensor reports number of bucket tips.  Each bucket tip is .5mm\n\n    decoder_logf(decoder, 2, __func__, \"Total Rain is %.1fmm\", total_rain);\n    decoder_log_bitrow(decoder, 2, __func__, b, bitbuffer->bits_per_row[0], \"Raw Message \");\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",             DATA_STRING, \"Acurite-Rain\",\n            \"id\",                   \"\",             DATA_INT,    id,\n            \"rain_mm\",              \"Total Rain\",   DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, total_rain,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nAcurite 609 Temperature and Humidity Sensor.\n\n5 byte messages:\n\n    II ST TT HH CC\n    II - ID byte, changes at each power up\n    S - Status bitmask, normally 0x2,\n        0xa - battery low (bit 0x80)\n    TTT - Temp in Celsius * 10, 12 bit with complement.\n    HH - Humidity\n    CC - Checksum\n\n@todo - see if the 3rd nybble is battery/status\n*/\nstatic int acurite_th_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const *bb = NULL;\n    int cksum, battery_low, valid = 0;\n    float tempc;\n    uint8_t humidity, id, status;\n    int result = 0;\n\n    for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) {\n        if (bitbuffer->bits_per_row[brow] != 40) {\n            result = DECODE_ABORT_LENGTH;\n            continue; // DECODE_ABORT_LENGTH\n        }\n\n        bb = bitbuffer->bb[brow];\n\n        cksum = (bb[0] + bb[1] + bb[2] + bb[3]);\n\n        if (cksum == 0 || ((cksum & 0xff) != bb[4])) {\n            result = DECODE_FAIL_MIC;\n            continue; // DECODE_FAIL_MIC\n        }\n\n        // Temperature in Celsius is encoded as a 12 bit integer value\n        // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2)\n        // negative values are recovered by sign extend from int16_t.\n        int temp_raw = (int16_t)(((bb[1] & 0x0f) << 12) | (bb[2] << 4));\n        tempc        = (temp_raw >> 4) * 0.1f;\n        id           = bb[0];\n        status       = (bb[1] & 0xf0) >> 4;\n        battery_low  = status & 0x8;\n        humidity     = bb[3];\n\n        if (humidity > 100) {\n            decoder_logf(decoder, 1, __func__, \"609txc 0x%04X: invalid humidity: %d %%rH\",\n                         id, humidity);\n            return DECODE_FAIL_SANITY;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Acurite-609TXC\",\n                \"id\",               \"\",             DATA_INT,    id,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, tempc,\n                \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT,    humidity,\n                \"status\",           \"\",             DATA_INT,    status,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        valid++;\n    }\n\n    if (valid)\n        return 1;\n\n    // Only returns the latest result, but better than nothing.\n    return result;\n}\n\n/**\nAcurite 06045m Lightning Sensor decoding.\n\nSpecs:\n- lightning strike count\n- estimated distance to front of storm, 1 to 25 miles / 1.6 to 40 km\n- Temperature -40 to 158 F / -40 to 70 C\n- Humidity 1 - 99% RH\n\nStatus Information sent per 06047M/01021 display\n- (RF) interference (preventing lightning detection)\n- low battery\n\nMessage format:\n\nSomewhat similar to 592TXR and 5-n-1 weather stations.\nSame pulse characteristics. checksum, and parity checking on data bytes.\n\n\n    Byte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6   Byte 7   Byte 8\n    CCIIIIII IIIIIIII pB101111 pHHHHHHH pA?TTTTT pTTTTTTT pLLLLLLL pLRDDDDD KKKKKKKK\n\n- C = Channel (2 bits)\n- I = Sensor ID (14 bit Static ID)\n- p = parity check bit\n- B = Battery OK (cleared for low)\n- H = Humidity (7 bits)\n- A = Active mode lightning detection (cleared for standby mode)\n- T = Temperature (12 bits)\n- L = Lightning strike count (8 bits)\n- D = Lightning distance (5 bits)\n- K = Checksum (8 bits)\n\nByte 0 - channel/ID\n- bitmask CCII IIII\n- 0xC0: channel (A: 0xC, B: 0x8, C: 00)\n- 0x3F: most significant 6 bits of Sensor ID\n   (14 bits, same as Acurite Tower sensor family)\n\nByte 1 - ID all 8 bits, no parity.\n- 0xFF = least significant 8 bits of Sensor ID\n\nByte 2 - Battery and Message type\n- Bitmask PBMMMMMM\n- 0x80 = Parity\n- 0x40 = 1 is battery OK, 0 is battery low\n- 0x3f = Message type 0x2f for 06045M lightning detector\n\nByte 3 - Humidity\n- 0x80 - even parity\n- 0x7f - humidity\n\nByte 4 - Status (2 bits) and Temperature MSB (5 bits)\n- Bitmask PA?TTTTT  (P = Parity, A = Active,  T = Temperature)\n- 0x80 - even parity\n- 0x40 - 1 is Active lightning detection Mode, 0 is standby\n- 0x20 - TBD: always off?\n- 0x1F - Temperature most significant 5 bits\n\nByte 5 - Temperature LSB (7 bits, 8th is parity)\n- 0x80 - even parity\n- 0x7F - Temperature least significant 7 bits\n\nByte 6 - Lightning Strike count (7 of 8 bit, 8th is parity)\n- 0x80 - even parity\n- 0x7F - strike count (upper 7 bits) wraps at 255 -> 0\n\n\nByte 7 - Edge of Storm Distance Approximation & other bits\n- Bits PLRDDDDD  (P = Parity, S = Status, D = Distance\n- 0x80 - even parity\n- 0x40 - LSB of 8 bit strike counter\n- 0x20 - RFI (radio frequency interference)\n- 0x1F - distance to edge of storm\n   value 0x1f is possible invalid value indication (value at power up)\n   @todo determine mapping function/table.\n\n\nByte 8 - checksum. 8 bits, no parity.\n\nData fields in rtl_433 messages:\n- active (vs standby) lightning detection mode\n    When active:\n      the AS39335 is in active scanning mode\n      6045M will transmit every 8 seconds instead of every 24.\n\n- RFI - radio frequency interference detected\n    The AS3935 uses broad RFI for detection\n    Somewhat correlates with the yellow LED on the sensor, but stays set longer\n    Short periods of RFI appears to be somewhat normal\n    long periods of RFI on indicates interference, relocate sensor until\n    yellow LED is no longer on solid\n\n- strike_count - count of detection events, 8 bits\n    counts up to 255, wraps around to 0\n    non-volatile (doesn't reset at power up)\n\n- storm_dist - statistically estimated distance to edge of storm\n    See AS3935 documentation\n    sensor will make calculate a distance estimate with each strike event\n    0x1f (31) is invalid/undefined value, used at power-up to indicate invalid\n    Only 5 bits available, needs to cover range of 25 miles/40 KM per spec.\n    Units unknown, data needed from people with Acurite consoles\n\n- exception - additional analysis of message maybe needed\n    Suggest reporting raw_msg for further examination.\n    bits that were invariant (for me) have changed.\n\nNotes:\n\n2020-08-29 - changed temperature decoding, was 2.0 F too low vs. Acurite Access\n\n@todo - storm_dist conversion to miles/KM (should match Acurite consoles)\n\n*/\nstatic int acurite_6045_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row)\n{\n    float tempf;\n    uint8_t humidity;\n    uint16_t sensor_id;\n    uint8_t strike_count, strike_distance;\n    int battery_low, active, rfi_detect;\n    int exception = 0;\n\n    int browlen = (bitbuffer->bits_per_row[row] + 7) / 8;\n    uint8_t const *bb = bitbuffer->bb[row];\n\n    char const *channel_str = acurite_getChannel(bb[0]); // same as TXR\n\n    // Tower sensor ID is the last 14 bits of byte 0 and 1\n    // CCII IIII | IIII IIII\n    sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; // same as TXR\n    battery_low = (bb[2] & 0x40) == 0;\n\n    humidity = (bb[3] & 0x7f); // 1-99 %rH, same as TXR\n    if (humidity > 100) {\n        decoder_logf(decoder, 1, __func__, \"6045m 0x%04X Ch %s : invalid humidity: %d %%rH\",\n                     sensor_id, channel_str, humidity);\n        return DECODE_FAIL_SANITY;\n    }\n\n    active = (bb[4] & 0x40) == 0x40;    // Sensor is actively listening for strikes\n    //message_type = bb[2] & 0x3f;\n\n    // 12 bits of temperature after removing parity and status bits.\n    // Message native format appears to be in 1/10 of a degree Fahrenheit\n    // Device Specification: -40 to 158 F  / -40 to 70 C\n    // Available range given 12 bits with +1480 offset: -148.0 F to +261.5 F\n    int temp_raw = ((bb[4] & 0x1F) << 7) | (bb[5] & 0x7F);\n    tempf = (temp_raw - 1480) * 0.1f;\n\n    if (tempf < -40.0 || tempf > 158.0) {\n        decoder_logf(decoder, 1, __func__, \"6045m 0x%04X Ch %s, invalid temperature: %0.1f F\",\n                     sensor_id, channel_str, tempf);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // flag if bits 13/14 of temperature are ever non-zero so\n    // they can be investigated\n    if (temp_raw & 0x3000)\n        exception++;\n\n    // Strike count is 8 bits, LSB in following byte\n    strike_count = ((bb[6] & 0x7f) << 1) | ((bb[7] & 0x40) >> 6);\n    strike_distance = bb[7] & 0x1f;\n    rfi_detect = (bb[7] & 0x20) == 0x20;\n\n    // Flag whether this message might need further analysis\n    if ((bb[4] & 0x20) != 0) // unknown status bits, always off\n        exception++;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Acurite-6045M\",\n            \"id\",               \"\",                 DATA_INT,    sensor_id,\n            \"channel\",          \"\",                 DATA_STRING, channel_str,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n            \"temperature_F\",    \"Temperature\",      DATA_FORMAT, \"%.1f F\",     DATA_DOUBLE,     tempf,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT,    humidity,\n            \"strike_count\",     \"Strike Count\",     DATA_INT,    strike_count,\n            \"storm_dist\",       \"Storm Distance\",   DATA_INT,    strike_distance,\n            \"active\",           \"Active Mode\",      DATA_INT,    active,\n            \"rfi\",              \"RFI Detect\",       DATA_INT,    rfi_detect,\n            \"exception\",        \"Data Exception\",   DATA_INT,    exception,\n            NULL);\n    /* clang-format on */\n\n    /*\n     * 2018-04-21 rct - There are still a number of unknown bits in the\n     * message that need to be figured out. Add the raw message hex to\n     * to the structured data output to allow future analysis without\n     * having to enable debug for long running rtl_433 processes.\n     */\n    char raw_str[31];\n    data = data_hex(data, \"raw_msg\", \"Raw Message\", NULL, bb, MIN(browlen, 15), raw_str);\n\n    decoder_output_data(decoder, data);\n\n    return 1; // If we got here 1 valid message was output\n}\n\n/**\nAcurite 899 Rain Gauge decoder\n\n*/\nstatic int acurite_899_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const *bb)\n{\n    (void)bitbuffer;\n    // MIC (checksum, parity) validated in calling function\n\n    uint16_t sensor_id = ((bb[0] & 0x3f) << 8) | bb[1]; //\n    int battery_low = (bb[2] & 0x40) == 0;\n\n    /*\n      @todo bug? channel output isn't consistent with the rest of he Acurite\n      devices in this family, should output ('A', 'B', or 'C')\n      Currently outputting 00 = A, 01 = B, 10 = C\n      Leaving as is to maintain compatibility for now\n    */\n\n    int channel = bb[0] >> 6;\n    // @todo replace the above with this:\n    // char const *channel_str = acurite_getChannel(bb[0]);\n\n\n    /*\n      Rain counter - one tip is 0.01 inch, i.e. 0.254mm\n      Note: Device native unit arguably Imperial\n      but this is being converted to metric here, so -C native won't work\n      Leaving as is to maintain compatibility\n    */\n    int raincounter = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7f);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                         DATA_STRING, \"Acurite-Rain899\",\n            \"id\",               \"\",                         DATA_INT,    sensor_id,\n            \"channel\",          \"\",                         DATA_INT,    channel,\n            // \"channel\",              \"\",             DATA_STRING, channel_str,\n            \"battery_ok\",       \"Battery\",                  DATA_INT,    !battery_low,\n            \"rain_mm\",          \"Rainfall Accumulation\",    DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, raincounter * 0.254,\n            \"mic\",              \"Integrity\",                DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1; // if we got here, 1 message was output\n}\n\n/**\nAcurite 3n1 Weather Station decoder\n\n*/\nstatic int acurite_3n1_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const *bb)\n{\n    // MIC (checksum, parity) validated in calling function\n    (void)bitbuffer;\n\n    char const *channel_str = acurite_getChannel(bb[0]);\n\n    // 3n1 sensor ID is 14 bits\n    uint16_t sensor_id = ((bb[0] & 0x3f) << 8) | bb[1];\n    uint8_t message_type = bb[2] & 0x3f;\n\n    if (*channel_str == 'E') {\n        decoder_logf(decoder, 1, __func__,\n                     \"bad channel Ch %s, msg type 0x%02x\",\n                     channel_str, message_type);\n        return DECODE_FAIL_SANITY;\n    }\n\n    /*\n      @todo bug, 3n1 data format includes sequence_num\n      which was copied from 5n1, but existing code 3n1 uses\n      14 bits for ID. so these bits are used twice.\n\n      Leaving for compatibility, but probably sequence_num\n      doesn't exist and should be deleted. If the 3n1 did use\n      a sequence number, the ID would change on each output.\n    */\n    uint8_t sequence_num = (bb[0] & 0x30) >> 4;\n\n    int battery_low = (bb[2] & 0x40) == 0;\n    uint8_t humidity = (bb[3] & 0x7f); // 1-99 %rH\n    if (humidity > 100) {\n        decoder_logf(decoder, 1, __func__, \"3n1 0x%04X Ch %s : invalid humidity: %d %%rH\",\n                     sensor_id, channel_str, humidity);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // note the 3n1 seems to have one more high bit than 5n1\n    // Spec: -40 to 158 F\n    int temp_raw = (bb[4] & 0x1F) << 7 | (bb[5] & 0x7F);\n    float tempf        = (temp_raw - 1480) * 0.1f; // regression yields (rawtemp-1480)*0.1\n\n    if (tempf < -40.0 || tempf > 158.0) {\n        decoder_logf(decoder, 1, __func__, \"3n1 0x%04X Ch %s, invalid temperature: %0.1f F\",\n                     sensor_id, channel_str, tempf);\n        return DECODE_FAIL_SANITY;\n    }\n\n\n    /*\n      @todo bug from original decoder\n      This can't be a float, must be uint8\n      leaving for compatibility\n    */\n    float wind_speed_mph = bb[6] & 0x7f; // seems to be plain MPH\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING,    \"Acurite-3n1\",\n            \"message_type\",     \"\",             DATA_INT,       message_type,\n            \"id\",               \"\",             DATA_FORMAT,    \"0x%02X\",   DATA_INT,       sensor_id,\n            \"channel\",          \"\",             DATA_STRING,    channel_str,\n            \"sequence_num\",     \"\",             DATA_INT,       sequence_num,\n            \"battery_ok\",       \"Battery\",      DATA_INT,       !battery_low,\n            \"wind_avg_mi_h\",    \"Wind Speed\",   DATA_FORMAT,    \"%.1f mi/h\", DATA_DOUBLE,     wind_speed_mph,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT,    \"%.1f F\", DATA_DOUBLE,    tempf,\n            \"humidity\",         \"\",             DATA_FORMAT,    \"%u %%\",   DATA_INT,   humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING,    \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1; // If we got here 1 valid message was output\n}\n\n\n/**\nAcurite 5n1 Weather Station decoder\n\nXXX todo docs\n\n*/\nstatic int acurite_5n1_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const *bb)\n{\n    // MIC (checksum, parity) validated in calling function\n    (void)bitbuffer;\n\n    char const *channel_str = acurite_getChannel(bb[0]);\n    uint16_t sensor_id = ((bb[0] & 0x0f) << 8) | bb[1];\n    uint8_t sequence_num = (bb[0] & 0x30) >> 4;\n    int battery_low = (bb[2] & 0x40) == 0;\n    uint8_t message_type = bb[2] & 0x3f;\n\n    // Wind raw number is cup rotations per 4 seconds\n    // 8 bits gives range of 0 - 212 KPH\n    // http://www.wxforum.net/index.php?topic=27244.0 (found from weewx driver)\n    int wind_speed_raw = ((bb[3] & 0x1F) << 3)| ((bb[4] & 0x70) >> 4);\n    float wind_speed_kph = 0;\n    if (wind_speed_raw > 0) {\n        wind_speed_kph = wind_speed_raw * 0.8278f + 1.0f;\n    }\n\n    if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL) {\n        // Wind speed, wind direction, and rain fall\n        float wind_dir = acurite_5n1_winddirections[bb[4] & 0x0f] * 22.5f;\n\n        // range: 0 to 99.99 in, 0.01 inch increments, accumulated\n        int raincounter = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7F);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                         DATA_STRING,    \"Acurite-5n1\",\n                \"message_type\",     \"\",                         DATA_INT,       message_type,\n                \"id\",               \"\",                         DATA_INT,       sensor_id,\n                \"channel\",          \"\",                         DATA_STRING,    channel_str,\n                \"sequence_num\",     \"\",                         DATA_INT,       sequence_num,\n                \"battery_ok\",       \"Battery\",                  DATA_INT,       !battery_low,\n                \"wind_avg_km_h\",    \"Wind Speed\",               DATA_FORMAT,    \"%.1f km/h\", DATA_DOUBLE,     wind_speed_kph,\n                \"wind_dir_deg\",     \"\",                         DATA_FORMAT,    \"%.1f\", DATA_DOUBLE,    wind_dir,\n                \"rain_in\",          \"Rainfall Accumulation\",    DATA_FORMAT,    \"%.2f in\", DATA_DOUBLE, raincounter * 0.01f,\n                \"mic\",              \"Integrity\",                DATA_STRING,    \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n    }\n    else if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY) {\n        // Wind speed, temperature and humidity\n\n        // range -40 to 158 F\n        int temp_raw = (bb[4] & 0x0F) << 7 | (bb[5] & 0x7F);\n        float tempf = (temp_raw - 400) * 0.1f;\n\n        if (tempf < -40.0 || tempf > 158.0) {\n            decoder_logf(decoder, 1, __func__, \"5n1 0x%04X Ch %s, invalid temperature: %0.1f F\",\n                         sensor_id, channel_str, tempf);\n            return DECODE_FAIL_SANITY;\n        }\n\n        uint8_t humidity = (bb[6] & 0x7f); // 1-99 %rH\n        if (humidity > 100) {\n            decoder_logf(decoder, 1, __func__, \"5n1 0x%04X Ch %s : invalid humidity: %d %%rH\",\n                         sensor_id, channel_str, humidity);\n            return DECODE_FAIL_SANITY;\n        }\n\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING,    \"Acurite-5n1\",\n                \"message_type\",     \"\",             DATA_INT,       message_type,\n                \"id\",               \"\",             DATA_INT,       sensor_id,\n                \"channel\",          \"\",             DATA_STRING,    channel_str,\n                \"sequence_num\",     \"\",             DATA_INT,       sequence_num,\n                \"battery_ok\",       \"Battery\",      DATA_INT,       !battery_low,\n                \"wind_avg_km_h\",    \"wind_speed\",   DATA_FORMAT,    \"%.1f km/h\", DATA_DOUBLE,     wind_speed_kph,\n                \"temperature_F\",    \"temperature\",  DATA_FORMAT,    \"%.1f F\", DATA_DOUBLE,    tempf,\n                \"humidity\",         \"\",             DATA_FORMAT,    \"%u %%\",   DATA_INT,   humidity,\n                \"mic\",              \"Integrity\",    DATA_STRING,    \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n    } else {\n        decoder_logf(decoder, 1, __func__, \"unknown message type 0x02%x\", message_type);\n        return DECODE_FAIL_SANITY;\n    }\n\n    return 1; // If we got here 1 valid message was output\n}\n\n\n/**\nAcurite Atlas weather and lightning sensor.\n\n| Reading           | Operating Range               | Reading Frequency | Accuracy |\n| ---               | ---                           | ---        | ---             |\n| Temperature Range | -40 to 158°F (-40 to 70°C)    | 30 seconds | ± 1°F |\n| Humidity Range    | 1-100% RH                     | 30 seconds | ± 2% RH |\n| Wind Speed        | 0-160 mph (0-257 km/h)        | 10 seconds | ± 1 mph ≤ 10 mph, ± 10% > 10 mph |\n| Wind Direction    | 360°                          | 30 seconds | ± 3° |\n| Rain              | .01 inch intervals (0.254 mm) | 30 seconds | ± 5% |\n| UV Index          | 0 to 15 index                 | 30 seconds | ± 1 |\n| Light Intensity   | to 120,000 Lumens             | 30 seconds | n/a |\n| Lightning         | Up to 25 miles away (40 km)   | 10 seconds | n/a |\n\nThe Atlas reports direction with an AS5600 hall effect sensor, it has 12-bit resolution according to the spec sheet. https://ams.com/as5600\n\nAcurite Atlas Message Type Format:\n\nMessage Type 0x25 (Wind Speed, Temperature, Relative Humidity, ???)\n\n    Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6   Byte 7   Byte 8   Byte 9   Byte 10\n    cc??ssdd dddddddd pb011011 pWWWWWWW pWTTTTTT pTTTTTTT pHHHHHHH pCCCCCCC pCCDDDDD kkkkkkkkk\n\nNote: 13 bits for Temp is too much, should only be 11 bits.\n\nMessage Type 0x26 (Wind Speed, Wind Vector, Rain Counter, ???)\n\n    Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6   Byte 7   Byte 8   Byte 9   Byte 10\n    cc??ssdd dddddddd pb011100 pWWWWWWW pW?VVVVV pVVVVVRR pRRRRRRR pCCCCCCC pCCDDDDD kkkkkkkkk\n\n    CHANNEL:2b xx ~SEQ:2d ~DEVICE:10d xx ~TYPE:6h SPEED:x~7bx~1b DIR:x~5bx~5bxx x~7b x~7b x~7b CHK:8h\n\nNote: 10 bits for Vector is too much, should only be 9 bits.\nNote: 7 bits for Rain not enough, should reasonably be 10 bits.\n\nMessage Type 0x27 (Wind Speed, UV and Lux data)\n\n    Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6   Byte 7   Byte 8   Byte 9   Byte 10\n    cc??ssdd dddddddd pb011101 pWWWWWWW pW??UUUU pLLLLLLL pLLLLLLL pCCCCCCC pCCDDDDD kkkkkkkkk\n\nNote: 6 bits for UV is too much, should only be 4 bits.\nJRH - Definitely only 4 bits, seeing the occasional value of 32 or 34. No idea what the 2 bits between\n      wind speed and UV are.\n\n    CHANNEL:2b xx ~SEQ:2d ~DEVICE:10d xx ~TYPE:6h SPEED:x~7bx~1b UV:~6d LUX:x~7bx~7b x~7b x~7b CHK:8h\n\nLux needs to multiplied by 10.\n\n- b = bATTERY\n- c = cHANNEL\n- d = dEVICE\n- k = CHECkSUM\n- p = pARITY\n- s = sEQUENCE\n- ? = uNKNOWN\n\n- H = relative Humidity (percent)\n- R = Rain (0.01 inch bucket tip count)\n- T = Temperature (Fahrenheit.  Subtract 400 then divide by 10.)\n- V = wind Vector (degrees decimal)\n- W = Wind speed (miles per hour)\n- U = UV Index\n- L = Lux\n- C = lightning strike Count\n- D = lightning Distance (miles)\n\n*/\nstatic int acurite_atlas_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row)\n{\n    int exception = 0;\n\n    int browlen = (bitbuffer->bits_per_row[row] + 7) / 8;\n    uint8_t const *bb = bitbuffer->bb[row];\n\n    uint8_t message_type     = bb[2] & 0x3f;\n    uint16_t sensor_id = ((bb[0] & 0x03) << 8) | bb[1];\n    char const *channel_str = acurite_getChannel(bb[0]);\n\n    // The sensor sends the same data three times, each of these have\n    // an indicator of which one of the three it is. This means the\n    // checksum and first byte will be different for each one.\n    // The bits 4,5 of byte 0 indicate which copy\n    //  xxxx 00 xx = first copy\n    //  xxxx 01 xx = second copy\n    //  xxxx 10 xx = third copy\n    uint8_t sequence_num = (bb[0] & 0x0c) >> 2;\n    // Battery status is the 7th bit 0x40. 1 = normal, 0 = low\n    int battery_low = (bb[2] & 0x40) == 0;\n\n    // Wind speed is 8-bits raw MPH\n    // Spec is 0-200 MPH\n    float wind_speed_mph = ((bb[3] & 0x7F) << 1) | ((bb[4] & 0x40) >> 6);\n\n    if (wind_speed_mph > 200) {\n        decoder_logf(decoder, 1, __func__, \"Atlas 0x%04X Ch %s, invalid wind speed: %.1f MPH\",\n                     sensor_id, channel_str, wind_speed_mph);\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",             DATA_STRING, \"Acurite-Atlas\",\n            \"id\",                   \"\",             DATA_INT,    sensor_id,\n            \"channel\",              \"\",             DATA_STRING, channel_str,\n            \"sequence_num\",         \"\",             DATA_INT,    sequence_num,\n            \"battery_ok\",           \"Battery\",      DATA_INT,    !battery_low,\n            \"message_type\",         \"\",             DATA_INT,    message_type,\n            \"wind_avg_mi_h\",        \"Wind Speed\",   DATA_FORMAT, \"%.1f mi/h\", DATA_DOUBLE, wind_speed_mph,\n            NULL);\n    /* clang-format on */\n\n    if (message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM ||\n            message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG) {\n        // Wind speed, temperature and humidity\n\n        // Spec: temperature range -40 to 158 F\n        // There seem to be 13 bits for temperature but only 11 needed.\n        // Decode as 11 bits, flag exception if the other two bits are ever\n        // non-zero so they can be investigated.\n        int temp_raw = (bb[4] & 0x0F) << 7 | (bb[5] & 0x7F);\n        if ((bb[4] & 0x30) != 0)\n            exception++;\n\n        float tempf = (temp_raw - 400) * 0.1f;\n        if (tempf < -40.0 || tempf > 158.0) {\n            decoder_logf(decoder, 1, __func__, \"Atlas 0x%04X Ch %s, invalid temperature: %0.1f F\",\n                         sensor_id, channel_str, tempf);\n            return DECODE_FAIL_SANITY;\n        }\n\n\n        // Fail sanity check over 100% humidity\n        // Allow 0 because very low battery or defective sensor will report\n        // those values.\n        uint8_t humidity = (bb[6] & 0x7f);\n        if (humidity > 100) {\n            decoder_logf(decoder, 1, __func__, \"0x%04X Ch %s : Impossible humidity: %d %%rH\",\n                         sensor_id, channel_str, humidity);\n            return DECODE_FAIL_SANITY;\n        }\n\n        if (humidity == 0)\n            exception++;\n\n\n        /* clang-format off */\n        data = data_dbl(data, \"temperature_F\",  \"Temperature\",  \"%.1f F\",   tempf);\n        data = data_int(data, \"humidity\",       \"\",             \"%u %%\",    humidity);\n        /* clang-format on */\n    }\n\n    if (message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN ||\n            message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG) {\n        // Wind speed, wind direction, and rain fall\n\n        // Wind direction is in degrees, 0-360, only 9 bits needed\n        // but historically decoded as 10 bits.\n        // There seems to be 11 bits available\n        // As with temperatuve message, flag msg if those two extra bits\n        // are ever non-zero so they can be investigated\n        // Note: output as float, but currently can only be decoded an integer\n        float wind_dir = ((bb[4] & 0x1f) << 5) | ((bb[5] & 0x7c) >> 2);\n        if ((bb[4] & 0x30) != 0)\n            exception++;\n\n        if (wind_dir > 360) {\n            decoder_logf(decoder, 1, __func__, \"Atlas 0x%04X Ch %s, invalid wind direction: %0.1fF\",\n                         sensor_id, channel_str, wind_dir);\n            return DECODE_FAIL_SANITY;\n        }\n\n        // range: 0 to 5.11 in, 0.01 inch increments, accumulated\n        // JRH: Confirmed 9 bits, counter rolls over after 5.11 inches\n        int raincounter = ((bb[5] & 0x03) << 7) | (bb[6] & 0x7F);\n\n        /* clang-format off */\n        data = data_dbl(data, \"wind_dir_deg\",   \"\",                         \"%.1f\",     wind_dir);\n        data = data_dbl(data, \"rain_in\",        \"Rainfall Accumulation\",    \"%.2f in\",  raincounter * 0.01f);\n        /* clang-format on */\n    }\n\n    if (message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX ||\n            message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG) {\n        // Wind speed, UV Index, Light Intensity, and optionally Lightning\n\n        // Spec UV index is 0-16 (but can only be 0-15)\n        int uv  = (bb[4] & 0x0f);\n\n        // Light intensity 0 - 120,000 lumens / 10\n        // 14 bits are available (0-16,383)\n        int lux = ((bb[5] & 0x7f) << 7) | (bb[6] & 0x7F);\n        if (lux > 12000) {\n            decoder_logf(decoder, 1, __func__, \"Atlas 0x%04X Ch %s, invalid lux %d\",\n                         sensor_id, channel_str, lux);\n            return DECODE_FAIL_SANITY;\n        }\n\n        /* clang-format off */\n        data = data_dbl(data, \"uvi\",    \"UV Index\",   \"%.0f\",   uv);\n        data = data_int(data, \"lux\",    \"\",             NULL,   lux * 10);\n        /* clang-format on */\n    }\n\n    if ((message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG ||\n                message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG ||\n                message_type == ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG)) {\n\n        // @todo decode strike_distance to miles or KM.\n        int strike_count    = ((bb[7] & 0x7f) << 2) | ((bb[8] & 0x60) >> 5);\n        int strike_distance = bb[8] & 0x1f;\n\n        /* clang-format off */\n        data = data_int(data, \"strike_count\",       \"\",     NULL,   strike_count);\n        data = data_int(data, \"strike_distance\",    \"\",     NULL,   strike_distance);\n        /* clang-format on */\n    }\n\n    // @todo only do this if exception != 0, but would be somewhat incompatible\n    data = data_int(data, \"exception\",  \"Data Exception\",   NULL,   exception);\n    // There are still a few unknown/unused bits in the message that\n    // message that could possibly hold some data. Add the raw message hex to\n    // to the structured data output to allow future analysis without\n    // having to enable debug for long running rtl_433 processes.\n    char raw_str[31];\n    data = data_hex(data, \"raw_msg\", \"Raw Message\", NULL, bb, MIN(browlen, 15), raw_str);\n\n    decoder_output_data(decoder, data);\n\n    return 1; // one valid message decoded\n}\n\n/**\nAcurite 592TXR Temperature Humidity sensor decoder.\n\nAlso:\n- Acurite 592TX (without humidity sensor)\n\nMessage Type 0x04, 7 bytes\n\n| Byte 0    | Byte 1    | Byte 2    | Byte 3    | Byte 4    | Byte 5    | Byte 6    |\n| --------- | --------- | --------- | --------- | --------- | --------- | --------- |\n| CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK |\n\n\n- C: Channel 00: C, 10: B, 11: A, (01 is invalid)\n- I: Device ID (14 bits)\n- B: Battery, 1 is battery OK, 0 is battery low (observed low < 2.5V)\n- M: Message type (6 bits), 0x04\n- T: Temperature Celsius (11 - 14 bits?), + 1000 * 10\n- H: Relative Humidity (%) (7 bits)\n- K: Checksum (8 bits)\n- p: Parity bit\n\nNotes:\n\n- Temperature\n  - Encoded as Celsius + 1000 * 10\n  - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F)\n  - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C\n  - @todo - check if high 3 bits ever used for anything else\n\n*/\nstatic int acurite_tower_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const *bb)\n{\n    // MIC (checksum, parity) validated in calling function\n\n    (void)bitbuffer;\n    int exception = 0;\n    char const *channel_str = acurite_getChannel(bb[0]);\n    int sensor_id = ((bb[0] & 0x3f) << 8) | bb[1];\n    int battery_low = (bb[2] & 0x40) == 0;\n\n    // Spec is relative humidity 1-99%\n    // Allowing value of 0, very low battery or broken sensor can return 0% or 1%\n    int humidity = (bb[3] & 0x7f);\n    if (humidity > 100 && humidity != 127) {\n        decoder_logf(decoder, 1, __func__, \"0x%04X Ch %s : invalid humidity: %d %%rH\",\n                sensor_id, channel_str, humidity);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // temperature encoding used by \"tower\" sensors 592txr\n    // 14 bits available after removing both parity bits.\n    // 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F)\n    // Possible ranges are -100 C to 1538.4 C, but most of that range\n    // is not possible on Earth.\n    // pIII IIII pIII IIII\n    int temp_raw = ((bb[4] & 0x7F) << 7) | (bb[5] & 0x7F);\n    float tempc = (temp_raw - 1000) * 0.1f;\n    if (tempc < -40 || tempc > 70) {\n        decoder_logf(decoder, 1, __func__, \"0x%04X Ch %s : invalid temperature: %0.2f C\",\n                sensor_id, channel_str, tempc);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // flag if bits 12-14 of temperature are ever non-zero\n    // so they can be investigated for other possible information\n    if ((temp_raw & 0x3800) != 0)\n        exception++;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",             DATA_STRING, \"Acurite-Tower\",\n            \"id\",                   \"\",             DATA_INT,    sensor_id,\n            \"channel\",              \"\",             DATA_STRING, channel_str,\n            \"battery_ok\",           \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",        \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, tempc,\n            \"humidity\",             \"Humidity\",     DATA_COND, humidity != 127, DATA_FORMAT, \"%u %%\", DATA_INT,    humidity,\n            \"mic\",                  \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    if (exception) {\n        // Add exception and raw message bytes to message to enable\n        // later analysis of unexpected/possibly undecoded data\n        /* clang-format off */\n        data = data_int(data, \"exception\",  \"Data Exception\",   NULL,   exception);\n        char buf_str[31];\n        data = data_hex(data, \"raw_msg\",    \"Raw Message\",      NULL,   bb, ACURITE_TXR_BYTELEN, buf_str);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\n/**\nAcurite 1190/1192 leak detector\n\nNote: it seems like Acurite has deleted this product and\nrelated information from their website so specs, manual, etc.\naren't easy to find\n\n*/\nstatic int acurite_1190_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const *bb)\n{\n    (void)bitbuffer;\n    // Channel is the first two bits of the 0th byte\n    // but only 3 of the 4 possible values are valid\n    char const *channel_str = acurite_getChannel(bb[0]);\n\n    // Tower sensor ID is the last 14 bits of byte 0 and 1\n    // CCII IIII | IIII IIII\n    int sensor_id = ((bb[0] & 0x3f) << 8) | bb[1];\n\n    // Battery status is the 7th bit 0x40. 1 = normal, 0 = low\n    int battery_low = (bb[2] & 0x40) == 0;\n\n    // Leak indicator bit is the 5th bit of byte 3. 1 = wet, 0 = dry\n    int is_wet = (bb[3] & 0x10) >> 4;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",             DATA_STRING, \"Acurite-Leak\",\n            \"id\",                   \"\",             DATA_INT,    sensor_id,\n            \"channel\",              \"\",             DATA_STRING, channel_str,\n            \"battery_ok\",           \"Battery\",      DATA_INT,    !battery_low,\n            \"leak_detected\",        \"Leak\",         DATA_INT,    is_wet,\n            \"mic\",                  \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\n/**\nDecode Acurite 515 Refrigerator/Freezer sensors\n\nByte 0    | Byte 1    | Byte 2    | Byte 3    | Byte 4    | Byte 5\nCCII IIII | IIII IIII | pBMM MMMM | bTTT TTTT | bTTT TTTT | KKKK KKKK\n\n- C: Channel 00: C, 10: B, 11: A\n- I: Device ID (14 bits), volatie, resets at power up\n- B: Battery, 1 is battery OK, 0 is battery low\n- M: Message type (6 bits), 0x8: Refrigerator, 0x9: Freezer\n- T: Temperature Fahrenheit (14 bits?), + 1480 * 10\n- K: Checksum (8 bits)\n- p: Parity bit\n\n*/\nstatic int acurite_515_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const *bb)\n{\n    // length, MIC (checksum, parity) validated in calling function\n\n    (void)bitbuffer;\n    int exception = 0;\n    char channel_type_str[3];\n    uint8_t message_type = bb[2] & 0x3f;\n\n    // Channel A, B, C, common with other Acurite devices\n    char const *channel_str = acurite_getChannel(bb[0]);\n\n    channel_type_str[0] = channel_str[0];\n\n    if (message_type == ACURITE_MSGTYPE_515_REFRIGERATOR)\n        channel_type_str[1] = 'R';\n    else if (message_type == ACURITE_MSGTYPE_515_FREEZER)\n        channel_type_str[1] = 'F';\n    else {\n        decoder_logf(decoder, 1, __func__, \"unknown message type 0x02%x\", message_type);\n        return DECODE_FAIL_SANITY;\n    }\n\n    channel_type_str[2] = 0;\n\n    // Sensor ID is the last 14 bits of byte 0 and 1\n    // CCII IIII | IIII IIII\n    // The sensor ID changes on each power-up of the sensor.\n    uint16_t sensor_id = ((bb[0] & 0x3f) << 8) | bb[1];\n\n    // temperature encoding 14 bits after removing both parity bits.\n    // Spec range from Manual: -40 F to 158 F  (-40 to 70 C)\n    // Offset to avoid negative values is 1480\n    // Possible encoding range with 14 bits (0-16383) is -148.0 F to 1490.3 F\n    // Only 12 bits needed to represent -40 F to 158 F with encoding offset of 1480.\n    //   encoding range at 12 bits with +1480 offset: -148.0 F to +261.5 F\n    int temp_raw = ((bb[3] & 0x7F) << 7) | (bb[4] & 0x7F);\n    float tempf = (temp_raw - 1480) * 0.1f;\n    if (tempf < -40.0 || tempf > 158.0) {\n        decoder_logf(decoder, 1, __func__, \"515 0x%04X Ch %s, invalid temperature: %0.1f F\",\n                     sensor_id, channel_str, tempf);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // flag if bits 13 - 14 of temperature are ever non-zero\n    // so they can be investigated\n    if ((temp_raw & 0x3000) != 0)\n        exception++;\n\n    // Battery status is the 7th bit 0x40. 1 = normal, 0 = low\n    int battery_low = (bb[2] & 0x40) == 0;\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",                \"\",             DATA_STRING, \"Acurite-515\",\n        \"id\",                   \"\",             DATA_INT,    sensor_id,\n        \"channel\",              \"\",             DATA_STRING, channel_type_str,\n        \"battery_ok\",           \"Battery\",      DATA_INT,    !battery_low,\n        \"temperature_F\",        \"Temperature\",  DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, tempf,\n        \"mic\",                  \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n        NULL);\n    /* clang-format on */\n\n    if (exception) {\n        // Add exception and raw message bytes to message to enable\n        // later analysis of unexpected/possibly undecoded data\n        /* clang-format off */\n        data = data_int(data, \"exception\",  \"Data Exception\",   NULL,   exception);\n        char buf_str[31];\n        data = data_hex(data, \"raw_msg\",    \"Raw Message\",      NULL,   bb, ACURITE_515_BYTELEN, buf_str);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\n/**\nCheck Acurite TXR message integrity (length, checksum, parity)\n\nNeed to pass in expected length - correct number of bytes for\nthat message type.\n\nReturn 0 for valid roe or DECODE_ABORT_LENGHT, DECODE_FAIL_MIC, DECODE_FAIL_SANITY\n\nLong rows with extra bits/bytes (from demod/bit slicing)\nwill be accepted as long the bytes up to the expected length\npass checksum and parity tests.\n*/\nstatic int acurite_txr_check(r_device *decoder, uint8_t const bb[], unsigned browlen, unsigned explen)\n{\n\n    // Currently shortest Acurite \"TXR\" message is 6 bytes\n    // 5 bytes could possibly be valid, but would only have\n    // a single data byte after Channel, ID, message type, and checksum\n    // Really short rows (1-2) bytes, should be rejected quietly earlier\n    // so real error types can be seen\n    if (browlen < 6)\n        return DECODE_ABORT_LENGTH;\n\n    if (browlen < explen) {\n        decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, \"wrong length for msg type\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // 8 bit checksum in the last byte\n    if ((add_bytes(bb, explen - 1) & 0xff) != bb[explen - 1]) {\n        decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, \"bad checksum\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Verify parity bits\n    // Bytes 2 ... n-1 should all have even parity\n    // (ID bytes and checksum byte are all 8 bit, so no parity check)\n    int parity = parity_bytes(&bb[2], explen - 3);\n\n    if (parity) {\n        decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8,\"bad parity\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // All of these devices have channel (A, B, C) in two bits (mask 0c0) of byte 0\n    // 00: C, 10: B, 11: A, (01 aka 'E' is invalid)\n    // check sanity to cut down an bad messages that pass MIC checks\n    char const *channel_str = acurite_getChannel(bb[0]);\n    if (*channel_str == 'E') {\n        uint8_t message_type = bb[2] & 0x3f;\n        decoder_logf(decoder, 1, __func__,\n                     \"bad channel Ch %s, msg type 0x%02x, msg len %d\",\n                     channel_str, message_type, browlen);\n        return DECODE_FAIL_SANITY;\n    }\n\n    return 0;\n}\n\n/**\nProcess messages for Acurite weather stations, tower and related sensors\n@sa acurite_1190_decode()\n@sa acurite_515_decode()\n@sa acurite_6045_decode()\n@sa acurite_899_decode()\n@sa acurite_3n1_decode()\n@sa acurite_5n1_decode()\n@sa acurite_atlas_decode()\n@sa acurite_tower_decode()\n\nThis callback is used for devices that use a very similar message format:\n\n- 592TXR / 592TX / 6002RM / 6044m Tower sensor and related temperature/humidity sensors\n- Atlas (7-in-1) Weather Station\n- Iris (5-in-1) weather station\n- Notos (3-in-1) Weather station\n- 6045M Lightning Detector with Temperature and Humidity\n- 899 Rain Fall Gauge\n- 515 Refrigerator/Freezer sensors\n- 1190/1192 Water alarm\n\nThese devices have a message type in the 3rd byte and an 8 bit checksum\nin the last byte.\n\n*/\nstatic int acurite_txr_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int decoded = 0;\n    int error_ret = 0;\n    int ret = 0;\n    uint8_t *bb;\n    uint8_t message_type;\n\n    bitbuffer_invert(bitbuffer);\n\n    for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) {\n        int row_bit_cnt = bitbuffer->bits_per_row[brow];\n        int browlen = row_bit_cnt / 8;  // assumption: safe to round down, extra bits are spurious\n\n        bb = bitbuffer->bb[brow];\n\n        // Known messages in this family are between 6 and 10 bytes\n        if (browlen < 6) {\n            continue; // quietly skip short rows\n        }\n\n        // Currently known longest message is 10 bytes (Atlas with lightning sensorr)\n        if (browlen > 10) {\n            decoder_logf(decoder, 2, __func__, \"Skipping wrong len row %u bits %u, bytes %d\",\n                         brow, row_bit_cnt, browlen);\n            error_ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n\n        decoder_logf(decoder, 2, __func__,\n                     \"row %u bits %u, bytes %d, extra bits %d, msg type 0x%02x\",\n                     brow, row_bit_cnt, browlen, row_bit_cnt % 8, bb[2] & 0x3f);\n\n\n        // quietly ignore rows of zeros (ID, msg type, checksum)\n        if (bb[0] == 0 && bb[1] == 0 && bb[2] == 0 && bb[browlen - 1] == 0)\n            continue;\n\n        // acurite sensors with a common format have a message type\n        // in the lower 6 bits of the 3rd byte.\n        // Format: PBMMMMMM\n        // P = Parity\n        // B = Battery Normal\n        // M = Message type\n        message_type = bb[2] & 0x3f;\n\n        // Check so that unknown message type can be flagged\n        // and dispatching to decoders can be easier to maintain\n        switch(message_type) {\n            case ACURITE_MSGTYPE_1190_DETECTOR:\n            case ACURITE_MSGTYPE_TOWER_SENSOR:\n            case ACURITE_MSGTYPE_6045M:\n            case ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL:\n            case ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG:\n            case ACURITE_MSGTYPE_515_REFRIGERATOR:\n            case ACURITE_MSGTYPE_515_FREEZER:\n            case ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY:\n            case ACURITE_MSGTYPE_899_RAINFALL:\n                break;\n\n            default:\n                decoder_log_bitrow(decoder, 1, __func__, bb, row_bit_cnt,\n                                   \"Unknown message type\");\n                error_ret = DECODE_FAIL_SANITY;\n                continue;\n                break;\n        }\n\n        // Check message type and dispatch to appropriate decoders\n        // NOTE: since we are processing each row, do not return\n        // until all rows have been processed\n        if (message_type == ACURITE_MSGTYPE_TOWER_SENSOR) {\n            if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_TXR_BYTELEN)) != 0) {\n                error_ret = ret;\n            } else {\n                    if ((ret = acurite_tower_decode(decoder, bitbuffer, bb)) > 0) {\n                    decoded += ret;\n                } else if (ret < 0) {\n                    error_ret = ret;\n                }\n            }\n        }\n\n        if (message_type == ACURITE_MSGTYPE_1190_DETECTOR) {\n            if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_1190_BYTELEN)) != 0) {\n                error_ret = ret;\n            } else {\n                if ((ret = acurite_1190_decode(decoder, bitbuffer, bb)) > 0) {\n                    decoded += ret;\n                } else if (ret < 0) {\n                    error_ret = ret;\n                }\n            }\n        }\n\n        if (message_type == ACURITE_MSGTYPE_6045M) {\n            if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_6045_BYTELEN)) != 0) {\n                error_ret = ret;\n            } else {\n                if ((ret = acurite_6045_decode(decoder, bitbuffer, brow)) > 0) {\n                    decoded += ret;\n                } else if (ret < 0) {\n                    error_ret = ret;\n                }\n            }\n        }\n\n        if (message_type == ACURITE_MSGTYPE_515_REFRIGERATOR ||\n            message_type == ACURITE_MSGTYPE_515_FREEZER) {\n            if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_515_BYTELEN)) != 0) {\n                error_ret = ret;\n            } else {\n                if ((ret = acurite_515_decode(decoder, bitbuffer, bb)) > 0) {\n                    decoded += ret;\n                } else if (ret < 0) {\n                    error_ret = ret;\n                }\n            }\n        }\n\n        if (message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_TEMP_HUMIDITY ||\n            message_type == ACURITE_MSGTYPE_5N1_WINDSPEED_WINDDIR_RAINFALL) {\n            if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_5N1_BYTELEN)) != 0) {\n                error_ret = ret;\n            } else {\n                if ((ret = acurite_5n1_decode(decoder, bitbuffer, bb)) > 0) {\n                    decoded += ret;\n                } else if (ret < 0) {\n                    error_ret = ret;\n                }\n            }\n        }\n\n        if (message_type == ACURITE_MSGTYPE_3N1_WINDSPEED_TEMP_HUMIDITY) {\n            /*\n              @todo - does 3n1 use parity checking?\n              3n1 g001 in rtl_433_test has odd parity the 2nd to last byte in both copies\n              but g002 passes parity check\n            */\n\n            if (browlen < ACURITE_3N1_BYTELEN) {\n                decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, \"3n1 wrong length\");\n                error_ret = DECODE_ABORT_LENGTH;\n                continue;\n            }\n\n            if ((add_bytes(bb, ACURITE_3N1_BYTELEN - 1) & 0xff) !=\n                bb[ACURITE_3N1_BYTELEN - 1]) {\n                decoder_log_bitrow(decoder, 1, __func__, bb, browlen * 8, \"bad checksum\");\n                error_ret = DECODE_FAIL_MIC;\n                continue;\n            }\n\n            if ((ret = acurite_3n1_decode(decoder, bitbuffer, bb)) > 0) {\n                decoded += ret;\n            } else if (ret < 0) {\n                error_ret = ret;\n            }\n        }\n\n        if (message_type == ACURITE_MSGTYPE_899_RAINFALL) {\n            /*\n              @todo - does the 899 use parity checking?\n              The available sample shows a parity bit in the message byte\n              but there isn't enough accumulated rain in the data bytes\n              to see if parity is used\n            */\n            if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_899_BYTELEN)) != 0) {\n                error_ret = ret;\n            } else {\n                if ((ret = acurite_899_decode(decoder, bitbuffer, bb)) > 0) {\n                    decoded += ret;\n                } else if (ret < 0) {\n                    error_ret = ret;\n                }\n            }\n        }\n\n        // process Atlas\n        switch(message_type) {\n            // Atlas messages without lightning sensor installed - 8 bytes\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX:\n                if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_ATLAS_BYTELEN)) != 0) {\n                    error_ret = ret;\n                } else {\n                    if ((ret = acurite_atlas_decode(decoder, bitbuffer, brow)) > 0) {\n                        decoded += ret;\n                    } else if (ret < 0) {\n                        error_ret = ret;\n                    }\n                }\n                break;\n\n            // Atlas messages with lightning sensor installed - 10 bytes\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_TEMP_HUM_LTNG:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_RAIN_LTNG:\n            case ACURITE_MSGTYPE_ATLAS_WNDSPD_UV_LUX_LTNG:\n                if ((ret = acurite_txr_check(decoder, bb, browlen, ACURITE_ATLAS_LTNG_BYTELEN)) != 0) {\n                    error_ret = ret;\n                } else {\n                    if ((ret = acurite_atlas_decode(decoder, bitbuffer, brow)) > 0) {\n                        decoded += ret;\n                    } else if (ret < 0) {\n                        error_ret = ret;\n                    }\n                }\n                break;\n\n        }\n\n        decoder_logf(decoder, 2, __func__,\n                     \"stats: row %u, msg type 0x%02x, bytes %d, decoded %d, error %d\",\n                     brow, message_type, browlen, decoded, error_ret);\n\n    }\n\n    if (decoded > 0)\n        return decoded;\n    else\n        return error_ret;\n}\n\n\n/**\nAcurite 00986 Refrigerator / Freezer Thermometer.\n\nIncludes two sensors and a display, labeled 1 and 2,\nby default 1 - Refrigerator, 2 - Freezer.\n\nPPM, 5 bytes, sent twice, no gap between repeaters\nstart/sync pulses two short, with short gaps, followed by\n4 long pulse/gaps.\n\n@todo, the 2 short sync pulses get confused as data.\n\nData Format - 5 bytes, sent LSB first, reversed:\n\n    TT II II SS CC\n- T - Temperature in Fahrenheit, integer, MSB = sign.\n      Encoding is \"Sign and magnitude\"\n- I - 16 bit sensor ID\n      changes at each power up\n- S - status/sensor type\n      0x01 = Sensor 2\n      0x02 = low battery\n- C = CRC (CRC-8 poly 0x07, little-endian)\n\n@todo\n- needs new PPM demod that can separate out the short\n  start/sync pulses which confuse things and cause\n  one data bit to be lost in the check value.\n\n2018-04 A user with a dedicated receiver indicated the\n  possibility that the transmitter actually drops the\n  last bit instead of the demod.\n\nleaving some of the debugging code until the missing\nbit issue gets resolved.\n*/\nstatic int acurite_986_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int const browlen = 5;\n    uint8_t sensor_num, status, crc, crcc;\n    uint8_t br[8];\n    int8_t tempf; // Raw Temp is 8 bit signed Fahrenheit\n    uint16_t sensor_id, valid_cnt = 0;\n    char sensor_type;\n    char const *channel_str;\n    int battery_low;\n\n    int result = 0;\n\n    for (uint16_t brow = 0; brow < bitbuffer->num_rows; ++brow) {\n\n        decoder_logf(decoder, 2, __func__, \"row %u bits %u, bytes %d\", brow, bitbuffer->bits_per_row[brow], browlen);\n\n        if (bitbuffer->bits_per_row[brow] < 39 || bitbuffer->bits_per_row[brow] > 43) {\n            if (bitbuffer->bits_per_row[brow] > 16)\n                decoder_log(decoder, 2, __func__,\"skipping wrong len\");\n            result = DECODE_ABORT_LENGTH;\n            continue; // DECODE_ABORT_LENGTH\n        }\n        uint8_t const *bb = bitbuffer->bb[brow];\n\n        // Reduce false positives\n        // may eliminate these with a better PPM (precise?) demod.\n        if ((bb[0] == 0xff && bb[1] == 0xff && bb[2] == 0xff) ||\n                (bb[0] == 0x00 && bb[1] == 0x00 && bb[2] == 0x00)) {\n            result = DECODE_ABORT_EARLY;\n            continue; // DECODE_ABORT_EARLY\n        }\n\n        // Reverse the bits, msg sent LSB first\n        for (int i = 0; i < browlen; i++)\n            br[i] = reverse8(bb[i]);\n\n        decoder_log_bitrow(decoder, 1, __func__, br, browlen * 8, \"reversed\");\n\n        tempf = br[0];\n        sensor_id = (br[1] << 8) + br[2];\n        status = br[3];\n        sensor_num = (status & 0x01) + 1;\n        status = status >> 1;\n        battery_low = ((status & 1) == 1);\n\n        // By default Sensor 1 is the Freezer, 2 Refrigerator\n        sensor_type = sensor_num == 2 ? 'F' : 'R';\n        channel_str = sensor_num == 2 ? \"2F\" : \"1R\";\n\n        crc = br[4];\n        crcc = crc8le(br, 4, 0x07, 0);\n\n        if (crcc != crc) {\n            decoder_logf_bitrow(decoder, 2, __func__, br, browlen * 8, \"bad CRC: %02x -\", crc8le(br, 4, 0x07, 0));\n            // HACK: rct 2018-04-22\n            // the message is often missing the last 1 bit either due to a\n            // problem with the device or demodulator\n            // Add 1 (0x80 because message is LSB) and retry CRC.\n            if (crcc == (crc | 0x80)) {\n                decoder_logf(decoder, 2, __func__, \"CRC fix %02x - %02x\", crc, crcc);\n            }\n            else {\n                continue; // DECODE_FAIL_MIC\n            }\n        }\n\n        if (tempf & 0x80) {\n            tempf = (tempf & 0x7f) * -1;\n        }\n\n        decoder_logf(decoder, 1, __func__, \"sensor 0x%04x - %d%c: %d F\", sensor_id, sensor_num, sensor_type, tempf);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Acurite-986\",\n                \"id\",               \"\",             DATA_INT,    sensor_id,\n                \"channel\",          \"\",             DATA_STRING, channel_str,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"temperature_F\",    \"temperature\",  DATA_FORMAT, \"%f F\", DATA_DOUBLE,    (float)tempf,\n                \"status\",           \"Status\",       DATA_INT,    status,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n\n        valid_cnt++;\n    }\n\n    if (valid_cnt)\n        return 1;\n\n    return result;\n}\n\n/**\nAcurite 606TX / Technoline TX960 Temperature sensor decoder.\n\nSpecs:\n- Temperature -40 to 158 F / -40 to 70 C\n\nStatus Information sent\n- button pressed\n- low battery\n- channel\n- id\n\nMessage format:\n\n    Byte 0   Byte 1   Byte 2   Byte 3   Byte 4\n    IIIIIIII BbCCTTTT TTTTTTTT KKKKKKKK f\n\n- I = Sensor ID (8 bits, changes with every battery replacement)\n- B = Battery OK (cleared for low)\n- b = Button pressed\n- C = Channel (2 bits, Channels 0, 1 or 2)\n- T = Temperature (12 bits)\n- K = Checksum (8 bits)\n- f = Final bit (== 0 for Acurite sensor, == !B for Technoline sensor)\n*/\nstatic int acurite_606_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b;\n    int row;\n    int16_t temp_raw; // temperature as read from the data packet\n    float temp_c;     // temperature in C\n    int battery_ok;   // the battery status: 1 is good, 0 is low\n    int channel;      // the channel\n    int button;       // the reset button: 1: pressed\n    int sensor_id;    // the sensor ID - basically a random number that gets reset whenever the battery is removed\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 3, 32); // expected are 6 rows\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 33)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[row];\n\n    // reject all blank messages\n    if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0)\n        return DECODE_FAIL_SANITY;\n\n    // calculate the checksum and only continue if we have a matching checksum\n    uint8_t chk = lfsr_digest8(b, 3, 0x98, 0xf1);\n    if (chk != b[3])\n        return DECODE_FAIL_MIC;\n\n    // Processing the temperature:\n    // Upper 4 bits are stored in nibble 1, lower 8 bits are stored in nibble 2\n    // upper 4 bits of nibble 1 are reserved for other usages (e.g. battery status)\n    sensor_id  = b[0];\n    battery_ok = (b[1] & 0x80) >> 7;\n    channel    = ((b[1] & 0x30) >> 4)+1; // Channel A,B,C / 1,2,3\n    button     = (b[1] & 0x40) >> 6; // SensorTX Button\n    temp_raw   = (int16_t)((b[1] << 12) | (b[2] << 4));\n    temp_raw   = temp_raw >> 4;\n    temp_c     = temp_raw * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Acurite-606TX\",\n            \"id\",               \"\",             DATA_INT, sensor_id,\n            \"channel\",          \"Channel\",      DATA_INT,   channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    battery_ok,\n            \"button\",           \"Button\",       DATA_INT,   button,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nAcurite 590TX temperature/humidity sensor.\n\nThe signal is OOK PPM with pulses of 500 us.\nThere is a sync pulse with a 3000 us gap, then 24 bits with 500 us / 1500 us gaps.\nThere is no packet gap -- the sync pulse will look like the 25th bit with 500 us gap.\nA transmission contains 14 repeats.\n\nWe'll read the packet after the sync and treat the next sync as a trailing 0 bit\n\n*/\nstatic int acurite_590tx_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row = bitbuffer_find_repeated_row(bitbuffer, 3, 25); // expected are min 3 rows\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 25)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = bitbuffer->bb[row];\n\n    if (b[4] != 0) // last byte should be zero\n        return DECODE_FAIL_SANITY;\n\n    // reject rows that are mostly zero\n    if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[3] == 0)\n        return DECODE_FAIL_SANITY;\n\n    // parity check: odd parity on bits [0 .. 10]\n    // i.e. 8 bytes and another 2 bits.\n    uint8_t parity = b[0]; // parity as byte\n    parity = (parity >> 4) ^ (parity & 0xF); // fold to nibble\n    parity = (parity >> 2) ^ (parity & 0x3); // fold to 2 bits\n    parity ^= b[1] >> 6; // add remaining bits\n    parity = (parity >> 1) ^ (parity & 0x1); // fold to 1 bit\n\n    if (!parity) {\n        decoder_log(decoder, 1, __func__, \"parity check failed\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // the sensor ID - basically a random number that gets reset whenever the battery is removed\n    int sensor_id  = b[0] & 0xFE;   // first 6 bits and it changes each time it resets or change the battery\n    int battery_ok = (b[0] & 0x01); // 1=ok, 0=low battery\n    // upper 4 bits of byte 1 are parity and channel\n    int channel = (b[1] >> 4) & 0x03;\n\n    // Upper 4 temperature bits are stored in byte 1, lower 8 bits are stored in byte 2\n    int temp_raw = (int16_t)(((b[1] & 0x0F) << 12) | (b[2] << 4));\n    temp_raw     = temp_raw >> 4; // sign-extend\n    float temp_c = (temp_raw - 500) * 0.1f; // a 50 degree offset\n\n    int humidity = -1;\n    if (temp_raw >= 0 && temp_raw <= 100) { // NOTE: no other way to differentiate humidity from temperature?\n        humidity = temp_raw;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Acurite-590TX\",\n            \"id\",               \"\",             DATA_INT,    sensor_id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    battery_ok,\n            \"humidity\",         \"Humidity\",     DATA_COND,   humidity != -1,    DATA_INT,    humidity,\n            \"temperature_C\",    \"Temperature\",  DATA_COND,   humidity == -1,    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nAcurite 00275rm Room Monitor sensors\n\n*/\nstatic int acurite_00275rm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int result = 0;\n    bitbuffer_invert(bitbuffer);\n\n    // This sensor repeats a signal three times. Combine as fallback.\n    uint8_t *b_rows[3] = {0};\n    int n_rows         = 0;\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        if (n_rows < 3 && bitbuffer->bits_per_row[row] == 88) {\n            b_rows[n_rows] = bitbuffer->bb[row];\n            n_rows++;\n        }\n    }\n\n    // Combine signal if exactly three repeats were found\n    if (n_rows == 3) {\n        bitbuffer_add_row(bitbuffer);\n        uint8_t *b = bitbuffer->bb[bitbuffer->num_rows - 1];\n        for (int i = 0; i < 11; ++i) {\n            // The majority bit count wins\n            b[i] = (b_rows[0][i] & b_rows[1][i]) |\n                    (b_rows[1][i] & b_rows[2][i]) |\n                    (b_rows[2][i] & b_rows[0][i]);\n        }\n        bitbuffer->bits_per_row[bitbuffer->num_rows - 1] = 88;\n    }\n\n    // Output the first valid row\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        if (bitbuffer->bits_per_row[row] != 88) {\n            result = DECODE_ABORT_LENGTH;\n            continue; // return DECODE_ABORT_LENGTH;\n        }\n        uint8_t *b = bitbuffer->bb[row];\n\n        // Check CRC\n        if (crc16lsb(b, 11, 0x00b2, 0x00d0) != 0) {\n            decoder_log_bitrow(decoder, 1, __func__, b, 11 * 8, \"sensor bad CRC\");\n            result = DECODE_FAIL_MIC;\n            continue; // return DECODE_FAIL_MIC;\n        }\n\n        //  Decode common fields\n        int id          = (b[0] << 16) | (b[1] << 8) | b[3];\n        int battery_low = (b[2] & 0x40) == 0;\n        int model_flag  = (b[2] & 1);\n        int temp_raw    = (b[4] << 4) | (b[5] >> 4);\n        float tempc     = (temp_raw - 1000) * 0.1f;\n        int probe       = b[5] & 3;\n        int humidity    = ((b[6] & 0x1f) << 2) | (b[7] >> 6);\n\n        //  Water probe (detects water leak)\n        int water = (b[7] & 0x0f) == 15; // valid only if (probe == 1)\n        //  Soil probe (detects temperature)\n        int ptemp_raw = ((b[7] & 0x0f) << 8) | (b[8]); // valid only if (probe == 2 || probe == 3)\n        float ptempc = (ptemp_raw - 1000) * 0.1f;\n        //  Spot probe (detects temperature and humidity)\n        int phumidity = b[9] & 0x7f; // valid only if (probe == 3)\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_COND,      model_flag, DATA_STRING, \"Acurite-00275rm\",\n                \"model\",            \"\",             DATA_COND,      !model_flag, DATA_STRING, \"Acurite-00276rm\",\n                \"subtype\",          \"Probe\",        DATA_INT,       probe,\n                \"id\",               \"\",             DATA_INT,       id,\n                \"battery_ok\",       \"Battery\",      DATA_INT,       !battery_low,\n                \"temperature_C\",    \"Celsius\",      DATA_FORMAT,    \"%.1f C\",  DATA_DOUBLE, tempc,\n                \"humidity\",         \"Humidity\",     DATA_FORMAT,    \"%u %%\", DATA_INT,      humidity,\n                \"water\",            \"\",             DATA_COND, probe == 1, DATA_INT,        water,\n                \"temperature_1_C\",  \"Celsius\",      DATA_COND, probe == 2, DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, ptempc,\n                \"temperature_1_C\",  \"Celsius\",      DATA_COND, probe == 3, DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, ptempc,\n                \"humidity_1\",       \"Humidity\",     DATA_COND, probe == 3, DATA_FORMAT, \"%u %%\",    DATA_INT,    phumidity,\n                \"mic\",              \"Integrity\",    DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n\n        return 1;\n    }\n    // Only returns the latest result, but better than nothing.\n    return result;\n}\n\nstatic char const *const acurite_rain_gauge_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"rain_mm\",\n        NULL,\n};\n\nr_device const acurite_rain_896 = {\n        .name        = \"Acurite 896 Rain Gauge\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 3500,\n        .reset_limit = 5000,\n        .decode_fn   = &acurite_rain_896_decode,\n        .priority    = 10, // Eliminate false positives by letting oregon scientific v1 protocol go earlier\n        .fields      = acurite_rain_gauge_output_fields,\n};\n\nstatic char const *const acurite_th_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"status\",\n        \"mic\",\n        NULL,\n};\n\nr_device const acurite_th = {\n        .name        = \"Acurite 609TXC Temperature and Humidity Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 3000,\n        .reset_limit = 10000,\n        .decode_fn   = &acurite_th_decode,\n        .fields      = acurite_th_output_fields,\n};\n\n/*\n * For Acurite 592 TXR Temp/Humidity, but\n * Should match Acurite 592TX, 5-n-1, etc.\n */\nstatic char const *const acurite_txr_output_fields[] = {\n        \"model\",\n        \"message_type\", // TODO: remove this\n        \"id\",\n        \"channel\",\n        \"sequence_num\",\n        \"battery_ok\",\n        \"leak_detected\",\n        \"temperature_C\",\n        \"temperature_F\",\n        \"humidity\",\n        \"wind_avg_mi_h\",\n        \"wind_avg_km_h\",\n        \"wind_dir_deg\",\n        \"rain_in\",\n        \"rain_mm\",\n        \"storm_dist\",\n        \"strike_count\",\n        \"strike_distance\",\n        \"uvi\",\n        \"lux\",\n        \"active\",\n        \"exception\",\n        \"raw_msg\",\n        \"rfi\",\n        \"mic\",\n        NULL,\n};\n\nr_device const acurite_txr = {\n        .name        = \"Acurite 592TXR temp/humidity, 592TX temp, 5n1, 3n1, Atlas weather station, 515 fridge/freezer, 6045 lightning, 899 rain, 1190/1192 leak\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 220,  // short pulse is 220 us + 392 us gap\n        .long_width  = 408,  // long pulse is 408 us + 204 us gap\n        .sync_width  = 620,  // sync pulse is 620 us + 596 us gap\n        .gap_limit   = 500,  // longest data gap is 392 us, sync gap is 596 us\n        .reset_limit = 4000, // packet gap is 2192 us\n        .decode_fn   = &acurite_txr_decode,\n        .fields      = acurite_txr_output_fields,\n};\n\n/*\n * Acurite 00986 Refrigerator / Freezer Thermometer\n *\n * Temperature only, Pulse Position\n *\n * A preamble: 2x of 216 us pulse + 276 us gap, 4x of 1600 us pulse + 1560 us gap\n * 39 bits of data: 220 us pulses with short gap of 520 us or long gap of 880 us\n * A transmission consists of two packets that run into each other.\n * There should be 40 bits of data though. But the last bit can't be detected.\n */\nstatic char const *const acurite_986_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"status\",\n        \"mic\",\n        NULL,\n};\n\nr_device const acurite_986 = {\n        .name        = \"Acurite 986 Refrigerator / Freezer Thermometer\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 520,\n        .long_width  = 880,\n        .gap_limit   = 1280,\n        .reset_limit = 4000,\n        .decode_fn   = &acurite_986_decode,\n        .fields      = acurite_986_output_fields,\n};\n\n/*\n * Acurite 00606TX Tower Sensor\n *\n * Temperature only\n *\n */\n\nstatic char const *const acurite_606_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const acurite_590_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\n// actually tests/acurite/02/gfile002.cu8, check this\n//.modulation     = OOK_PULSE_PWM,\n//.short_width    = 576,\n//.long_width     = 1076,\n//.gap_limit      = 1200,\n//.reset_limit    = 12000,\nr_device const acurite_606 = {\n        .name        = \"Acurite 606TX / Technoline TX960 Temperature Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 7000,\n        .reset_limit = 10000,\n        .decode_fn   = &acurite_606_decode,\n        .fields      = acurite_606_output_fields,\n};\n\nstatic char const *const acurite_00275rm_output_fields[] = {\n        \"model\",\n        \"subtype\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"water\",\n        \"temperature_1_C\",\n        \"humidity_1\",\n        \"mic\",\n        NULL,\n};\n\nr_device const acurite_00275rm = {\n        .name        = \"Acurite 00275rm,00276rm Temp/Humidity with optional probe\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 232, // short pulse is 232 us\n        .long_width  = 420, // long pulse is 420 us\n        .gap_limit   = 520, // long gap is 384 us, sync gap is 592 us\n        .reset_limit = 708, // no packet gap, sync gap is 592 us\n        .sync_width  = 632, // sync pulse is 632 us\n        .decode_fn   = &acurite_00275rm_decode,\n        .fields      = acurite_00275rm_output_fields,\n};\n\nr_device const acurite_590tx = {\n        .name        = \"Acurite 590TX Temperature with optional Humidity\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 500,  // short gap is 500 us\n        .long_width  = 1500, // long gap is 1500 us\n        .gap_limit   = 2000, // (preceeding) sync gap is 3000 us\n        .reset_limit = 3500, // no packet gap, gap before sync is 500 us\n        .decode_fn   = &acurite_590tx_decode,\n        .fields      = acurite_590_output_fields,\n};\n"
  },
  {
    "path": "src/devices/acurite_01185m.c",
    "content": "/** @file\n    Decoder for Acurite Grill/Meat Thermometer 01185M.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n    Based on work by Joe \"exeljb\"\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for Acurite Grill/Meat Thermometer 01185M.\n\nModulation:\n\n- 56 bit PWM data\n- short is 840 us pulse, 2028 us gap\n- long is 2070 us pulse, 800 us gap,\n- sync is 6600 us pulse, 4080 gap,\n- there is no packet gap and 8 repeats\n- data is inverted (short=0, long=1) and byte-reflected\n\nS.a. #1824\n\nTemperature is 16 bit, degrees F, scaled x10 +900.\nThe first reading is the \"Meat\" channel and the second is for the \"Ambient\" or grill temperature.\nThe range would be around -57F to 572F with the manual stating temps higher than 700F could damage the sensor.\n\n- A value of 0x1b58 (7000 / 610F) indicates the sensor is unplugged and sending an E1 error to the displays.\n- A value of 0x00c8 (200 / -70F) indicates a sensor problem, which is noted in the manual as E2 error.\n\nThe battery status is the MSB of the second byte, 0 for good battery, 1 for low battery signal.\n\nChannel appears random. There are no switches like on other acurite devices and the manual doesn't state anything about channels either.\nThe channel value seems to be limited to 3, 6, 12 and 15.\n\nData layout:\n\n    II BC MM MM TT TT XX\n\n- I: 8 bit ID\n- B: 4 bit Battery-Low `b???`\n- C: 4 bit Random channel, values seen 3, 6, 12, 15\n- M: 16 bit Temperature 1 in F x10 +900 (Meat)\n- T: 16 bit Temperature 2 in F x10 +900 (Ambient/Grill)\n- X: 8 bit Checksum, add with carry\n\n*/\n\nstatic int acurite_01185m_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int result = 0;\n    bitbuffer_invert(bitbuffer);\n\n    // Output the first valid row\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        if (bitbuffer->bits_per_row[row] != 56) {\n            result = DECODE_ABORT_LENGTH;\n            continue; // return DECODE_ABORT_LENGTH;\n        }\n\n        uint8_t *b = bitbuffer->bb[row];\n        reflect_bytes(b, 7);\n        decoder_log_bitrow(decoder, 2, __func__, b, 7 * 8, \"\");\n\n        // Verify checksum, add with carry\n        int sum = add_bytes(b, 6);\n        if ((sum & 0xff) != b[6]) {\n            decoder_log_bitrow(decoder, 1, __func__, b, 7 * 8, \"bad checksum\");\n            result = DECODE_FAIL_MIC;\n            continue; // return DECODE_FAIL_MIC;\n        }\n        /* A sanity check to detect some false positives. The following in\n           particular checks for a row of 56 \"0\"s, which would be unreasonable\n           temperatures, channel and id of 0, an 'ok' battery, which all\n           happens to result in a '0' checksum as well.\n        */\n        if (sum == 0) {\n            return DECODE_FAIL_SANITY;\n        }\n\n        // Decode fields\n        int id        = (b[0]);\n        int batt_low  = (b[1] >> 7);\n        int channel   = (b[1] & 0x0f);\n        int temp1_raw = (b[2] << 8) | b[3];\n        int temp2_raw = (b[4] << 8) | b[5];\n        int temp1_ok  = temp1_raw > 200 && temp1_raw < 7000;\n        int temp2_ok  = temp2_raw > 200 && temp2_raw < 7000;\n        float temp1_f = (temp1_raw - 900) * 0.1f;\n        float temp2_f = (temp2_raw - 900) * 0.1f;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING,    \"Acurite-01185M\",\n                \"id\",               \"\",             DATA_INT,       id,\n                \"channel\",          \"\",             DATA_INT,       channel,\n                \"battery_ok\",       \"Battery\",      DATA_INT,       !batt_low,\n                \"temperature_1_F\",  \"Meat\",         DATA_COND, temp1_ok, DATA_FORMAT, \"%.1f F\",   DATA_DOUBLE, temp1_f,\n                \"temperature_2_F\",  \"Ambient\",      DATA_COND, temp2_ok, DATA_FORMAT, \"%.1f F\",   DATA_DOUBLE, temp2_f,\n                \"mic\",              \"Integrity\",    DATA_STRING,    \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    // Only returns the latest result, but better than nothing.\n    return result;\n}\n\nstatic char const *const acurite_01185m_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_1_F\",\n        \"temperature_2_F\",\n        \"mic\",\n        NULL,\n};\n\nr_device const acurite_01185m = {\n        .name        = \"Acurite Grill/Meat Thermometer 01185M\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 840,  // short pulse is 840 us\n        .long_width  = 2070, // long pulse is 2070 us\n        .sync_width  = 6600, // sync pulse is 6600 us\n        .gap_limit   = 3000, // long gap is 2028 us, sync gap is 4080 us\n        .reset_limit = 6000, // no packet gap, sync gap is 4080 us\n        .decode_fn   = &acurite_01185m_decode,\n        .fields      = acurite_01185m_output_fields,\n};\n"
  },
  {
    "path": "src/devices/akhan_100F14.c",
    "content": "/** @file\n    Akhan remote keyless entry system.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nAkhan remote keyless entry system.\n\nThis RKE system uses a HS1527 OTP encoder (http://sc-tech.cn/en/hs1527.pdf)\nEach message consists of a preamble, 20 bit id and 4 data bits.\n\n(code based on chuango.c and generic_remote.c)\n\nNote: simple 24 bit fixed ID protocol (x1527 style) and should be handled by the flex decoder.\n*/\n\n#include \"decoder.h\"\n\nstatic int akhan_rke_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int id;\n    int cmd;\n    char const *cmd_str;\n\n    if (bitbuffer->bits_per_row[0] != 25)\n        return DECODE_ABORT_LENGTH;\n    b = bitbuffer->bb[0];\n\n    // invert bits, short pulse is 0, long pulse is 1\n    b[0] = ~b[0];\n    b[1] = ~b[1];\n    b[2] = ~b[2];\n\n    id  = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4);\n    cmd = b[2] & 0x0F;\n    switch (cmd) {\n    case 0x1: cmd_str = \"0x1 (Lock)\"; break;\n    case 0x2: cmd_str = \"0x2 (Unlock)\"; break;\n    case 0x4: cmd_str = \"0x4 (Mute)\"; break;\n    case 0x8: cmd_str = \"0x8 (Alarm)\"; break;\n    default: cmd_str = NULL; break;\n    }\n\n    if (!cmd_str)\n        return DECODE_FAIL_SANITY;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",    \"\",             DATA_STRING, \"Akhan-100F14\",\n            \"id\",       \"ID (20bit)\",   DATA_FORMAT, \"0x%x\", DATA_INT, id,\n            \"data\",     \"Data (4bit)\",  DATA_STRING, cmd_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"data\",\n        NULL,\n};\n\nr_device const akhan_100F14 = {\n        .name        = \"Akhan 100F14 remote keyless entry\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 316,\n        .long_width  = 1020,\n        .reset_limit = 1800,\n        .sync_width  = 0,\n        .tolerance   = 80, // us\n        .decode_fn   = &akhan_rke_callback,\n        .fields      = output_fields,\n        .disabled    = 1, // false positives with generic EV1527 devices\n};\n"
  },
  {
    "path": "src/devices/alecto.c",
    "content": "/** @file\n    AlectoV1 Weather Sensor protocol.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/** @fn int alectov1_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nAlectoV1 Weather Sensor decoder.\nDocumentation also at http://www.tfd.hu/tfdhu/files/wsprotocol/auriol_protocol_v20.pdf\n\nAlso Unitec W186-F (bought from Migros).\n\nPPM with pulse width 500 us, long gap 4000 us, short gap 2000 us, sync gap 9000 us.\n\nSome sensors transmit 8 long pulses (1-bits) as first row.\nSome sensors transmit 3 lone pulses (sync bits) between packets.\n\nMessage Format: (9 nibbles, 36 bits):\nPlease note that bytes need to be reversed before processing!\n\nFormat for Temperature Humidity:\n\n    IIIICCII BMMP TTTT TTTT TTTT HHHHHHHH CCCC\n    RC       Type Temperature___ Humidity Checksum\n\n- I: 8 bit Random Device ID, includes 2 bit channel (X, 1, 2, 3)\n- B: 1 bit Battery status (0 normal, 1 voltage is below ~2.6 V)\n- M: 2 bit Message type, Temp/Humidity if not '11' else wind/rain sensor\n- P: 1 bit a 0 indicates regular transmission, 1 indicates requested by pushbutton\n- T: 12 bit Temperature (two's complement)\n- H: 8 bit Humidity BCD format\n- C: 4 bit Checksum\n\nFormat for Rain:\n\n    IIIIIIII BMMP 1100 RRRR RRRR RRRR RRRR CCCC\n    RC       Type      Rain                Checksum\n\n- I: 8 bit Random Device ID, includes 2 bit channel (X, 1, 2, 3)\n- B: 1 bit Battery status (0 normal, 1 voltage is below ~2.6 V)\n- M: 2 bit Message type, Temp/Humidity if not '11' else wind/rain sensor\n- P: 1 bit a 0 indicates regular transmission, 1 indicates requested by pushbutton\n- R: 16 bit Rain (bitvalue * 0.25 mm)\n- C: 4 bit Checksum\n\nFormat for Windspeed:\n\n    IIIIIIII BMMP 1000 0000 0000 WWWWWWWW CCCC\n    RC       Type                Windspd  Checksum\n\n- I: 8 bit Random Device ID, includes 2 bit channel (X, 1, 2, 3)\n- B: 1 bit Battery status (0 normal, 1 voltage is below ~2.6 V)\n- M: 2 bit Message type, Temp/Humidity if not '11' else wind/rain sensor\n- P: 1 bit a 0 indicates regular transmission, 1 indicates requested by pushbutton\n- W: 8 bit Windspeed  (bitvalue * 0.2 m/s, correction for webapp = 3600/1000 * 0.2 * 100 = 72)\n- C: 4 bit Checksum\n\n\nFormat for Winddirection & Windgust:\n\n    IIIIIIII BMMP 111D DDDD DDDD GGGGGGGG CCCC\n    RC       Type      Winddir   Windgust Checksum\n\n- I: 8 bit Random Device ID, includes 2 bit channel (X, 1, 2, 3)\n- B: 1 bit Battery status (0 normal, 1 voltage is below ~2.6 V)\n- M: 2 bit Message type, Temp/Humidity if not '11' else wind/rain sensor\n- P: 1 bit a 0 indicates regular transmission, 1 indicates requested by pushbutton\n- D: 9 bit Wind direction\n- G: 8 bit Windgust (bitvalue * 0.2 m/s, correction for webapp = 3600/1000 * 0.2 * 100 = 72)\n- C: 4 bit Checksum\n*/\n\n#include \"decoder.h\"\n\n// return 1 if the checksum passes and 0 if it fails\nstatic int alecto_checksum(uint8_t *b)\n{\n    int csum = 0;\n    for (int i = 0; i < 4; i++) {\n        uint8_t tmp = reverse8(b[i]);\n        csum += (tmp & 0xf) + ((tmp & 0xf0) >> 4);\n    }\n\n    csum = ((b[1] & 0x7f) == 0x6c) ? (csum + 0x7) : (0xf - csum);\n    csum = reverse8((csum & 0xf) << 4);\n\n    // Test the checksum\n    return (csum == (b[4] >> 4));\n}\n\nstatic uint8_t bcd_decode8(uint8_t x)\n{\n    return ((x & 0xF0) >> 4) * 10 + (x & 0x0F);\n}\n\nstatic int alectov1_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t *b = bitbuffer->bb[1];\n    int temp_raw, humidity;\n    float temp_c;\n    data_t *data;\n    unsigned bits = bitbuffer->bits_per_row[1];\n\n    if (bits != 36)\n        return DECODE_ABORT_LENGTH;\n\n    if (bb[1][0] != bb[5][0] || bb[2][0] != bb[6][0]\n            || (bb[1][4] & 0xf) != 0 || (bb[5][4] & 0xf) != 0\n            || bb[5][0] == 0 || bb[5][1] == 0)\n        return DECODE_ABORT_EARLY;\n\n    if (!alecto_checksum(bb[1]) || !alecto_checksum(bb[5])) {\n        decoder_log(decoder, 1, __func__, \"AlectoV1 Checksum/Parity error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int battery_low = (b[1] & 0x80) >> 7;\n    int msg_type    = (b[1] & 0x60) >> 5;\n    //int button      = (b[1] & 0x10) >> 4;\n    int msg_rain    = (b[1] & 0x0f) == 0x0c;\n    //int msg_wind    = (b[1] & 0x0f) == 0x08 && b[2] == 0;\n    //int msg_gust    = (b[1] & 0x0e) == 0x0e;\n    int channel     = (b[0] & 0xc) >> 2;\n    int sensor_id   = reverse8(b[0]);\n\n    //decoder_logf(decoder, 0, __func__, \"AlectoV1 type : %d rain : %d wind : %d gust : %d\", msg_type, msg_rain, msg_wind, msg_gust);\n\n    if (msg_type == 0x3 && !msg_rain) {\n        // Wind sensor\n        int skip = -1;\n        // Untested code written according to the specification, may not decode correctly\n        if ((b[1] & 0xe) == 0x8 && b[2] == 0) {\n            skip = 0;\n        }\n        else if ((b[1] & 0xe) == 0xe) {\n            skip = 4;\n        } // According to supplied data!\n        if (skip >= 0) {\n            double speed  = reverse8(bb[1 + skip][3]);\n            double gust   = reverse8(bb[5 + skip][3]);\n            int direction = (reverse8(bb[5 + skip][2]) << 1) | (bb[5 + skip][1] & 0x1);\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"AlectoV1-Wind\",\n                    \"id\",               \"House Code\",       DATA_INT,    sensor_id,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n                    \"wind_avg_m_s\",     \"Wind speed\",       DATA_FORMAT, \"%.2f m/s\", DATA_DOUBLE, speed * 0.2F,\n                    \"wind_max_m_s\",     \"Wind gust\",        DATA_FORMAT, \"%.2f m/s\", DATA_DOUBLE, gust * 0.2F,\n                    \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT,    direction,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n    }\n\n    else if (msg_type == 0x3 && msg_rain) {\n        // Rain sensor\n        double rain_mm = ((reverse8(b[3]) << 8) | reverse8(b[2])) * 0.25F;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"AlectoV1-Rain\",\n                \"id\",           \"House Code\",   DATA_INT,    sensor_id,\n                \"channel\",      \"Channel\",      DATA_INT,    channel,\n                \"battery_ok\",   \"Battery\",      DATA_INT,    !battery_low,\n                \"rain_mm\",      \"Total Rain\",   DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, rain_mm,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    else if (msg_type != 0x3\n            && bb[2][0] == bb[3][0] && bb[3][0] == bb[4][0]\n            && bb[4][0] == bb[5][0] && bb[5][0] == bb[6][0]\n            && (bb[3][4] & 0xf) == 0 && (bb[5][4] & 0xf) == 0) {\n        //static char const *const temp_states[4] = {\"stable\", \"increasing\", \"decreasing\", \"invalid\"};\n        temp_raw = (int16_t)((reverse8(b[1]) & 0xf0) | (reverse8(b[2]) << 8)); // sign-extend\n        temp_c   = (temp_raw >> 4) * 0.1f;\n        humidity = bcd_decode8(reverse8(b[3]));\n        if (humidity > 100)\n            return DECODE_FAIL_SANITY; // detect false positive, prologue is also 36bits and sometimes detected as alecto\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",            DATA_STRING, \"AlectoV1-Temperature\",\n                \"id\",            \"House Code\",  DATA_INT,    sensor_id,\n                \"channel\",       \"Channel\",     DATA_INT,    channel,\n                \"battery_ok\",    \"Battery\",     DATA_INT,    !battery_low,\n                \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",      \"Humidity\",    DATA_FORMAT, \"%u %%\",   DATA_INT, humidity,\n                \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return DECODE_FAIL_SANITY;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"rain_mm\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"wind_dir_deg\",\n        \"mic\",\n        NULL,\n};\n\nr_device const alectov1 = {\n        .name        = \"AlectoV1 Weather Sensor (Alecto WS3500 WS4500 Ventus W155/W044 Oregon)\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 7000,\n        .reset_limit = 10000,\n        .decode_fn   = &alectov1_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ambient_weather.c",
    "content": "/** @file\n    Ambient Weather F007TH Thermo-Hygrometer.\n\n    contributed by David Ediger\n    discovered by Ron C. Lewis\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nDecode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH.\n\nDevices supported:\n\n- Ambient Weather F007TH Thermo-Hygrometer.\n- Ambient Weather F012TH Indoor/Display Thermo-Hygrometer.\n- TFA senders 30.3208.02 from the TFA \"Klima-Monitor\" 30.3054,\n- SwitchDoc Labs F016TH.\n- Suomen Lämpömittari 7411\n\nThis decoder handles the 433mhz/868mhz thermo-hygrometers.\nThe 915mhz (WH*) family of devices use different modulation/encoding.\n\n\nByte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5\nxxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM\n\n- x: Unknown 0x04 on F007TH/F012TH\n- M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH\n- I: ID byte (8 bits), volatie, changes at power up,\n- B: Battery Low\n- C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting\n- T: Temperature 12 bits - Fahrenheit * 10 + 400\n- H: Humidity (8 bits)\n- M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64\n\n*/\n\n#include \"decoder.h\"\n\nstatic int ambient_weather_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    uint8_t b[6];\n\n    bitbuffer_extract_bytes(bitbuffer, row, bitpos, b, 6 * 8);\n\n    uint8_t expected   = b[5];\n    uint8_t calculated = lfsr_digest8(b, 5, 0x98, 0x3e) ^ 0x64;\n\n    if (expected != calculated) {\n        decoder_logf_bitrow(decoder, 1, __func__, b, 48, \"Checksum error, expected: %02x calculated: %02x\", expected, calculated);\n        return DECODE_FAIL_MIC;\n    }\n\n    // int model_number = b[0] & 0x0F; // fixed 0x05, at least for \"SwitchDoc Labs F016TH\"\n    int deviceID    = b[1];\n    int battery_low = (b[2] & 0x80) != 0; // if not zero, battery is low\n    int channel     = ((b[2] & 0x70) >> 4) + 1;\n    int temp_raw    = ((b[2] & 0x0f) << 8) | b[3];\n    float temp_f    = (temp_raw - 400) * 0.1f;\n    int humidity    = b[4];\n\n    /*\n    Sanity checks to reduce false positives and other bad data\n\n    Packets with Bad data often pass the MIC check.\n\n    - humidity > 100 (such as 255) and\n    - temperatures >= 344 F (0xf00 to 0xfff values)\n\n    Specs in the F007TH and F012TH manuals state the range is:\n\n    - Temperature: -40 to 140 F (7411 model up 150 C / 302 F)\n    - Humidity: 10 to 99%\n\n    @todo - sanity check b[0] \"model number\"\n\n    - 0x45 - F007TH and F012TH\n    - 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5)\n    - ? - TFA 30.3208.02\n\n    */\n\n    if (humidity > 100) {\n        decoder_logf_bitrow(decoder, 1, __func__, b, 48, \"Humidity failed sanity check 0x%02x\", humidity);\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (temp_f < -40.0f || temp_f >= 344.0f) {\n        decoder_logf_bitrow(decoder, 1, __func__, b, 48, \"Temperature failed sanity check 0x%03x\", temp_raw);\n        return DECODE_FAIL_SANITY;\n    }\n\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",          \"\",             DATA_STRING, \"Ambientweather-F007TH\",\n            \"id\",             \"House Code\",   DATA_INT,    deviceID,\n            \"channel\",        \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",     \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_F\",  \"Temperature\",  DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",       \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",            \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nAmbient Weather F007TH Thermo-Hygrometer.\n@sa ambient_weather_decode()\n*/\nstatic int ambient_weather_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // three repeats without gap\n    // full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146)\n    // and on decoding also 0xffd45\n    uint8_t const preamble_pattern[2]  = {0x01, 0x45}; // 12 bits\n    uint8_t const preamble_inverted[2] = {0xfd, 0x45}; // 12 bits\n\n    int row;\n    unsigned bitpos;\n    int ret = 0;\n\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        bitpos = 0;\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos, preamble_pattern, 12)) + 8 + 6 * 8 <=\n                bitbuffer->bits_per_row[row]) {\n            ret = ambient_weather_decode(decoder, bitbuffer, row, bitpos + 8);\n            if (ret > 0)\n                return ret; // for now, break after first successful message\n            bitpos += 16;\n        }\n        bitpos = 0;\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos, preamble_inverted, 12)) + 8 + 6 * 8 <=\n                bitbuffer->bits_per_row[row]) {\n            ret = ambient_weather_decode(decoder, bitbuffer, row, bitpos + 8);\n            if (ret > 0)\n                return ret; // for now, break after first successful message\n            bitpos += 15;\n        }\n    }\n\n    // TODO: returns 0 when no data is found in the messages.\n    // What would be a better return value? Maybe DECODE_ABORT_SANITY?\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ambient_weather = {\n        .name        = \"Ambient Weather F007TH, TFA 30.3208.02, SwitchDocLabs F016TH temperature sensor\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 500,\n        .long_width  = 0, // not used\n        .reset_limit = 2400,\n        .decode_fn   = &ambient_weather_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ambientweather_tx8300.c",
    "content": "/** @file\n    Ambient Weather TX-8300 (also sold as TFA 30.3211.02).\n\n    Copyright (C) 2018 ionum-projekte and Roger\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/** @fn int ambientweather_tx8300_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nAmbient Weather TX-8300 (also sold as TFA 30.3211.02).\n\n1970us pulse with variable gap (third pulse 3920 us).\nAbove 79% humidity, gap after third pulse is 5848 us.\n\n- Bit 1 : 1970us pulse with 3888 us gap\n- Bit 0 : 1970us pulse with 1936 us gap\n\n74 bit (2 bit preamble and 72 bit data => 9 bytes => 18 nibbles)\nThe preamble seems to be a repeat counter (00, and 01 seen),\nthe first 4 bytes are data,\nthe second 4 bytes the same data inverted,\nthe last byte is a checksum.\n\nPreamble format (2 bits):\n\n    [1 bit (0)] [1 bit rolling count]\n\nPayload format (32 bits):\n\n    HHHHhhhh ??CCNIII IIIITTTT ttttuuuu\n\n- H = First BCD digit humidity (the MSB might be distorted by the demod)\n- h = Second BCD digit humidity, invalid humidity seems to be 0x0e\n- ? = Likely battery flag, 2 bits\n- C = Channel, 2 bits\n- N = Negative temperature sign bit\n- I = ID, 7-bit\n- T = First BCD digit temperature\n- t = Second BCD digit temperature\n- u = Third BCD digit temperature\n\nThe Checksum seems to covers the 4 data bytes and is something like Fletcher-8.\n*/\n\n#include \"decoder.h\"\n\nstatic uint8_t tx8300_chk(uint8_t *b)\n{\n    uint16_t x = 0;\n    uint16_t y = 0;\n    for (int i = 0; i < 4; ++i) {\n        x += (b[i] & 0xF) + ((b[i] & 0xF0) >> 4);\n        y += (b[i] & 0x5) + ((b[i] & 0x50) >> 4);\n    }\n    uint8_t c0 = (~(x & 0xF)) & 0xF;\n    uint8_t c1 = (~(y & 0xF)) & 0xF;\n    return c0 << 4 | c1;\n}\n\nstatic int ambientweather_tx8300_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t b[9] = {0};\n\n    /* length check */\n    if (74 != bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 2, __func__, \"AmbientWeather-TX8300: wrong size (%u bits)\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    /* dropping 2 bit preamble */\n    bitbuffer_extract_bytes(bitbuffer, 0, 2, b, 72);\n\n    // flip inverted bytes\n    b[4] ^= 0xff;\n    b[5] ^= 0xff;\n    b[6] ^= 0xff;\n    b[7] ^= 0xff;\n\n    // restore first MSB\n    b[0] = (b[0] & 0x7f) | (b[4] & 0x80);\n\n    decoder_logf(decoder, 2, __func__, \"H: %02x, F:%02x\", b[0], b[1] & 0xc0);\n\n    // check bit-wise parity\n    if (b[0] != b[4] || b[1] != b[5] || b[2] != b[6] || b[3] != b[7])\n        return DECODE_FAIL_MIC;\n\n    // verify checksum\n    if (tx8300_chk(b) ^ b[8])\n        return DECODE_FAIL_MIC;\n\n    float temp      = (b[2] & 0x0f) * 10 + ((b[3] & 0xf0) >> 4) + (b[3] & 0x0f) * 0.1F;\n    int channel     = (b[1] & 0x30) >> 4;\n    int battery_low = (b[1] & 0xc0) >> 6; // bit mapping unknown\n    int minus       = (b[1] & 0x08) >> 3;\n    int humidity    = ((b[0] & 0xf0) >> 4) * 10 + (b[0] & 0x0f);\n    int sensor_id   = ((b[1] & 0x07) << 4) | ((b[2] & 0xf0) >> 4);\n    float temp_c    = (minus == 1 ? temp * -1 : temp);\n    if (((b[0] & 0xf0) >> 4) > 9 || (b[0] & 0x0f) > 9) // invalid humidity\n        humidity = -1;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"AmbientWeather-TX8300\",\n            \"id\",            \"\",            DATA_INT,    sensor_id,\n            \"channel\",       \"\",            DATA_INT,    channel,\n            \"battery\",       \"Battery\",     DATA_INT,    battery_low, // mapping unknown\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",      \"Humidity\",    DATA_COND,   humidity >= 0, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",           \"MIC\",         DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ambientweather_tx8300 = {\n        .name        = \"Ambient Weather TX-8300 Temperature/Humidity Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 6500,\n        .reset_limit = 8000,\n        .decode_fn   = &ambientweather_tx8300_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ambientweather_wh31e.c",
    "content": "/** @file\n    Ambient Weather WH31E, EcoWitt WH40 protocol.\n\n    Copyright (C) 2018 Christian W. Zuckschwerdt <zany@triq.net>\n    based on protocol analysis by James Cuff and Michele Clamp,\n    EcoWitt WH40 analysis by Helmut Bachmann,\n    Ecowitt WS68 analysis by Tolip Wen improved by Bruno Octau,\n    EcoWitt WH31B analysis by Michael Turk.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nAmbient Weather WH31E protocol.\n915 MHz FSK PCM Thermo-Hygrometer Sensor (bundled as Ambient Weather WS-3000-X5).\n\nNote that Ambient Weather and EcoWitt are likely rebranded Fine Offset products.\n\n56 us bit length with a warm-up of 1336 us mark(pulse), 1996 us space(gap),\na preamble of 48 bit flips (0xaaaaaaaaaaaa) and a 0x2dd4 sync-word.\n\nData layout:\n\n    YY II CT TT HH XX AA ?? ?? ?? ??\n\n- Y is a fixed Type Code of 0x30\n- I is a device ID\n- C is 6 bits Channel number (3 bits) and flags: \"1CCC0B\"\n- T is 10 bits Temperature in C, scaled by 10, offset 400\n- H is Humidity\n- X is CRC-8, poly 0x31, init 0x00\n- A is SUM-8\n\nData decoding:\n\n    TYPE:8h ID:8h ?1b CH:3b ?1b BATT:1b TEMP:10d HUM:8d CRC:8h SUM:8h ?8h8h8h8h\n\nExample packets:\n\n    {177} aa aa aa aa aa aa  2d d4  30 c3 8 20a 5e  df bc   07 56 a7 ae  00 00 00 00\n    {178} aa aa aa aa aa aa  2d d4  30 44 9 21a 39  5a b3   07 45 04 5f  00 00 00 00\n\nSome payloads:\n\n    30 c3 81 d5 5c 2a cf 08 35 44 2c\n    30 35 c2 2f 3c 0f a1 07 52 29 9f\n    30 35 c2 2e 3c fb 8c 07 52 29 9f\n    30 c9 a2 1e 40 0c 05 07 34 c6 b1\n    30 2b b2 14 3d 94 f2 08 53 78 e6\n    30 c9 a2 1f 40 f8 f2 07 34 c6 b1\n    30 44 92 13 3e 0e 65 07 45 04 5f\n    30 44 92 15 3d 07 5f 07 45 04 5f\n    30 c3 81 d6 5b 90 35 08 35 44 2c\n\n\nAmbient Weather WH31E Radio Controlled Clock (RCC) packet WWVB\n\nThese packets are sent with this schedule, according to the manual:\n    After the remote sensor is powered up, the sensor will transmit weather\n    data for 30 seconds, and then the sensor will begin radio controlled clock\n    (RCC) reception. During the RCC time reception period (maximum 5 minutes),\n    no weather data will be transmitted to avoid interference.\n\n    If the signal reception is not successful within 3 minute, the signal\n    search will be cancelled and will automatically resume every two hours\n    until the signal is successfully captured. The regular RF link will resume\n    once RCC reception routine is finished.\n\n / time message type 0x52\n |  / station id\n |  |  / unknown\n |  |  |  / 20xx year in BCD\n |  |  |  |  / month in BCD\n |  |  |  |  |  / day in BCD\n |  |  |  |  |  |  / hour in BCD\n |  |  |  |  |  |  |  / minute in BCD\n |  |  |  |  |  |  |  |  / second in BCD\n |  |  |  |  |  |  |  |  |  / CRC-8, poly 0x31, init 0x00\n |  |  |  |  |  |  |  |  |  |  / SUM-8\nYY II UU YY MM DD HH mm SS CC XX\n 0  1  2  3  4  5  6  7  8  9 10 - byte index\n\nUU has kept the value 0x4a.  Data it may represent that is broadcast from WWVB:\n- Daylight savings upcoming/active (it WAS active during the captures) (2 bits)\n- Leap year (1 bit)\n- Leap second at the end of this month (1 bit)\n- DUT1, difference between UTC and UT1 (4-7 bits depending on re-encoding)\nThe upper bits of the upper nibbles M, D, H, m, S may possibly be used to\nencode this information, given their maximum valid digits of 1, 3, 2, 6, 6,\nrespectively.\n\nPackets observed\nReception time               Payload\n2020-10-20T02:06:55.809Z  52 27 4a 20 10 20 02 06 55 05 75\n2020-10-20T02:08:02.793Z  52 27 4a 20 10 20 02 08 02 81 a0\n2020-10-20T07:35:04.290Z  52 75 4a 20 10 20 07 35 03 8a 2a\n2020-10-20T07:35:52.394Z  52 58 4a 20 10 20 07 35 51 48 19\n2020-10-20T07:36:06.287Z  52 75 4a 20 10 20 07 36 05 01 a4\n2020-10-20T07:36:55.305Z  52 58 4a 20 10 20 07 36 54 90 65\n2020-10-20T07:37:08.284Z  52 75 4a 20 10 20 07 37 07 97 3d\n2020-10-20T07:37:58.355Z  52 58 4a 20 10 20 07 37 57 37 10\n2020-10-20T07:38:10.280Z  52 75 4a 20 10 20 07 38 09 11 ba\n2020-10-20T07:39:01.398Z  52 58 4a 20 10 20 07 39 00 b3 37\n2020-10-20T08:05:50.830Z  52 a0 4a 20 10 20 08 05 50 0f f8\n2020-10-20T08:06:58.862Z  52 a0 4a 20 10 20 08 06 58 9b 8d\n2020-10-20T08:08:06.883Z  52 a0 4a 20 10 20 08 08 06 97 39\n2020-10-20T08:09:14.785Z  52 a0 4a 20 10 20 08 09 14 42 f3\n\n\nEcoWitt WH40 protocol.\nSeems to be the same as Fine Offset WH5360 or Ecowitt WH5360B.\n\nData layout:\n\n    YY 00 IIII FV RRRR XX AA 00 02 ?? 00 00\n\n- Y is a fixed Type Code of 0x40\n- I is a device ID\n- F is perhaps flags, but only seen fixed 0x10 so far\n- V is battery voltage, ( FV & 0x1f ) * 0.1f\n- R is the rain bucket tip count, 0.1mm increments\n- X is CRC-8, poly 0x31, init 0x00\n- A is SUM-8\n\nSome payloads:\n\n    4000 cd6f 10 0000  64 f0 ; 00 027b 0000\n    4000 cd6f 10 0001  55 e2 ; 00 02f6 0000\n    4000 cd6f 10 0002  06 94 ; 00 02ed 0000\n    4000 cd6f 10 0003  37 c6 ; 00 02db 0000\n    4000 cd6f 10 0004  a0 30 ; 00 02b7 0000\n    4000 cd6f 10 0005  91 22 ; 00 02de 0000\n    4000 cd6f 10 0006  c2 54 ; 00 02bd 0000\n    4000 cd6f 10 0007  f3 86 ; 00 027b 0000\n    4000 cd6f 10 0008  dd 71 ; 00 02f6 0000\n    4000 cd6f 10 0009  ec 81 ; 00 02ed 0000\n    4000 cd6f 10 000a  bf 55 ; 00 02db 0000\n\nSamples with 1.2V battery (last 2 samples contain 1 manual bucket tip)\n\n    4000 cd6f 10 0000  64 f0 ; 00 01de 00b0\n    4000 cd6f 10 0000  64 f0 ; 00 02de 00b0\n    4000 cd6f 10 0000  64 f0 ; 00 02bd 0000\n    4000 cd6f 10 0001  55 e2 ; 00 027b 0000\n    4000 cd6f 10 0001  55 e2 ; 00 027b 0000\n\nSamples with 0.9V battery (last 3 samples contain 1 manual bucket tip)\n\n    4000 cd6f 10 0000  64 f0 ; 00 16de 0000\n    4000 cd6f 10 0000  64 f0 ; 00 02de 0000\n    4000 cd6f 10 0001  55 e2 ; 00 02bd 0000\n    4000 cd6f 10 0001  55 e2 ; 00 027b 0000\n    4000 cd6f 10 0001  55 e2 ; 00 027b 0000\n\nEcowitt WS68 Anemometer protocol with LUX and UVI.\n\nUnits confirmed from issue #2786 , LUX and UVI decoding as well\nWind unit and decoding from issue #2867\n\nData layout:\n\n    TYPE:8h ?8h ID:16h LUX:16h BATT:8d ?1b WGUST_MSB:1b WDIR_MSB:1b WSPEED_MSB:1b ?4h 8h8h WSPEED_LSB:8d WDIR_LSB:8h WGUST_LSB:8d UVI:8h CRC:8h SUM:8h ?8h4h\n\nSome payloads:\n    TT ?? IIII LLLL BB WH f ffff WSL WDL WGL UV CC SS ???\n    68 00 00c5 0000 4b  0 f ffff  00  5a  00 00 d0 af 104\n    68 00 00c5 0000 4b  0 f ffff  00  b4  00 00 79 b2 102\n    68 00 00c5 0000 4b  0 f ffff  7e  e0  94 00 75 ec 102\n    68 00 00c5 0000 4b  2 f ffff  00  0e  00 00 80 33 208\n    68 00 00c5 000f 4b  0 f ffff  00  2e  00 00 d3 95 108\n    68 00 00c5 0107 4b  0 f ffff  00  2e  00 02 a6 63 100\n\n*/\n\n#include \"decoder.h\"\n\nstatic int ambientweather_whx_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int events = 0;\n    uint8_t b[18]; // actually only 6/9/17.5 bytes, no indication what the last 5 might be\n    int row;\n    int msg_type;\n    uint8_t const wh31e_type_code = 0x30; // 48\n    uint8_t const wh31b_type_code = 0x37; // 55\n\n    uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // (partial) preamble and sync word\n\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        // Validate message and reject it as fast as possible : check for preamble\n        unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, 24);\n        // no preamble detected, move to the next row\n        if (start_pos == bitbuffer->bits_per_row[row])\n            continue; // DECODE_ABORT_EARLY\n        decoder_logf(decoder, 1, __func__, \"WH31E/WH31B/WH40 detected, buffer is %u bits length\", bitbuffer->bits_per_row[row]);\n\n        // remove preamble, keep whole payload\n        bitbuffer_extract_bytes(bitbuffer, row, start_pos + 24, b, 18 * 8);\n        msg_type = b[0];\n\n        if (msg_type == wh31e_type_code || msg_type == wh31b_type_code) {\n            uint8_t c_crc = crc8(b, 6, 0x31, 0x00);\n            if (c_crc) {\n                decoder_logf(decoder, 1, __func__, \"WH31E/WH31B (%d) bad CRC\", msg_type);\n                continue; // DECODE_FAIL_MIC\n            }\n            uint8_t c_sum = add_bytes(b, 6) - b[6];\n            if (c_sum) {\n                decoder_logf(decoder, 1, __func__, \"WH31E/WH31B (%d) bad SUM\", msg_type);\n                continue; // DECODE_FAIL_MIC\n            }\n\n            int id       = b[1];\n            int batt_low = ((b[2] & 0x04) >> 2);\n            int channel  = ((b[2] & 0x70) >> 4) + 1;\n            int temp_raw = ((b[2] & 0x03) << 8) | (b[3]);\n            float temp_c = (temp_raw - 400) * 0.1f;\n            int humidity = b[4];\n            char extra[11];\n            snprintf(extra, sizeof(extra), \"%02x%02x%02x%02x%02x\", b[6], b[7], b[8], b[9], b[10]);\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",             DATA_COND, msg_type == 0x30, DATA_STRING, \"AmbientWeather-WH31E\",\n                    \"model\",            \"\",             DATA_COND, msg_type == 0x37, DATA_STRING, \"AmbientWeather-WH31B\",\n                    \"id\",               \"\",             DATA_INT,    id,\n                    \"channel\",          \"Channel\",      DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery\",      DATA_INT,    !batt_low,\n                    \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                    \"data\",             \"Extra Data\",   DATA_STRING, extra,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            events++;\n        }\n\n        else if (msg_type == 0x52) {\n            // WH31E (others?) RCC\n            uint8_t c_crc = crc8(b, 10, 0x31, 0x00);\n            if (c_crc) {\n                decoder_log(decoder, 1, __func__, \"WH31E RCC bad CRC\");\n                continue; // DECODE_FAIL_MIC\n            }\n            uint8_t c_sum = add_bytes(b, 10) - b[10];\n            if (c_sum) {\n                decoder_log(decoder, 1, __func__, \"WH31E RCC bad SUM\");\n                continue; // DECODE_FAIL_MIC\n            }\n\n            int id      = b[1];\n            int unknown = b[2];\n            int year    = ((b[3] & 0xF0) >> 4) * 10 + (b[3] & 0x0F) + 2000;\n            int month   = ((b[4] & 0x10) >> 4) * 10 + (b[4] & 0x0F);\n            int day     = ((b[5] & 0x30) >> 4) * 10 + (b[5] & 0x0F);\n            int hours   = ((b[6] & 0x30) >> 4) * 10 + (b[6] & 0x0F);\n            int minutes = ((b[7] & 0x70) >> 4) * 10 + (b[7] & 0x0F);\n            int seconds = ((b[8] & 0x70) >> 4) * 10 + (b[8] & 0x0F);\n\n            char clock_str[23];\n            snprintf(clock_str, sizeof(clock_str), \"%04d-%02d-%02dT%02d:%02d:%02dZ\",\n                    year, month, day, hours, minutes, seconds);\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",        \"\",             DATA_STRING,    \"AmbientWeather-WH31E\",\n                    \"id\",           \"Station ID\",   DATA_INT,       id,\n                    \"data\",         \"Unknown\",      DATA_INT,       unknown,\n                    \"radio_clock\",  \"Radio Clock\",  DATA_STRING,    clock_str,\n                    \"mic\",          \"Integrity\",    DATA_STRING,    \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            events++;\n        }\n\n        else if (msg_type == 0x40) {\n            // WH40\n            uint8_t c_crc = crc8(b, 8, 0x31, 0x00);\n            if (c_crc) {\n                decoder_log(decoder, 1, __func__, \"WH40 bad CRC\");\n                continue; // DECODE_FAIL_MIC\n            }\n            uint8_t c_sum = add_bytes(b, 8) - b[8];\n            if (c_sum) {\n                decoder_log(decoder, 1, __func__, \"WH40 bad SUM\");\n                continue; // DECODE_FAIL_MIC\n            }\n\n            int id         = (b[2] << 8) | b[3];\n            int battery_v  = (b[4] & 0x1f);\n            int battery_lvl = battery_v <= 9 ? 0 : 100 * (battery_v - 9) / 6; // 0.9V-1.5V is 0-100\n            int rain_raw   = (b[5] << 8) | b[6];\n            char extra[11];\n            snprintf(extra, sizeof(extra), \"%02x%02x%02x%02x%02x\", b[9], b[10], b[11], b[12], b[13]);\n\n            if (battery_lvl > 100) {\n                battery_lvl = 100;\n            }\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                DATA_STRING, \"EcoWitt-WH40\",\n                    \"id\",               \"\",                DATA_INT,    id,\n                    \"battery_V\",        \"Battery Voltage\", DATA_COND, battery_v != 0, DATA_FORMAT, \"%f V\", DATA_DOUBLE, battery_v * 0.1f,\n                    \"battery_ok\",       \"Battery level\",   DATA_COND, battery_v != 0, DATA_DOUBLE, battery_lvl * 0.01f,\n                    \"rain_mm\",          \"Total Rain\",      DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_raw * 0.1,\n                    \"data\",             \"Extra Data\",      DATA_STRING, extra,\n                    \"mic\",              \"Integrity\",       DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            events++;\n        }\n\n        else if (msg_type == 0x68) {\n            // WS68\n            uint8_t c_crc = crc8(b, 15, 0x31, 0x00);\n            if (c_crc) {\n                decoder_log(decoder, 1, __func__, \"WS68 bad CRC\");\n                continue; // DECODE_FAIL_MIC\n            }\n            uint8_t c_sum = add_bytes(b, 15) - b[15];\n            if (c_sum) {\n                decoder_log(decoder, 1, __func__, \"WS68 bad SUM\");\n                continue; // DECODE_FAIL_MIC\n            }\n\n            int id      = (b[2] << 8) | b[3];\n            int lux_raw = ((b[4] << 8) | b[5]);\n            int light_lux = lux_raw * 10;\n            int batt    = b[6];\n            int batt_ok = batt > 0x20; // wild guess\n            int wspeed  = ((b[7] & 0x10) << 4) | (b[10]);\n            int wdir    = ((b[7] & 0x20) << 3) | (b[11]);\n            int wgust   = ((b[7] & 0x40) << 2) | (b[12]);\n            int uvindex = (int)b[13] * 0.1f;\n            char extra[4];\n            snprintf(extra, sizeof(extra), \"%02x%01x\", b[16], b[17] >> 4);\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",             DATA_STRING, \"EcoWitt-WS68\",\n                    \"id\",               \"\",             DATA_INT,    id,\n                    \"battery_raw\",      \"Battery Raw\",  DATA_INT,    batt,\n                    \"battery_ok\",       \"Battery OK\",   DATA_INT,    batt_ok,\n                    \"light_lux\",        \"Lux\",          DATA_FORMAT, \"%u lux\",   DATA_INT,    light_lux,\n                    \"wind_avg_m_s\",     \"Wind Speed\",   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wspeed * 0.1f,\n                    \"wind_max_m_s\",     \"Wind Gust\",    DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wgust * 0.1f,\n                    \"uvi\",              \"UV Index\",     DATA_FORMAT, \"%.0f\",     DATA_DOUBLE, (double)uvindex,\n                    \"wind_dir_deg\",     \"Wind dir\",     DATA_INT,    wdir,\n                    \"data\",             \"Extra Data\",   DATA_STRING, extra,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            events++;\n        }\n\n        else {\n            decoder_logf(decoder, 1, __func__, \"unknown message type %02x (expected 0x30/0x40/0x68)\", msg_type);\n        }\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"battery_V\",\n        \"temperature_C\",\n        \"humidity\",\n        \"rain_mm\",\n        \"uvi\",\n        \"light_lux\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"wind_dir_deg\",\n        \"data\",\n        \"radio_clock\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ambientweather_wh31e = {\n        .name        = \"Ambient Weather WH31E Thermo-Hygrometer Sensor, EcoWitt WH40 rain gauge, WS68 weather station\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 56,\n        .long_width  = 56,\n        .reset_limit = 1500,\n        .gap_limit   = 1800,\n        .decode_fn   = &ambientweather_whx_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ant_antplus.c",
    "content": "/** @file\n    ANT and ANT+ decoder.\n\n    Copyright (C) 2022 Roberto Cazzaro <https://github.com/robcazzaro>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nANT and ANT+ decoder.\n\nANT and ANT+ communication standards are defined by a division of Garmin\nhttps://www.thisisant.com/ and used widely for low power devices.\nThe ANT radio transmits for less than 150 µs per message, allowing a single\nchannel to be divided into hundreds of time slots and avoiding collisions.\nANT and ANT+ devices use a modified Shockburst protocol, in the 2.4GHz ISM band,\nwith 160kHz deviation and 1Mbps data rates, GFSK encoded. The low level\nlayer is not documented anywhere. ANT chips use an 8 byte key to generate a 2 byte\nnetwork ID using an unspecified algorithm. Valid keys are only assigned by Garmin\nand require specific licensing terms (and in some cases a payment)\nANT+ uses the basic ANT message structure but is a managed network with a specific\nnetwork key and defined device types, each sending \"data pages\" of 8 bytes with\nspecific data for the device type. Most ANT+ devices are sports focused like\nheart rate monitors, bicycle sensors, or environmental sensors.\n\nPlease note that unlike most devices in the rtl_433 repository, ANT+ devices\noperate in the ISM band between 2.4GHz and 2.5GHz. Decoding these signals\nrequires an SDR capable of operating above 2.4GHz (e.g. PlutoSDR) or the\nuse of a downconverter for rtl_sdr. The ISM band is very noisy, so it's\nrecommended to get the device very close, use only a ~30mm wire antenna and\nuse mid-low gains. Finally, since the protocol encodes at 1Mbps, sampling\nrate should be -s 4M or higher. It can work with a sampling rate as low as\n2Msps, but unreliably. To avoid excessive warnings when running with default\n250k sampling rate, the decoder is disabled by default.\n\nThe following works well with PlutoSDR:\n\nrtl_433 -d driver=plutosdr,uri=ip:192.168.2.1 -g 20 -f 2457.025M -s 4M\n\nWithout knowing the 8 byte key, existing ANT chips do not allow sniffing\nof the data packets. Hence the need for this decoder. At the moment it's\nnot possible to recover the ANT key from the 16 bit on-air network key.\n\nThis decoder only captures and displays the low-level packets, identifying the\n2 byte network key plus all other device characteristics and 8 byte ANT payload.\nIt identifies ANT+ packets using the unique network key used (0xa6c5)\nRefer to ANT+ documentation for ech specific device to parse the content of\nthe pages sent as 8 byte ANT+ payload.\n\nThe ANT protocol is using an uncommon strategy for the preamble: either 0x55 or\n0xaa, depending on the value of the first bit of the following byte (0 and 1\nrespectively). The nRF24L01, on which the ANT protocol is based, uses the same\npreamble strategy. In order to determine if the packet is using 0x55 or 0xaa,\nboth packets are extracted and the only valid one is determined by matching\nthe packet CRC with a calculated CRC value for both alternative packets.\n\nThe payload is 18 bytes long structured as follows:\n\n    PNNDDTXLPPPPPPPPCC\n\n- P: Preamble: either 0x55 or 0xAA, depending on the value of first bit of the next byte\n- N: Network key, assume LSB first (ANT+ uses 0xc5a6, most invalid keys 0x255b)\n- D: Device number, 16 bit. LSB first\n- X: Transmission type\n- L: ANT payload length including CRC\n- P: 8 byte ANT or ANT+ payload\n- C: 16 bit CRC (CRC-16/CCITT-FALSE)\n\nWeirdly, L is always 10, because at the moment the ANT payload is always 8 bytes\n\nCREDITS:\nhttps://github.com/sghctoma/antfs-poc-defcon24\nhttps://reveng.sourceforge.io/ to reverse engineer the CRC algorithm used\n*/\n\n#include \"decoder.h\"\n\nstatic int ant_antplus_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xAA};\n    uint8_t b[17]; // aligned packet data for both preambles/offsets\n    unsigned bit_offset;\n    int antplus_flag = 0;\n\n    // validate buffer: ANT messages are shorter than 150us, i.e. ~140 bits at 1Mbps\n    if (bitbuffer->bits_per_row[0] < 120 || bitbuffer->bits_per_row[0] > 200) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // find a data package and extract data buffer\n    bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // did not find a big enough package\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // ANT and ANT+ packets have either aa or 55 preamble, depending on the first bit of\n    // the following byte. i.e. 10101010 1xxxxxxx or 01010101 0xxxxxxx\n    // the best way to know which is being used, is to verify which one has a valid CRC\n    // the following code relies on the fact that 55 is aa shifted right\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8); // assume preamble is aa\n\n    // calculate CRC for both alternatives, starting with aa (used by all ANT+ devices)\n    // if the two crc bytes b[15] and b[16] are included in the crc calculation, a valid packet returns 0\n    if (crc16(b, 17, 0x1021, 0xffff) != 0) { // no, preamble is not aa\n        bitbuffer_extract_bytes(bitbuffer, 0, bit_offset + 1, b, sizeof(b) * 8); // shift one bit right for 55 preamble\n        if (crc16(b, 17, 0x1021, 0xffff) != 0) // nope, not preamble = 55 either, invalid packet, abort\n            return DECODE_FAIL_MIC;\n    }\n\n    uint16_t net_key    = (b[1] << 8) | b[0]; // undocumented, assume it's LSB first, as the device id\n    uint16_t id         = (b[3] << 8) | b[2]; // id is always LSB first\n    uint8_t device_type = b[4];\n    uint8_t tx_type     = b[5];\n    // display ANT and ANT+ payload in the same format used by ANT tools\n    char payload[8 * 3 + 1]; // payload is 8 hex pairs for ANT and ANT+\n    snprintf(payload, sizeof(payload), \"%02x %02x %02x %02x %02x %02x %02x %02x\", b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14]);\n\n    // display ANT or ANT+ depending on the network key used.\n    if (0xc5a6 == net_key)\n        antplus_flag = 1;\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",       \"\",               DATA_STRING, \"Garmin-ANT\",\n        \"network\",     \"Network\",        DATA_COND,   antplus_flag == 1,  DATA_STRING, \"ANT+\",\n        \"network\",     \"Network\",        DATA_COND,   antplus_flag == 0,  DATA_STRING, \"ANT\",\n        \"channel\",     \"Net key\",        DATA_FORMAT, \"0x%04x\", DATA_INT, net_key,\n        \"id\",          \"Device #\",       DATA_FORMAT, \"0x%04x\", DATA_INT, id,\n        \"device_type\", \"Device type\",    DATA_INT,    device_type,\n        \"tx_type\",     \"TX type\",        DATA_INT,    tx_type,\n        \"payload\",     \"Payload\",        DATA_STRING, payload,\n        \"mic\",         \"Integrity\",      DATA_STRING, \"CRC\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"network\",\n        \"channel\",\n        \"id\",\n        \"device_type\",\n        \"tx_type\",\n        \"payload\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ant_antplus = {\n        .name        = \"ANT and ANT+ devices\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 1,\n        .long_width  = 1,\n        .sync_width  = 8,\n        .gap_limit   = 500,\n        .reset_limit = 500,\n        .decode_fn   = &ant_antplus_decode,\n        .fields      = output_fields,\n        .disabled    = 1, // disabled by default, because of higher than default sampling requirements (s = 4M)\n};\n"
  },
  {
    "path": "src/devices/apator_metra_eitn30.c",
    "content": "/** @file\n    Apator Metra E-ITN 30 Heat cost allocator.\n\n    Copyright (C) 2025 Alex Carp (\\@carpalex)\n    Copyright (c) 2026 Bruno Octau (\\@ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nApator Metra E-ITN 30 Heat cost allocator.\n\nS.a issue #3012, for E-RM 30, #3452, for E-ITN 30\n\n- Both E-RM 30 (Water Meter) and E-ITN 30 (Heat Cost Allocator) are using the same approach, same protocol.\n- Only the message length differ between the 2 sensors.\n\nCoding:\n- Frames are transmitted with a preamble (0xaa 0xaa ...), followed by the 0x699a syncword.\n- 2 levels of Data coding : It is first whitened using IBM Code, discovered into #3452, but the data payload are also encrypted.\n- Each message is composed of one byte for the payload lengh, the encrypted payload and 2 byte for the CRC-16.\n- Depends on the sensor, the payload length is : 19 byte for water meter and 17 byte for heat meter.\n- CRC-16 must be checked after unwhitening and before decrypting the payload.\n- The payload is encrypted using nibble substitution of 16 values.\n\nE-ITN 30:\n\nFlex decoder:\n\n    rtl_433 -X \"n=Apator_eitn30,m=FSK_PCM,s=25,l=25,r=5000, preamble=aaaa699a\"\n\n    Sample   ee c2 5e db 8e 00 3d 15 84 ca df 36 78 f9 30 c1 f7 bd c6 ec\n    Sample   ee c2 5e db 8e 00 3d 15 84 ca df 34 78 f9 30 0a 82 bd f5 57\n    Sample   ee c2 5e db 8e 00 3d 15 84 ca 5f 32 78 f9 30 c5 eb bd 89 cd\n    Sample   ee c2 5e db 8e 00 3d 15 84 ca 5f f8 78 fc 30 85 ef bd 53 f5\n    Sample   ee c2 5e db 8e 00 3d 15 84 ca 1f fc 78 fc 30 64 8c bd 91 bc\n\n    UnWhiten 11 23 43 41 63 85 0e 31 6e b0 0d 0f 08 6e 67 cb a3 c0 eb 34\n    UnWhiten 11 23 43 41 63 85 0e 31 6e b0 0d 0d 08 6e 67 00 d6 c0 d8 8f\n    UnWhiten 11 23 43 41 63 85 0e 31 6e b0 8d 0b 08 6e 67 cf bf c0 a4 15\n    UnWhiten 11 23 43 41 63 85 0e 31 6e b0 8d c1 08 6b 67 8f bb c0 7e 2d\n    UnWhiten 11 23 43 41 63 85 0e 31 6e b0 cd c5 08 6b 67 6e d8 c0 bc 64\n\n\nData layout:\n\n    Byte Position   0   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17  18 19\n    unwhiten       11  23 43 41 63 85 0e 31 6e b0 0d 0f 08 6e 67 cb a3 c0  eb 34\n                   LL  EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE  CC CC\n\n- LL: {8} Message length except CRC, 0x11 = 17 bytes.\n- EE: {136} Encrypted message, see substitution table\n- CC:{16} CRC-16, poly 0x8005, init 0xFFFF, final XOR 0x0000, over data after IBM unwhitened but still coded.\n\nNibble substitution table:\n\n| Coded | Decoded |\n| --- | --- |\n| 0 | 0 |\n| 1 | 7 |\n| 2 | F |\n| 3 | 9 |\n| 4 | E |\n| 5 | D |\n| 6 | 3 |\n| 7 | 4 |\n| 8 | 2 |\n| 9 | 6 |\n| A | C |\n| B | B |\n| C | 1 |\n| D | 8 |\n| E | A |\n| F | 5 |\n\nPayload:\n\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16\n    unwhitened     23 43 41 63 85 0e 31 6e b0 0d 0f 08 6e 67 cb a3 c0\n    decoded        F9 E9 E7 39 2D 0A 97 3A B0 08 05 02 3A 34 1B C9 10\n                   II II II II PP PP ?? ?? ?? ?? VV VV MD YY ?? ?? ??\n\n- II: {25} little endian, serial number of the sensor\n- PP: {16} little endian, last year value\n- VV: {16} little endian, current value\n- MDYY {16} little endian, current date, distributed like that : YEAR offset 2000 {7} MONTH {4} DAY {5}\n- ??: Unknown value\n\n*/\n\n#include \"decoder.h\"\n\n#define MAX_LEN 20     // 1 Byte LEN + 17 Byte MSG + 2 Byte CRC\n\n#define CRC_LEN 2\n#define LEN_LEN 1\n\n#define ID_STR_LEN 9\n#define VOL_STR_LEN 9\n#define DATE_STR_LEN 10\n#define BIT_LEN_STR_LEN 6\n\nstatic int apator_metra_eitn30_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {\n                                 0xaa, 0xaa,  // preamble\n                                 0x69, 0x9a   // sync word\n    };\n\n    uint8_t const ibm_whiten_key[22] = {0xff, 0xe1, 0x1d, 0x9a, 0xed, 0x85, 0x33, 0x24, 0xea, 0x7a, 0xd2, 0x39, 0x70, 0x97, 0x57, 0x0a, 0x54, 0x7d, 0x2d, 0xd8, 0x6d, 0x0d};\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble, 8 * sizeof(preamble));\n\n    if (start_pos == bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 1, __func__, \"Preamble Sync word not found\");\n        return DECODE_ABORT_EARLY; // no preamble and / or sync word detected\n    }\n\n    uint8_t len;\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos + 8 * sizeof(preamble), &len, 8);\n\n    decoder_logf(decoder, 1, __func__, \"MSG LEN: %d\", len);\n\n    len ^= 0xff;\n    if (len != 0x11) {\n        decoder_logf(decoder, 1, __func__, \"MSG LEN does not match 17: %d\", len);\n        return DECODE_ABORT_EARLY; // unknown model\n    }\n\n    uint8_t frame[MAX_LEN] = {0}; // uint8_t max bytes + 2 bytes crc + 1 byte length\n    // get frame (length field and CRC16 non included in len)\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos + 8 * sizeof(preamble), frame, 8 * (MAX_LEN));\n\n    // Unwhiten the data coded with IBM Whitening Algorithm LFSR, simple XOR is enough to decode.\n    for (int i = 0; i < len + CRC_LEN + LEN_LEN; i++) {\n        frame[i] ^= ibm_whiten_key[i];\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, frame, 8 * (MAX_LEN), \"Unwhitened\");\n\n    uint16_t frame_crc = frame[len + 1] << 8 | frame[len + 2];\n    uint16_t computed_crc = crc16(frame, len + LEN_LEN, 0x8005, 0xffff);\n    if ( frame_crc != computed_crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC 16 does not match, current %04x, expected %04x\", frame_crc, computed_crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    //decoder_log(decoder, 1, __func__, \"CRC 16 OK\");\n\n    uint8_t p[MAX_LEN] = {0};\n\n    //decrypt the message, nibble substitution\n    uint8_t const nibble_map[16] = {0x0, 0x7, 0xf, 0x9, 0xe, 0xd, 0x3, 0x4, 0x2, 0x6, 0xc, 0xb, 0x1, 0x8, 0xa, 0x5};\n\n    for (int i = 0; i < 2 * len ; i++) {\n        uint8_t nibble_encr, nibble_decr;\n\n        unsigned int bitshift = (i % 2) ? 0 : 4;\n\n        nibble_encr = (frame[1 + (i / 2)] >> bitshift) & 0x0f;\n        nibble_decr = nibble_map[nibble_encr];\n\n        p[i / 2] |= nibble_decr << bitshift;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, p, 8 * (len), \"MSG Decoded\");\n\n    uint32_t id      = (p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]) ^ 0x38000000;\n\n    uint16_t current = p[11] << 8 | p[10];\n    uint16_t last_yr = p[5]  << 8 | p[4];\n    uint16_t date    = p[13] << 8 | p[12];\n    uint8_t  day     = date & 0x1f;\n    uint8_t  month   = (date >> 5) & 0x0f;\n    uint8_t  year    = (date >> 9) & 0x7f;\n\n    char date_str[DATE_STR_LEN + 1];\n    sprintf(date_str, \"%04d-%02d-%02d\", 2000 + year, month, day);\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",           \"\",                         DATA_STRING, \"ApatorMetra-EITN30\",\n        \"id\",              \"ID\",                       DATA_FORMAT, \"%09d\",  DATA_INT,    id,\n        \"len\",             \"Frame length\",             DATA_INT,    len,\n        \"current_heating\", \"Current Heating\",          DATA_INT,    current,\n        \"last_yr_heating\", \"Last Year Heating\",        DATA_INT,    last_yr,\n        \"date\",            \"Date\",                     DATA_STRING, date_str,\n        \"mic\",             \"Integrity\",                DATA_STRING, \"CRC\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n    \"model\",\n    \"id\",\n    \"len\",\n    \"current_heating\",\n    \"last_yr_heating\",\n    \"date\",\n    \"mic\",\n    NULL,\n};\n\nr_device const apator_metra_eitn30 = {\n    .name        = \"Apator Metra E-ITN 30 heat cost allocator\",\n    .modulation  = FSK_PULSE_PCM,\n    .short_width = 25,\n    .long_width  = 25,\n    .reset_limit = 5000,\n    .decode_fn   = &apator_metra_eitn30_decode,\n    .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/apator_metra_erm30.c",
    "content": "/** @file\n    Apator Metra E-RM 30 Water Meters.\n\n    Copyright (C) 2025 Alex Carp (\\@carpalex)\n    Copyright (c) 2026 Bruno Octau (\\@ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nApator Metra E-RM 30 Water Meters.\n\nS.a issue #3012, for E-RM 30, #3452, for E-ITN 30\n\n- Both E-RM 30 (Water Meter) and E-ITN 30 (Heat Cost Allocator) are using the same approach, same protocol.\n- Only the message length differ between the 2 sensors.\n\nCoding:\n- Frames are transmitted with a preamble (0xaa 0xaa ...), followed by the 0x699a syncword.\n- 2 levels of Data coding : It is first whitened using IBM Code, discovered into #3452, but the data payload are also encrypted.\n- Each message is composed of one byte for the payload lengh, the encrypted payload and 2 byte for the CRC-16.\n- Depends on the sensor, the payload length is : 19 byte for water meter and 17 byte for heat meter.\n- CRC-16 must be checked after unwhitening and before decrypting the payload.\n- The payload is encrypted using nibble substitution of 16 values.\n\nE-RM 30 Message layout:\n\n     Byte  0  1  2  3  4  5  6  7  8  9 10 11 12 13 15 16 17 18 19 20  21 22\n     SSSS LL EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE  CC CC\n\n- S  16b: syncword: 0x699a (16 bits)\n- L   8b: payload length (seems to be always 19 = 0x13; does not include length and CRC)\n- E 304b: encrypted payload (19 bytes), nibble substitution.\n- C  16b: CRC-16 with poly=0x8005 and init=0xffff over data after IBM unwhitened but still coded (length field + encrypted payload)\n\nNibble substitution table:\n\n| Coded | Decoded |\n| --- | --- |\n| 0 | 0 |\n| 1 | 7 |\n| 2 | F |\n| 3 | 9 |\n| 4 | E |\n| 5 | D |\n| 6 | 3 |\n| 7 | 4 |\n| 8 | 2 |\n| 9 | 6 |\n| A | C |\n| B | B |\n| C | 1 |\n| D | 8 |\n| E | A |\n| F | 5 |\n\nE-RM 30 Payload fields:\n\n           0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18\n          II II II II VV VV VV VV ?? ?? ?? ?? ?? ?? ?? DD DD ?? ??\n\n- I  32b: little-endian, id, visible on the radio module (not the one on the actual analog meter)\n- V  25b: little-endian, volume in liters (or scale 1000 in m3), VOL = (little-endian of the 32b & 0x0fffffff) >> 3\n- ?  56b: unknown\n- D  16b: little-endian, date, bit distribution : Year (offset 2000) {7} Month {4} Day {5}\n- ?  16b: unknown\n\nAccording to the technical manual, the radio module also transmits other fields,\nlike reverse flow volume, date of magnetic tampering, date of mechanical tampering\netc., but they were not (yet) identified\n\n*/\n\n#include \"decoder.h\"\n\n#define MAX_LEN 22     // 1 Byte LEN + 19 Byte MSG + 2 Byte CRC\n\n#define CRC_LEN 2\n#define LEN_LEN 1\n\n#define ID_STR_LEN 9\n#define VOL_STR_LEN 9\n#define DATE_STR_LEN 10\n#define BIT_LEN_STR_LEN 6\n\nstatic int apator_metra_erm30_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {\n                                 0xaa, 0xaa,  // preamble\n                                 0x69, 0x9a   // sync word\n    };\n\n    uint8_t const ibm_whiten_key[22] = {0xff, 0xe1, 0x1d, 0x9a, 0xed, 0x85, 0x33, 0x24, 0xea, 0x7a, 0xd2, 0x39, 0x70, 0x97, 0x57, 0x0a, 0x54, 0x7d, 0x2d, 0xd8, 0x6d, 0x0d};\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble, 8 * sizeof(preamble));\n\n    if (start_pos == bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 1, __func__, \"Preamble Sync word not found\");\n        return DECODE_ABORT_EARLY; // no preamble and / or sync word detected\n    }\n\n    uint8_t len;\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos + 8 * sizeof(preamble), &len, 8);\n\n    decoder_logf(decoder, 1, __func__, \"MSG LEN: %d\", len);\n\n    len ^= 0xff;\n    if (len != 0x13) {\n        decoder_logf(decoder, 1, __func__, \"MSG LEN does not match 19: %d\", len);\n        return DECODE_ABORT_EARLY; // unknown model\n    }\n\n    uint8_t frame[MAX_LEN] = {0}; // uint8_t max bytes + 2 bytes crc + 1 byte length\n    // get frame (length field and CRC16 non included in len)\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos + 8 * sizeof(preamble), frame, 8 * (MAX_LEN));\n\n    // Unwhiten the data coded with IBM Whitening Algorithm LFSR, simple XOR is enough to decode.\n    for (int i = 0; i < len + CRC_LEN + LEN_LEN; i++) {\n        frame[i] ^= ibm_whiten_key[i];\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, frame, 8 * (MAX_LEN), \"Unwhitened\");\n\n    uint16_t frame_crc = frame[len + 1] << 8 | frame[len + 2];\n    uint16_t computed_crc = crc16(frame, len + LEN_LEN, 0x8005, 0xffff);\n    if ( frame_crc != computed_crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC 16 does not match, current %04x, expected %04x\", frame_crc, computed_crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    //decoder_log(decoder, 1, __func__, \"CRC 16 OK\");\n\n    uint8_t p[MAX_LEN] = {0};\n\n    //decrypt the message, nibble substitution\n    uint8_t const nibble_map[16] = {0x0, 0x7, 0xf, 0x9, 0xe, 0xd, 0x3, 0x4, 0x2, 0x6, 0xc, 0xb, 0x1, 0x8, 0xa, 0x5};\n\n    for (int i = 0; i < 2 * len ; i++) {\n        uint8_t nibble_encr, nibble_decr;\n\n        unsigned int bitshift = (i % 2) ? 0 : 4;\n\n        nibble_encr = (frame[1 + (i / 2)] >> bitshift) & 0x0f;\n        nibble_decr = nibble_map[nibble_encr];\n\n        p[i / 2] |= nibble_decr << bitshift;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, p, 8 * (len), \"MSG Decoded\");\n\n    uint32_t id      = (p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]) ^ 0x30000000;\n\n    uint32_t vol_raw = ((p[7] << 24 | p[6] << 16 | p[5] << 8 | p[4]) & 0x0fffffff) >> 3;\n    float    volume  = vol_raw / 1000.0f;\n    uint16_t date    = p[16] << 8 | p[15];\n    uint8_t  day     = date & 0x1f;\n    uint8_t  month   = (date >> 5) & 0x0f;\n    uint8_t  year    = (date >> 9) & 0x7f;\n\n    char date_str[DATE_STR_LEN + 1];\n    sprintf(date_str, \"%04d-%02d-%02d\", 2000 + year, month, day);\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",           \"\",                         DATA_STRING, \"ApatorMetra-ERM30\",\n        \"id\",              \"ID\",                       DATA_FORMAT, \"%09d\",    DATA_INT,    id,\n        \"len\",             \"Frame length\",             DATA_INT,    len,\n        \"volume_m3\",       \"Volume\",                   DATA_FORMAT, \"%.3f m3\", DATA_DOUBLE, volume,\n        \"date\",            \"Date\",                     DATA_STRING, date_str,\n        \"mic\",             \"Integrity\",                DATA_STRING, \"CRC\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n    \"model\",\n    \"id\",\n    \"len\",\n    \"volume_m3\",\n    \"date\",\n    \"mic\",\n    NULL,\n};\n\nr_device const apator_metra_erm30 = {\n    .name        = \"Apator Metra E-RM 30 water meter\",\n    .modulation  = FSK_PULSE_PCM,\n    .short_width = 25,\n    .long_width  = 25,\n    .reset_limit = 5000,\n    .decode_fn   = &apator_metra_erm30_decode,\n    .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/arad_ms_meter.c",
    "content": "/** @file\n    Arad/Master Meter Dialog3G water utility meter.\n\n    Copyright (C) 2022 avicarmeli\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nArad/Master Meter Dialog3G water utility meter.\n\nFCC-Id: TKCET-733\n\nSee notes in https://45851052.fs1.hubspotusercontent-na1.net/hubfs/45851052/documents/files/Interpreter-II-Register_v0710.20F.pdf\nand https://www.arad.co.il/wp-content/uploads/Dialog-3G-register-information-sheet_Eng-002.pdf\n\nProgrammable parameters:\n- Meter User ID:\n  A municipal Meter ID number of up to 5 digits (16 or 17 bits needed)\n- Transponder No:\n  Meter’s Dialog 3GTM transponder number of up to 12 digits (40 bits needed)\n- Reading:\n  The transmitted Dialog 3G TM meter reading (up to 9 digits), (30 bits needed)\n  the accumulated and the display readout are always equivalent.\n- Meter Type:\n  Meter type such as water, gas or electricity\n- Count Factor:\n  Meter count unit. It is a pre scale factor which is initially programmed\n  into the Dialog 3G TM unit is order to get the standaed measurement units\n  for the system billing, management and calculation (Gallons or Cubic/ Mettic)\n- Alarms Temper:\n  A warning temper sign, in case of unauthorized meter tampering. CCW: Reverse\n  consumption by the meter.\n- Gear Ratio:\n  Water meter mechanical gear ratio parameter for the 3G Interpreter register types\n\nProgrammable registration includes USG, CF, or M3, while\nresolution of the flow multiplier provides a custom-tailored\nenhanced display (.01, 0.1, 1, 10, 100).\n\nMessage is being sent once every 30 seconds.\nThe message looks like:\n\n    00000000FFFFFFFFFFFFFFSSSSSSSSXXCCCCCCXXXF?????????XFF\n\nwhere:\n\n- 00000000 is preamble.\n- FFFFFFFFFFFFFF  is fixed in time and the same for other meters in the neighborhood. Probably gearing ratio. The payload is 3e690aec7ac84b.\n- SSSSSSSS  is Meter serial number.  for instance fa1c9073 =>  fa1c90 = 09444602, little endian 73= 'S'\n- XX no idea.\n- CCCCCC is the counter reading little endian for instance a80600= 1704\n- XXX no idea.\n- F  is fixed in time and the same for other meters in the neighborhood. With payload of 5.\n- ????????? probably some kind of CRC or checksum - here is where I need help.\n- X is getting either 8 or 0 same for other meters in the neighborhood.\n- FF is fixed in time and the same for other meters in the neighborhood.With payload f8.\n\nFormat string:\n\n    UID:56h SERIAL: <24d c 8h COUNTER: <32d 8h8h 8h8h 8h8h  SUFFIX:hh\n\n*/\n\nstatic int arad_mm_dialog3g_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x96, 0xf5, 0x13, 0x85, 0x37, 0xb4}; // 48 bit preamble\n\n    int row = bitbuffer_find_repeated_row(bitbuffer, 1, 168); // expected 1 row with minimum of 48+120= 168 bits.\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, 48);\n    start_pos += 48; // skip preamble\n\n    if (start_pos + 120 > bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_LENGTH; // short buffer or preamble not found\n    }\n\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t b[15];\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos, b, 120);\n\n    int serno    = b[0] | (b[1] << 8) | (b[2] << 16); // 24 bit little endian Meter Serial number\n    int wreadraw = b[5] | (b[6] << 8) | (b[7] << 16); // 24 bit little endian Meter water consumption reading\n    float wread = wreadraw * 0.1f;\n\n    char sernoout[12];\n    sprintf(sernoout, \"%08u-%02x\", serno, b[3]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",       \"\",               DATA_STRING,    \"AradMsMeter-Dialog3G\",\n            \"id\",          \"Serial No\",      DATA_STRING,    sernoout,\n            \"volume_m3\",   \"Volume\",         DATA_FORMAT,    \"%.1f m3\",  DATA_DOUBLE, wread,\n            //\"mic\",         \"Integrity\",      DATA_STRING,    \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"volume_m3\",\n        //\"mic\",\n        NULL,\n};\n\nr_device const arad_ms_meter = {\n        .name        = \"Arad/Master Meter Dialog3G water utility meter\",\n        .modulation  = FSK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 8.4,\n        .long_width  = 8.4, // not used\n        .reset_limit = 30,\n        .decode_fn   = &arad_mm_dialog3g_decode,\n        .disabled    = 1, // checksum not implemented\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/archos_tbh.c",
    "content": "/** @file\n    Decoder for TBH Archos devices.\n\n    Copyright (c) 2019 duc996 <duc_996@gmx.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n/**\nDecoder for devices from the TBH project (https://www.projet-tbh.fr)\n\n- Modulation: FSK PCM\n- Frequency: 433.93MHz +-10kHz\n- 212 us symbol/bit time\n\nThere exist several device types (power, meteo, gaz,...)\n\nPayload format:\n- Preamble          {32} 0xaaaaaaaa\n- Syncword          {32} 0xd391d391\n- Length            {8}\n- Payload           {n}\n- Checksum          {16} CRC16 poly=0x8005 init=0xffff\n\nTo get raw data:\n\n    ./rtl_433 -f 433901000 -X 'n=tbh,m=FSK_PCM,s=212,l=212,r=3000,preamble=aad391d391'\n\nThe application data is obfuscated by doing data[n] xor data[n-1] xor info[n%16].\n\nPayload format:\n- Device id         {32}\n- Frame type        {8}\n- Frame Data        {x}\n\nFrame types:\n- Raw data      1\n- Weather       2\n- Battery level 3\n- Battery low   4\n\nWeather frame format:\n- Type        {8} 02\n- Temperature {16} unsigned in 0.1 Celsius steps\n- Humidity    {16} unsigned rel%\n\nRaw data frame (power index):\n- Version {8}\n- Index     {24}\n- Timestamp {34}\n- MaxPower  {16}\n- some additional data ???\n- CRC8 poly=0x7 the crc includes a length byte at the beginning\n*/\n\n#include \"decoder.h\"\n\nstatic int archos_tbh_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {\n            /*0xaa, 0xaa, */ 0xaa, 0xaa, // preamble\n            0xd3, 0x91, 0xd3, 0x91       // sync word\n    };\n\n    data_t *data;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int row = 0;\n    // Validate message and reject it as fast as possible : check for preamble\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, sizeof (preamble) * 8);\n\n    if (start_pos == bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_EARLY; // no preamble detected\n    }\n\n    // check min length\n    if (bitbuffer->bits_per_row[row] < 12 * 8) { //sync(4) + preamble(4) + len(1) + data(1) + crc(2)\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t len;\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos + sizeof (preamble) * 8, &len, 8);\n\n    if (len > 60) {\n        decoder_logf(decoder, 1, __func__, \"packet to large (%d bytes), drop it\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t frame[63] = {0}; // TODO check max size, I have no idea, arbitrary limit of 60 bytes + 2 bytes crc\n    frame[0] = len;\n    // Get frame (len don't include the length byte and the crc16 bytes)\n    bitbuffer_extract_bytes(bitbuffer, row,\n            start_pos + (sizeof (preamble) + 1) * 8,\n            &frame[1], (len + 2) * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, frame, (len + 1) * 8, \"frame data\");\n\n    uint16_t crc = crc16(frame, len + 1, 0x8005, 0xffff);\n\n    if ((frame[len + 1] << 8 | frame[len + 2]) != crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC invalid %04x != %04x\",\n                frame[len + 1] << 8 | frame[len + 2], crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    uint8_t const info[] = {\n            0x19, 0xF8, 0x28, 0x30, 0x6d, 0x0c, 0x94, 0x54,\n            0x22, 0xf2, 0x37, 0xc9, 0x66, 0xa3, 0x97, 0x57\n    };\n\n    uint8_t payload[62] = {0};\n\n    payload[0] = frame[1] ^ info[0];\n    for (int i = 1; i < len; ++i) {\n        payload[i] = frame[i] ^ frame[i + 1] ^ info[i % sizeof (info)];\n    }\n    decoder_log_bitrow(decoder, 2, __func__, payload, len * 8, \"frame data\");\n\n    uint8_t type = payload[4];\n    uint32_t id  = payload[0] | payload[1] << 8 | payload[2] << 16 | (uint32_t)(payload[3]) << 24;\n\n    if (type == 1) {\n        // raw data\n        decoder_logf(decoder, 1, __func__, \"raw data from ID: %08x\", id);\n\n        payload[4] = len - 4; //write len for crc (len - 4b ID)\n\n        decoder_log_bitrow(decoder, 2, __func__, &payload[4], (len - 4) * 8, \"data\");\n\n        uint8_t c = crc8(&payload[4], len - 5, 0x07, 0x00);\n\n        if (c != payload[len - 1]) {\n            decoder_log(decoder, 1, __func__, \"crc error\");\n            return DECODE_FAIL_MIC;\n        }\n\n        int idx      = payload[6] << 16 | payload[7] << 8 | payload[8];\n        int ts       = payload[9] << 16 | payload[10] << 8 | payload[11];\n        int maxPower = payload[12] << 8 | payload[13];\n\n        decoder_logf(decoder, 2, __func__, \"index: %d, timestamp: %d, maxPower: %d\",\n                    idx, ts, maxPower);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",                 DATA_STRING, \"Archos-TBH\",\n                \"id\",           \"Station ID\",       DATA_FORMAT, \"%08X\", DATA_INT, id,\n                \"power_idx\",    \"Power index\",      DATA_FORMAT, \"%d\", DATA_INT, idx,\n                \"power_max\",    \"Power max\",        DATA_FORMAT, \"%d\", DATA_INT, maxPower,\n                \"timestamp\",    \"Timestamp\",        DATA_FORMAT, \"%d s\", DATA_INT, ts / 8,\n                \"mic\",          \"Integrity\",        DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 2) {\n        // temp and humidity\n        int temp_raw = (payload[6] << 8 | payload[5]) - 2732;\n        float temp_c = temp_raw * 0.1f;\n        int humidity = payload[7];\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",                 DATA_STRING, \"Archos-TBH\",\n                \"id\",           \"Station ID\",       DATA_FORMAT, \"%08X\", DATA_INT, id,\n                \"temperature_C\", \"Temperature\",     DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",     \"Humidity\",         DATA_FORMAT, \"%d %%\", DATA_INT, humidity,\n                \"mic\",          \"Integrity\",        DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 3) {\n        // bat level, 0-100%\n        int batt_level = payload[5];\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",                 DATA_STRING, \"Archos-TBH\",\n                \"id\",           \"Station ID\",       DATA_FORMAT, \"%08X\", DATA_INT, id,\n                \"battery_ok\",   \"Battery level\",    DATA_FORMAT, \"%0.2f\", DATA_DOUBLE, batt_level * 0.01,\n                \"mic\",          \"Integrity\",        DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 4) {\n        // battery low\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",                 DATA_STRING, \"Archos-TBH\",\n                \"id\",           \"Station ID\",       DATA_FORMAT, \"%08X\", DATA_INT, id,\n                \"battery_ok\",   \"Battery level\",    DATA_INT,    0, // fixed\n                \"mic\",          \"Integrity\",        DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else {\n        decoder_log(decoder, 1, __func__, \"unknown frame received\");\n        return DECODE_FAIL_SANITY;\n    }\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"power_idx\",\n        \"power_max\",\n        \"timestamp\",\n        \"mic\",\n        NULL,\n};\n\nr_device const archos_tbh = {\n        .name        = \"TBH weather sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 212,\n        .long_width  = 212,\n        .reset_limit = 3000,\n        .decode_fn   = &archos_tbh_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/arexx_ml.c",
    "content": "/** @file\n    Arexx Multilogger.\n\n    Copyright (C) 2023 Christian W. Zuckschwerdt <zany@triq.net>\n    Protocol analysis by MacH-21, TSN-70E by inonoob.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nArexx Multilogger.\n\n- Arexx IP-HA90 (MCP9808 sensor) s.a. #2388\n- Arexx IP-TH78EXT\n- Arexx TSN-70E (Sensirion SHT-10 sensor) s.a. #2482\n\nThe IP-HA90 has a Microchip RFPIC12f675f at 433.92M and a Microchip MCP9808 temperature sensor.\nThe TSN-70E has a Sensirion SHT-10 temperature and humidity and temperature sensor.\n\nFSK modulated with Manchester encoding, half-bit width is 208 us (2400bps MC).\nThe sensors transmit approx. every 45 seconds alternating Temperature/Humidity.\nPolarity is inverted (IEEE MC) and the preamble+sync is aaaaaaaa55.\n\nIntegrity check is done using CRC8 using poly=0x31 init=0x00.\n\nExample raw messages:\n\n    55555555aa f8 71fe fedf f777 5b a4  ff\n    55555555aa f8 71fe fedf f727 80 7f  ff\n    55555555aa f8 71fe fedf f6e7 8e 71  ff\n    55555555aa f8 71fe fedf f69f b4 4b  ff\n    55555555aa f8 71fe fedf f66f c0 3f  ff\n    55555555aa f8 71fe fedf f63f 1b e4  ff\n    55555555aa f8 71fe fedf f61f 38 c7  ff\n    55555555aa f8 71fe fedf f607 67 98  ff\n    55555555aa f8 71fe fedf f5f7 46 b9  ff\n    55555555aa f8 71fe fedf f5d7 65 9a  ff\n    55555555aa f8 71fe fedf f5b7 00 ff  ff\n    55555555aa f8 71fe fedf f59f e1 1e  ff\n    55555555aa fa 15b2 e90f 6c ff  faf7 7b1c e3\n    55555555aa fa 14b2 f90e 51 ff  faf7 7b1a e41\n    55555555aa fa 14b2 f991 01 ff  faf7 7b2a 401\n    55555555aa fa 15b2 e678 0f ff  faf7 7bf8 41\n\nMessage format (preamble 5555aa then invert):\n\n    LEN:8h ID:<16h SENS:16h ?:8h8h CHK:8h CHKINV:8h 16x\n\nMessage layout:\n\n    LL IIII SSSS UUUU XX YY\n\n- L : 8 bit: message length 7 or 5 (including length byte, excluding checksum)\n- I : 16 bit: ID, little-endian, even number = Temperature\n- S : 16 bit: raw sensor value\n- U : 16 bit: optional extra data, unknown\n- X : 8 bit: CRC, poly 0x31, init 0xc0\n- Y : 8 bit: inverted CRC check, only IP-HA90\n\n*/\n\nstatic int arexx_ml_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0xaa, 0x55}; // 24 bits\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY; // we expect a single row\n    }\n    if (bitbuffer->bits_per_row[0] < 64 || bitbuffer->bits_per_row[1] > 130) {\n        return DECODE_ABORT_EARLY; // we expect around 88 to 104 bits\n    }\n    bitbuffer_invert(bitbuffer);\n\n    int msg_len = -1;\n    uint8_t b[9]; // allow up to 9 byte messages\n    for (int i = 0; i < bitbuffer->num_rows; ++i) {\n        unsigned pos = bitbuffer_search(bitbuffer, i, 0, preamble, 24);\n        pos += 24;\n\n        if (pos + 64 > bitbuffer->bits_per_row[i])\n            continue; // too short or not found\n\n        bitbuffer_extract_bytes(bitbuffer, i, pos, b, sizeof(b) * 8);\n        msg_len = b[0];\n        break;\n    }\n    if (msg_len <= 0) {\n        decoder_log(decoder, 2, __func__, \"Couldn't find preamble\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    int chk = crc8le(b, msg_len, 0x31, 0x00);\n    if (chk != b[msg_len]) { // || (chk ^ b[msg_len + 1]) != 0) {\n        decoder_log(decoder, 2, __func__, \"CRC fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Extract data from buffer\n    int id       = (b[2] << 8) | (b[1]); // little-endian\n    int sens_val = (b[3] << 8) | (b[4]); // big-endian?\n\n    // Decode readings\n    float temp_c = 0.0;\n    float humidity = 0.0;\n    int is_humi = 0;\n    int is_temp = 0;\n    int is_alert = 0;\n    int temp_alert = 0;\n\n    if (msg_len == 5 && (id & 0xF000) == 0x2000) {\n        // temperature reading from TL-3TSN, TSN-33MN, etc.\n        is_temp = 1;\n        temp_c = (int16_t)sens_val * 0.0078125f;\n    }\n    else if (msg_len == 5 && (id & 0xF001) == 0x4000) {\n        // temperature reading from TSN-TH70E, IP-TH70EXT, etc.\n        is_temp = 1;\n        // SHT10 Temperature\n        temp_c = sens_val * 0.01f - 40.0f; // offset actually varies by Vdd\n    }\n    else if (msg_len == 5 && (id & 0xF001) == 0x4001) {\n        // humidity reading from TSN-TH70E, IP-TH70EXT, etc.\n        is_humi = 1;\n        sens_val = (int16_t)sens_val;\n        // SHT10 Humidity\n        humidity = -2.0468 + 0.0367 * sens_val - 1.5955E-6 * sens_val * sens_val;\n    }\n    else if (msg_len == 6) {\n        is_temp = is_alert = 1;\n        // MCP9808 Ambient Temperature Register \"5-4\":\n        temp_alert = (sens_val >> 13) & 7;\n        int temp_raw = (int16_t)(sens_val << 3); // uses sign-extend\n        temp_c = temp_raw / 128;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Arexx-ML\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%04x\",    DATA_INT, id,\n            \"temperature_C\",    \"Temperature\",      DATA_COND, is_temp,     DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n            \"temperature_alert\",\"Alert\",            DATA_COND, is_alert,    DATA_FORMAT, \"%x\", DATA_INT, temp_alert,\n            \"humidity\",         \"Humidity\",         DATA_COND, is_humi,     DATA_FORMAT, \"%.1f %%\", DATA_DOUBLE, humidity,\n            \"sensor_raw\",       \"Sensor Raw\",       DATA_FORMAT, \"%04x\",    DATA_INT, sens_val,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const arexx_ml_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"temperature_alert\",\n        \"humidity\",\n        \"sensor_raw\",\n        \"mic\",\n        NULL,\n};\n\nr_device const arexx_ml = {\n        .name        = \"Arexx Multilogger IP-HA90, IP-TH78EXT, TSN-70E\",\n        .modulation  = FSK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 208, // 2400bps MC\n        .long_width  = 208, // not used\n        .reset_limit = 450,\n        .decode_fn   = &arexx_ml_decode,\n        .fields      = arexx_ml_output_fields,\n};\n"
  },
  {
    "path": "src/devices/atech_ws308.c",
    "content": "/** @file\n    Decoder for Atech-WS308 temperature sensor.\n\n    Copyright (C) 2020 Marc Prieur https://github.com/marco402\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int atech_ws308_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nAtech WS-308 \"433 tech remote sensor\" for Atech wireless weather station.\n\nS.a. #1605\n\nCoding:\n\n- 28 bit, PWM encoded as PCM RZ 1600us/1832us\n- PCM-RZ to PWM coding: 10->0, 1110->1\n\nExample:\n\n    rtl_433 -R 0 -X 'n=name,m=OOK_PCM,s=1600,l=1800,g=2500,r=9000' Atech-433/g001_433.92M_250k.cu8\n    {9}ff0, {71}aaeeaaaabbaabaaaec\n\n    111111110000\n    10 10 10 10 1110 1110 10 10 10 10 10 10 10 10 10 1110 1110 10 10 10 10 1110 10 10 10 10 10 1110 110\n     0  0  0  0   1    1   0  0  0  0  0  0  0  0  0   1    1   0  0  0  0   1   0  0  0  0  0   1   x\n    y 0000 1100 0000 0001 1000 0100 0001 x\n    x 0 c 0 1 8 4 1 ; 18.4 C, XOR=0\n\nData layout:\n\n- nibble 0: sync or id, b0000\n- nibble 1: sync or id, b1100\n- nibble 2: Temperature sign, 3rd bit: ??S?\n- nibble 3: Temperature BCD hundreds\n- nibble 4: Temperature BCD tenths\n- nibble 5: Temperature BCD units\n- nibble 6: checksum XOR even parity of all nibbles\n\n*/\n\nstatic unsigned pwm_decode(uint8_t *bits, unsigned bit_len, uint8_t *out, unsigned out_len)\n{\n    unsigned pos = 0;\n    unsigned cnt = 0;\n    for (unsigned i = 0; i < bit_len; ++i) {\n        if (bits[i / 8] & (1 << (7 - (i % 8)))) {\n            // count 1's\n            cnt++;\n        }\n        else {\n            // decide at 0\n            // 10->0, 1110->1, otherwise error\n            if (pos % 8 == 0)\n                out[pos / 8] = 0;\n            if (cnt == 1) {\n                // add a zero\n                pos++;\n            }\n            else if (cnt == 3) {\n                // add a one\n                out[pos / 8] |= (1 << (7 - (pos % 8)));\n                pos++;\n            }\n            else {\n                break;\n            }\n            if (pos >= out_len)\n                break;\n            cnt = 0;\n        }\n    }\n    return pos;\n}\n\nstatic int atech_ws308_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 2)\n        return DECODE_ABORT_EARLY;\n    if (bitbuffer->bits_per_row[1] < 58)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t b[4]; // 28 bit\n    int len = pwm_decode(bitbuffer->bb[1], bitbuffer->bits_per_row[1], b, 32);\n    //decoder_log_bitrow(decoder, 0, __func__, b, len, \"\");\n    if (len < 28)\n        return DECODE_ABORT_LENGTH;\n\n    //if (b[0] != 0x0c)\n    //    return DECODE_FAIL_SANITY;\n\n    // check even nibble parity\n    int chk = xor_bytes(b, 3);\n    chk = ((chk ^ b[3]) >> 4) ^ (chk & 0xf); // fold nibbles\n    if (chk != 0)\n        return DECODE_FAIL_MIC;\n\n    int id       = b[0]; // actually fixed 0x0c\n    int temp_raw = ((b[1] & 0xf) * 100) + ((b[2] >> 4) * 10) + (b[2] & 0xf);\n    int sign     = (b[1] & 0x20) ? -1 : 1;\n    float temp_c = sign * temp_raw * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Atech-WS308\",\n            \"id\",               \"Fixed ID\",     DATA_INT,    id,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const atech_ws308 = {\n        .name        = \"Atech-WS308 temperature sensor\",\n        .modulation  = OOK_PULSE_RZ,\n        .short_width = 1600,\n        .long_width  = 1832,\n        .gap_limit   = 2500,\n        .reset_limit = 9000,\n        .decode_fn   = &atech_ws308_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/auriol_4ld5661.c",
    "content": "/** @file\n    Auriol 4-LD5661/4-LD5972/4-LD6313 sensors.\n\n    Copyright (C) 2021 Balazs H.\n    Copyright (C) 2023 Peter Soos\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nLidl Auriol 4-LD5661/4-LD5972/4-LD6313 sensors.\n\nSee also issues #1857, #2631 and PR #2633\n\nData layout:\n\n    II B TTT F RRRRRR\n\n- I: id, 8 bit: factory (hard)coded random ID\n- B: battery, 4 bit: 0x8 if normal, 0x0 if low\n- T: temperature, 12 bit: 2's complement, scaled by 10\n- F: 4 bit: seems to be 0xf constantly, a separator between temp and rain\n- R: rain sensor, probably the remaining 24 bit: a counter for every 0.3 mm (4-LD5661) or 0.242 mm (4-LD6313)\n\n*/\n\n#include \"decoder.h\"\n\nstatic int auriol_4ld5661_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int ret = 0;\n\n    for (int i = 0; i < bitbuffer->num_rows; i++) {\n        if (bitbuffer->bits_per_row[i] != 52) {\n            ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n\n        uint8_t *b  = bitbuffer->bb[i];\n        int id      = b[0];\n        int batt_ok = b[1] >> 7;\n\n        if (b[3] != 0xf0 || (b[1] & 0x70) != 0) {\n            ret = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        int temp_raw = (int16_t)(((b[1] & 0x0f) << 12) | (b[2] << 4)); // uses sign extend\n        float temp_c = (temp_raw >> 4) * 0.1F;\n\n        int rain_raw = (b[4] << 12) | (b[5] << 4) | b[6] >> 4;\n\n        /* The display unit which comes with this devices, multiplies gauge tip counts by 0.3 mm, which seems\n           to be very inaccurate. We did a lot of measurements, the gauge's capacity is about 7.5 ml, the\n           rain collection surface diameter is 96mm, 7.5 ml /((9.6 cm/2)^2*pi) ~= 1 mm of rain. Therefore\n           we decided to correct this multiplier.\n           The rain bucket tips at 7.2 ml for 4-LS6313. The main unit counts 0.242 mm per sensor tips.\n           The physical parameters are same. The calculation and the result is similar:\n               7.2 ml / ((96 mm / 2)^2 * pi) ~= 1 mm (more exactly 0.995 mm).\n           Similar calculation is valid for 4-LD5972 (See PR #2633).\n           See also:\n               https://github.com/merbanan/rtl_433/issues/1837\n               https://github.com/merbanan/rtl_433/pull/2633\n        */\n        float rain   = rain_raw * 1.0F;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"Model\",        DATA_STRING, \"Auriol-4LD5661\",\n                \"id\",               \"ID\",           DATA_FORMAT, \"%02x\", DATA_INT, id,\n                \"battery_ok\",       \"Battery OK\",   DATA_INT, batt_ok,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain,\n                \"rain\",             \"Rain tips\",    DATA_INT, rain_raw,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"rain_mm\",\n        \"rain\",\n        NULL,\n};\n\nr_device const auriol_4ld5661 = {\n        .name        = \"Auriol 4-LD5661/4-LD5972/4-LD6313 temperature/rain sensors\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .sync_width  = 2500,\n        .gap_limit   = 2500,\n        .reset_limit = 4000,\n        .decode_fn   = &auriol_4ld5661_decode,\n        .disabled    = 1, // no sync-word, no fix id, no checksum\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/auriol_aft77b2.c",
    "content": "/** @file\n    Auriol AFT 77 B2 sensor protocol.\n\n    Copyright (C) 2021 P. Tellenbach\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/** @fn int auriol_aft77_b2_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nAuriol AFT 77 B2 protocol. The sensor can be bought at Lidl.\n\nThe sensor sends 68 bits at least 3 times, before the packets are 9 sync pulses\nof 1900us length.\nThe packets are ppm modulated (distance coding) with a pulse of ~488 us\nfollowed by a short gap of ~488 us for a 0 bit or a long ~976 us gap for a\n1 bit, the sync gap is ~1170 us.\n\nThe data is grouped in 17 nibbles\n\n    [prefix] [0x05] [0x0C] [id0] [id1] [0x00] [flags] [sign] [temp0] [temp1] [temp2]\n    [0x00] [0x00] [sum] [sum] [lsrc] [lsrc]\n\nBitbuffer example from rtl_433 -a:\n\n    [00] { 0}                            :\n    [01] { 0}                            :\n    [02] { 0}                            :\n    [03] { 0}                            :\n    [04] { 0}                            :\n    [05] { 0}                            :\n    [06] { 0}                            :\n    [07] { 0}                            :\n    [08] { 0}                            :\n    [09] {68} a5 cf 80 20 17 30 0c ac 90\n    [10] { 0}                            :\n\n- prefix: 4 bit fixed 1010 (0x0A) ignored when calculating the checksum and lsrc\n- id: 8 bit a random id that is generated when the sensor starts\n- flags(1): was set at first start and reset after a restart\n- flags(3): might be the battery status (not yet decoded)\n- sign(3): is 1 when the reading is negative\n- temp: a BCD number scaled by 10, 175 is 17.5C\n- sum: 8 bit sum of the previous bytes\n- lsrc: Galois LFSR, bits reflected, gen 0x83, key 0xEC\n\n*/\n\n#include \"decoder.h\"\n\n#define GEN 0x83\n#define KEY 0xEC\n\n#define LEN 8\n\nstatic uint8_t lsrc(uint8_t frame[], int len)\n{\n    uint8_t result = 0;\n    uint8_t key    = KEY;\n\n    for (int i = 0; i < len; i++) {\n        uint8_t byte = frame[i];\n\n        for (uint8_t mask = 0x80; mask > 0; mask >>= 1) {\n            if ((byte & mask) != 0)\n                result ^= key;\n\n            if ((key & 1) != 0)\n                key = (key >> 1) ^ GEN;\n            else\n                key = (key >> 1);\n        }\n    }\n\n    return result;\n}\n\nstatic int search_row(bitbuffer_t *bitbuffer)\n{\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] == 68)\n            return row;\n    }\n\n    return -1;\n}\n\nstatic int auriol_aft77_b2_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n\n    // Search a suitable row in the bit buffer\n    int row = search_row(bitbuffer);\n\n    // Check if found\n    if (row == -1)\n        return DECODE_ABORT_EARLY;\n\n    uint8_t *ptr = bitbuffer->bb[row];\n\n    // Check the prefix\n    if (*ptr != 0xA5)\n        return DECODE_ABORT_EARLY;\n\n    uint8_t frame[LEN];\n\n    // Drop the prefix and align the bytes\n    for (int i = 0; i < LEN; i++)\n        frame[i] = (ptr[i] << 4) | (ptr[i + 1] >> 4);\n\n    // Check the sum\n    if ((uint8_t)add_bytes(frame, 6) != frame[6])\n        return DECODE_FAIL_MIC;\n\n    // Check the lsrc\n    if (lsrc(frame, 6) != frame[7])\n        return DECODE_FAIL_MIC;\n\n    int id = frame[1];\n\n    int temp_raw = (ptr[4] >> 4) * 100 + (ptr[4] & 0x0F) * 10 + (ptr[5] >> 4);\n\n    if ((ptr[3] & 0x08) != 0)\n        temp_raw = -temp_raw;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Auriol-AFT77B2\",\n            \"id\",            \"\",            DATA_INT, id,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_raw * 0.1,\n            \"mic\",              \"Integrity\",         DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const auriol_aft77b2 = {\n        .name        = \"Auriol AFT 77 B2 temperature sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 500,\n        .long_width  = 920,\n        .gap_limit   = 1104,\n        .reset_limit = 2275,\n        .decode_fn   = &auriol_aft77_b2_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/auriol_afw2a1.c",
    "content": "/** @file\n    Auriol AFW 2 A1 sensor.\n\n    Copyright (C) 2019 LiberationFrequency\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nLidl Auriol AFW 2 A1 sensor (IAN 311588).\n\nTechnical data for the external sensor:\n- Temperature measuring range/accuracy:       -20 to +65°C (-4 to +149°F) / ±1.5 °C (± 2.7 °F)\n- Relative humidity measuring range/accuracy: 20 to 99% / ± 5%\n- Relative humidity resolution:               1%\n- Transmission frequencies:                   433 MHz (ch1:~433919300,ch2:~433915200,ch3:~433918000, various?)\n- Transmission output:                        < 10 dBm / < 10 mW\n\nThe ID is retained even if the batteries are changed.\nThe device has three channels and a transmit button.\n\nData layout:\nThe sensor transmits 12 identical messages of 36 bits in a single package each ~60 seconds, depending on the temperature.\ne.g.:\n\n    [00] {36} 90 80 ba a3 a0 : 10010000 10000000 10111010 10100011 1010\n    ...\n    [11] {36} 90 80 ba a3 a0 : ...\n     0           1           2           3           4\n     9    0      8    0      b    a      a    3      a    0\n    |1001|0000| |1000|0000| |1011|1010| |1010|0011| |1010|\n    |id       | |chan|temp            | |fix |hum        |\n    --------------------------------------------------------\n    10010000  = id=0x90=144; 8 bit\n    1         = battery_ok; 1 bit\n    0         = tx_button; 1 bit\n    00        = channel; 2 bit\n    0000      = temperature leading sign,\n                1110=0xe(-51.1°C to -25.7°C),\n                1111=0xf(-25.6°C to - 0.1°C),\n                0000=0x0(  0.0°C to  25,5°C),\n                0001=0x1( 25.6°C to  51.1°C),\n                0010=0x2( 51.2°C to  76.7°C); 4 bit\n    10111010  = temperature=0xba=186=18,6°C; 8 bit\n    1010      = fixed; 4 bit\n    0011 1010 = humidity=0x3a=58%; 8 bit\n*/\n\n#include \"decoder.h\"\n\nstatic int auriol_afw2a1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int row;\n    int id;\n    int channel;\n    int battery_ok;\n    int tx_button;\n    int temp_raw;\n    float temp_c;\n    int humidity;\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 12, 36);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY; // no repeated row found\n    }\n\n    b = bitbuffer->bb[row];\n\n    id         = b[0];\n    battery_ok = b[1] >> 7;\n    tx_button  = (b[1] & 0x40) >> 6;\n    channel    = (b[1] & 0x30) >> 4;\n    temp_raw   = (int16_t)(((b[1] & 0x0f) << 12) | (b[2] << 4)); // uses sign extend\n    temp_c     = (temp_raw >> 4) * 0.1f;\n    // 0xa is fixed. If it differs, it is a wrong device. Could anyone confirm that?\n    if ((b[3] >> 4) != 0xa) {\n        decoder_log(decoder, 1, __func__, \"Not an Auriol-AFW2A1 device\");\n        return DECODE_FAIL_SANITY;\n    }\n    humidity = (((b[3] & 0x0f) << 4) | (b[4] >> 4));\n\n    if ((humidity > 0x64) || (humidity < 0x00) || (temp_c < -51.1) || (temp_c > 76.7)) {\n        decoder_log(decoder, 1, __func__, \"Auriol-AFW2A1 data error\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                  DATA_STRING, \"Auriol-AFW2A1\",\n            \"id\",               \"\",                  DATA_INT,    id,\n            \"channel\",          \"Channel\",           DATA_INT,    channel + 1,\n            \"battery_ok\",       \"Battery\",           DATA_INT,    battery_ok,\n            \"button\",           \"Button\",            DATA_INT,    tx_button,\n            \"temperature_C\",    \"Temperature\",       DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",          DATA_FORMAT, \"%.0f %%\", DATA_DOUBLE, (float)humidity,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_C\",\n        \"humidity\",\n        NULL,\n};\n\n// ToDo: The timings have come about through trial and error. Audit this against weak signals!\nr_device const auriol_afw2a1 = {\n        .name        = \"Auriol AFW2A1 temperature/humidity sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 576,\n        .long_width  = 1536,\n        .sync_width  = 0, // No sync bit used\n        .gap_limit   = 2012,\n        .reset_limit = 3954,\n        .decode_fn   = &auriol_afw2a1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/auriol_ahfl.c",
    "content": "/** @file\n    Auriol AHFL 433B2 IPX4\n\n    Copyright (C) 2021 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nLidl Auriol Auriol AHFL 433B2 IPX4\n\n[00] {42} f2  00     ef 7c 41 40 : 11110010 00000000 11101111 01111100 01000001 01\n\n          II [BXCC]T TT HH FS [SS--]\n\n  42 bit message\n\n  I - id, 8 bits\n  B - batter, 1 bit\n  X - tx-button, 1 bit (might not work)\n  C - channel, 2 bits\n  T - temperature, 12 bits\n  H - humidity, 7 bits data, 1 bit 0\n  F - always 0x4 (0100)\n  S - nibble sum, 6 bits\n*/\n\n#include \"decoder.h\"\n\nstatic int auriol_ahfl_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int row;\n    int id;\n    int channel;\n    int tx_button;\n    int battery_ok;\n    int temp_raw;\n    float temp_c;\n    int humidity;\n    int nibble_sum, checksum;\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 2, 42);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY; // no repeated row found\n    }\n\n    if (bitbuffer->bits_per_row[row] != 42)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[row];\n\n    /* Check fixed message values */\n    if (((b[4] & 0xF0) != 0x40) || ((b[3] & 0x1) != 0x0)) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // calculate nibble sum\n    nibble_sum = (b[0] & 0xF) + (b[0] >> 4) +\n                 (b[1] & 0xF) + (b[1] >> 4) +\n                 (b[2] & 0xF) + (b[2] >> 4) +\n                 (b[3] & 0xF) + (b[3] >> 4) +\n                 (b[4] >> 4);\n    checksum = ((b[4] & 0xF) << 2) | ((b[5] & 0xC0) >> 6);\n\n    // check 6 bits of nibble sum\n    if ((nibble_sum & 0x3F) != checksum)\n        return DECODE_FAIL_MIC;\n\n    id         = b[0];\n    battery_ok = b[1] >> 7;\n    channel    = (b[1] & 0x30) >> 4;\n    tx_button  = (b[1] & 0x40) >> 6;\n    temp_raw   = (int16_t)(((b[1] & 0x0f) << 12) | (b[2] << 4)); // uses sign extend\n    temp_c     = (temp_raw >> 4) * 0.1f;\n    humidity   = b[3] >> 1;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                  DATA_STRING, \"Auriol-AHFL\",\n            \"id\",               \"\",                  DATA_INT,    id,\n            \"channel\",          \"Channel\",           DATA_INT,    channel + 1,\n            \"battery_ok\",       \"Battery\",           DATA_INT,    battery_ok,\n            \"button\",           \"Button\",            DATA_INT,    tx_button,\n            \"temperature_C\",    \"Temperature\",       DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",          DATA_FORMAT, \"%d %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",         DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const auriol_ahfl = {\n        .name        = \"Auriol AHFL temperature/humidity sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2100,\n        .long_width  = 4150,\n        .sync_width  = 0, // No sync bit used\n        .gap_limit   = 4248,\n        .reset_limit = 9150,\n        .decode_fn   = &auriol_ahfl_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/auriol_hg02832.c",
    "content": "/** @file\n    Auriol HG02832 sensor.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nLidl Auriol HG02832 sensor, also Rubicson 48957 (Transmitter for 48956).\n\nS.a. (#1161), (#1205).\n\nAlso works for the newer version HG05124A-DCF, IAN 321304_1901, version 07/2019.\nHowever, the display occasionally shows 0.1 C incorrectly, especially with odd values.\nBut this is not an error of the evaluation of a single message, the sensor sends it this way.\nPerhaps the value is averaged in the station.\n\nPWM with 252 us short, 612 us long, and 860 us sync.\nPreamble is a long pulsem, then 3 times sync pulse, sync gap, then data.\nThe 61ms packet gap is too long to capture repeats in one bitbuffer.\n\nData layout:\n\n    II HH F TTT CC\n\n- I: id, 8 bit\n- H: humidity, 8 bit\n- F: flags, 4 bit (Batt, TX-Button, Chan, Chan)\n- T: temperature, 12 bit, deg. C scale 10\n- C: checksum, 8 bit\n\n*/\n\n#include \"decoder.h\"\n\nstatic int auriol_hg02832_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int id, humidity, batt_low, button, channel;\n    int temp_raw;\n    float temp_c;\n\n    if (bitbuffer->num_rows != 2)\n        return DECODE_ABORT_EARLY;\n    if (bitbuffer->bits_per_row[0] != 1 || bitbuffer->bits_per_row[1] != 40)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_invert(bitbuffer);\n\n    b = bitbuffer->bb[1];\n\n    // They tried to implement CRC-8 poly 0x31, but (accidentally?) reset the key every new byte.\n    // (equivalent key stream is 7a 3d 86 43 b9 c4 62 31 repeated 4 times.)\n    uint8_t d0  = b[0] ^ b[1] ^ b[2] ^ b[3];\n    uint8_t chk = crc8(&d0, 1, 0x31, 0x53) ^ b[4];\n\n    if (chk)\n        return DECODE_FAIL_MIC; // prevent false positive checksum\n\n    id       = b[0];\n    humidity = b[1];\n    //flags    = b[2] >> 4;\n    batt_low = (b[2]) >> 7;\n    button   = (b[2] & 0x40) >> 6;\n    channel  = (b[2] & 0x30) >> 4;\n\n    temp_raw = (int16_t)(((b[2] & 0x0f) << 12) | (b[3] << 4)); // uses sign extend\n    temp_c   = (temp_raw >> 4) * 0.1f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Auriol-HG02832\",\n            \"id\",               \"\",             DATA_INT,    id,\n            \"channel\",          \"\",             DATA_INT,    channel + 1,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !batt_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%.0f %%\", DATA_DOUBLE, (float)humidity,\n            \"button\",           \"Button\",       DATA_INT,    button,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        \"mic\",\n        NULL,\n};\n\nr_device const auriol_hg02832 = {\n        .name        = \"Auriol HG02832, HG05124A-DCF, Rubicson 48957 temperature/humidity sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 252,\n        .long_width  = 612,\n        .sync_width  = 860,\n        .gap_limit   = 750,\n        .reset_limit = 62990, // 61ms packet gap\n        .decode_fn   = &auriol_hg02832_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/badger_orion_endpoint.c",
    "content": "/** @file\n    Orion Water Endpoint Meter.\n\n    Copyright (C) 2025 Bruno OCTAU (\\@ProfBoc75), \\@klyubin\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n#define PREAMBLE_BYTELEN   6\n#define DATA_BYTELEN      23\n\n#define PREAMBLE_BITLEN   48  //  6*8\n#define DATA_BITLEN      184  // 23*8\n\n#define MSG_MIN_BITLEN   232  // PREAMBLE+DATA\n#define MSG_MAX_BITLEN   290  // PREAMBLE+DATA+TRAILING_BITS\n\n/**\nOrion Water Endpoint Meter.\n\n- Issue #2995 open by \\@ddffnn, other key contributors \\@zuckschwerdt , \\@jjemelka, \\@klyubin , \\@shawntoffel, ... others in the issue.\n\nManufacturer: Badger Meter Inc\n\nFCCID : GIF2014W-OSE\n\n- Orion Cellular Endpoint, water meter, several models based on the Serial Number, According to https://badgermeter.widen.net/content/vodetxkyxh/original?download=false&x.app=api:\n\nSerial number ranges:\n-  30 000 000 …  59 999 999: ME or SE\n-  60 000 000 …  69 999 999: Mobile M\n-  70 000 000 …  89 999 999: Classic (CE)\n- 110 000 000 … 119 999 999: LTE\n- 120 000 000 … 129 999 999: LTE-M or LTE-MS\n- 130 000 000 … 139 999 999: C or CS\n- 140 000 000 … 148 999 999: HLA\n- 149 000 000 … 149 999 999: HLC\n- 150 000 000 … 159 999 999: HLB\n- 160 000 000 … 169 999 999: HLD\n- 170 000 000 … 179 999 999: HLFX\n- 180 000 000 … 189 999 999: HLG\n\nAll models above may not be compatible with this decoder as few of them are using mobile frequency, like LTE models.\n\nFrequency Hopping Spread Spectrum Intentional Radiators Operating within the 902-928MHz band, source : https://fcc.report/FCC-ID/GIF2014W-OSE/2499315\n\n2 Hopping options, Fixed Mode or Mobile Mode:\n- Fixed Mode, 400 Khz between channels, total of 50 channels from 904.56 Mhz to 924.56 Mhz\n- Mobile Mode, 400.55 Khz between channels, total of 48 channels from 904.45 Mhz to 923.675 Mhz\n\nFrequency channel changed every 150 secondes (#2995)\n\n- Message is encoded using IBM Whitening Algorithm.\n\nFlex decoder:\n\n    rtl_433 -X \"n=orion_endpoint,m=FSK_PCM,s=10,l=10,r=1000,preamble=aaaaec62ec62\" 2>&1 | grep codes\n\n    codes: {185}eb e1 1d 9a ed 6d 4a 4d e8 71 93 3a 78 23 57 0a ae ce 2d d8 7d 3f 4e 0\n    codes: {184}eb e1 1d 9a ed 5f 99 7c e8 71 92 31 42 62 14 0a b3 95 6e d8 7d 59 7e\n\nData layout:\n\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25\n    Sample         eb e1 1d 9a ed 6d 4a 4d e8 71 93 3a 78 23 57 0a ae ce 2d d8 7d 3f 4e 0\n    unwhiten       14 00 00 00 00 e8 79 69 02 0b 41 03 08 b4 00 00 fa b3 00 00 10 32 f4 4\n                   LL 11 11 11 11 SS SS SS SS xx |x xx RR RR RR RR DD DD DD DD 22 CC CC TT TT TT\n                                                 |\n                                              +--+---+\n                                              | xxLx |\n                                              +------+\n\n\n- LL: {8} Message length except CRC, mostly 0x14 = 20 bytes, to be confirmed.\n- II:{32} Fixed value, 0x00000000, could be reverse flow water counter ?\n- SS: {32} Serial Number, little-endian value\n- xx: Unknown, values look fixed and depends on the model, could be flags also, battery level too, to be guessed\n- L:{1} Leak\n- xx: other unknown values, flags, model, unit, battery low ? to be guessed.\n- RR: {32} Reading value, scale 10 gallon, little-endian value\n- DD: {32} Daily Reading Value, scale 10 gallon, little-endian value\n- FF:{8} Fixed value, always 0x10\n- CC:{16} CRC-16, poly 0x8005, init 0xFFFF, final XOR 0x0000, from previous 21 bytes.\n- TT: Trailing bits\n\n*/\nstatic int orion_endpoint_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[PREAMBLE_BYTELEN] = {0xaa, 0xaa, 0xec, 0x62, 0xec, 0x62};\n\n    // ibm_whiten() function added to bit_util but requires CPU cycles, as we just need XOR with fixed value this will take less resources.\n    uint8_t const ibm_whiten_key[DATA_BYTELEN] = {0xff, 0xe1, 0x1d, 0x9a, 0xed, 0x85, 0x33, 0x24, 0xea, 0x7a, 0xd2, 0x39, 0x70, 0x97, 0x57, 0x0a, 0x54, 0x7d, 0x2d, 0xd8, 0x6d, 0x0d, 0xba};\n\n    uint8_t b[DATA_BYTELEN];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n\n    if (msg_len < MSG_MIN_BITLEN || msg_len > MSG_MAX_BITLEN) {\n        decoder_logf(decoder, 1, __func__, \"Message length error: must be between 232 and 290 bits, found %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, PREAMBLE_BITLEN);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += PREAMBLE_BITLEN;\n\n    if ((msg_len - offset ) < DATA_BITLEN ) {\n        decoder_logf(decoder, 1, __func__, \"Expected %d bits, Packet too short: %d bits\", DATA_BITLEN, msg_len - offset);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, DATA_BITLEN);\n\n    // Unwhiten the data coded with IBM Whitening Algorithm LFSR.\n    // ibm_whitening(b,23); // replace by XOR from ibm_whiten_key to reduce CPU cycles and resources.\n    for (int i = 0; i < DATA_BYTELEN; i++) {\n        b[i] ^= ibm_whiten_key[i];\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, DATA_BITLEN, \"Unwhiten MSG\");\n\n    if (crc16(b, DATA_BYTELEN, 0x8005, 0xffff)) {\n        decoder_log(decoder, 1, __func__, \"CRC 16 do not match\");\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, DATA_BITLEN, \"Valid MSG\");\n\n    // uint8_t msg_len   = b[0]; not used\n    uint32_t id            = b[8] << 24 | b[7] << 16 | b[6] << 8 | b[5];\n    uint32_t flags_1       = b[9] << 16 | b[10] << 8 | b[11];\n    int leaking            = (b[10] & 0x20) >> 5;\n    uint32_t reading_raw   = b[15] << 24 | b[14] << 16 | b[13] << 8 | b[12];\n    uint32_t daily_raw     = b[19] << 24 | b[18] << 16 | b[17] << 8 | b[16];\n    uint8_t flags_2        = b[20];\n\n    // Define Endpoint Model\n    char const *endpoint_model = \"Unknown Model\";\n    if (id >= 30000000 && id <= 59999999) {\n        endpoint_model = \"ME or SE\";\n    }\n    else if (id >= 60000000 && id <= 69999999) {\n        endpoint_model = \"Mobile M\";\n    }\n    else if (id >= 70000000 && id <= 89999999) {\n        endpoint_model = \"Classic (CE)\";\n    }\n    else if (id >= 110000000 && id <= 119999999) {\n        endpoint_model = \"LTE\";\n    }\n    else if (id >= 120000000 && id <= 129999999) {\n        endpoint_model = \"LTE-M or LTE-MS\";\n    }\n    else if (id >= 130000000 && id <= 139999999) {\n        endpoint_model = \"C or CS\";\n    }\n    else if (id >= 140000000 && id <= 148999999) {\n        endpoint_model = \"HLA\";\n    }\n    else if (id >= 149000000 && id <= 149999999) {\n        endpoint_model = \"HLC\";\n    }\n    else if (id >= 150000000 && id <= 159999999) {\n        endpoint_model = \"HLB\";\n    }\n    else if (id >= 160000000 && id <= 169999999) {\n        endpoint_model = \"HLD\";\n    }\n    else if (id >= 170000000 && id <= 179999999) {\n        endpoint_model = \"HLFX\";\n    }\n    else if (id >= 180000000 && id <= 189999999) {\n        endpoint_model = \"HLG\";\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",             \"\",                    DATA_STRING, \"Orion-Endpoint\",\n            \"id\",                \"\",                    DATA_INT,    id,\n            \"endpoint_model\",    \"Endpoint Model\",      DATA_STRING, endpoint_model,\n            \"leaking\",           \"Leaking\",             DATA_INT,    leaking,\n            \"reading\",           \"Reading\",             DATA_INT,    reading_raw,\n            \"daily_reading\",     \"Daily Reading\",       DATA_COND,   daily_raw, DATA_INT, daily_raw,\n            \"flags_1\",           \"Flags-1\",             DATA_FORMAT, \"%06x\",    DATA_INT, flags_1,\n            \"flags_2\",           \"Flags-2\",             DATA_FORMAT, \"%02x\",    DATA_INT, flags_2,\n            \"mic\",               \"Integrity\",           DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"endpoint_model\",\n        \"leaking\",\n        \"reading\",\n        \"daily_reading\",\n        \"flags_1\",\n        \"flags_2\",\n        \"mic\",\n        NULL,\n};\n\nr_device const orion_endpoint = {\n        .name        = \"Orion Endpoint from Badger Meter, GIF2014W-OSE, water meter, hopping from 904.4 Mhz to 924.6Mhz (-s 1600k)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 10,\n        .long_width  = 10,\n        .reset_limit = 1000,\n        .decode_fn   = &orion_endpoint_decode,\n        .fields      = output_fields,\n};\n\nr_device const orion_endpoint_2020 = {\n        .name        = \"Orion Endpoint from Badger Meter, GIF2020OCECNA, water meter, hopping from 904.4 Mhz to 924.6Mhz (-s 1600k)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 5,\n        .long_width  = 5,\n        .reset_limit = 1000,\n        .decode_fn   = &orion_endpoint_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/badger_water.c",
    "content": "/** @file\n    Badger ORION water meter support.\n\n    Copyright (C) 2022 Nicko van Someren\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn static int badger_orion_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nBadger ORION water meter.\n\nS.a. https://fccid.io/GIF2006B\n\nFor the single-frequency models the center frequency is 916.45MHz. The bit rate is\n100KHz, so the sample rate should be at least 1.2MHz; using 1.6MHz may work better\nwhen the signal is weak or noisy.\n\nThe low-level encoding is much the same as M-Bus mode T, but the payload differs.\n\nThe specification sheet states that \"The endpoint broadcasts its unique endpoint\nserial number, current meter reading and applicable status indicators\" and also that\nstatus reports include \"Premise Leak Detection\", \"Cut-Wire Indication\", \"Reverse Flow\nIndication\", \"No Usage Indication\" and \"Encoder Error\", but the specific flag values\nare not known.\n\nThe data is preceded by several sync bytes of 01010101, followed by the ten bit\npreamble of 0000 1111 01. This is followed by 10 bytes encoded using a 4:6 NRZ\nencoding. This code treats 6 bits of the sync sequence as part of a 16 bit preamble.\n\nOnce the data has been decoded with the NRZ 6:4 decoding, it has the following format:\n- Device ID: 3 bytes, little-endian. Typically utility provider's number, mod 2^24 or mod 10^7.\n- Device flags: 1 byte. Fields not known\n- Meter reading: 3 bytes, little-endian. Value in gallons for meters with 1-gallon resolution.\n- Status flags: 1 byte. Fields not known\n- CRC: 2 bytes, crc16, polynomial 0x3D65\n\n*/\n\n// Mapping from 6 bits to 4 bits. \"3of6\" coding used for Mode T\nstatic uint8_t badger_decode_3of6(uint8_t byte)\n{\n    uint8_t out = 0xFF; // Error\n    switch(byte) {\n    case 22:    out = 0x0;  break;  // 0x16\n    case 13:    out = 0x1;  break;  // 0x0D\n    case 14:    out = 0x2;  break;  // 0x0E\n    case 11:    out = 0x3;  break;  // 0x0B\n    case 28:    out = 0x4;  break;  // 0x1C\n    case 25:    out = 0x5;  break;  // 0x19\n    case 26:    out = 0x6;  break;  // 0x1A\n    case 19:    out = 0x7;  break;  // 0x13\n    case 44:    out = 0x8;  break;  // 0x2C\n    case 37:    out = 0x9;  break;  // 0x25\n    case 38:    out = 0xA;  break;  // 0x26\n    case 35:    out = 0xB;  break;  // 0x23\n    case 52:    out = 0xC;  break;  // 0x34\n    case 49:    out = 0xD;  break;  // 0x31\n    case 50:    out = 0xE;  break;  // 0x32\n    case 41:    out = 0xF;  break;  // 0x29\n    default:    break;  // Error\n    }\n    return out;\n}\n\n// Decode the DC-free 4:6 encoding\nstatic int badger_decode_3of6_buffer(uint8_t const *bits, unsigned bit_offset, uint8_t *output)\n{\n    for (unsigned n=0; n<10; ++n) {\n        uint8_t nibble_h = badger_decode_3of6(bitrow_get_byte(bits, n*12+bit_offset) >> 2);\n        uint8_t nibble_l = badger_decode_3of6(bitrow_get_byte(bits, n*12+bit_offset+6) >> 2);\n        if ((nibble_h | nibble_l) > 15) {\n            return 1;\n        }\n        output[n] = (nibble_h << 4) | nibble_l;\n    }\n    return 0;\n}\n\nstatic int badger_orion_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static uint8_t const preamble_pattern[] = {0x54, 0x3D};\n\n    uint8_t data_in[10] = {0}; // Data from Physical layer decoded to bytes\n    data_t *data;\n\n    // Validate package length\n    // The minimum preamble is 16 bits and the payload is 10 4:6 encoded bytes.\n    // There is often a long preamble and a 64 or more bits of tail, so the maximum likely length is longer\n    if (bitbuffer->bits_per_row[0] < (16 + 12 * 10) || bitbuffer->bits_per_row[0] > (128 + 16 + 12 * 10 + 96)) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find the preamble\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n    if (bit_offset + 12 * 10 >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"Preamble found at: %u\", bit_offset);\n    bit_offset += sizeof(preamble_pattern) * 8; // skip preamble\n\n    if (badger_decode_3of6_buffer(bitbuffer->bb[0], bit_offset, data_in) < 0) {\n        return DECODE_FAIL_MIC;\n    }\n\n    uint16_t crc_read = (((uint16_t)data_in[8] << 8) | data_in[9]);\n    uint16_t crc_calc = ~crc16(data_in, 8, 0x3D65, 0);\n    if (crc_calc != crc_read) {\n        decoder_logf(decoder, 1, __func__,\n                \"Badger ORION: CRC error: Calculated 0x%X, Read 0x%X\",\n                (unsigned)crc_calc, (unsigned) crc_read);\n        return DECODE_FAIL_MIC;\n    }\n\n    uint32_t device_id = data_in[0] | (data_in[1] << 8) | (data_in[2] << 16);\n    uint8_t flags_1 = data_in[3];\n    uint32_t volume = data_in[4] | (data_in[5] << 8) | (data_in[6] << 16);\n    uint8_t flags_2 = data_in[7];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",      \"\",          DATA_STRING, \"Badger-ORION\",\n            \"id\",         \"ID\",        DATA_INT,    device_id,\n            \"flags_1\",    \"Flags-1\",   DATA_INT,    flags_1,\n            \"volume_gal\", \"Volume\",    DATA_INT,    volume,\n            \"flags_2\",    \"Flags-2\",   DATA_INT,    flags_2,\n            \"mic\",        \"Integrity\", DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n// Note: At this time the exact meaning of the flags is not known.\nstatic char const *const badger_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"flags_1\",\n        \"volume_gal\",\n        \"flags_2\",\n        \"mic\",\n        NULL,\n};\n\n// Badger ORION water meter,\n// Frequency 916.45 MHz, Bitrate 100 kbps, Modulation NRZ FSK\nr_device const badger_orion = {\n        .name        = \"Badger ORION water meter, 100kbps (-f 916.45M -s 1200k)\", // Minimum samplerate = 1.2 MHz (12 samples of 100kb/s)\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 10,   // Bit rate: 100 kb/s\n        .long_width  = 10,   // NRZ encoding (bit width = pulse width)\n        .reset_limit = 1000, //\n        .decode_fn   = &badger_orion_decode,\n        .fields      = badger_output_fields,\n};\n"
  },
  {
    "path": "src/devices/baldr_rain.c",
    "content": "/** @file\n    Baldr / RainPoint Rain Gauge protocol.\n\n    Copyright (C) 2023 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n#include \"decoder.h\"\n\n/**\nBaldr / RainPoint Rain Gauge protocol.\n\nFor Baldr Wireless Weather Station with Rain Gauge.\nSee #2394\n\nOnly reports rain. There's a separate temperature sensor captured by Nexus-TH.\n\nThe sensor sends 36 bits 13 times,\nthe packets are ppm modulated (distance coding) with a pulse of ~500 us\nfollowed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a\n1 bit, the sync gap is ~4000 us.\n\nSample data:\n\n    {36}75b000000 [0 mm]\n    {36}75b000027 [0.9 mm]\n    {36}75b000050 [2.0 mm]\n    {36}75b8000cf [5.2 mm]\n    {36}75b80017a [9.6 mm]\n    {36}75b800224 [13.9 mm]\n    {36}75b8002a3 [17.1 mm]\n\nThe data is grouped in 9 nibbles:\n\n    II IF RR RR R\n\n- I : 8 or 12-bit ID, could contain a model type nibble\n- F : 4 bit, some flags\n- R : 20 bit rain in inch/1000\n\n*/\nstatic int baldr_rain_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // we expect 36 bits but there might be a trailing 0 bit\n    if (bitbuffer->bits_per_row[r] > 37) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Reduce false positives.\n    if ((b[0] == 0 && b[2] == 0 && b[3] == 0)\n            || (b[0] == 0xff &&  b[2] == 0xff && b[3] == 0xff)) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int id      = (b[0] << 4) | (b[1] >> 4);\n    int flags   = (b[1] & 0x0f);\n    int rain_in = (b[2] << 12) | (b[3] << 4) | (b[4] >> 4);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",         DATA_STRING, \"Baldr-Rain\",\n            \"id\",           \"\",         DATA_FORMAT, \"%03x\", DATA_INT, id,\n            \"flags\",        \"Flags\",    DATA_FORMAT, \"%x\", DATA_INT, flags,\n            \"rain_in\",      \"Rain\",     DATA_FORMAT, \"%.3f in\", DATA_DOUBLE, rain_in * 0.001,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"flags\",\n        \"rain_in\",\n        NULL,\n};\n\nr_device const baldr_rain = {\n        .name        = \"Baldr / RainPoint rain gauge.\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 3000,\n        .reset_limit = 5000,\n        .decode_fn   = &baldr_rain_decode,\n        .priority    = 10, // Eliminate false positives by letting Rubicson-Temperature go earlier\n        .fields      = output_fields,\n        .disabled    = 1, // no validity, no checksum\n};\n"
  },
  {
    "path": "src/devices/baldr_therm.c",
    "content": "/** @file\n    Baldr Thermo-Hygrometer protocol.\n\n    Copyright (C) 2025 Samuel Holland <samuel@sholland.org>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n#include \"decoder.h\"\n\n/**\nBaldr E0666TH Thermo-Hygrometer, which is the remote sensor for the BaldrTherm\nB0598T4H4 Solar Thermo-Hygrometer set. There is a channel selection switch (1-3)\ninside the battery compartment.\n\nThe sensor sends 64 bits 8 times. The packets are ppm modulated (distance\ncoding) with a pulse of ~500 us followed by a short gap of ~1000 us for a 0 bit\nor a long ~2000 us gap for a 1 bit. The sync gap is ~4000 us.\n\nSame modulation as Baldr-Rain, with a format similar to Rubicson-Temperature,\nbut with more repetitions and no CRC.\n\nSample data:\n\n    1st device:\n      {64}60811bf2c0000800 [CH1, 28.3C, 44%, 3.10V battery]\n      {64}60811df380000800 [CH1, 28.5C, 56%, 3.10V battery]\n      {64}609124f2d0000800 [CH2, 29.2C, 45%, 3.10V battery]\n      {64}609121f2c0000000 [CH2, 28.9C, 44%, 3.10V battery, 13 minutes uptime]\n    2nd device:\n      {64}86811ef2d000080e [CH1, 28.6C, 45%, 2.78V battery]\n      {64}868120f2c000080e [CH1, 28.8C, 44%, 3.10V battery]\n      {64}860121f2c000080e [CH1, 28.9C, 44%, 2.51V battery]\n    3rd device:\n      {64}9c211af2d0000812 [CH3, 28.2C, 45%, 2.50V battery]\n      {64}9ca11df2e0000812 [CH3, 28.5C, 46%, 2.65V battery]\n\nThe data is grouped in 16 nibbles:\n\n    II FT TT fH H0 00 0S JJ\n\n- I : 8 bit ID, persistent after battery changes\n- F : 4 bit flags (battery ok, unused, channel number x2)\n- T : 12 bit temperature value (Celsius * 10)\n- f : always 0xf\n- H : 8 bit humidity value (percent)\n- 0 : always 0x0000\n- S : 4 bit flags (startup indicator, unused x3)\n- J : 8 bit ID, persistent after battery changes\n\nThe startup indicator transitions from 1 to 0 after 10-15 minutes.\n\n*/\nstatic int baldr_therm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 8, 64);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // we expect 64 bits but there might be a trailing 0 bit\n    if (bitbuffer->bits_per_row[r] > 65) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Reduce false positives.\n    if ((b[1] & 0x40) != 0x00 || (b[3] & 0xf0) != 0xf0 ||\n            (b[4] & 0x0f) != 0x00 || b[5] != 0x00 || (b[6] & 0xf7) != 0x00) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int id       = (b[0] << 8) | b[7];\n    int battery  = (b[1] & 0x80);\n    int channel  = ((b[1] & 0x30) >> 4) + 1;\n    int temp_raw = (int16_t)((b[1] << 12) | (b[2] << 4)); // sign-extend\n    float temp_c = (temp_raw >> 4) * 0.1f;\n    int humidity = (uint8_t)((b[3] << 4) | (b[4] >> 4));\n    int startup  = (b[6] & 0x08);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Baldr-E0666TH\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !!battery,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"startup\",          \"Startup\",      DATA_INT,    !!startup,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"startup\",\n        NULL,\n};\n\nr_device const baldr_therm = {\n        .name        = \"Baldr E0666TH Thermo-Hygrometer\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 3000,\n        .reset_limit = 5000,\n        .decode_fn   = &baldr_therm_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/blueline.c",
    "content": "/** @file\n    Blueline PowerCost Monitor protocol.\n\n    Copyright (C) 2020 Justin Brzozoski\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n#include <stdlib.h>\n#include \"decoder.h\"\n\n/**\nBlueLine Innovations Power Cost Monitor, tested with BLI-28000.\n\nMuch of the groundwork for this implementation was based on reading the source and notes from older\nimplementations, but this implementation was a fresh rewrite by Justin Brzozoski in 2020.  I would\nnot have been able to figure this out without the other implementations to look at, but I wanted an\nimplementation that didn't need to know the Kh factor or monitor ID ahead of time, which required\nchanges.\n\nSome references used include:\n\nhttps://github.com/merbanan/rtl_433/pull/38 - an abandoned pull request on rtl_433 by radredgreen\n\nhttps://github.com/CapnBry/Powermon433 - a standalone Arduino-based Blueline monitor\n\nhttp://scruss.com/blog/2013/12/03/blueline-black-decker-power-monitor-rf-packets/ - the blog post\nwhere the other authors were trading notes in the comments\n\nThe IR-reader/sensor will transmit 3 bursts every ~30 seconds.  The low-level encoding is on/off\nkeyed pulse-position modulation (OOK_PPM).  The on pulses are always 0.5ms, while the off pulses\nare either 0.5ms for logic 1 or 1.0ms for logic 0.  Each burst is 32 bits long.  The pauses\nbetween the 3 grouped bursts is roughly 100ms.\n\nData is sent less significant byte first for multi-byte fields.\n\nThe basic layout of all bursts is as follows:\n\n- First is a 1 byte header, which is always the value 0xFE.\n- Second is a 2 byte payload, which is interpreted differently based on the two lowest bits of the first byte\n- Finally is a 1 byte CRC, calculated across the 2 payload bytes (not the header)\n\nThe CRC is a CRC-8-ATM with polynomial 100000111, but it may be required to modify the payload bytes before\ncalculating it depending on message type.\n\nThere are 4 message types that can be indicated by the 2 lowest bits of the first payload byte:\n- 0: ID message (payload is not offset)\n- 1: power message (payload is offset)\n- 2: temperature/status message (payload is offset)\n- 3: energy message (payload is offset)\n\nFor the ID message (0), the CRC can be calculated directly on the payload as sent, and when the payload is\ninterpreted as a 16-bit integer it gives the ID of the transmitter.  This message is sent when the\nmonitor is first powered on and if the button on the monitor is pressed briefly.  If the button on the\nmonitor is held for >10 seconds, the monitor will change it's ID and report the new one.\n\nWhile the transmitter ID's are 16-bit, none of them can have any of the two lowest bits set or they\nwould not be able to transmit their ID as message type 0, and when the offset is calculated (see below) they\nwould also change the message type.\n\nFor the 3 other message types, the payload must be offset before calculating the CRC or\ninterpreting the data.  The offset is done by treating the whole payload as a single 16-bit integer and\nthen subtracting the ID of the transmitter.  After the offset is done, then the CRC may be calculated\nand the payload may be interpreted.\n\nNote that if the transmitter's ID isn't known, the code can't easily determine if messages other than an\nID payload are good or bad, and can't interpret their data correctly.  However, if the \"auto\" mode is enabled,\nthe system can try to learn the transmitter's ID by various methods. (See USAGE HINTS below)\n\nFor the power message (1), the offset payload gives the number of milliseconds gap between impulses for the most\nrecent impulses seen by the monitor.  To convert from this 'gap' to kilowatts, you will need your meter's\nKh value. The Kh value is written obviously on the front of most meters, and 1.0 and 7.2 are very common.\n\n    kW = (3600/gap) * Kh\n\nNote that the 'gap' value clamps to a maximum of 65533 (0xFFFD), so there is a non-zero floor when calculating the\nkW value using this report.  For example, with a Kh of 7.2, the lowest kW value you will ever see when monitoring\nthe 'gap' value is (3600/65533)*7.2 = 0.395kW.  If you need power monitoring for impulse rates slower than every\n65.533 seconds to do things like confirm that your power consumption is 0kW, you need to monitor the impulse\ncounts and timing between energy messages (see 3 below).\n\nFor the temperature message (2), the offset payload gives the temperature in an odd scaling in the last byte,\nand has some flag bits in the first byte.  The only known flag bit is the battery.  rtl_433 handles scaling back to\ndegrees celsius automatically.\n\nFor the energy message (3), the offset payload contains a continuously running power impulse accumulator. I'm\nnot sure if there is a way to reset the accumulator.  The intended way to use it is to remember the accumulator\nvalue at the beginning of a time period, and then subtract that from the value at the end of the time period.  The\naccumulator will roll over to 0 after 65535.\n\n    kWh = 0.001 * (accumulated pulses) * Kh\n\nSince the Kh value on all meters can vary, we do not handle it in rtl_433 and just report the raw millisecond\ngap and accumulated impulses as received directly from the monitor.\n\n## Usage hints:\n\nThe requirement of knowing the ID before being able to receive a message means that this decoder\nwill generally require a parameter to be useful.  When running in the default mode with no parameters,\nthe only message it is able to decode is the one that announces a monitor's ID.  So, assuming you can get to\nthe monitor to power cycle it or hit the button, this is the recommended method:\n\n- 1) Start rtl_433\n- 2) Tap the button or power cycle the monitor\n- 3) Look for the rtl_433 output indicating the BlueLine monitor ID and note the ID field\n- 4) Stop rtl_433\n- 5) Restart rtl_433, explicitly passing the ID as a parameter to this decoder\n\nFor example, if you see the ID 45364 in step 3, you would start the decoder with a command like:\n\n    rtl_433 -R 176:45364\n\nIf you are unable to access the monitor to have it send the ID message, you can also use the \"auto\" parameter:\n\n    rtl_433 -vv -R 176:auto\n\nVerbose mode should be specified first on the command line to see what the \"auto\" mode is doing.\n\nThe auto parameter will try to brute-force the ID on any messages that look like they are from a\nBlueLine monitor.  This method usually succeeds within a few minutes, but is likely to get false positives\nif there is more than one monitor in range or the messages being received are all identical (i.e. if the\nmeter is continuously reporting 0 watts).  If it succeeds, it will start reporting data with the new ID,\nwhich you should then use as a parameter when you re-run rtl_433 in the future.\n\nFinally, passing a parameter to this decoder requires specifying it explicitly, which normally disables all\nother default decoders.  If you want to pass an option to this decoder without disabling all the other defaults,\nthe simplest method is to explicitly exclude this one decoder (which implicitly says to leave all other defaults\nenabled), then add this decoder back with a parameter.  The command line looks like this:\n\n    rtl_433 -R -176 -R 176:45364\n\n*/\n\n#define BLUELINE_BITLEN      32\n#define BLUELINE_STARTBYTE   0xFE\n#define BLUELINE_CRC_POLY    0x07\n#define BLUELINE_CRC_INIT    0x00\n#define BLUELINE_CRC_BYTELEN 2\n#define BLUELINE_TXID_MSG        0x00\n#define BLUELINE_POWER_MSG       0x01\n#define BLUELINE_TEMPERATURE_MSG 0x02\n#define BLUELINE_ENERGY_MSG      0x03\n\n#define BLUELINE_ID_STEP_SIZE 4\n#define MAX_POSSIBLE_BLUELINE_IDS (65536/BLUELINE_ID_STEP_SIZE)\n#define BLUELINE_ID_GUESS_THRESHOLD 4\n\nstruct blueline_stateful_context {\n    unsigned id_guess_hits[MAX_POSSIBLE_BLUELINE_IDS];\n    uint16_t current_sensor_id;\n    unsigned searching_for_new_id;\n};\n\nstatic uint8_t rev_crc8(uint8_t const message[], unsigned nBytes, uint8_t polynomial, uint8_t remainder)\n{\n    unsigned byte, bit;\n\n    // Run a CRC backwards to find out what the init value would have been.\n    // Alternatively, put a known init value in the first byte, and it will\n    // return a value that could be used in that place to get that init.\n\n    // This logic only works assuming the polynomial has the lowest bit set,\n    // Which should be true for most CRC polynomials, but let's be safe...\n    if ((polynomial & 0x01) == 0) {\n        fprintf(stderr, \"Cannot run reverse CRC-8 with this polynomial!\\n\");\n        return 0xFF;\n    }\n    polynomial = (polynomial >> 1) | 0x80;\n\n    byte = nBytes;\n    while (byte--) {\n        bit = 8;\n        while (bit--) {\n            if (remainder & 0x01) {\n                remainder = (remainder >> 1) ^ polynomial;\n            }\n            else {\n                remainder = remainder >> 1;\n            }\n        }\n        remainder ^= message[byte];\n    }\n    return remainder;\n}\n\nstatic uint16_t guess_blueline_id(r_device *decoder, const uint8_t *current_row)\n{\n    struct blueline_stateful_context *const context = decoder_user_data(decoder);\n    const uint16_t start_value = ((current_row[2] << 8) | current_row[1]);\n    const uint8_t recv_crc = current_row[3];\n    const uint8_t rcv_msg_type = (current_row[1] & 0x03);\n    uint16_t working_value;\n    uint8_t working_buffer[2];\n    uint8_t reverse_crc_result;\n    uint16_t best_id;\n    unsigned best_hits;\n    unsigned num_at_best_hits;\n    unsigned high_byte_steps;\n\n    // TL;DR - Try all possible IDs against every incoming message, and count how many times each one\n    // succeeds.  If one of them passes a threshold, assume it must be the right one and return it.\n\n    // We do some optimizations to try and do all those checks quickly, but it's still about the\n    // same as doing a CRC across 512 bytes of data for every 2 byte payload received.\n\n    working_buffer[0] = BLUELINE_CRC_INIT;\n    working_buffer[1] = current_row[2];\n    high_byte_steps = 256;\n    best_id = 0;\n    best_hits = 0;\n    num_at_best_hits = 0;\n    while (high_byte_steps--) {\n        reverse_crc_result = rev_crc8(working_buffer, BLUELINE_CRC_BYTELEN, BLUELINE_CRC_POLY, recv_crc);\n        // Would this byte value have been usable while still being the same type of message we received?\n        if ((reverse_crc_result & 0x03) == rcv_msg_type) {\n            working_value = ((working_buffer[1] << 8) | reverse_crc_result);\n            working_value = start_value - working_value;\n            context->id_guess_hits[(working_value/BLUELINE_ID_STEP_SIZE)]++;\n            if (context->id_guess_hits[(working_value/BLUELINE_ID_STEP_SIZE)] >= best_hits) {\n                if (context->id_guess_hits[(working_value/BLUELINE_ID_STEP_SIZE)] > best_hits) {\n                    best_hits = context->id_guess_hits[(working_value / BLUELINE_ID_STEP_SIZE)];\n                    best_id = working_value;\n                    num_at_best_hits = 1;\n                } else {\n                    num_at_best_hits++;\n                }\n            }\n        }\n        working_buffer[1] += 1;\n    }\n\n    decoder_logf(decoder, 1, __func__, \"Attempting Blueline autodetect: best_hits=%u num_at_best_hits=%u\", best_hits, num_at_best_hits);\n    return ((best_hits >= BLUELINE_ID_GUESS_THRESHOLD) && (num_at_best_hits == 1)) ? best_id : 0;\n}\n\nstatic int blueline_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    struct blueline_stateful_context *const context = decoder_user_data(decoder);\n    data_t *data;\n    int row_index;\n    uint8_t *current_row;\n    int payloads_decoded = 0;\n    int most_applicable_failure = 0;\n    uint8_t calc_crc;\n    uint16_t offset_payload_u16 = 0;\n    uint8_t offset_payload_u8[BLUELINE_CRC_BYTELEN] = {0};\n\n    // Blueline uses inverted 0/1\n    bitbuffer_invert(bitbuffer);\n\n    // Look at each row we just received independently\n    for (row_index = 0; row_index < bitbuffer->num_rows; row_index++) {\n        current_row = bitbuffer->bb[row_index];\n\n        // All valid rows will have a fixed length and start with the same byte\n        if ((bitbuffer->bits_per_row[row_index] != BLUELINE_BITLEN) || (current_row[0] != BLUELINE_STARTBYTE)) {\n            if (DECODE_ABORT_LENGTH < most_applicable_failure) {\n                most_applicable_failure = DECODE_ABORT_LENGTH;\n            }\n            continue;\n        }\n\n        // We need to know which type of message to decide how to check CRC\n        const unsigned message_type = (current_row[1] & 0x03);\n        const uint8_t recv_crc = current_row[3];\n\n        if (message_type == BLUELINE_TXID_MSG) {\n            // No offset required before CRC or data handling\n            calc_crc = crc8(&current_row[1], BLUELINE_CRC_BYTELEN, BLUELINE_CRC_POLY, BLUELINE_CRC_INIT);\n        } else {\n            // Offset required before CRC or datahandling\n            offset_payload_u16 = ((current_row[2] << 8) | current_row[1]) - context->current_sensor_id;\n            offset_payload_u8[0] = (offset_payload_u16 & 0xFF);\n            offset_payload_u8[1] = (offset_payload_u16 >> 8);\n            calc_crc = crc8(&offset_payload_u8[0], BLUELINE_CRC_BYTELEN, BLUELINE_CRC_POLY, BLUELINE_CRC_INIT);\n        }\n\n        // If the CRC didn't match up, ignore this row!\n        if (calc_crc != recv_crc) {\n            if ((context->searching_for_new_id) && (message_type != BLUELINE_TXID_MSG)) {\n                uint16_t id_guess = guess_blueline_id(decoder, current_row);\n                if (id_guess != 0) {\n                    decoder_logf(decoder, 1, __func__,\"Switching to auto-detected Blueline ID %u\", id_guess);\n                    context->current_sensor_id = id_guess;\n                    context->searching_for_new_id = 0;\n                }\n            }\n            if (DECODE_FAIL_MIC < most_applicable_failure) {\n                most_applicable_failure = DECODE_FAIL_MIC;\n            }\n            continue;\n        }\n\n        if (message_type == BLUELINE_TXID_MSG) {\n            const uint16_t received_sensor_id = ((current_row[2] << 8) | current_row[1]);\n            /* clang-format off */\n            data = data_make(\n                    \"model\",        \"\",             DATA_STRING, \"Blueline-PowerCost\",\n                    \"id\",           \"\",             DATA_INT,    received_sensor_id,\n                    \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            payloads_decoded++;\n            if (context->searching_for_new_id) {\n                decoder_logf(decoder, 1, __func__,\"Switching to received Blueline ID %u\", received_sensor_id);\n                context->current_sensor_id = received_sensor_id;\n                context->searching_for_new_id = 0;\n            }\n        } else if (message_type == BLUELINE_POWER_MSG) {\n            const uint16_t ms_per_pulse = offset_payload_u16;\n            /* clang-format off */\n            data = data_make(\n                    \"model\",        \"\",             DATA_STRING, \"Blueline-PowerCost\",\n                    \"id\",           \"\",             DATA_INT,    context->current_sensor_id,\n                    \"gap\",          \"\",             DATA_INT,    ms_per_pulse,\n                    \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            payloads_decoded++;\n        } else if (message_type == BLUELINE_TEMPERATURE_MSG) {\n            // TODO - Confirm battery flag is working properly\n\n            // These were the estimates from Powermon433.\n            // But they didn't line up perfectly with my LCD display.\n            //\n            // A: deg_f = 0.823 * recvd_temp - 28.63\n            // B: deg_c = 0.457 * recvd_temp - 33.68\n\n            // I logged raw radio values and their resulting display temperatures\n            // for a range of -13 to 34 degrees C, and it's not perfectly linear.\n            // It's not so far off that I think we should use something other than\n            // a linear fit, but it's likely got some fixed-point truncation errors\n            // in the official display code.\n            //\n            // I put all the points I had into Excel and asked for the best\n            // linear estimate, and it gave me roughly this:\n            //\n            // deg_C = 0.436 * recvd_temp - 30.36\n\n            // In case anyone else wants to continue try and find a better equation,\n            // a full copy of my logged data is in the comments of this GitHub pull\n            // request:\n            //\n            // https://github.com/merbanan/rtl_433/pull/1590\n\n            const uint8_t temperature = offset_payload_u8[1];\n            const uint8_t flags = offset_payload_u8[0] >> 2;\n            const uint8_t battery = (flags & 0x20) >> 5;\n            const float temperature_C = (0.436f * temperature) - 30.36f;\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_STRING, \"Blueline-PowerCost\",\n                    \"id\",               \"\",             DATA_INT,    context->current_sensor_id,\n                    \"flags\",            \"\",             DATA_FORMAT, \"%02x\", DATA_INT, flags,\n                    \"battery_ok\",       \"Battery\",      DATA_INT,    !battery,\n                    \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature_C,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            payloads_decoded++;\n        } else { // Assume BLUELINE_ENERGY_MSG\n            // (The lowest two bits of the pulse count will always be the same because message_type is overlaid there)\n            const uint16_t pulses = offset_payload_u16;\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_STRING, \"Blueline-PowerCost\",\n                    \"id\",               \"\",             DATA_INT, context->current_sensor_id,\n                    \"impulses\",         \"\",             DATA_INT,    pulses,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            payloads_decoded++;\n        }\n    }\n\n    return ((payloads_decoded > 0) ? payloads_decoded : most_applicable_failure);\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"flags\",\n        \"gap\",\n        \"impulses\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const blueline;\n\nstatic r_device *blueline_create(char *arg)\n{\n    r_device *r_dev = decoder_create(&blueline, sizeof(struct blueline_stateful_context));\n    if (!r_dev) {\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    struct blueline_stateful_context *context = decoder_user_data(r_dev);\n\n    if (arg != NULL) {\n        if (strcmp(arg, \"auto\") == 0) {\n            // Setup for auto identification\n            context->searching_for_new_id = 1;\n            //fprintf(stderr, \"Blueline decoder will try to autodetect ID.\\n\");\n        } else {\n            // Assume user is trying to pass in hex ID\n            context->current_sensor_id = strtoul(arg, NULL, 0);\n            //fprintf(stderr, \"Blueline decoder using ID %u\\n\", context->current_sensor_id);\n        }\n    }\n\n    return r_dev;\n}\n\nr_device const blueline = {\n        .name        = \"BlueLine Innovations Power Cost Monitor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 500,\n        .long_width  = 1000,\n        .gap_limit   = 2000,\n        .reset_limit = 8000,\n        .decode_fn   = &blueline_decode,\n        .create_fn   = &blueline_create,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/blyss.c",
    "content": "/** @file\n    Generic remote Blyss DC5-UK-WH as sold by B&Q.\n\n    Copyright (C) 2016 John Jore\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGeneric remote Blyss DC5-UK-WH as sold by B&Q.\n\nDC5-UK-WH pair with receivers, the codes used may be specific to a receiver - use with caution\n\nwarm-up pulse 5552 us, 2072 gap\nshort is 512 us pulse, 1484 us gap\nlong is 1508 us pulse, 488 us gap\npacket gap is 6964 us\n\n*/\n#include \"decoder.h\"\n\nstatic int blyss_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    for (int i = 0; i < bitbuffer->num_rows; ++i) {\n        if (bitbuffer->bits_per_row[i] != 33) // last row is 32\n            continue; // DECODE_ABORT_LENGTH\n\n        uint8_t *b = bitbuffer->bb[i];\n\n        //This needs additional validation, but works on mine. Suspect each DC5-UK-WH uses different codes as the transmitter\n        //is paired to the receivers to avoid being triggered by the neighbours transmitter ?!?\n        // TODO: cleaner implementation with 2 preamble arrays\n        if (((b[0] != 0xce) || (b[1] != 0x8e) || (b[2] != 0x2a) || (b[3] != 0x6c) || (b[4] != 0x80)) &&\n                ((b[0] != 0xe7) || (b[1] != 0x37) || (b[2] != 0x7a) || (b[3] != 0x2c) || (b[4] != 0x80)))\n            continue; // DECODE_ABORT_EARLY\n\n        char id_str[16];\n        snprintf(id_str, sizeof(id_str), \"%02x%02x%02x%02x\", b[0], b[1], b[2], b[3]);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",    \"\", DATA_STRING, \"Blyss-DC5ukwh\",\n                \"id\",       \"\", DATA_STRING, id_str,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return DECODE_FAIL_SANITY;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        NULL,\n};\n\nr_device const blyss = {\n        .name        = \"Blyss DC5-UK-WH\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 500,\n        .long_width  = 1500,\n        .gap_limit   = 2500,\n        .reset_limit = 8000,\n        .decode_fn   = &blyss_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bm5.c",
    "content": "/** @file\n    bm5-v2 12V Automotive Wireless Battery Monitor.\n\n    Copyright (C) 2025 Cameron Murphy\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nbm5-v2 12V Automotive Battery Monitor.\n\nSold as \"ANCEL BM200\" on Amazon, and \"QUICKLYNKS BM5-D\" on AliExpress\n\nThe sensor transmits a single message, with all relevant data about every 1-2\nseconds at 433.92 MHz,\n\nThe transmission is inverted from the normal OOK_PULSE_PWM decoder, with a \"0\"\nrepresented as a short pulse of 225us, and a 675us gap, and a \"1\" represented as\na long 675us pulse, and a 225us gap.  The implementation below initially\ninverters the buffer to correct for this.\n\nEach message consists of a preamble (long pulse, plus eight 50% symbol length\npulses) sent at double the normal data rate, then a one byte pause (at regular\ndata rate), then ten bytes of payload, plus a one byte Checksum.  The preamble\nis decoded as (0x7F 0x80) by rtl_433 (in the native, non-inverted state) due to\nthe initial pulse.\n\nFlex decoder:  `rtl_433 -R 0 -X 'n=bm5-v2,m=OOK_PWM,s=227,l=675,r=6000,invert'`\n\n\nPayload:\n\n- I = 3 byte ID\n- S = 7 bits for battery State of Health (SOH) - 0 to 100 percent\n- C = 1 bit flag for charging system error (!CHARGING on display --probably\ntriggered if running voltage below ~13v)\n- s = 7 bits for battery State of Charge (SOC) 0 to 100 percent\n- c = 1 bit flag for cranking system error. (!CRANKING indicator on display -\ntriggered if starting voltage drops for too long -- excessive cranking)\n- T = 7 bits for sensor temperature magnitude (degrees C, converted if necessary\nin display)\n- t = 1 bit for temperature sign (0 = positive, 1 = negative)\n- V = 16 bits, little endian for current battery voltage (Voltage is displayed\nas a float with 2 significant digits.  The 16 bit int represents this voltage,\nmultiplied by 1600. -- note:  The display truncates the voltages to 2 decimal\npoints.  I've chosen to round instead of truncate, as this seems a better\nrepresentation of the true value.)\n- v = 16 bits, little endian for previous low voltage during last start.  (Is\nprobably used for the algorithm to determine battery health.  This value will be\ncloser to resting voltage for healthy batteries) Same 1600 multiplier as above.\n- R = 1 byte Checksum\n\n    msg:\nIIIIIIIIIIIIIIIIIIIIIIIISSSSSSSCssssssscTTTTTTTtVVVVVVVVVVVVVVVVvvvvvvvvvvvvvvvvRRRRRRRR\n    ID:24h SOH:7d CHARGING:1b SOC:7d CRANKING:1b TEMP:8s V_CUR:16d V_START:16d\nCHECKSUM:8h\n\n*/\n\n#include \"decoder.h\"\n\nstatic int bm5_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t b[11];\n\n    bitbuffer_invert(bitbuffer); // This device sends data inverted relative to\n                                 // the OOK_PWM decoder output.\n\n    if (bitbuffer->num_rows != 1) { // Only one message per transmission\n        return DECODE_ABORT_EARLY;\n    }\n\n    // check correct data length\n    if (bitbuffer->bits_per_row[0] != 88) { // 10 bytes data + 1 byte checksum)\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, 0, b, sizeof(b) * 8);\n\n    // reduce false positives\n    if (b[0] == 0 && b[1] == 0 && b[2] == 0 && b[10] == 0) {\n        return DECODE_FAIL_MIC; // false positive\n    }\n\n    // check for valid checksum\n    if ((unsigned char)add_bytes(&b[0], 10) != b[10]) {\n        return DECODE_FAIL_MIC; // failed checksum - invalid message\n    }\n\n    int id             = (b[0] << 16) | (b[1] << 8) | b[2];\n    int soh            = b[3] >> 1;          // State of Health encoded in 1st 7 bits\n    int charging_error = b[3] & 0x01;        // Charging error flag in bit 8 of byte 4\n    int soc            = b[4] >> 1;          // State pf Charge encoded in 1st 7 bits\n    int cranking_error = b[4] & 0x01;        // Cranking error flag in bit 8 of byte 5\n    int temp           = b[5] >> 1;          // Temperature magnitude in degrees C in first 7 bits of byte 6\n    int temp_sign      = b[5] & 0x01;        // Temperature sign in bit 8 of byte 6\n    int volt1          = (b[7] << 8) | b[6]; // Current voltage\n    int volt2          = (b[9] << 8) | b[8]; // Previous starting voltage\n                                             //\n    if (temp_sign == 1) {\n        temp = -temp; // Invert temp value\n    }\n\n    float battery_volt = volt1 * 0.000625f; // Convert transmitted values to floats.  Rounded to 2\n                                            // decimal places in \"data_make\" below.\n    float starting_volt = volt2 * 0.000625f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                       DATA_STRING,   \"BM5-v2\",\n            \"id\",               \"Device_ID\",              DATA_FORMAT,   \"%X\",            DATA_INT,          id,\n            \"health_pct\",       \"State of Health\",        DATA_FORMAT,   \"%d %%\",         DATA_INT,          soh,\n            \"cranking_error\",   \"Cranking System Error\",  DATA_INT,       cranking_error,\n            \"charge_pct\",       \"State of Charge\",        DATA_FORMAT,   \"%d %%\",         DATA_INT,          soc,\n            \"charging_error\",   \"Charging System Error\",  DATA_INT,       charging_error,\n            \"temperature_C\",    \"Temperature\",            DATA_FORMAT,   \"%.1f C\",        DATA_DOUBLE,       (float) temp,\n            \"battery_V\",        \"Current Battery Voltage\",DATA_FORMAT,   \"%.2f V\",        DATA_DOUBLE,       battery_volt,\n            \"starting_V\",       \"Starting Voltage\",       DATA_FORMAT,   \"%.2f V\",        DATA_DOUBLE,       starting_volt,\n            \"mic\",              \"Integrity\",              DATA_STRING,   \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"health_pct\",\n        \"cranking_error\",\n        \"charge_pct\",\n        \"charging_error\",\n        \"temperature_C\",\n        \"battery_V\",\n        \"starting_V\",\n        \"mic\",\n        NULL,\n};\n\nr_device const bm5 = {\n        .name        = \"bm5-v2 12V Battery Monitor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 225,\n        .long_width  = 675,\n        .reset_limit = 6000,\n        .decode_fn   = &bm5_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/brennenstuhl_rcs_2044.c",
    "content": "/** @file\n    Brennenstuhl RCS 2044 remote control on 433.92MHz likely x1527.\n\n    Copyright (C) 2015 Paul Ortyl\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nBrennenstuhl RCS 2044 remote control on 433.92MHz likely x1527.\n\nReceiver for the \"RCS 2044 N Comfort Wireless Controller Set\" sold under\nthe \"Brennenstuhl\" brand.\n\nThe protocol is also implemented for raspi controlled transmitter on 433.92 MHz:\nhttps://github.com/xkonni/raspberry-remote\n*/\n\n#include \"decoder.h\"\n\nstatic int brennenstuhl_rcs_2044_process_row(r_device *decoder, bitbuffer_t *bitbuffer, int row)\n{\n    uint8_t const *b = bitbuffer->bb[row];\n    int const length = bitbuffer->bits_per_row[row];\n    data_t *data;\n\n    /* Test bit pattern for every second bit being 1 */\n    if (25 != length\n            || (b[0]&0xaa) != 0xaa\n            || (b[1]&0xaa) != 0xaa\n            || (b[2]&0xaa) != 0xaa\n            || (b[3] != 0x80))\n        return 0; /* something is wrong, exit now */\n\n    /* Only odd bits contain information, even bits are set to 1\n     * First 5 odd bits contain system code (the dip switch on the remote),\n     * following 5 odd bits code button row pressed on the remote,\n     * following 2 odd bits code button column pressed on the remote.\n     *\n     * One can press many buttons at a time and the corresponding code will be sent.\n     * In the code below only use of a single button at a time is reported,\n     * all other messages are discarded as invalid.\n     */\n\n    /* extract bits for system code */\n    int system_code =\n            (b[0] & 0x40) >> 2\n            | (b[0] & 0x10) >> 1\n            | (b[0] & 0x04)\n            | (b[0] & 0x01) << 1\n            | (b[1] & 0x40) >> 6;\n\n    /* extract bits for pressed key row */\n    int control_key =\n            (b[1] & 0x10)\n            | (b[1] & 0x04) << 1\n            | (b[1] & 0x01) << 2\n            | (b[2] & 0x40) >> 5\n            | (b[2] & 0x10) >> 4;\n\n    /* Test if the message is valid. It is possible to press multiple keys on the\n     * remote at the same time.    As all keys are transmitted orthogonally, this\n     * information can be transmitted.    This use case is not the usual use case\n     * so we can use it for validation of the message:\n     * ONLY ONE KEY AT A TIME IS ACCEPTED.\n     */\n    char const *key = NULL;\n    if (control_key == 0x10)\n        key = \"A\";\n    else if (control_key == 0x08)\n        key = \"B\";\n    else if (control_key == 0x04)\n        key = \"C\";\n    else if (control_key == 0x02)\n        key = \"D\";\n    else if (control_key == 0x01)\n        key = \"E\"; /* (does not exist on the remote, but can be set and is accepted by receiver) */\n    else\n        return 0;\n    /* None of the keys has been pressed and we still received a message.\n     * Skip it. It happens sometimes as the last code repetition\n     */\n\n    /* extract on/off bits (first or second key column on the remote) */\n    int on_off = (b[2] & 0x04) >> 1 | (b[2] & 0x01);\n\n    if (on_off != 0x02 && on_off != 0x01)\n        return 0; /* Pressing simultaneously ON and OFF key is not useful either */\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",    \"Model\",    DATA_STRING, \"Brennenstuhl-RCS2044\",\n            \"id\",       \"id\",       DATA_INT, system_code,\n            \"key\",      \"key\",      DATA_STRING, key,\n            \"state\",    \"state\",    DATA_STRING, (on_off == 0x02 ? \"ON\" : \"OFF\"),\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa brennenstuhl_rcs_2044_process_row() */\nstatic int brennenstuhl_rcs_2044_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int counter = 0;\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        counter += brennenstuhl_rcs_2044_process_row(decoder, bitbuffer, row);\n    }\n    return counter;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"key\",\n        \"state\",\n        NULL,\n};\n\nr_device const brennenstuhl_rcs_2044 = {\n        .name        = \"Brennenstuhl RCS 2044\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 320,\n        .long_width  = 968,\n        .gap_limit   = 1500,\n        .reset_limit = 4000,\n        .decode_fn   = &brennenstuhl_rcs_2044_callback,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_3ch.c",
    "content": "/** @file\n    Bresser sensor protocol.\n\n    Copyright (C) 2015 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nBresser sensor protocol.\n\nThe protocol is for the wireless Temperature/Humidity sensor\n- Bresser Thermo-/Hygro-Sensor 3CH\n  Another sensor sold under the same generic name is handled by\n  bresser_st1005h.c.\n- also works for Renkforce DM-7511\n\nThe sensor sends 15 identical packages of 40 bits each ~60s.\nThe bits are PWM modulated with On Off Keying.\n\nA short pulse of 250 us followed by a 500 us gap is a 0 bit,\na long pulse of 500 us followed by a 250 us gap is a 1 bit,\nthere is a sync preamble of pulse, gap, 750 us each, repeated 4 times.\nActual received and demodulated timings might be 2% shorter.\n\nThe data is grouped in 5 bytes / 10 nibbles\n\n    [id] [id] [flags] [temp] [temp] [temp] [humi] [humi] [chk] [chk]\n\n- id is an 8 bit random id that is generated when the sensor starts\n- flags are 4 bits battery low indicator, test button press and channel\n- temp is 12 bit unsigned fahrenheit offset by 90 and scaled by 10\n- humi is 8 bit relative humidity percentage\n- chk is the sum of the four data bytes\n*/\n#include \"decoder.h\"\n\nstatic int bresser_3ch_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n\n    int id, battery_low, channel, temp_raw, humidity;\n    // int status, test;\n    float temp_f;\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 40);\n    if (r < 0 || bitbuffer->bits_per_row[r] > 42) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = bitbuffer->bb[r];\n    b[0] = ~b[0];\n    b[1] = ~b[1];\n    b[2] = ~b[2];\n    b[3] = ~b[3];\n    b[4] = ~b[4];\n\n    if (((b[0] + b[1] + b[2] + b[3] - b[4]) & 0xFF) != 0) {\n        decoder_log(decoder, 1, __func__, \"checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    id = b[0];\n    //status = b[1];\n    battery_low = (b[1] & 0x80) >> 7;\n    //test = (b[1] & 0x40) >> 6;\n    channel = (b[1] & 0x30) >> 4;\n\n    temp_raw = ((b[1] & 0x0F) << 8) + b[2];\n    // 12 bits allows for values -90.0 F - 319.6 F (-67 C - 159 C)\n    temp_f = (temp_raw - 900) * 0.1f;\n\n    humidity = b[3];\n\n    if ((channel == 0) || (humidity > 100) || (temp_f < -20.0) || (temp_f > 160.0)) {\n        decoder_log(decoder, 1, __func__, \"data error\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Bresser-3CH\",\n            \"id\",            \"Id\",          DATA_INT,    id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !battery_low,\n            \"temperature_F\", \"Temperature\", DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",      \"Humidity\",    DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const bresser_3ch = {\n        .name        = \"Bresser Thermo-/Hygro-Sensor 3CH\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 250,  // short pulse is ~250 us\n        .long_width  = 500,  // long pulse is ~500 us\n        .sync_width  = 750,  // sync pulse is ~750 us\n        .gap_limit   = 625,  // long gap (with short pulse) is ~500 us, sync gap is ~750 us\n        .reset_limit = 1250, // maximum gap is 1000 us (long gap + longer sync gap on last repeat)\n        .decode_fn   = &bresser_3ch_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_5in1.c",
    "content": "/** @file\n    Decoder for Bresser Weather Center 5-in-1.\n\n    Copyright (C) 2018 Daniel Krueger\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for Bresser Weather Center 5-in-1.\n\nThe compact 5-in-1 multifunction outdoor sensor transmits the data on 868.3 MHz.\nThe device uses FSK-PCM encoding,\nThe device sends a transmission every 12 seconds.\nA transmission starts with a preamble of 0xAA.\n\nDecoding borrowed from https://github.com/andreafabrizi/BresserWeatherCenter\n\nPreamble:\n\n    aa aa aa aa aa 2d d4\n\nPacket payload without preamble (203 bits):\n\n     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25\n    -----------------------------------------------------------------------------\n    ed ee 46 ff ff ff ef 9f ff 8b 7d eb ff 12 11 b9 00 00 00 10 60 00 74 82 14 00 00 00 (Rain Gauge)\n    e9 ee 46 ff ff ff ef 99 ff 8b 8b eb ff 16 11 b9 00 00 00 10 66 00 74 74 14 00 00 00 (Rain Gauge)\n    ec 3d 45 ff ff ff ef 66 ff 8e bf fe ff 13 c2 ba 00 00 00 10 99 00 71 40 01 00 00 00 (Rain Gauge)\n    eb 28 c4 ff ff ff ef ea fe 8e ff ff ff 14 d7 3b 00 00 00 10 15 01 71 00 00 00 00 00 (Rain Gauge, immediately after reset)\n    e9 28 44 ff ff ff ef 6a ff 8e fe ff ff 16 d7 bb 00 00 00 10 95 00 71 01 00 00 00 00 (same Rain Gauge, 90 min after reset)\n    ee 93 7f f7 bf fb ef 9e fe ae bf ff ff 11 6c 80 08 40 04 10 61 01 51 40 00 00\n    ed 93 7f ff 0f ff ef b8 fe 7d bf ff ff 12 6c 80 00 f0 00 10 47 01 82 40 00 00\n    eb 93 7f eb 9f ee ef fc fc d6 bf ff ff 14 6c 80 14 60 11 10 03 03 29 40 00 00\n    ed 93 7f f7 cf f7 ef ed fc ce bf ff ff 12 6c 80 08 30 08 10 12 03 31 40 00 00\n    f1 fd 7f ff af ff ef bd fd b7 c9 ff ff 0e 02 80 00 50 00 10 42 02 48 36 00 00 00 00 (from https://github.com/merbanan/rtl_433/issues/719#issuecomment-388896758)\n    ee b7 7f ff 1f ff ef cb fe 7b d7 fc ff 11 48 80 00 e0 00 10 34 01 84 28 03 00 (from https://github.com/andreafabrizi/BresserWeatherCenter)\n    e3 fd 7f 89 7e 8a ed 68 fe af 9b fd ff 1c 02 80 76 81 75 12 97 01 50 64 02 00 00 00 (Large Wind Values, Gust=37.4m/s Avg=27.5m/s from https://github.com/merbanan/rtl_433/issues/1315)\n    ef a1 ff ff 1f ff ef dc ff de df ff 7f 10 5e 00 00 e0 00 10 23 00 21 20 00 80 00 00 (low batt +ve temp)\n    ed a1 ff ff 1f ff ef 8f ff d6 df ff 77 12 5e 00 00 e0 00 10 70 00 29 20 00 88 00 00 (low batt -ve temp -7.0C)\n    ec 91 ff ff 1f fb ef e7 fe ad ed ff f7 13 6e 00 00 e0 04 10 18 01 52 12 00 08 00 00 (good batt -ve temp)\n    ed a8 7f ff 3f ff ef f0 ff f0 fb ff ff 12 57 80 00 c0 00 10 0f 00 0f 04 00 00 00 00 (bad temp /hum data)\n    CC CC CC CC CC CC CC CC CC CC CC CC CC uu II sS GG DG WW  W TT  T HH RR RR Bt\n                                              G-MSB ^     ^ W-MSB  (strange but consistent order)\n\n- C = check, inverted data of 13 byte further\n- uu = checksum (number/count of set bits within bytes 14-25)\n- I = station ID (maybe)\n- s = startup, MSb is 0b0 after power-on/reset and 0b1 after 1 hour\n- S = sensor type, 0x9/0xA/0xB for Bresser Professional Rain Gauge\n- G = wind gust in 1/10 m/s, normal binary coded, GGxG = 0x76D1 => 0x0176 = 256 + 118 = 374 => 37.4 m/s.  MSB is out of sequence.\n- D = wind direction 0..F = N..NNE..E..S..W..NNW\n- W = wind speed in 1/10 m/s, BCD coded, WWxW = 0x7512 => 0x0275 = 275 => 27.5 m/s. MSB is out of sequence.\n- T = temperature in 1/10 °C, BCD coded, TTxT = 1203 => 31.2 °C, 0xf on error\n- t = temperature sign, minus if unequal 0\n- H = humidity in percent, BCD coded, HH = 23 => 23 %, 0xf on error\n- R = rain in mm, BCD coded, RRRR = 1203 => 031.2 mm\n- B = battery, 0=Ok, 8=Low\n*/\n\nstatic int bresser_5in1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xaa, 0x2d, 0xd4};\n\n    data_t *data;\n    uint8_t msg[26];\n    uint16_t sensor_id;\n    unsigned len = 0;\n\n    if (bitbuffer->num_rows != 1\n            || bitbuffer->bits_per_row[0] < 248\n            || bitbuffer->bits_per_row[0] > 440) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof (preamble_pattern) * 8);\n\n    if (start_pos == bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n    start_pos += sizeof (preamble_pattern) * 8;\n    len = bitbuffer->bits_per_row[0] - start_pos;\n    if (((len + 7) / 8) < sizeof (msg)) {\n        decoder_logf(decoder, 2, __func__, \"%u too short\", len);\n        return DECODE_ABORT_LENGTH; // message too short\n    }\n    // truncate any excessive bits\n    len = MIN(len, sizeof (msg) * 8);\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, len);\n\n    // First 13 bytes need to match inverse of last 13 bytes\n    for (unsigned col = 0; col < sizeof (msg) / 2; ++col) {\n        if ((msg[col] ^ msg[col + 13]) != 0xff) {\n            decoder_logf(decoder, 2, __func__, \"Parity wrong at %u\", col);\n            return DECODE_FAIL_MIC; // message isn't correct\n        }\n    }\n\n    // check popcount\n    // uu = checksum (number/count of set bits within bytes 14-25)\n\n    sensor_id = msg[14];\n\n    int temp_ok = (msg[20] & 0x0f) <= 9; // BCD, 0x0f on error\n    int temp_raw = (msg[20] & 0x0f) + ((msg[20] & 0xf0) >> 4) * 10 + (msg[21] &0x0f) * 100;\n    if (msg[25] & 0x0f)\n        temp_raw = -temp_raw;\n    float temperature = temp_raw * 0.1f;\n\n    int humidity_ok = (msg[22] & 0x0f) <= 9; // BCD, 0x0f on error\n    int humidity = (msg[22] & 0x0f) + ((msg[22] & 0xf0) >> 4) * 10;\n\n    float wind_direction_deg = ((msg[17] & 0xf0) >> 4) * 22.5f;\n\n    int gust_raw = ((msg[17] & 0x0f) << 8) + msg[16]; //fix merbanan/rtl_433#1315\n    float wind_gust = gust_raw * 0.1f;\n\n    int wind_raw = (msg[18] & 0x0f) + ((msg[18] & 0xf0) >> 4) * 10 + (msg[19] & 0x0f) * 100; //fix merbanan/rtl_433#1315\n    float wind_avg = wind_raw * 0.1f;\n\n    int rain_raw = (msg[23] & 0x0f) + ((msg[23] & 0xf0) >> 4) * 10 + (msg[24] & 0x0f) * 100 + ((msg[24] & 0xf0) >> 4) * 1000;\n    float rain = rain_raw * 0.1f;\n\n    int battery_low = (msg[25] & 0x80);\n\n    int sensor_type = (msg[15] & 0x7f);\n\n    /* check if the message is from a Bresser Professional Rain Gauge */\n    if ((sensor_type >= 0x39) && (sensor_type <= 0x3b)) {\n        // rescale the rain sensor readings\n        rain = rain * 2.5f;\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Bresser-ProRainGauge\",\n                \"id\",               \"\",             DATA_INT,    sensor_id,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_COND,   temp_ok,       DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\",     DATA_DOUBLE, rain,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n    } else {\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Bresser-5in1\",\n                \"id\",               \"\",             DATA_INT,    sensor_id,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_COND,   temp_ok,       DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"humidity\",         \"Humidity\",     DATA_COND,   humidity_ok,   DATA_INT,    humidity,\n                \"wind_max_m_s\",     \"Wind Gust\",    DATA_FORMAT, \"%.1f m/s\",    DATA_DOUBLE, wind_gust,\n                \"wind_avg_m_s\",     \"Wind Speed\",   DATA_FORMAT, \"%.1f m/s\",    DATA_DOUBLE, wind_avg,\n                \"wind_dir_deg\",     \"Direction\",    DATA_FORMAT, \"%.1f\",        DATA_DOUBLE, wind_direction_deg,\n                \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\",     DATA_DOUBLE, rain,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n    }\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_max_m_s\",\n        \"wind_avg_m_s\",\n        \"wind_dir_deg\",\n        \"rain_mm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const bresser_5in1 = {\n        .name        = \"Bresser Weather Center 5-in-1\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 124,\n        .long_width  = 124,\n        .reset_limit = 25000,\n        .decode_fn   = &bresser_5in1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_6in1.c",
    "content": "/** @file\n    Decoder for Bresser Weather Center 6-in-1.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for Bresser Weather Center 6-in-1.\n\n- also Bresser Weather Center 7-in-1 indoor sensor.\n- also Bresser new 5-in-1 sensors.\n- also Froggit WH6000 sensors.\n- also rebranded as Ventus C8488A (W835)\n- also Bresser 3-in-1 Professional Wind Gauge / Anemometer, PN 7002531\n- also Bresser soil temperature and moisture meter, PN 7009972\n- also Bresser Thermo-/Hygro-Sensor 7 Channel 868 MHz, PN 7009999\n- also Bresser High Precision Thermo-/Hygro-Sensor 7 Channel 868 MHz, PN 7009971\n- also Bresser Pool / Spa Thermometer, PN 7009973 (STYPE = 3)\n- also SENCOR SWS 9898\n- also Ambient Weather TX-3110B Wireless Thermo-Hygrometer\n- also likely Ambient Weather TX-3102 Soil Moisture Meter & Thermometer (unconfirmed)\n- also likely Ambient Weather TX-3107 Floating Pool and Spa Thermometer (unconfirmed)\n\nThere are at least two different message types:\n- 24 seconds interval for temperature, hum, uv and rain (alternating messages)\n- 12 seconds interval for wind data (every message)\n\nAlso Bresser Explore Scientific SM60020 Soil moisture Sensor.\nhttps://www.bresser.de/en/Weather-Time/Accessories/EXPLORE-SCIENTIFIC-Soil-Moisture-and-Soil-Temperature-Sensor.html\n\nMoisture:\n\n    f16e 187000e34 7 ffffff0000 252 2 16 fff 004 000 [25,2, 99%, CH 7]\n    DIGEST:16h ID?32h STYPE:4h STARTUP:1b CH:3d WSPEED?~8h~4h ~4h~8h WDIR?12h ?4h | TEMP:8h.4h TNEG:1b ?1b BATT:1b ?1b MOIST:8h | UV?~12h ?4h CHKSUM:8h\n\nMoisture is transmitted in the humidity field as index 1-16: 0, 7, 13, 20, 27, 33, 40, 47, 53, 60, 67, 73, 80, 87, 93, 99.\nThe Wind speed and direction fields decode to valid zero but we exclude them from the output.\nA Moisture message is identical to a Temperature message but with a Sensor type of 4, wind data is not valid.\n\n    aaaa2dd4e3ae1870079341ffffff0000221201fff279 [Batt ok]\n    aaaa2dd43d2c1870079341ffffff0000219001fff2fc [Batt low]\n\n    {206}55555555545ba83e803100058631ff11fe6611ffffffff01cc00 [Hum 96% Temp 3.8 C Wind 0.7 m/s]\n    {205}55555555545ba999263100058631fffffe66d006092bffe0cff8 [Hum 95% Temp 3.0 C Wind 0.0 m/s]\n    {199}55555555545ba840523100058631ff77fe668000495fff0bbe [Hum 95% Temp 3.0 C Wind 0.4 m/s]\n    {205}55555555545ba94d063100058631fffffe665006092bffe14ff8\n    {206}55555555545ba860703100058631fffffe6651ffffffff0135fc [Hum 95% Temp 3.0 C Wind 0.0 m/s]\n    {205}55555555545ba924d23100058631ff99fe68b004e92dffe073f8 [Hum 96% Temp 2.7 C Wind 0.4 m/s]\n    {202}55555555545ba813403100058631ff77fe6810050929ffe1180 [Hum 94% Temp 2.8 C Wind 0.4 m/s]\n    {205}55555555545ba98be83100058631fffffe6130050929ffe17800 [Hum 95% Temp 2.8 C Wind 0.8 m/s]\n\n    2dd4  1f 40 18 80 02 c3 18 ff 88 ff 33 08 ff ff ff ff 80 e6 00 [Hum 96% Temp 3.8 C Wind 0.7 m/s]\n    2dd4  cc 93 18 80 02 c3 18 ff ff ff 33 68 03 04 95 ff f0 67 3f [Hum 95% Temp 3.0 C Wind 0.0 m/s]\n    2dd4  20 29 18 80 02 c3 18 ff bb ff 33 40 00 24 af ff 85 df    [Hum 95% Temp 3.0 C Wind 0.4 m/s]\n    2dd4  a6 83 18 80 02 c3 18 ff ff ff 33 28 03 04 95 ff f0 a7 3f\n    2dd4  30 38 18 80 02 c3 18 ff ff ff 33 28 ff ff ff ff 80 9a 7f [Hum 95% Temp 3.0 C Wind 0.0 m/s]\n    2dd4  92 69 18 80 02 c3 18 ff cc ff 34 58 02 74 96 ff f0 39 3f [Hum 96% Temp 2.7 C Wind 0.4 m/s]\n    2dd4  09 a0 18 80 02 c3 18 ff bb ff 34 08 02 84 94 ff f0 8c 0  [Hum 94% Temp 2.8 C Wind 0.4 m/s]\n    2dd4  c5 f4 18 80 02 c3 18 ff ff ff 30 98 02 84 94 ff f0 bc 00 [Hum 95% Temp 2.8 C Wind 0.8 m/s]\n\n    {147} 5e aa 18 80 02 c3 18 fa 8f fb 27 68 11 84 81 ff f0 72 00 [Temp 11.8 C  Hum 81%]\n    {149} ae d1 18 80 02 c3 18 fa 8d fb 26 78 ff ff ff fe 02 db f0\n    {150} f8 2e 18 80 02 c3 18 fc c6 fd 26 38 11 84 81 ff f0 68 00 [Temp 11.8 C  Hum 81%]\n    {149} c4 7d 18 80 02 c3 18 fc 78 fd 29 28 ff ff ff fe 03 97 f0\n    {149} 28 1e 18 80 02 c3 18 fb b7 fc 26 58 ff ff ff fe 02 c3 f0\n    {150} 21 e8 18 80 02 c3 18 fb 9c fc 33 08 11 84 81 ff f0 b7 f8 [Temp 11.8 C  Hum 81%]\n    {149} 83 ae 18 80 02 c3 18 fc 78 fc 29 28 ff ff ff fe 03 98 00\n    {150} 5c e4 18 80 02 c3 18 fb ba fc 26 98 11 84 81 ff f0 16 00 [Temp 11.8 C  Hum 81%]\n    {148} d0 bd 18 80 02 c3 18 f9 ad fa 26 48 ff ff ff fe 02 ff f0\n\nWind and Temperature/Humidity or Rain:\n\n    IF-TEMP: DIGEST:16h ID:32h STYPE:4h STARTUP:1b CH:3d WSPEED:~8h~4h ~4h~8h WDIR:12h ?4h | TEMP:8h.4h TNEG:1b ?1b BATT:1b ?1b HUM:8h . | UV?~12h RAINFLAG:4h CHKSUM:8h\n    IF-RAIN: DIGEST:16h ID:32h STYPE:4h STARTUP:1b CH:3d WSPEED:~8h~4h ~4h~8h WDIR:12h ?4h | RAIN:~24h .......................... | UV:12h RAINFLAG:4h CHKSUM:8h\n\nDigest is LFSR-16 gen 0x8810 key 0x5412, excluding the add-checksum and trailer.\nChecksum is 8-bit add (with carry) to 0xff.\n\nNotes on different sensors:\n\n- 1910 084d 18 : RebeckaJohansson, VENTUS W835\n- 2030 088d 10 : mvdgrift, Wi-Fi Colour Weather Station with 5in1 Sensor, Art.No.: 7002580, ff 01 in the UV field is (obviously) invalid.\n- 1970 0d57 18 : danrhjones, bresser 5-in-1 model 7002580, no UV\n- 18b0 0301 18 : konserninjohtaja 6-in-1 outdoor sensor\n- 18c0 0f10 18 : rege245 BRESSER-PC-Weather-station-with-6-in-1-outdoor-sensor\n- 1880 02c3 18 : f4gqk 6-in-1\n- 18b0 0887 18 : npkap\n*/\n\nstatic int bresser_6in1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0x2d, 0xd4};\n\n    int const moisture_map[] = {0, 7, 13, 20, 27, 33, 40, 47, 53, 60, 67, 73, 80, 87, 93, 99}; // scale is 20/3\n\n    uint8_t msg[18];\n\n    if (bitbuffer->num_rows != 1\n            || bitbuffer->bits_per_row[0] < 160\n            || bitbuffer->bits_per_row[0] > 440) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n\n    unsigned const start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof (preamble_pattern) * 8)\n            + sizeof (preamble_pattern) * 8;\n\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    unsigned const len = bitbuffer->bits_per_row[0] - start_pos;\n    if (len < sizeof(msg) * 8) {\n        decoder_logf(decoder, 2, __func__, \"%u too short\", len);\n        return DECODE_ABORT_LENGTH; // message too short\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"\");\n\n    // LFSR-16 digest, generator 0x8810 init 0x5412\n    int const chkdgst = (msg[0] << 8) | msg[1];\n    int const digest  = lfsr_digest16(&msg[2], 15, 0x8810, 0x5412);\n    if (chkdgst != digest) {\n        decoder_logf(decoder, 2, __func__, \"Digest check failed %04x vs %04x\", chkdgst, digest);\n        return DECODE_FAIL_MIC;\n    }\n    // Checksum, add with carry\n    int const chksum = msg[17];\n    int const sum    = add_bytes(&msg[2], 16); // msg[2] to msg[17]\n    if ((sum & 0xff) != 0xff) {\n        decoder_logf(decoder, 2, __func__, \"Checksum failed %04x vs %04x\", chksum, sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    uint32_t const id = ((uint32_t)msg[2] << 24) | (msg[3] << 16) | (msg[4] << 8) | (msg[5]);\n    int const s_type  = (msg[6] >> 4); // 1: weather station, 2: thermo/hygro sensor, 3: pool thermometer, 4: soil probe\n    int const startup = (msg[6] >> 3) & 1; // s.a. #1214\n    int const chan    = (msg[6] & 0x7);\n    int const battery = (msg[13] >> 1) & 1; // b[13] & 0x02 is battery_good, s.a. #1993\n\n    // temperature, humidity, shared with rain counter, only if valid BCD digits\n    int const temp_ok   = msg[12] <= 0x99 && (msg[13] & 0xf0) <= 0x90;\n    int const temp_raw  = (msg[12] >> 4) * 100 + (msg[12] & 0x0f) * 10 + (msg[13] >> 4);\n    int const temp_sign = (msg[13] >> 3) & 1;\n    float temp_c  = temp_raw * 0.1f;\n    if (temp_sign) {\n        temp_c = (temp_raw - 1000) * 0.1f;\n    }\n    // Correction for Bresser 3-in-1 Professional Wind Gauge, PN 7002531\n    if (temp_c < -50.0) {\n        temp_c = -temp_raw * 0.1f;\n    }\n\n    int const humidity = (msg[14] >> 4) * 10 + (msg[14] & 0x0f);\n\n    // apparently ff01 or 0000 if not available, ???0 if valid inverted BCD\n    int uv_ok  = (msg[16] & 0x0f) == 0 && (~msg[15] & 0xff) <= 0x99 && (~msg[16] & 0xf0) <= 0x90;\n    int const uv_raw = ((~msg[15] & 0xf0) >> 4) * 100 + (~msg[15] & 0x0f) * 10 + ((~msg[16] & 0xf0) >> 4);\n    float const uvi   = uv_raw * 0.1f;\n    int const flags  = (msg[16] & 0x0f); // looks like some flags, not sure\n\n    //int const unk_ok  = (msg[16] & 0xf0) == 0xf0;\n    //int const unk_raw = ((msg[15] & 0xf0) >> 4) * 10 + (msg[15] & 0x0f);\n\n    // invert 3 bytes wind speeds\n    msg[7] ^= 0xff;\n    msg[8] ^= 0xff;\n    msg[9] ^= 0xff;\n    int wind_ok = (msg[7] <= 0x99) && (msg[8] <= 0x99) && (msg[9] <= 0x99);\n\n    int const gust_raw    = (msg[7] >> 4) * 100 + (msg[7] & 0x0f) * 10 + (msg[8] >> 4);\n    float const wind_gust = gust_raw * 0.1f;\n    int const wavg_raw    = (msg[9] >> 4) * 100 + (msg[9] & 0x0f) * 10 + (msg[8] & 0x0f);\n    float const wind_avg  = wavg_raw * 0.1f;\n    int const wind_dir    = ((msg[10] & 0xf0) >> 4) * 100 + (msg[10] & 0x0f) * 10 + ((msg[11] & 0xf0) >> 4);\n\n    // rain counter, inverted 3 bytes BCD, shared with temp/hum, only if valid digits\n    msg[12] ^= 0xff;\n    msg[13] ^= 0xff;\n    msg[14] ^= 0xff;\n    int const rain_ok   = msg[16] & 1;\n    //int const rain_ok   = msg[12] <= 0x99 && msg[13] <= 0x99 && msg[14] <= 0x99;\n    int const rain_raw  = (msg[12] >> 4) * 100000 + (msg[12] & 0x0f) * 10000\n            + (msg[13] >> 4) * 1000 + (msg[13] & 0x0f) * 100\n            + (msg[14] >> 4) * 10 + (msg[14] & 0x0f);\n    float const rain_mm = rain_raw * 0.1f;\n\n    // the thermo/hygro sensor and the soil moisture sensor might present valid readings\n    // but do not have the hardware\n    if (s_type == 2 || s_type == 4) {\n        wind_ok = 0;\n        uv_ok = 0;\n    }\n\n    int moisture = -1;\n    if (s_type == 4 && temp_ok && humidity >= 1 && humidity <= 16)\n        moisture = moisture_map[humidity - 1];\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Bresser-6in1\",\n            \"id\",               \"\",             DATA_FORMAT, \"%08x\", DATA_INT,    id,\n            \"channel\",          \"\",             DATA_INT,    chan,\n            \"battery_ok\",       \"Battery\",      DATA_COND, !rain_ok, DATA_INT,    battery,\n            \"temperature_C\",    \"Temperature\",  DATA_COND, temp_ok, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_COND, temp_ok && moisture < 0, DATA_INT,    humidity,\n            \"sensor_type\",      \"Sensor type\",  DATA_INT,    s_type,\n            \"moisture\",         \"Moisture\",     DATA_COND, moisture >= 0, DATA_FORMAT, \"%d %%\", DATA_INT, moisture,\n            \"wind_max_m_s\",     \"Wind Gust\",    DATA_COND, wind_ok, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_gust,\n            \"wind_avg_m_s\",     \"Wind Speed\",   DATA_COND, wind_ok, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_avg,\n            \"wind_dir_deg\",     \"Direction\",    DATA_COND, wind_ok, DATA_INT,    wind_dir,\n            \"rain_mm\",          \"Rain\",         DATA_COND, rain_ok, DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n            //\"unknown\",          \"Unknown\",      DATA_COND, unk_ok, DATA_INT,    unk_raw,\n            \"uvi\",              \"UV Index\",     DATA_COND, uv_ok, DATA_FORMAT, \"%.1f\", DATA_DOUBLE,    uvi,\n            \"startup\",          \"Startup\",      DATA_COND,   startup,   DATA_INT,    startup,\n            \"flags\",            \"Flags\",        DATA_INT,    flags,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"sensor_type\",\n        \"moisture\",\n        \"wind_max_m_s\",\n        \"wind_avg_m_s\",\n        \"wind_dir_deg\",\n        \"rain_mm\",\n        \"uvi\",\n        \"startup\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const bresser_6in1 = {\n        .name        = \"Bresser Weather Center 6-in-1, 7-in-1 indoor, soil, new 5-in-1, 3-in-1 wind gauge, Froggit WH6000, Ventus C8488A\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 124,\n        .long_width  = 124,\n        .reset_limit = 25000,\n        .decode_fn   = &bresser_6in1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_7in1.c",
    "content": "/** @file\n    Decoder for Bresser Weather Center 7-in-1 and Air quality sensors.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n#define SENSOR_TYPE_WEATHER   1\n#define SENSOR_TYPE_AIR_PM    8\n#define SENSOR_TYPE_CO2      10\n#define SENSOR_TYPE_HCHO_VOC 11\n#define SENSOR_TYPE_WEATHER3 12\n#define SENSOR_TYPE_WEATHER8 13\n\n/**\nDecoder for Bresser Weather Center 7-in-1 and Air quality sensors.\n- Air Quality PM2.5/PM10 PN 7009970\n- CO2 sensor             PN 7009977\n- HCHO/VOC sensor        PN 7009978\n- 3-in-1 Weather Station PN 7002530\n- 8-in-1 Weather Station PN 7003150\n\nSee\nhttps://github.com/merbanan/rtl_433/issues/1492\nand\nhttps://github.com/merbanan/rtl_433/issues/2693\nand\nhttps://github.com/merbanan/rtl_433/issues/3424\n\nPreamble:\n\n    aa aa aa aa aa 2d d4\n\nObserved length depends on reset_limit.\nThe data (not including STYPE, STARTUP, CH and maybe ID) has a whitening of 0xaa.\n\nWeather Center\nData layout:\n\n    {271}631d05c09e9a18abaabaaaaaaaaa8adacbacff9cafcaaaaaaa000000000000000000\n\n\n    {262}10b8b4a5a3ca10aaaaaaaaaaaaaa8bcacbaaaa2aaaaaaaaaaa0000000000000000 [0.08 klx]\n    {220}543bb4a5a3ca10aaaaaaaaaaaaaa8bcacbaaaa28aaaaaaaaaa00000 [0.08 klx]\n    {273}2492b4a5a3ca10aaaaaaaaaaaaaa8bdacbaaaa2daaaaaaaaaa0000000000000000000 [0.08klx]\n\n    {269}9a59b4a5a3da10aaaaaaaaaaaaaa8bdac8afea28a8caaaaaaa000000000000000000 [54.0 klx UV=2.6]\n    {230}fe15b4a5a3da10aaaaaaaaaaaaaa8bdacbba382aacdaaaaaaa00000000 [109.2klx   UV=6.7]\n    {254}2544b4a5a32a10aaaaaaaaaaaaaa8bdac88aaaaabeaaaaaaaa00000000000000 [200.000 klx UV=14\n\n    DIGEST:8h8h ID?8h8h WDIR:8h4h 4h 8h WGUST:8h.4h WAVG:8h.4h RAIN:8h8h4h.4h RAIN?:8h TEMP:8h.4hC FLAGS?:4h HUM:8h% LIGHT:8h4h,8h4hKL UV:8h.4h TRAILER:8h8h8h4h\n\n\nUnit of light is kLux (not W/m²).\n\nAir Quality Sensor PM2.5 / PM10 Sensor (PN 7009970)\nData layout:\n\n    DIGEST:8h8h ID?8h8h ?8h8h STYPE:4h STARTUP:1b CH:3b ?8h 4h ?4h8h4h PM_2_5:4h8h4h PM10:4h8h4h ?4h ?8h4h BATT:1b ?3b ?8h8h8h8h8h8h TRAILER:8h8h8h\n\nAir Quality Sensor CO2 (PN 7009977) : issue #2813\n\nFrom user manual , co2 ppm is from 400 to 5000 ppm, so it's 16 bits coded.\n\nSamples :\nRaw :\n                  SType Startup & Channel\n\n                      | |\n    {207}dab6d782acd9 a 1 ad9aad9aad9aaaaaaaaaaaaaaaaae99aaaaa00 Type = 0xa = 10, Startup = 0, ch = 1\n    {207}04a9d782acd8 a 1 ad9aad9aad9aaaaaaaaaaaaaaaaae99aaaaa00 Type = 0xa = 10, Startup = 0, ch = 1\n    {207}04a9d782acd8 a 1 ad9aad9aad9aaaaaaaaaaaaaaaaae99aaaaa00 Type = 0xa = 10, Startup = 0, ch = 1\n    {207}0dd1d782b8ee a 1 ad9aad9aad9aaaaaaaaaaaaaaaaae99aaaaa00 Type = 0xa = 10, Startup = 0, ch = 1\n\nData layout raw :\n    DIGEST:16h ID:16h 8x8x STYPE:4h STARTUP:1b CH:3d 8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x TRAILER:8x\n\nXOR / de-whitened :\n\n          0 1  2 3  4 5  6 7 8 9101112131415161718192021222324\n       DIGEST   ID  ppm                  bat\n            |    |    |                    |\n    {200}701c 7d28 0673 0b073007300730000000000000000043300000 [ XOR from g001_868.34M_1000k.cu8 co2 ppm  673]\n    {200}ae03 7d28 0672 0b073007300730000000000000000043300000 [ XOR from g001_868.34M_250k.cu8  co2 ppm  672]\n    {200}ae03 7d28 0672 0b073007300730000000000000000043300000 [ XOR from g002_868.34M_1000k.cu8 co2 ppm  672]\n    {200}a77b 7d28 1244 0b073007300730000000000000000043300000 [ XOR from g002_868.34M_250k.cu8  co2 ppm 1244]\n\nData layout de-whitened :\n    DIGEST:16h ID:16h PPM:16h 8x8x8x8x8x8x8x8x8x8x4x BATT:1b 3x8x8x8x8x8x8x TRAILER:16x\n\nAir Quality Sensor HCHO/VOC (PN 7009978) : issue #2814\n\nFrom user manual , hcho ppb is from 0 to 1000 ppm, so it's 16 bits coded.\n              and voc level is from 1 (bad air quality) to 5 (good air quality), so it's 4 bits coded.\n\nSamples:\nRaw :\n                  SType Startup & Channel\n                      | |\n    {207}3f2dc4a5aaaf b 1 aaa8aaa8aaa8aaaaaaaaaaaaaaaae9feaaaa00 Type = 0xb = 11, Startup = 0, ch = 1\n    {207}0c1cc4a5aaaf b 1 aaa8aaa8aaa8aaaaaaaaaaaaaaaae9ffaaaa00 Type = 0xb = 11, Startup = 0, ch = 1\n    {207}3f2dc4a5aaaf b 1 aaa8aaa8aaa8aaaaaaaaaaaaaaaae9feaaaa00 Type = 0xb = 11, Startup = 0, ch = 1\n    {207}0c1cc4a5aaaf b 1 aaa8aaa8aaa8aaaaaaaaaaaaaaaae9ffaaaa00 Type = 0xb = 11, Startup = 0, ch = 1\n    {207}61afc4a5aaa2 b 9 aaa8aaa8aaa9aaaaaaaaaaaaaaaae9f8aaaa00 Type = 0xb = 11, Startup = 1, ch = 1\n    {207}ecddc4a5aaae b 9 aaa8aaa8aaa9aaaaaaaaaaaaaaaae9fbaaaa00 Type = 0xb = 11, Startup = 1, ch = 1\n\nData layout raw :\n    DIGEST:16h ID:16h 8x8x STYPE:4h STARTUP:1b CH:3d 8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x8x TRAILER:8x\n\nXOR / de-whitened :\n\n          0 1  2 3  4 5  6 7 8 9101112131415161718192021 22 2324\n       DIGEST   ID  ppb                  bat            voc\n            |    |    |                    |              |\n    {200}9587 6e0f 0005 1b0002000200020000000000000000435 4 0000 [XOR from g001_868.34M_1000k.cu8 hcho_ppb 5 voc_level 4]\n    {200}a6b6 6e0f 0005 1b0002000200020000000000000000435 5 0000 [XOR from g001_868.34M_250k.cu8  hcho_ppb 5 voc_level 5]\n    {200}9587 6e0f 0005 1b0002000200020000000000000000435 4 0000 [XOR from g002_868.34M_1000k.cu8 hcho_ppb 5 voc_level 4]\n    {200}a6b6 6e0f 0005 1b0002000200020000000000000000435 5 0000 [XOR from g001_868.34M_250k.cu8  hcho_ppb 5 voc_level 5]\n    {200}cb05 6e0f 0008 130002000200030000000000000000435 2 0000 [XOR from g003_868.34M_1000k.cu8 hcho_ppb 8 voc_level 2]\n    {200}4677 6e0f 0004 130002000200030000000000000000435 1 0000 [XOR from g004_868.34M_1000k.cu8 hcho_ppb 4 voc_level 1]\n\nData layout de-whitened :\n    DIGEST:16h ID:16h PPB:16h 8x8x8x8x8x8x8x8x8x8x4x BATT:1b 3x8x8x8x8x8x4x VOC:4h TRAILER:16x\n\n#2816 Bresser Air Quality sensors, ignore first packet:\n    The first signal is not sending the good BCD values , all at 0xF and need to be excluded from result (BCD value can't be > 9) .\n\nData with empty wind and light fields (from 3-in-1):\n\n    aaaaaaaaaa 2dd4 a6b2bf4b55aac8555555aa3e8f3eabca3355555555555555aa000000000000000000\n\nFirst two bytes are an LFSR-16 digest, generator 0x8810 key 0xba95 with a final xor 0x6df1, which likely means we got that wrong.\n*/\n\nstatic int bresser_7in1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xaa, 0x2d, 0xd4};\n\n    data_t *data;\n    uint8_t msg[25];\n\n    if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[0] < 240 - 80) {\n        decoder_logf(decoder, 2, __func__, \"to few bits (%u)\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH; // unrecognized\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n    start_pos += sizeof(preamble_pattern) * 8;\n\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"preamble not found\");\n        return DECODE_ABORT_EARLY; // no preamble found\n    }\n    //if (start_pos + sizeof (msg) * 8 >= bitbuffer->bits_per_row[0]) {\n    if (start_pos + 21*8 >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 2, __func__, \"message too short (%u)\", bitbuffer->bits_per_row[0] - start_pos);\n        return DECODE_ABORT_LENGTH; // message too short\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof (msg) * 8);\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"MSG\");\n\n    if (msg[21] == 0x00) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    int s_type   = msg[6] >> 4;\n    int nstartup = (msg[6] & 0x08) >> 3;\n    int chan     = msg[6] & 0x07;\n\n    // data whitening\n    for (unsigned i = 0; i < sizeof (msg); ++i) {\n        msg[i] ^= 0xaa;\n    }\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"XOR\");\n\n    // LFSR-16 digest, generator 0x8810 key 0xba95 final xor 0x6df1\n    int chk    = (msg[0] << 8) | msg[1];\n    int digest = lfsr_digest16(&msg[2], 23, 0x8810, 0xba95);\n    if ((chk ^ digest) != 0x6df1) {\n        decoder_logf(decoder, 2, __func__, \"Digest check failed %04x vs %04x (%04x)\", chk, digest, chk ^ digest);\n        return DECODE_FAIL_MIC;\n    }\n\n    int id          = (msg[2] << 8) | (msg[3]);\n    int flags       = (msg[15] & 0x0f);\n    int battery_low = (flags & 0x06) == 0x06;\n\n    if ((s_type == SENSOR_TYPE_WEATHER) || (s_type == SENSOR_TYPE_WEATHER3) || (s_type == SENSOR_TYPE_WEATHER8)) {\n        int wdir     = (msg[4] >> 4) * 100 + (msg[4] & 0x0f) * 10 + (msg[5] >> 4);\n        int wgst_raw = (msg[7] >> 4) * 100 + (msg[7] & 0x0f) * 10 + (msg[8] >> 4);\n        int wavg_raw = (msg[8] & 0x0f) * 100 + (msg[9] >> 4) * 10 + (msg[9] & 0x0f);\n        int rain_raw = (msg[10] >> 4) * 100000 + (msg[10] & 0x0f) * 10000 + (msg[11] >> 4) * 1000\n                + (msg[11] & 0x0f) * 100 + (msg[12] >> 4) * 10 + (msg[12] & 0x0f) * 1; // 6 digits\n        float rain_mm = rain_raw * 0.1f;\n        int temp_raw = (msg[14] >> 4) * 100 + (msg[14] & 0x0f) * 10 + (msg[15] >> 4);\n        float temp_c = temp_raw * 0.1f;\n\n        if (temp_raw > 600)\n            temp_c = (temp_raw - 1000) * 0.1f;\n        int humidity = (msg[16] >> 4) * 10 + (msg[16] & 0x0f);\n        int lght_raw = (msg[17] >> 4) * 100000 + (msg[17] & 0x0f) * 10000 + (msg[18] >> 4) * 1000\n                + (msg[18] & 0x0f) * 100 + (msg[19] >> 4) * 10 + (msg[19] & 0x0f);\n        int uv_raw =   (msg[20] >> 4) * 100 + (msg[20] & 0x0f) * 10 + (msg[21] >> 4);\n\n        float light_klx = lght_raw * 0.001f; // TODO: remove this\n        float light_lux = lght_raw;\n        float uv_index = uv_raw * 0.1f;\n\n        // 3-in-1 (type 12) features Temp/Hum/Rain only\n        int wind_light_ok = s_type != SENSOR_TYPE_WEATHER3;\n\n        int tglobe_ok = 0;\n        float tglobe_c = 0.0f;\n        if (s_type == SENSOR_TYPE_WEATHER8) {\n            // Globe thermometer temperature, only present in the 8-in-1 sensor\n            if ((msg[23] >> 4) < 10) {\n                tglobe_ok = 1;\n                tglobe_c = (msg[22] >> 4) * 10 + (msg[22] & 0x0f) + (msg[23] >> 4) * 0.1f;\n            }\n        }\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Bresser-7in1\",\n                \"id\",               \"\",             DATA_INT,    id,\n                \"startup\",          \"Startup\",      DATA_COND,   !nstartup,  DATA_INT, !nstartup,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",         \"Humidity\",     DATA_INT,    humidity,\n                \"wind_max_m_s\",     \"Wind Gust\",    DATA_COND,   wind_light_ok, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wgst_raw * 0.1f,\n                \"wind_avg_m_s\",     \"Wind Speed\",   DATA_COND,   wind_light_ok, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wavg_raw * 0.1f,\n                \"wind_dir_deg\",     \"Direction\",    DATA_COND,   wind_light_ok, DATA_INT,    wdir,\n                \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n                \"light_klx\",        \"Light\",        DATA_COND,   wind_light_ok, DATA_FORMAT, \"%.3f klx\", DATA_DOUBLE, light_klx, // TODO: remove this\n                \"light_lux\",        \"Light\",        DATA_COND,   wind_light_ok, DATA_FORMAT, \"%.3f lux\", DATA_DOUBLE, light_lux,\n                \"uvi\",              \"UV Index\",     DATA_COND,   wind_light_ok, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, uv_index,\n                \"temperature_1_C\",  \"Globe Temp\",   DATA_COND,   tglobe_ok, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, tglobe_c,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n\n    } else if (s_type == SENSOR_TYPE_AIR_PM) {\n        int pm_2_5      = (msg[10] & 0x0f) * 1000 + (msg[11] >> 4) * 100 + (msg[11] & 0x0f) * 10 + (msg[12] >> 4);\n        int pm_10       = (msg[12] & 0x0f) * 1000 + (msg[13] >> 4) * 100 + (msg[13] & 0x0f) * 10 + (msg[14] >> 4);\n        int pm_2_5_init = (msg[10] & 0x0f) == 0x0f; // confirmed by https://github.com/merbanan/rtl_433/issues/2816#issuecomment-1935439318\n        int pm_10_init  = (msg[12] & 0x0f) == 0x0f; // confirmed by https://github.com/merbanan/rtl_433/issues/2816#issuecomment-1935439318\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                         DATA_STRING, \"Bresser-7in1\",  // should be Bresser-Air-PM\n                \"id\",               \"\",                         DATA_INT,    id,\n                \"channel\",          \"\",                         DATA_INT,    chan,\n                \"startup\",          \"Startup\",                  DATA_COND,   !nstartup,   DATA_INT, !nstartup,\n                \"battery_ok\",       \"Battery\",                  DATA_INT,    !battery_low,\n                \"pm2_5_ug_m3\",      \"PM2.5 Mass Concentration\", DATA_COND,   !pm_2_5_init,   DATA_INT, pm_2_5,\n                \"pm10_0_ug_m3\",     \"PM10 Mass Concentraton\",   DATA_COND,   !pm_10_init,    DATA_INT, pm_10,\n                \"mic\",              \"Integrity\",                DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n\n    } else if (s_type == SENSOR_TYPE_CO2) {\n        int co2      = ((msg[4]& 0xf0) >> 4) * 1000 + (msg[4]& 0x0f) * 100 + ((msg[5]& 0xf0) >> 4) * 10 + (msg[5] & 0x0f);\n        int co2_init = (msg[5] & 0x0f) == 0x0f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                         DATA_STRING, \"Bresser-CO2\",\n                \"id\",               \"\",                         DATA_INT,    id,\n                \"channel\",          \"\",                         DATA_INT,    chan,\n                \"startup\",          \"Startup\",                  DATA_COND,   !nstartup,  DATA_INT, !nstartup,\n                \"battery_ok\",       \"Battery\",                  DATA_INT,    !battery_low,\n                \"co2_ppm\",          \"Carbon Dioxide\",           DATA_COND,   !co2_init,     DATA_FORMAT, \"%d ppm\", DATA_INT, co2,\n                \"mic\",              \"Integrity\",                DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n\n    } else if (s_type == SENSOR_TYPE_HCHO_VOC) {\n        int hcho      = ((msg[4]& 0xf0) >> 4) * 1000 + (msg[4]& 0x0f) * 100 + ((msg[5]& 0xf0) >> 4) * 10 + (msg[5] & 0x0f);\n        int voc       = (msg[22]& 0x0f);\n        int hcho_init = (msg[5] & 0x0f) == 0x0f;\n        int voc_init  = voc == 0x0f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                           DATA_STRING, \"Bresser-HCHOVOC\",\n                \"id\",               \"\",                           DATA_INT,    id,\n                \"channel\",          \"\",                           DATA_INT,    chan,\n                \"startup\",          \"Startup\",                    DATA_COND,   !nstartup,  DATA_INT, !nstartup,\n                \"battery_ok\",       \"Battery\",                    DATA_INT,    !battery_low,\n                \"hcho_ppb\",         \"Formaldehyde\",               DATA_COND,   !hcho_init, DATA_FORMAT, \"%d ppb\", DATA_INT, hcho,\n                \"voc_level\",        \"Volatile Organic Compounds\", DATA_COND,   !voc_init,  DATA_FORMAT, \"%d\",     DATA_INT, voc, // from 1 bad air quality to 5 very good air quality\n                \"mic\",              \"Integrity\",                  DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n\n        // To Do: identify further data\n\n    } else {\n        decoder_logf(decoder, 2, __func__, \"DECODE_FAIL_SANITY, s_type=%d not implemented\", s_type);\n        return DECODE_FAIL_SANITY;\n\n    }\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"startup\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_max_m_s\",\n        \"wind_avg_m_s\",\n        \"wind_dir_deg\",\n        \"rain_mm\",\n        \"light_klx\", // TODO: remove this\n        \"light_lux\",\n        \"uvi\",\n        \"temperature_1_C\",\n        \"pm2_5_ug_m3\",\n        \"pm10_0_ug_m3\",\n        \"battery_ok\",\n        \"co2_ppm\",\n        \"hcho_ppb\",\n        \"voc_level\",\n        \"mic\",\n        NULL,\n};\n\nr_device const bresser_7in1 = {\n        .name        = \"Bresser Weather Center 7-in-1, Air Quality PM2.5/PM10 7009970, CO2 7009977, HCHO/VOC 7009978 sensors\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 124,\n        .long_width  = 124,\n        .reset_limit = 25000,\n        .decode_fn   = &bresser_7in1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_leakage.c",
    "content": "/** @file\n    Bresser Water Leakage Sensor.\n\n    Copyright (C) 2023 Matthias Prinke <m.prinke@arcor.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n#define SENSOR_TYPE_LEAKAGE 5\n\n/**\nBresser Water Leakage Sensor.\n\nDecoder for Bresser Water Leakage outdoor sensor, PN 7009975\n\nsee https://github.com/merbanan/rtl_433/issues/2576\n\nBased on bresser_6in1.c\n\nPreamble: aa aa 2d d4\n\nData layout:\n\n    CCCCCCCC CCCCCCCC IIIIIIII IIIIIIII IIIIIIII IIIIIIII SSSSQHHH ANBBFFFF\n\n- C: 16-bit, crc16/xmodem, polynomial: 0x1021, init: 0x0000, range: byte 2...6\n- I: 24-bit little-endian id; changes on power-up/reset\n- S: 4 bit sensor type\n- Q: 1 bit startup; changes from 0 to 1 approx. one hour after power-on/reset\n- H: 3 bit channel; set via switch on the device, latched at power-on/reset\n- A: 1 bit alarm\n- N: 1 bit no_alarm; inverse of alarm\n- B: 2 bit battery state; 0b11 if battery is o.k.\n- F: 4 bit flags (always 0b0000)\n\n\nExamples:\n\n    [Bresser Water Leakage Sensor, PN 7009975]\n\n    [00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25]\n\n     C7 70 35 97 04 08 57 70 00 00 00 00 00 00 00 00 03 FF FF FF FF FF FF FF FF FF [CH7]\n     DF 7D 36 49 27 09 56 70 00 00 00 00 00 00 00 00 03 FF FF FF FF FF FF FF FF FF [CH6]\n     9E 30 79 84 33 06 55 70 00 00 00 00 00 00 00 00 03 FF FD DF FF BF FF DF FF FF [CH5]\n     E2 C8 68 27 91 24 54 70 00 00 00 00 00 00 00 00 03 FF FF FF FF FF FF FF FF FF [CH4]\n     B3 DA 55 57 17 40 53 70 00 00 00 00 00 00 00 00 03 FF FF FF FF FF FF FF FF FB [CH3]\n     37 FA 84 73 03 02 52 70 00 00 00 00 00 00 00 00 03 FF FF FF DF FF FF FF FF FF [CH2]\n     27 F3 80 02 52 88 51 70 00 00 00 00 00 00 00 00 03 FF FF FF FF FF DF FF FF FF [CH1]\n     A6 FB 80 02 52 88 59 70 00 00 00 00 00 00 00 00 03 FD F7 FF FF BF FF FF FF FF [CH1+NSTARTUP]\n     A6 FB 80 02 52 88 59 B0 00 00 00 00 00 00 00 00 03 FF FF FF FD FF F7 FF FF FF [CH1+NSTARTUP+ALARM]\n     A6 FB 80 02 52 88 59 70 00 00 00 00 00 00 00 00 03 FF FF BF F7 F7 FD 7F FF FF [CH1+NSTARTUP]\n     [Reset]\n     C0 10 36 79 37 09 51 70 00 00 00 00 00 00 00 00 01 1E FD FD FF FF FF DF FF FF [CH1]\n     C0 10 36 79 37 09 51 B0 00 00 00 00 00 00 00 00 03 FE FD FF AF FF FF FF FF FD [CH1+ALARM]\n     [Reset]\n     71 9C 54 81 72 09 51 40 00 00 00 00 00 00 00 00 0F FF FF FF FF FF FF DF FF FE [CH1+BATT_LO]\n     71 9C 54 81 72 09 51 40 00 00 00 00 00 00 00 00 0F FE FF FF FF FF FB FF FF FF\n     71 9C 54 81 72 09 51 40 00 00 00 00 00 00 00 00 07 FD F7 FF DF FF FF DF FF FF\n     71 9C 54 81 72 09 51 80 00 00 00 00 00 00 00 00 1F FF FF F7 FF FF FF FF FF FF [CH1+BATT_LO+ALARM]\n     F0 94 54 81 72 09 59 40 00 00 00 00 00 00 00 00 0F FF DF FF FF FF FF BF FD F7 [CH1+BATT_LO+NSTARTUP]\n     F0 94 54 81 72 09 59 80 00 00 00 00 00 00 00 00 03 FF B7 FF ED FF FF FF DF FF [CH1+BATT_LO+NSTARTUP+ALARM]\n\n */\n\nstatic int bresser_leakage_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0x2d, 0xd4};\n    uint8_t msg[18];\n\n    if (bitbuffer->num_rows != 1\n            || bitbuffer->bits_per_row[0] < 160\n            || bitbuffer->bits_per_row[0] > 440) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof (preamble_pattern) * 8);\n\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n    start_pos += sizeof (preamble_pattern) * 8;\n\n    unsigned len = bitbuffer->bits_per_row[0] - start_pos;\n    if (len < sizeof(msg) * 8) {\n        decoder_logf(decoder, 2, __func__, \"%u too short\", len);\n        return DECODE_ABORT_LENGTH; // message too short\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"\");\n\n    // CRC check\n    uint16_t crc_calculated = crc16(&msg[2], 5, 0x1021, 0x0000);\n    uint16_t crc_received = msg[0] << 8 | msg[1];\n    decoder_logf(decoder, 2, __func__, \"CRC 0x%04X = 0x%04X\", crc_calculated, crc_received);\n    if (crc_received != crc_calculated) {\n        decoder_logf(decoder, 1, __func__, \"CRC check failed (0x%04X != 0x%04X)\", crc_calculated, crc_received);\n        return DECODE_FAIL_MIC;\n    }\n\n    uint32_t sensor_id = ((uint32_t)msg[2] << 24) | (msg[3] << 16) | (msg[4] << 8) | (msg[5]);\n    int s_type         = msg[6] >> 4;\n    int chan           = (msg[6] & 0x7);\n    int battery_ok     = ((msg[7] & 0x30) != 0x00);\n    int nstartup       = (msg[6] & 0x08) >> 3;\n    int alarm          = (msg[7] & 0x80) >> 7;\n    int no_alarm       = (msg[7] & 0x40) >> 6;\n\n    // Sanity checks\n    if (s_type != SENSOR_TYPE_LEAKAGE\n            || alarm == no_alarm\n            || chan == 0) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Bresser-Leakage\",\n            \"id\",               \"\",             DATA_FORMAT, \"%08x\",   DATA_INT,    sensor_id,\n            \"channel\",          \"\",             DATA_INT,    chan,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    battery_ok,\n            \"alarm\",            \"Alarm\",        DATA_INT,    alarm,\n            \"startup\",          \"Startup\",      DATA_COND,   !nstartup,  DATA_INT,  !nstartup,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"alarm\",\n        \"startup\",\n        NULL,\n};\n\nr_device const bresser_leakage = {\n        .name        = \"Bresser water leakage\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 124,\n        .long_width  = 124,\n        .reset_limit = 25000,\n        .decode_fn   = &bresser_leakage_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_lightning.c",
    "content": "/** @file\n    Bresser Lightning Sensor.\n\n    Copyright (C) 2023 The rtl_433 Project\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n#define SENSOR_TYPE_LIGHTNING 9\n\n/**\nBresser Lightning Sensor.\n\nDecoder for Bresser lightning outdoor sensor, PN 7009976\n\nsee https://github.com/merbanan/rtl_433/issues/2140\n\nPreamble: aa aa 2d d4\n\nData layout:\n    DIGEST:8h8h ID:8h8h CTR:12h BATT:1b ?3b STYPE:4h STARTUP:1b CH:3d KM:8d ?8h8h\n\nBased on bresser_7in1.c but msg length is 10 byte\n\nThe data (not including STYPE, STARTUP, CH and maybe ID) has a whitening of 0xaa.\nCH is always 0.\n\nFirst two bytes are an LFSR-16 digest, generator 0x8810 key 0xabf9 with a final xor 0x899e\n*/\n\nstatic int bresser_lightning_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0x2d, 0xd4};\n    uint8_t msg[10];  // not 25\n\n    /* clang-format off */\n    if (   bitbuffer->num_rows != 1\n        || bitbuffer->bits_per_row[0] < 112\n        || bitbuffer->bits_per_row[0] > 440) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n    /* clang-format on */\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n    start_pos += sizeof(preamble_pattern) * 8;\n\n    unsigned len = bitbuffer->bits_per_row[0] - start_pos;\n    if (len < sizeof(msg) * 8) {\n        decoder_logf(decoder, 2, __func__, \"%u too short\", len);\n        return DECODE_ABORT_LENGTH; // message too short\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"MSG\");\n\n    int s_type      = msg[6] >> 4;\n    int chan        = msg[6] & 0x07;\n    int battery_low = (msg[5] & 0x08) >> 3;\n    int nstartup    = (msg[6] & 0x08) >> 3;\n\n    // data de-whitening\n    for (unsigned i = 0; i < sizeof(msg); ++i) {\n        msg[i] ^= 0xaa;\n    }\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"XOR\");\n\n    // LFSR-16 digest, generator 0x8810 key 0xabf9 with a final xor 0x899e\n    int chk    = (msg[0] << 8) | msg[1];\n    int digest = lfsr_digest16(&msg[2], 8, 0x8810, 0xabf9);\n    if ((chk ^ digest) != 0x899e) {\n        decoder_logf(decoder, 2, __func__, \"Digest check failed %04x vs %04x (%04x)\", chk, digest, chk ^ digest);\n        return DECODE_FAIL_MIC;\n    }\n\n    int sensor_id   = (msg[2] << 8) | (msg[3]);\n    int distance_km = msg[7];\n    // Counter encoded as BCD with most significant digit counting up to 15! -> Maximum value: 1599\n    int count       = (msg[4] >> 4) * 100 + (msg[4] & 0xf) * 10 + (msg[5] >> 4);\n    int unknown1    = ((msg[5] & 0x0f) << 8) | msg[6];\n    int unknown2    = (msg[8] << 8) | msg[9];\n\n    // Sanity checks\n    if ((s_type != SENSOR_TYPE_LIGHTNING) || (chan != 0)) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                     DATA_STRING, \"Bresser-Lightning\",\n            \"id\",               \"\",                     DATA_FORMAT, \"%08x\",      DATA_INT,    sensor_id,\n            \"startup\",          \"Startup\",              DATA_COND,   !nstartup,   DATA_INT,    !nstartup,\n            \"battery_ok\",       \"Battery\",              DATA_INT,    !battery_low,\n            \"storm_dist_km\",    \"Storm Distance\",       DATA_FORMAT, \"%d km\",     DATA_INT,    distance_km,\n            \"strike_count\",     \"Strike Count\",         DATA_INT,    count,\n            \"unknown1\",         \"Unknown1\",             DATA_FORMAT, \"%03x\",      DATA_INT,    unknown1,\n            \"unknown2\",         \"Unknown2\",             DATA_FORMAT, \"%04x\",      DATA_INT,    unknown2,\n            \"mic\",              \"Integrity\",            DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"startup\",\n        \"battery_ok\",\n        \"storm_dist_km\",\n        \"strike_count\",\n        \"unknown1\",\n        \"unknown2\",\n        NULL,\n};\n\nr_device const bresser_lightning = {\n        .name        = \"Bresser lightning\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 124,\n        .long_width  = 124,\n        .reset_limit = 25000,\n        .decode_fn   = &bresser_lightning_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bresser_st1005h.c",
    "content": "/** @file\n    Bresser ST1005H sensor protocol.\n\n    Copyright (C) 2024 David Kalnischkies <david@kalnischkies.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nBresser ST1005H sensor protocol.\n\nThe protocol is for a(nother) variant of wireless Temperature/Humidity sensor\n- Bresser Thermo-/Hygro-Sensor 3CH [7009984]\n  https://www.bresser.com/p/bresser-thermo-hygro-sensor-7009984\n  A \"Bresser\" sticker is covering the \"EXPLORE SCIENTIFIC\" logo on the front\n  Multi-Language Manual is branded \"EXPLORE® SCIENTIFIC\" Art.No.: ST1005H\n\nAnother sensor sold under the same generic name is handled by bresser_3ch.c.\n\nThe data of this sensor is grouped in 38 bits that are repeated a few times,\nand is send roughly every 90 secs (plus each time TX button is pressed).\n\nData layout:\n\nThe data has the following categorization of the bits:\n\n    01234567 89012345 67890123 45678901 234567\n    0IIIIIII ILBCCTTT TTTTTTTT THHHHHHH XXXXXX\n\nwhere:\n-  0 prefixed always null bit\n-  I identity (changed by battery replacement)\n-  L low battery (assumed, always 0 in tests)\n-  B triggered by TX button in battery compartment\n-  C channel 1-3 choosen by switch in battery compartment\n-  T temperature in °C (with one decimal) multiplied by 10\n-  H humidity, values higher than 95 are shown as HH in display\n-  X checksum of nibbles added\n\n\nExamples with their temp/humanity reading in display:\n\n    0 12345678 9 0 12 345678901234 5678901 234567\n    0 IIIIIIII L B CC TTTTTTTTTTTT HHHHHHH ======   hum  temp\n    0 01111101 0 0 00 000010110001 1000110 110100   70%  17.7°\n    0 01111101 0 0 00 000010110010 1000110 110101   70%  17.8°\n    0 01111101 0 1 00 000010110011 1000110 111010   70%  17.9°\n    0 01111101 0 1 00 000010110011 1001001 110001   73%  17.9°\n    0 01111101 0 1 00 000010110100 1000111 111101   71%  18.0°\n    0 01111101 0 1 00 000010110101 1000101 111010   69%  18.1°\n    0 11011101 0 0 00 000010110101 1000101 111100   69%  18.1°\n    0 01001010 0 0 00 000010110101 1000110 110010   70%  18.1°\n    0 10100000 0 0 00 000010110110 1000100 101011   68%  18.2°\n    0 01101010 0 0 00 000010110110 1000101 110011   69%  18.2°\n    0 01101010 0 1 00 000010110110 1000100 110101   68%  18.2°\n    0 01101010 0 0 00 000010110011 1000101 110000   69%  17.9°\n    0 01101010 0 1 00 000010110011 1000101 110100   69%  17.9°\n    0 01101010 0 0 00 000011000101 1101110 111010   HH%  19.7°\n    0 01101010 0 0 00 000011010000 1101110 110110   HH%  20.8°\n    0 01101010 0 0 00 000011010011 1011111 111001   95%  21.1°\n    0 11000010 0 1 00 111101011100 1010011 000010   83% -16.4°\n    0 11000010 0 1 00 111101110100 1001110 000001   78% -14.0°\n    0 11000010 0 1 00 111110100010 1101110 000110   HH%  -9.4°\n    0 11000010 0 1 00 000000001101 1011101 110100   93%   1.3°\n    0 11110000 0 0 00 000010011100 1010010 110010   82%  15.6°\n    0 11110000 0 1 00 000010011100 1010010 110110   82%  15.6°\n    0 11110000 0 1 01 000010011100 1010010 110111   82%  15.6°\n    0 11110000 0 1 10 000010011100 1010010 111000   82%  15.6°\n\nThe device has a second button in the battery compartment to flip\nbetween °C and °F in the display (default is °C), but its state\ndoes not change the transmission in any way.\n\nThe device \"Oregon Scientific SL109H Remote Thermal Hygro Sensor\" works\nwith the same row length, but a completely different interpretation.\nAs such, if the bits align both decoders can misdetect data from the\nother sensor as valid from their sensor with \"plausable\" but usually\ncompletely wrong values.\n\nExamples which are misdetected by Oregon:\n\n    0 01101010 0 1 00 000010101011 1000110 111101   70%  17.1°\n    0 11000010 0 0 00 000001010010 1101110 101110   HH%   8.2°\n*/\nstatic int bresser_st1005h_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 38);\n    if (r < 0 || bitbuffer->bits_per_row[r] > 38) {\n        return DECODE_ABORT_LENGTH;\n    }\n    uint8_t *b = bitbuffer->bb[r];\n\n    if (bitrow_get_bit(b, 0) != 0) {\n        decoder_log(decoder, 1, __func__, \"prefix null bit is not null\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    uint8_t msg[4];\n    bitbuffer_extract_bytes(bitbuffer, r, 1, msg, 4*8);\n    msg[3] &= 0xfe; // remove last bit, it is part of the checksum\n    int chk = b[4] >> 2;\n    int sum = add_nibbles(msg, 4);\n\n    // reduce false positives\n    if (sum == 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (chk != (sum & 0x3f)) {\n        decoder_log(decoder, 1, __func__, \"checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id          = msg[0];\n    int battery_low = msg[1] >> 7;\n    int button      = (msg[1] >> 6) & 0x1;\n    int channel     = ((msg[1] >> 4) & 0x3) + 1;\n    int temp_raw    = (int16_t)(((msg[1] & 0xf) << 12) | (msg[2] << 4)); // uses sign-extend from 12 bit\n    double temp_c   = (temp_raw >> 4) * 0.1;\n    int humidity    = msg[3] >> 1;\n\n    if ((channel >= 4) || (humidity > 110) || (temp_c < -30.0) || (temp_c > 160.0)) {\n        decoder_log(decoder, 1, __func__, \"data error\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Bresser-ST1005H\",\n            \"id\",            \"Id\",          DATA_INT,    id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !battery_low,\n            \"button\",        \"Button\",      DATA_INT,    button,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",      \"Humidity\",    DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const bresser_st1005h = {\n        .name        = \"Bresser Thermo-/Hygro-Sensor Explore Scientific ST1005H\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2500,\n        .long_width  = 4500,\n        .gap_limit   = 4500,\n        .reset_limit = 10000,\n        .decode_fn   = &bresser_st1005h_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/bt_rain.c",
    "content": "/** @file\n    Biltema-Rain sensor.\n\n    Copyright (C) 2017 Timopen, cleanup by Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nBiltema-Rain sensor.\n\nBased on the springfield.c code, there is a lack of samples and data\nthus the decoder is disabled by default.\n\n- nibble[0] and nibble[1] is the id, changes with every reset.\n- nibble[2] first bit is battery (0=OK).\n- nibble[3] bit 1 is tx button pressed.\n- nibble[3] bit 2 = below zero, subtract temperature with 1024. I.e. 11 bit 2's complement.\n- nibble[3](bit 3 and 4) + nibble[4] + nibble[5] is the temperature in Celsius with one decimal.\n- nibble[2](bit 2-4) + nibble[6] + nibble[7] is the rain rate, increases 25!? with every tilt of\n  the teeter (1.3 mm rain) after 82 tilts it starts over but carries the rest to the next round\n  e.g tilt 82 = 2 divide by 19.23 to get mm.\n- nibble[8] is checksum, have not figured it out yet. Last bit is sync? or included in checksum?.\n*/\n\n#include \"decoder.h\"\n\n// Actually 37 bits for all but last transmission which is 36 bits\n#define NUM_BITS 36\n\nstatic int bt_rain_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int row;\n    int id, battery, rain, button, channel;\n    int temp_raw;\n    float temp_c, rainrate;\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 4, NUM_BITS);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] != NUM_BITS && bitbuffer->bits_per_row[row] != NUM_BITS + 1)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[row];\n\n    if (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff && b[3] == 0xff)\n        return DECODE_FAIL_SANITY; // prevent false positive checksum\n\n    id      = b[0];\n    battery = b[1] >> 7;\n    channel = ((b[1] & 0x30) >> 4) + 1; // either this or the rain top bits could be wrong\n    button  = (b[1] & 0x08) >> 3;\n\n    temp_raw = (int16_t)(((b[1] & 0x07) << 13) | (b[2] << 5)); // uses sign extend\n    temp_c   = (temp_raw >> 5) * 0.1f;\n\n    rain     = ((b[1] & 0x07) << 4) | b[3]; // either b[1] or the channel above bould be wrong\n    int rest = rain % 25;\n    if (rest % 2)\n        rain += ((rest / 2) * 2048);\n    else\n        rain += ((rest + 1) / 2) * 2048 + 12 * 2048;\n    rainrate = rain * 0.052f; // 19.23mm per tip\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Biltema-Rain\",\n            \"id\",               \"ID\",               DATA_INT,    id,\n            \"channel\",          \"Channel\",          DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !battery,\n            \"transmit\",         \"Transmit\",         DATA_STRING, button ? \"MANUAL\" : \"AUTO\", // TODO: delete this\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"rain_rate_mm_h\",   \"Rain per hour\",    DATA_FORMAT, \"%.2f mm/h\", DATA_DOUBLE, rainrate,\n            \"button\",           \"Button\",       DATA_INT, button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"transmit\", // TODO: delete this\n        \"temperature_C\",\n        \"rain_rate_mm_h\",\n        \"button\",\n        NULL,\n};\n\nr_device const bt_rain = {\n        .name        = \"Biltema rain gauge\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1940,\n        .long_width  = 3900,\n        .gap_limit   = 4100,\n        .reset_limit = 8800,\n        .decode_fn   = &bt_rain_decode,\n        .disabled    = 1,\n        .fields      = output_fields};\n"
  },
  {
    "path": "src/devices/burnhardbbq.c",
    "content": "/** @file\n    Burnhard BBQ thermometer.\n\n    Copyright (C) 2021 Christian Fetzer\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nBurnhard BBQ thermometer.\n\nData format:\n\n    1f 22 00 9052 44 1425e5 1e 8\n    AA SD ?? TTTT mt XXYXYY CC ?\n\n- AA   device code (changes when battery is removed)\n- S    settings, temperature_alarm, timer_alarm, unit, timer_active\n- D    thermometer probe number (0, 1, 2)\n- ??   always 0 so far\n- TTTT timer min and sec (bcd)\n- m    meat (0=free, 1=beef, 2=veal, 3=pork, 4=chick, 5=lamb, 6=fish, 7=ham)\n- t    taste (0=rare, 1=medium rare, 2=medium, 3=medium well, 4=well done, 5 when m is set to free)\n- XXX  temperature setpoint in celsius (-500, /10)\n- YYY  temperature (-500, /10)\n- CC   CRC\n- ?    a single bit (coding artefact)\n*/\n\n#include \"decoder.h\"\n\nstatic int burnhardbbq_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b;\n    data_t *data;\n\n    bitbuffer_invert(bitbuffer);\n\n    // All three rows contain the same information. Return on first decoded row.\n    int ret = 0;\n    for (int i = 0; i < bitbuffer->num_rows; ++i) {\n        // A row typically has 81 bits, but the last is just a coding artefact.\n        if (bitbuffer->bits_per_row[i] < 80 || bitbuffer->bits_per_row[i] > 81) {\n            ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n        b = bitbuffer->bb[i];\n\n        // reduce false positives\n        if (b[0] == 0 && b[9] == 0) {\n            ret = DECODE_ABORT_EARLY;\n            continue;\n        }\n\n        // Sanity check (digest last byte).\n        if (lfsr_digest8_reflect(b, 9, 0x31, 0xf4) != b[9]) {\n            ret = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        int id           = (b[0]);\n        int channel      = (b[1] & 0x07);\n        int temp_alarm   = (b[1] & 0x80) > 7;\n        int timer_alarm  = (b[1] & 0x40) > 6;\n        int timer_active = (b[1] & 0x10) > 4;\n        int setpoint_raw = ((b[7] & 0x0f) << 8) | b[6];\n        int temp_raw     = ((b[7] & 0xf0) << 4) | b[8];\n        float setpoint_c = (setpoint_raw - 500) * 0.1f;\n        float temp_c     = (temp_raw - 500) * 0.1f;\n\n        char timer_str[6];\n        snprintf(timer_str, sizeof(timer_str), \"%02x:%02x\", b[3], b[4] & 0x7f);\n\n        char const *meat;\n        switch (b[5] >> 4) {\n        case 0: meat = \"free\"; break;\n        case 1: meat = \"beef\"; break;\n        case 2: meat = \"veal\"; break;\n        case 3: meat = \"pork\"; break;\n        case 4: meat = \"chicken\"; break;\n        case 5: meat = \"lamb\"; break;\n        case 6: meat = \"fish\"; break;\n        case 7: meat = \"ham\"; break;\n        default: meat = \"\";\n        }\n\n        char const *taste;\n        switch (b[5] & 0x0f) {\n        case 0: taste = \"rare\"; break;\n        case 1: taste = \"medium rare\"; break;\n        case 2: taste = \"medium\"; break;\n        case 3: taste = \"medium well\"; break;\n        case 4: taste = \"well done\"; break;\n        default: taste = \"\";\n        }\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",             \"\",                     DATA_STRING, \"BurnhardBBQ\",\n                \"id\",                \"ID\",                   DATA_INT,    id,\n                \"channel\",           \"Channel\",              DATA_INT,    channel,\n                \"temperature_C\",     \"Temperature\",          DATA_COND,   temp_raw != 0, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                \"setpoint_C\",        \"Temperature setpoint\", DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, setpoint_c,\n                \"temperature_alarm\", \"Temperature alarm\",    DATA_INT,    temp_alarm,\n                \"timer\",             \"Timer\",                DATA_STRING, timer_str,\n                \"timer_active\",      \"Timer active\",         DATA_INT,    timer_active,\n                \"timer_alarm\",       \"Timer alarm\",          DATA_INT,    timer_alarm,\n                \"meat\",              \"Meat\",                 DATA_COND,   meat[0] != '\\0', DATA_STRING, meat,\n                \"taste\",             \"Taste\",                DATA_COND,   taste[0] != '\\0', DATA_STRING, taste,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"temperature_C\",\n        \"setpoint_C\",\n        \"temperature_alarm\",\n        \"timer\",\n        \"timer_active\",\n        \"timer_alarm\",\n        \"meat\",\n        \"taste\",\n        NULL,\n};\n\nr_device const burnhardbbq = {\n        .name        = \"Burnhard BBQ thermometer\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 240,\n        .long_width  = 484,\n        .sync_width  = 840,\n        .reset_limit = 848,\n        .decode_fn   = &burnhardbbq_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/calibeur.c",
    "content": "/** @file\n    Shenzhen Calibeur Industries Co. Ltd Wireless Thermometer RF-104 Temperature/Humidity sensor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nShenzhen Calibeur Industries Co. Ltd Wireless Thermometer RF-104 Temperature/Humidity sensor.\n\nRF-104 Temperature/Humidity sensor\naka Biltema Art. 84-056 (Sold in Denmark)\naka ...\n\nNB. Only 3 unique sensors can be detected!\n\nUpdate (LED flash) each 2:53\n\nPulse Width Modulation with fixed rate and startbit\n\n    Startbit     = 390 samples = 1560 µs\n    Short pulse  = 190 samples =  760 µs = Logic 0\n    Long pulse   = 560 samples = 2240 µs = Logic 1\n    Pulse rate   = 740 samples = 2960 µs\n    Burst length = 81000 samples = 324 ms\n\nSequence of 5 times 21 bit separated by start bit (total of 111 pulses)\n\n    S 21 S 21 S 21 S 21 S 21 S\n\n- Channel number is encoded into fractional temperature\n- Temperature is oddly arranged and offset for negative temperatures = [6543210] - 41 C\n- Always an odd number of 1s (odd parity)\n\nEncoding legend:\n\n    f = fractional temperature + [ch no] * 10\n    0-6 = integer temperature + 41C\n    p = parity\n    H = Most significant bits of humidity [5:6]\n    h = Least significant bits of humidity [0:4]\n\n    LSB                 MSB\n    ffffff45 01236pHH hhhhh Encoding\n\n*/\n\n#include \"decoder.h\"\n\nstatic int calibeur_rf104_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t id;\n    float temperature;\n    float humidity;\n    uint8_t *b = bitbuffer->bb[1];\n    uint8_t *b2 = bitbuffer->bb[2];\n\n    // row [0] is empty due to sync bit\n    // No need to decode/extract values for simple test\n    // check for 0x00 and 0xff\n    if ((!b[0] && !b[1] && !b[2])\n            || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff)) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00 or 0xFF\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    bitbuffer_invert(bitbuffer);\n    // Validate package (row [0] is empty due to sync bit)\n    if (bitbuffer->bits_per_row[1] != 21) // Don't waste time on a long/short package\n        return DECODE_ABORT_LENGTH;\n    if (crc8(b, 3, 0x80, 0) == 0) // It should be odd parity\n        return DECODE_FAIL_MIC;\n    if ((b[0] != b2[0]) || (b[1] != b2[1]) || (b[2] != b2[2])) // We want at least two messages in a row\n        return DECODE_FAIL_SANITY;\n\n    uint8_t bits;\n\n    bits  = ((b[0] & 0x80) >> 7);   // [0]\n    bits |= ((b[0] & 0x40) >> 5);   // [1]\n    bits |= ((b[0] & 0x20) >> 3);   // [2]\n    bits |= ((b[0] & 0x10) >> 1);   // [3]\n    bits |= ((b[0] & 0x08) << 1);   // [4]\n    bits |= ((b[0] & 0x04) << 3);   // [5]\n    id = bits / 10;\n    temperature = (float)(bits % 10) * 0.1f;\n\n    bits  = ((b[0] & 0x02) << 3);   // [4]\n    bits |= ((b[0] & 0x01) << 5);   // [5]\n    bits |= ((b[1] & 0x80) >> 7);   // [0]\n    bits |= ((b[1] & 0x40) >> 5);   // [1]\n    bits |= ((b[1] & 0x20) >> 3);   // [2]\n    bits |= ((b[1] & 0x10) >> 1);   // [3]\n    bits |= ((b[1] & 0x08) << 3);   // [6]\n    temperature += (float)bits - 41.0f;\n\n    bits  = ((b[1] & 0x02) << 4);   // [5]\n    bits |= ((b[1] & 0x01) << 6);   // [6]\n    bits |= ((b[2] & 0x80) >> 7);   // [0]\n    bits |= ((b[2] & 0x40) >> 5);   // [1]\n    bits |= ((b[2] & 0x20) >> 3);   // [2]\n    bits |= ((b[2] & 0x10) >> 1);   // [3]\n    bits |= ((b[2] & 0x08) << 1);   // [4]\n    humidity = bits;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Calibeur-RF104\",\n            \"id\",            \"ID\",          DATA_INT,    id,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"humidity\",      \"Humidity\",    DATA_FORMAT, \"%.0f %%\", DATA_DOUBLE, humidity,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const calibeur_RF104 = {\n        .name        = \"Calibeur RF-104 Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 760,  // Short pulse 760µs\n        .long_width  = 2240, // Long pulse 2240µs\n        .reset_limit = 3200, // Longest gap (2960-760µs)\n        .sync_width  = 1560, // Startbit 1560µs\n        .decode_fn   = &calibeur_rf104_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/cardin.c",
    "content": "/** @file\n    Cardin S466-TX2 generic garage door remote control on 27.195 Mhz.\n\n    Copyright (C) 2018 Christian W. Zuckschwerdt <zany@triq.net>\n    original implementation 2015 Denis Bodor\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nCardin S466-TX2 generic garage door remote control on 27.195 Mhz.\n\nNote: Similar to an EV1527 / SC2260, but there is a 6152 us sync pulse first, then 24 bit of 732 us / 1412 us leading-gap PWM.\nDecodes to 9 tri-state DIP-switches and a 2-bit button.\n\nRemember to set the correct freq with -f 27.195M\nMay be useful for other Cardin product too\n\n- \"11R\"  = on-on    Right button used\n- \"10R\"  = on-off   Right button used\n- \"01R\"  = off-on   Right button used\n- \"00L?\" = off-off  Left button used or right button does the same as the left\n*/\nstatic int cardin_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->bits_per_row[0] != 24) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t *b = bitbuffer->bb[0];\n\n    // validate message as best as we can\n    // constrain b[2] & 0x3f (the button) to 0x03, 0x06, 0x09, 0x0c\n    if ((b[2] & 0x3f) != 0x03\n            && (b[2] & 0x3f) != 0x09\n            && (b[2] & 0x3f) != 0x0c\n            && (b[2] & 0x3f) != 0x06) {\n        return DECODE_ABORT_EARLY;\n    }\n    // Disallow the fourth tri-state option on the 9 DIP switches\n    if (((b[0] & 8) == 0 && (b[1] & 8) != 0)\n            || ((b[0] & 16) == 0 && (b[1] & 16) != 0)\n            || ((b[0] & 32) == 0 && (b[1] & 32) != 0)\n            || ((b[0] & 64) == 0 && (b[1] & 64) != 0)\n            || ((b[0] & 128) == 0 && (b[1] & 128) != 0)\n            || ((b[2] & 128) == 0 && (b[2] & 64) != 0)\n            || ((b[0] & 1) == 0 && (b[1] & 1) != 0)\n            || ((b[0] & 2) == 0 && (b[1] & 2) != 0)\n            || ((b[0] & 4) == 0 && (b[1] & 4) != 0)) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Get button code\n    char const *const rbutton[4] = { \"11R\", \"10R\", \"01R\", \"00L?\" };\n    char const *const button = rbutton[((b[2] & 0x0f) / 3) - 1];\n\n    // Get DIP tri-state switches\n    char dip[10] = {'-','-','-','-','-','-','-','-','-', '\\0'};\n\n    // Dip 1\n    if (b[0] & 8) {\n        dip[0] = 'o';\n        if (b[1] & 8)\n            dip[0] = '+';\n    }\n    // Dip 2\n    if (b[0] & 16) {\n        dip[1] = 'o';\n        if (b[1] & 16)\n            dip[1] = '+';\n    }\n    // Dip 3\n    if (b[0] & 32) {\n        dip[2] = 'o';\n        if (b[1] & 32)\n            dip[2] = '+';\n    }\n    // Dip 4\n    if (b[0] & 64) {\n        dip[3] = 'o';\n        if (b[1] & 64)\n            dip[3] = '+';\n    }\n    // Dip 5\n    if (b[0] & 128) {\n        dip[4] = 'o';\n        if (b[1] & 128)\n            dip[4] = '+';\n    }\n    // Dip 6\n    if (b[2] & 128) {\n        dip[5] = 'o';\n        if (b[2] & 64)\n            dip[5] = '+';\n    }\n    // Dip 7\n    if (b[0] & 1) {\n        dip[6] = 'o';\n        if (b[1] & 1)\n            dip[6] = '+';\n    }\n    // Dip 8\n    if (b[0] & 2) {\n        dip[7] = 'o';\n        if (b[1] & 2)\n            dip[7] = '+';\n    }\n    // Dip 9\n    if (b[0] & 4) {\n        dip[8] = 'o';\n        if (b[1] & 4)\n            dip[8] = '+';\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",      \"\",                       DATA_STRING, \"Cardin-S466\",\n            \"dipswitch\",  \"dipswitch\",              DATA_STRING, dip,\n            \"rbutton\",    \"right button switches\",  DATA_STRING, button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"dipswitch\",\n        \"rbutton\",\n        NULL,\n};\n\nr_device const cardin = {\n        .name        = \"Cardin S466-TX2\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 730,\n        .long_width  = 1400,\n        .sync_width  = 6150,\n        .gap_limit   = 1600,\n        .reset_limit = 32000,\n        .decode_fn   = &cardin_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/cavius.c",
    "content": "/** @file\n    Cavius smoke, heat and water detector.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nCavius smoke, heat and water detector decoder.\n\nThe alarm units use HopeRF RF69 chips on 869.67 MHz, FSK modulation, 4800 bps.\nThey seem to use 'Cavi' as a sync word on the chips.\nEverything after the sync word is Manchester coded.\nThe unpacked payload is 11 bytes long structured as follows:\n\n    NNNNMMCSSSS\n\n- N: Network ID (Device ID of the Master device)\n- M: Message bytes. Second byte is the first byte inverted (0xFF ^ M)\n- C: CRC-8 (Maxim type) of NNNNMM (the first 6 bytes in the payload)\n- S: Sending device ID\n\nMessage bits as far as we can tell:\n\n- 0x80: PAIRING\n- 0x40: TEST\n- 0x20: ALARM\n- 0x10: WARNING\n- 0x08: BATTLOW\n- 0x04: MUTE\n- 0x02: UNKNOWN2\n- 0x01: UNKNOWN1\n\nSometimes the receiver samplerate has to be at 250ksps to decode properly.\n*/\n\n#include \"decoder.h\"\n\nstatic int cavius_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x43, 0x61, 0x76, 0x69};\n\n    enum cavius_message {\n        cavius_pairing  = 0x80,\n        cavius_test     = 0x40,\n        cavius_alarm    = 0x20,\n        cavius_warning  = 0x10,\n        cavius_battlow  = 0x08,\n        cavius_mute     = 0x04,\n        cavius_unknown2 = 0x02,\n        cavius_unknown1 = 0x01,\n    };\n\n    // Find the sync\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8);\n    if (bit_offset + 22 * 8 >= bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        return DECODE_ABORT_EARLY;\n    }\n    bit_offset += sizeof(preamble) * 8; // skip sync\n\n    bitbuffer_t databits = {0};\n\n    bitbuffer_manchester_decode(bitbuffer, 0, bit_offset, &databits, 11 * 8);\n    bitbuffer_invert(&databits);\n\n    // we require 11 bytes\n    if (databits.bits_per_row[0] < 11 * 8) {\n        return DECODE_FAIL_SANITY; // manchester_decode fail\n    }\n\n    uint8_t *b = databits.bb[0];\n\n    int crc = crc8le(b, 7, 0x31, 0x0);\n    if (crc != 0) {\n        return DECODE_FAIL_MIC; // invalid CRC\n    }\n\n    uint32_t net_id    = ((uint32_t)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | (b[3]);\n    uint32_t sender_id = ((uint32_t)b[7] << 24) | (b[8] << 16) | (b[9] << 8) | (b[10]);\n    int batt_low       = (b[4] & cavius_battlow) != 0;\n    int message        = (b[4] & ~cavius_battlow); // exclude batt_low bit\n\n    char const *text = batt_low ? \"Battery low\" : \"Unknown\";\n    switch (message) {\n    case cavius_alarm:\n        text = \"Fire alarm\";\n        break;\n    case cavius_mute:\n        text = \"Alarm muted\";\n        break;\n    case cavius_pairing:\n        text = \"Pairing\";\n        break;\n    case cavius_test:\n        text = \"Test alarm\";\n        break;\n    case cavius_warning:\n        text = \"Warning/Water detected\";\n        break;\n    default:\n        break;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"Cavius-Security\",\n            \"id\",           \"Device ID\",    DATA_INT,    sender_id,\n            \"battery_ok\",   \"Battery\",      DATA_INT,    !batt_low,\n            \"net_id\",       \"Net ID\",       DATA_INT,    net_id,\n            \"message\",      \"Message\",      DATA_INT,    message,\n            \"text\",         \"Description\",  DATA_STRING, text,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"net_id\",\n        \"message\",\n        \"text\",\n        \"mic\",\n        NULL,\n};\n\nr_device const cavius = {\n        .name        = \"Cavius smoke, heat and water detector\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 206,\n        .long_width  = 206,\n        .sync_width  = 2700,\n        .gap_limit   = 1000,\n        .reset_limit = 1000,\n        .decode_fn   = &cavius_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ced7000.c",
    "content": "/** @file\n    CED7000 Shot Timer\n\n    Copyright (C) 2023 Pierros Papadeas <pierros@papadeas.gr>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nCED7000 Shot Timer, also CED8000.\n\nFSK_PCM with 1300 us short, 1300 us long, and 3500 us gap.\nSync is a 0xaa4d5e, then payload.\nThe data is repeated 3 times.\n\nData layout:\n\n    II II CC FF FF FS SS SS UU UU U\n\n- I: RFID, 16 bit LSB, reversed in order, decimal representation per 4 bits, 4 digits\n- C: shot counter, 8 bit LSB, reversed in order, decimal representation per 4 bits, 2 digits\n- F: final time, 20 bit LSB, reversed in order, decimal representation per 4 bits, 5 digits with 2 decimal points assumed\n- S: split time, 20 bit LSB, reversed in order, decimal representation per 4 bits, 5 digits with 2 decimal points assumed\n- U: unknown 20 bits, possible checksum and ending sync word\n\n*/\n\n#include \"decoder.h\"\n\n#define NUM_BITS_PREAMBLE (32)\n#define NUM_BITS_DATA (169)\n#define NUM_BITS_TOTAL (201)\n\nstatic int ced7000_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_t decoded = { 0 };\n    int ret = 0;\n    int bitpos = 0;\n    uint8_t *b;\n\n    /* Find row repeated at least twice */\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 6*16+3*8);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    /* Search for 24 bit sync pattern */\n    uint8_t const sync_pattern[3] = {0xaa, 0x4d, 0x5e};\n    bitpos = bitbuffer_search(bitbuffer, row, bitpos, sync_pattern, 24) + 24;\n\n    if (bitpos >= bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    bitbuffer_invert(bitbuffer);\n\n    /* Check and decode the Manchester bits */\n    ret = bitbuffer_manchester_decode(bitbuffer, row, bitpos, &decoded, NUM_BITS_DATA);\n    if (ret != NUM_BITS_TOTAL + 1) {\n        decoder_log(decoder, 2, __func__, \"invalid Manchester data\");\n        return DECODE_FAIL_MIC;\n    }\n\n    /* Get the decoded data fields */\n    /* IIIIIIII IIIIIIII CCCCCCCC FFFFFFFF FFFFFFFF FFFFSSSS\n       SSSSSSSS SSSSSSSS UUUUUUUU UUUUUUUU UUUUxxxx*/\n\n    b = decoded.bb[0];\n\n    /* Reverse the bit order per nibble */\n    reflect_nibbles(b, ret / 8);\n\n    /* Read the values */\n    int id = (b[1] & 0xF) * 1000 + (b[1] >> 4) * 100 + (b[0] & 0xF) * 10 + (b[0] >> 4);\n    int count = (b[2] & 0xF) * 10 + (b[2] >> 4);\n    float final = (b[5] >> 4) * 100 + (b[4] & 0xF) * 10 + (b[4] >> 4) + (b[3] & 0xF) * 0.1 + (b[3] >> 4) * 0.01f;\n    float split = (b[7] & 0xF) * 100 + (b[7] >> 4) * 10 + (b[6] & 0xF) + (b[6] >> 4) * 0.1 + (b[5] & 0xF) * 0.01f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",    \"Model\",       DATA_STRING, \"CED7000\",\n            \"id\",       \"ID\",          DATA_FORMAT, \"%04u\",    DATA_INT, id,\n            \"count\",    \"Shot Count\",  DATA_INT,    count,\n            \"final\",    \"Final Time\",  DATA_FORMAT, \"%.2f s\", DATA_DOUBLE, final,\n            \"split\",    \"Split Time\",  DATA_FORMAT, \"%.2f s\", DATA_DOUBLE, split,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"count\",\n        \"final\",\n        \"split\",\n        NULL,\n};\n\nr_device const ced7000 = {\n        .name        = \"CED7000 Shot Timer\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 1300,\n        .long_width  = 1300,\n        .gap_limit   = 3500,\n        .reset_limit = 9000,\n        .decode_fn   = &ced7000_decode,\n        .disabled    = 1, // no fix id, no checksum\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/celsia_czc1.c",
    "content": "/** @file\n    Celsia CZC1 Thermostat.\n\n    Copyright (C) 2023 Liban Hannan <liban.p@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nCelsia CZC1 Thermostat.\n\nA PID thermostat compatible with various manufacturers' heaters.\n\ndemod: OOK_PCM\nshort: 1220\nlong: 1220\nreset: 4880\n\nA packet starts with a preamble of {40}cccccccccccccccccccc, followed by a sync\nof {32}55555555 signalling the start of the data symbols. The packet is\nterminated with {8}f0.  Each symbol is 4 'raw' bits long: 0101(5) = 0, 1010(a)\n= 1. Command packets have 5 bytes of data, pairing packets have 4.\n\n```\nrtl_433 -X n=CZC1,m=OOK_PCM,s=1220,l=1220,r=4880,preamble=cccccccc55555555\n```\n\nData layout:\n\nCommand packet (5 bytes)\n\n- ID:   {16} ID\n- Type: {8}  type\n- Heat: {8}  heating level 0-255 (bit reflected unsigned integer)\n- CRC:  {8}  CRC-8, poly 0x31, init 0xd7\n\nPairing packet (4 bytes)\n\n- ID:   {16} ID\n- Type: {8}  type\n- CRC:  {8}  CRC-8, poly 0x31, init 0xd7\n\n*/\n\nstatic int celsia_czc1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xcc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55};\n    // data section in command packet == 160 bits\n    // data section in pair packet == 128 bits\n    // terminal 0xf == 4 bits\n\n    if (bitbuffer->num_rows > 1 || bitbuffer->bits_per_row[0] < 144) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned preamble_end = bitbuffer_search(bitbuffer, 0, 0, preamble, 64) + 64;\n    unsigned first_byte = preamble_end >> 3;\n\n    if (preamble_end >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((preamble_end + 132) > bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_t decoded_bits = {0};\n    //convert raw bits to symbols\n\n    uint8_t *bits = bitbuffer->bb[0];\n    unsigned int n_bytes = bitbuffer->bits_per_row[0] >> 3;\n    unsigned int ipos = first_byte;\n    while (ipos < n_bytes) {\n        if (bits[ipos] == 0xf0) {\n            break;\n        }\n        switch (bits[ipos]) {\n        case 0x55:\n            bitbuffer_add_bit(&decoded_bits, 0);\n            bitbuffer_add_bit(&decoded_bits, 0);\n            break;\n        case 0x5a:\n            bitbuffer_add_bit(&decoded_bits, 0);\n            bitbuffer_add_bit(&decoded_bits, 1);\n            break;\n        case 0xa5:\n            bitbuffer_add_bit(&decoded_bits, 1);\n            bitbuffer_add_bit(&decoded_bits, 0);\n            break;\n        case 0xaa:\n            bitbuffer_add_bit(&decoded_bits, 1);\n            bitbuffer_add_bit(&decoded_bits, 1);\n            break;\n        }\n        ipos++;\n    }\n\n    decoder_log_bitbuffer(decoder, 2, __func__, &decoded_bits, \"Extracted data\");\n    uint8_t *b = decoded_bits.bb[0];\n\n    uint8_t crc = crc8(b, 8, 0x31, 0xd7);\n    if (crc != 0) {\n        decoder_log(decoder, 2, __func__, \"Decode failed: CRC failed\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Check if a 0x00 pair packet or a 0xf0 command packet\n    if (b[2] != 0x00 && b[2] != 0xf0) {\n        decoder_log(decoder, 1, __func__, \"Unknown packet type\");\n        return DECODE_FAIL_OTHER;\n    }\n\n    int id      = (b[0] << 8) | b[1];\n    int heat_ok = b[2] == 0xf0;   // is it a command packet?\n    int heat    = reverse8(b[3]); // command packet only\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",    \"\",             DATA_STRING, \"Celsia-CZC1\",\n        \"id\",       \"\",             DATA_FORMAT, \"%x\",    DATA_INT, id,\n        \"heat\",     \"Heat\",         DATA_COND,   heat_ok, DATA_INT, heat,\n        \"mic\",      \"Integrity\",    DATA_STRING, \"CRC\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"heat\",\n        \"mic\",\n        NULL,\n};\n\n//rtl_433 -X n=CZC1,m=OOK_PCM,s=1220,l=1220,r=4880,preamble=cccccccc55555555\n\nr_device const celsia_czc1 = {\n        .name        = \"Celsia CZC1 Thermostat\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 1220, // each pulse is ~1220 us (nominal bit width)\n        .long_width  = 1220, // each pulse is ~1220 us (nominal bit width)\n        .reset_limit = 4880, // larger than gap between start pulse and first frame (6644 us = 11 x nominal bit width) to put start pulse and first frame in two rows, but smaller than inter-frame space of 30415 us\n        .tolerance   = 20,\n        .decode_fn   = &celsia_czc1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/chamberlain_cwpirc.c",
    "content": "/** @file\n    Chamberlain CWPIRC pir sensor.\n\n    Copyright (C) 2023 Bruno OCTAU\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nChamberlain CWPIRC pir sensor.\nIssue #2582 open by \\@kuenkin\n\nThis is the webpage of the product itself: https://www.chamberlain.com/ca/cwp-wireless-motion-alert-add-on-sensor/p/CWPIRC\n\nThe pir sensor have a learn feature for pairing purpose with the base station up to 8 sensors.\n\nData layout :\n\n    Byte position                00 01 02 03 04 05 06 07 08 09 10 11 12 13\n        55 55 ... 55 55 55 2D D4 00 xx xx xx xx xx 01 yy yy yy yy yy CC CC\n       |                  |     |                 |                 |     |\n       |               ,--'     |                 |                 |     '--------,\n       |Sync           |Preamble|Message 0        |Message 1        |CRC-16/XMODEM |\n\n- Message 0   {48} 00 xx xx xx xx xx, always starting with 0x00\n- Message 1   {48} 01 yy yy yy yy yy, always starting with 0x01\n- CRC-16XModem{16} cc cc  from 00 to 11 byte\n\n- Message 0 and 1 change regularly (every 30 / 35 minutes) , ID is not yet decoded from these 2 messages, tbd.\n- Could be a rolling code and the learn feature could help to get the key ?\n- In case of low battery the base emits a short beep, every 35 minutes. So the low battery information is coded into the 2 messages.\n*/\nstatic int chamberlain_cwpirc_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x55, 0x2D, 0xD4};\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 2, __func__, \"Expected 1 Row, here %d\", bitbuffer->num_rows);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned bits = bitbuffer->bits_per_row[0];\n\n    if (bits < 136 ) {                 // too small\n        decoder_logf(decoder, 2, __func__, \"less than 136 bits, %d is too short\", bits);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof (preamble) * 8);\n\n    if (pos >= bits) {\n        decoder_logf(decoder, 2, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    data_t *data;\n    uint8_t b[14];\n\n    pos += sizeof(preamble) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, pos, b, sizeof(b) * 8);\n\n    if (b[0] != 0 && b[6] != 1) {\n        decoder_logf(decoder, 2, __func__, \"Message 0 and 1 not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint16_t crc_calc = crc16(b, 14, 0x1021, 0x0000);\n\n    if (crc_calc != 0 ) {\n        decoder_log(decoder, 1, __func__, \"CRC error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    char msg0[20], msg1[20];\n    sprintf(msg0, \"%02x%02x%02x%02x%02x\", b[1], b[2], b[3], b[4], b[5]);\n    sprintf(msg1, \"%02x%02x%02x%02x%02x\", b[7], b[8], b[9], b[10], b[11]);\n\n    /* clang-format off */\n    data = data_make(\n        \"model\",   \"Model\",     DATA_STRING,   \"Chamberlain-CWPIRC\",\n        \"msg_0\",   \"Message 0\", DATA_STRING,    msg0,\n        \"msg_1\",   \"Message 1\", DATA_STRING,    msg1,\n        \"mic\",     \"Integrity\", DATA_STRING,   \"CRC\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"msg_0\",\n        \"msg_1\",\n        \"mic\",\n        NULL,\n};\n\nr_device const chamberlain_cwpirc = {\n        .name        = \"Chamberlain CWPIRC PIR Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 25,\n        .long_width  = 25,\n        .reset_limit = 500,\n        .decode_fn   = &chamberlain_cwpirc_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/chuango.c",
    "content": "/** @file\n    Chuango Security Technology.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nChuango Security Technology.\n\nLikely based on HS1527 or compatible\n\nTested devices:\n- G5 GSM/SMS/RFID Touch Alarm System (Alarm, Disarm, ...)\n- DWC-100 Door sensor (Default: Normal Zone)\n- DWC-102 Door sensor (Default: Normal Zone)\n- KP-700 Wireless Keypad (Arm, Disarm, Home Mode, Alarm!)\n- PIR-900 PIR sensor (Default: Home Mode Zone)\n- RC-80 Remote Control (Arm, Disarm, Home Mode, Alarm!)\n- SMK-500 Smoke sensor (Default: 24H Zone)\n- WI-200 Water sensor (Default: 24H Zone)\n- newer DWC-102 additionally generates a cmd=12 signal on door/windows being closed\n\nNote: simple 24 bit fixed ID protocol (x1527 style) and should be handled by the flex decoder.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int chuango_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int id;\n    int cmd;\n    char const *cmd_str;\n\n    if (bitbuffer->bits_per_row[0] != 25)\n        return DECODE_ABORT_LENGTH;\n    b = bitbuffer->bb[0];\n\n    b[0] = ~b[0];\n    b[1] = ~b[1];\n    b[2] = ~b[2];\n\n    // Validate package\n    if (!(b[3] & 0x80)                           // Last bit (MSB here) is always 1\n            || (!b[0] && !b[1] && !(b[2] & 0xF0))) // Reduce false positives. ID 0x00000 not supported\n        return DECODE_ABORT_EARLY;\n\n    id  = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4); // ID is 20 bits (Ad: \"1 Million combinations\" :-)\n    cmd = b[2] & 0x0F;\n\n    switch (cmd) {\n    case 0xF: cmd_str = \"?\"; break;\n    case 0xE: cmd_str = \"?\"; break;\n    case 0xD: cmd_str = \"Low Battery\"; break;\n    case 0xC: cmd_str = \"Closing\"; break;\n    case 0xB: cmd_str = \"24H Zone\"; break;\n    case 0xA: cmd_str = \"Single Delay Zone\"; break;\n    case 0x9: cmd_str = \"?\"; break;\n    case 0x8: cmd_str = \"Arm\"; break;\n    case 0x7: cmd_str = \"Normal Zone\"; break;\n    case 0x6: cmd_str = \"Home Mode Zone\"; break;\n    case 0x5: cmd_str = \"On\"; break;\n    case 0x4: cmd_str = \"Home Mode\"; break;\n    case 0x3: cmd_str = \"Tamper\"; break;\n    case 0x2: cmd_str = \"Alarm\"; break;\n    case 0x1: cmd_str = \"Disarm\"; break;\n    case 0x0: cmd_str = \"Test\"; break;\n    default: cmd_str = \"\"; break;\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",    \"\",             DATA_STRING, \"Chuango-Security\",\n            \"id\",       \"ID\",           DATA_INT,    id,\n            \"cmd\",      \"CMD\",          DATA_STRING, cmd_str,\n            \"cmd_id\",   \"CMD_ID\",       DATA_INT,    cmd,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"cmd\",\n        \"cmd_id\",\n        NULL,\n};\n\nr_device const chuango = {\n        .name        = \"Chuango Security Technology\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 568,  // Pulse: Short 568µs, Long 1704µs\n        .long_width  = 1704, // Gaps:  Short 568µs, Long 1696µs\n        .reset_limit = 1800, // Intermessage Gap 17200µs (individually for now)\n        .sync_width  = 0,    // No sync bit used\n        .tolerance   = 160,  // us\n        .decode_fn   = &chuango_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/cmr113.c",
    "content": "/** @file\n    Clipsal CMR113 cent-a-meter power meter.\n\n    Copyright (C) 2021 Michael Neuling <mikey@neuling.org>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nClipsal CMR113 cent-a-meter power meter.\n\nThe demodulation comes in a few stages:\n\nA) Firstly we look at the pulse lengths both high and low. These\n   are demodulated using OOK_PULSE_PIWM_DC before we hit this\n   driver. Any short pulse (high or low) is assigned a 1 and a\n   long pulse (high or low) is assigned a 0. ie every pulse is a\n   bit.\n\nB) We then look for two patterns in this new bitstream:\n    - 0b00 (ie long long from stream A)\n    - 0b011 (ie long short short from stream A)\n\nC) We start off with an output bit of '0'.  When we see a 0b00\n   (from B), the next output bit is the same as the last\n   bit. When we see a 0b011 (from B), the next output is\n   toggled. If we don't see ether of these patterns, we fail.\n\nD) The output from C represents the final bitstream. This is 83\n   bits repeated twice. There are some timestamps, transmitter\n   IDs and CRC but all we decode below are the 3 current values\n   which are 10 bits each representing AMPS/10. We do check the\n   two 83 bit are identical and fail if not.\n\nKudos to Jon Oxer for decoding this stream and putting it here:\nhttps://github.com/jonoxer/CentAReceiver\n\n*/\n\n#define COMPARE_BITS  83\n#define COMPARE_BYTES ((COMPARE_BITS + 7) / 8)\n\nstatic int cmr113_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int start, bit;\n    uint8_t buf[4];\n    uint8_t b1[COMPARE_BYTES], b2[COMPARE_BYTES];\n    bitbuffer_t b = {0};\n    double current[3];\n    data_t *data;\n\n    if ((bitbuffer->bits_per_row[0] < 350) || (bitbuffer->bits_per_row[0] > 450))\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, 0, buf, 32);\n    if ((buf[0] != 0xb0) || (buf[1] != 0x00) || (buf[2] != 0x00))\n        return DECODE_ABORT_EARLY;\n\n    start = 0;\n    bit = 0;\n    bitbuffer_clear(&b);\n    while ((start + 3) < bitbuffer->bits_per_row[0]) {\n        bitbuffer_extract_bytes(bitbuffer, 0, start, buf, 3);\n        if ((buf[0] >> 6) == 0x00) { // top two bits are 0b00 = no toggle\n            start += 2;\n            bitbuffer_add_bit(&b, bit);\n        } else if ((buf[0] >> 5) == 0x03) { // top two bits are 0b011 = toggle\n            start += 3;\n            bit = 1 - bit; // toggle\n            bitbuffer_add_bit(&b, bit);\n        } else if (start == 0)\n            start += 1; // first bit doesn't decode\n        else\n            // we don't have enough bits\n            return DECODE_ABORT_LENGTH;\n    }\n\n    if (b.bits_per_row[0] < 2 * COMPARE_BITS + 2)\n        return DECODE_ABORT_LENGTH;\n\n    // Compare the repeated section to ensure data integrity\n    bitbuffer_extract_bytes(&b, 0, 0, b1, COMPARE_BITS);\n    bitbuffer_extract_bytes(&b, 0, COMPARE_BITS + 2, b2, COMPARE_BITS);\n    if (memcmp(b1, b2, COMPARE_BYTES) != 0)\n        return DECODE_FAIL_MIC;\n\n    // Data is all good, so extract 3 phases of current\n    for (int i = 0; i < 3; i++) {\n        bitbuffer_extract_bytes(&b, 0, 36 + i * 10, buf, 10);\n        reflect_bytes(buf, 2);\n        current[i] = ((float)buf[0] + ((buf[1] & 0x3) << 8)) * 0.1;\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"Clipsal-CMR113\",\n            \"current_1_A\",  \"Current 1\",    DATA_FORMAT, \"%.1f A\", DATA_DOUBLE, current[0],\n            \"current_2_A\",  \"Current 2\",    DATA_FORMAT, \"%.1f A\", DATA_DOUBLE, current[1],\n            \"current_3_A\",  \"Current 3\",    DATA_FORMAT, \"%.1f A\", DATA_DOUBLE, current[2],\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"current_1_A\",\n        \"current_2_A\",\n        \"current_3_A\",\n        NULL,\n};\n\n// Short high and low pulses are quite different in length so we have a high tolerance of 200\nr_device const cmr113 = {\n        .name        = \"Clipsal CMR113 Cent-a-meter power meter\",\n        .modulation  = OOK_PULSE_PIWM_DC,\n        .short_width = 480,\n        .long_width  = 976,\n        .sync_width  = 2028,\n        .reset_limit = 2069,\n        .tolerance   = 200,\n        .decode_fn   = &cmr113_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/companion_wtr001.c",
    "content": "/** @file\n    Companion WTR001 Temperature Sensor decoder.\n\n    Copyright (C) 2019 Karl Lohner <klohner@thespill.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/**\nCompanion WTR001 Temperature Sensor decoder.\n\nThe device uses PWM encoding with 2928 us for each pulse plus gap.\n- Logical 0 is encoded as 732 us pulse and 2196 us gap,\n- Logical 1 is encoded as 2196 us pulse and 732 us gap,\n- SYNC is encoded as 1464 us and 1464 us gap.\n\nA transmission starts with the SYNC,\nthere are 5 repeated packets, each ending with a SYNC.\n\nFull message is (1+5*(14+1))*2928 us = 304*2928us = 890,112 us.\nFinal 1464 us is gap silence, though.\n\nE.g. rtl_433 -R 0 -X 'n=WTR001,m=OOK_PWM,s=732,l=2196,y=1464,r=2928,bits>=14,invert'\n\nData layout (14 bits):\n\n    DDDDDXTT TTTTTP\n\n| Ordered Bits     | Description\n|------------------|-------------\n| 4,3,2,1,0        | DDDDD: Fractional part of Temperature. (DDDDD - 10) / 10\n| 5                | X: Always 0 in testing. Maybe battery_OK or fixed\n| 12,7,6,11,10,9,8 | TTTTTTT: Temperature in Celsius = (TTTTTTT + ((DDDDD - 10) / 10)) - 41\n| 13               | P: Parity to ensure count of set bits in data is odd.\n\nTemperature in Celsius = (bin2dec(bits 12,7,6,11,10,9,8) + ((bin2dec(bits 4,3,2,1,0) - 10) / 10) - 41\n\nPublished range of device is -29.9C to 69.9C\n*/\n\n#include \"decoder.h\"\n\n#define MYDEVICE_BITLEN      14\n#define MYDEVICE_MINREPEATS  3\n\nstatic int companion_wtr001_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n\n    data_t *data;\n    int r; // a row index\n    uint8_t b[2];\n    float temperature;\n\n    r = bitbuffer_find_repeated_row(bitbuffer, MYDEVICE_MINREPEATS, MYDEVICE_BITLEN);\n    if (r < 0 || bitbuffer->bits_per_row[r] != 14) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, r, 0, b, 14);\n\n    // Invert these 14 bits, PWM with short pulse is 0, long pulse is 1\n    b[0] = ~b[0];\n    b[1] = ~b[1] & 0xfc;\n\n    // Make sure bit 5 is not set\n    if ((b[0] & 0x04) == 0x04) {\n        decoder_log(decoder, 2, __func__, \"companion_wtr001: Fixed Bit set (and it shouldn't be)\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* Parity check (must be ODD) */\n    if (!parity_bytes(b, 2)) {\n        decoder_log(decoder, 2, __func__, \"companion_wtr001: parity check failed (should be ODD)\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Get the tenth of a degree C as bits 0,1,2,3,4 reversed, minus 0x0a\n    // bin2dec(bits 4,3,2,1,0) - 10)\n    uint8_t temp_tenth_raw = reverse8(b[0] & 0xf8);\n\n    if (temp_tenth_raw < 0x0a) {\n        // Value is too low\n        decoder_logf(decoder, 2, __func__, \"companion_wtr001: Temperature Degree Tenth too low (%d - 10 is less than 0\", temp_tenth_raw);\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (temp_tenth_raw > 0x13) {\n        // Value is too high\n        decoder_logf(decoder, 2, __func__, \"companion_wtr001: Temperature Degree Tenth too high (%d - 10 is greater than 9\", temp_tenth_raw);\n        return DECODE_FAIL_SANITY;\n    }\n\n    temp_tenth_raw -= 0x0a;\n\n    // Shift these 7 bits around into the right order\n    // bin2dec(bits 12,7,6,11,10,9,8)\n    uint8_t temp_whole_raw = reverse8(b[1] & 0xf0) | reverse8(b[0] & 0x03) >> 2 | (b[1] & 0x08) << 3;\n\n    if (temp_whole_raw < 11) {\n        // Value is too low (outside published specs)\n        decoder_logf(decoder, 2, __func__, \"companion_wtr001: Whole part of Temperature is too low (%d - 41 is less than -30)\", temp_whole_raw);\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (temp_whole_raw > 111) {\n        // Value is too high (outside published specs)\n        decoder_logf(decoder, 2, __func__, \"companion_wtr001: Whole part of Temperature is too high (%d - 41 is greater than 70)\", temp_whole_raw);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Add whole temperature part to (tenth temperature part / 10), then subtract 41 for final (float) temperature reading\n    temperature = (temp_whole_raw + (temp_tenth_raw * 0.1f)) - 41.0f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Companion-WTR001\",\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const companion_wtr001 = {\n        .name        = \"Companion WTR001 Temperature Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 732,  // 732 us pulse + 2196 us gap is 1 (will be inverted in code)\n        .long_width  = 2196, // 2196 us pulse + 732 us gap is 0 (will be inverted in code)\n        .gap_limit   = 4000, // max gap is 2928 us\n        .reset_limit = 8000, //\n        .sync_width  = 1464, // 1464 us pulse + 1464 us gap between each row\n        .decode_fn   = &companion_wtr001_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/cotech_36_7959.c",
    "content": "/** @file\n    Cotech 36-7959 Weatherstation.\n\n    Copyright (C) 2020 Andreas Holmberg <andreas@burken.se>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nCotech 36-7959 Weatherstation, 433Mhz.\n\nAlso: SwitchDoc Labs Weather FT020T.\nAlso: Sainlogic Weather Station WS019T\nAlso: Sainlogic Weather Station FT0300\nAlso: Ragova WiFi Weather Station FT-0310\nAlso: NicetyMeter Weather Station 0366 (without Lux or UV index)\n\nOOK modulated with Manchester encoding, halfbit-width 500 us.\nMessage length is 112 bit, every second time it will transmit two identical messages, packet gap 5400 us.\nExample raw message: {112}c6880000e80000846d20fffbfb39\n\nIntegrity check is done using CRC8 using poly=0x31  init=0xc0\n\nMessage layout\n\n    AAAA BBBBBBBB C D E F GGGGGGGG HHHHHHHH IIIIIIII JJJJ KKKKKKKKKKKK LLLL MMMMMMMMMMMM NNNNNNNN OOOOOOOOOOOOOOOO PPPPPPPP XXXXXXXX\n\n- A : 4 bit: ?? Type code? part of id?, never seems to change\n- B : 8 bit: Id, changes when reset\n- C : 1 bit: Battery indicator 0 = Ok, 1 = Battery low\n- D : 1 bit: MSB of Wind direction\n- E : 1 bit: MSB of Wind Gust value\n- F : 1 bit: MSB of Wind Avg value\n- G : 8 bit: Wind Avg, scaled by 10\n- H : 8 bit: Wind Gust, scaled by 10\n- I : 8 bit: Wind direction in degrees.\n- J : 4 bit: ? Might belong to the rain value\n- K : 12 bit: Total rain in mm, scaled by 10\n- L : 4 bit: Flag bitmask, always the same sequence: 1000\n- M : 12 bit: Temperature in Fahrenheit, offset 400, scaled by 10\n- N : 8 bit: Humidity\n- O : 16 bit: Sunlight intensity, 0 to 200,000 lumens\n- P : 8 bit: UV index (1-15)\n- X : 8 bit: CRC, poly 0x31, init 0xc0\n\nData format:\n\n    TYPE:h ID:8h FLAGS:h WIND:8d GUST:8d DIR:8d ?:h RAIN:12d FLAGS:h TEMP:12d HUM:8d LIGHT:16d UV:8d CRC:8h\n\n*/\n\nstatic int cotech_36_7959_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x01, 0x40}; // 12 bits\n\n    int r = -1;\n    uint8_t b[14]; // 112 bits are 14 bytes\n    data_t *data;\n\n    if (bitbuffer->num_rows > 2) {\n        return DECODE_ABORT_EARLY;\n    }\n    if (bitbuffer->bits_per_row[0] < 112 && bitbuffer->bits_per_row[1] < 112) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    for (int i = 0; i < bitbuffer->num_rows; ++i) {\n        unsigned pos = bitbuffer_search(bitbuffer, i, 0, preamble, 12);\n        pos += 12;\n\n        if (pos + 112 > bitbuffer->bits_per_row[i])\n            continue; // too short or not found\n\n        r = i;\n        bitbuffer_extract_bytes(bitbuffer, i, pos, b, 112);\n        break;\n    }\n\n    if (r < 0) {\n        decoder_log(decoder, 2, __func__, \"Couldn't find preamble\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (crc8(b, 14, 0x31, 0xc0)) {\n        decoder_log(decoder, 2, __func__, \"CRC8 fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Extract data from buffer\n    //int subtype  = (b[0] >> 4);                                   // [0:4]\n    int id        = ((b[0] & 0x0f) << 4) | (b[1] >> 4);           // [4:8]\n    int batt_low  = (b[1] & 0x08) >> 3;                           // [12:1]\n    int deg_msb   = (b[1] & 0x04) >> 2;                           // [13:1]\n    int gust_msb  = (b[1] & 0x02) >> 1;                           // [14:1]\n    int wind_msb  = (b[1] & 0x01);                                // [15:1]\n    int wind      = (wind_msb << 8) | b[2];                       // [16:8]\n    int gust      = (gust_msb << 8) | b[3];                       // [24:8]\n    int wind_dir  = (deg_msb << 8) | b[4];                        // [32:8]\n    //int rain_msb  = (b[5] >> 4);                                  // [40:4]\n    int rain      = ((b[5] & 0x0f) << 8) | (b[6]);                // [44:12]\n    //int flags     = (b[7] & 0xf0) >> 4;                           // [56:4]\n    int temp_raw  = ((b[7] & 0x0f) << 8) | (b[8]);                // [60:12]\n    int humidity  = (b[9]);                                       // [72:8]\n    int light_lux = (b[10] << 8) | b[11] | ((b[7] & 0x80) << 9);  // [56:1][80:16]\n    int uvi       = (b[12]);                                      // [96:8]\n    //int crc       = (b[13]);                                      // [104:8]\n\n    float temp_c = (temp_raw - 400) * 0.1f;\n\n    // On models without a light sensor, the value read for UV index is out of bounds with its top bits set\n    int light_is_valid = (uvi <= 150); // error value seems to be 0xfb, lux would be 0xfffb\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Cotech-367959\",\n            //\"subtype\",          \"Type code\",        DATA_INT, subtype,\n            \"id\",               \"ID\",               DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !batt_low,\n            \"temperature_F\",    \"Temperature\",      DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"rain_mm\",          \"Rain\",             DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain * 0.1f,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_INT,    wind_dir,\n            \"wind_avg_m_s\",     \"Wind\",             DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind * 0.1f,\n            \"wind_max_m_s\",     \"Gust\",             DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, gust * 0.1f,\n            \"light_lux\",        \"Light Intensity\",  DATA_COND, light_is_valid, DATA_FORMAT, \"%u lux\", DATA_INT, light_lux,\n            \"uvi\",              \"UV Index\",         DATA_COND, light_is_valid, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, uvi * 0.1f,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const cotech_36_7959_output_fields[] = {\n        \"model\",\n        //\"subtype\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"humidity\",\n        \"rain_mm\",\n        \"wind_dir_deg\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"light_lux\",\n        \"uvi\",\n        \"mic\",\n        NULL,\n};\n\nr_device const cotech_36_7959 = {\n        .name        = \"Cotech 36-7959, SwitchDocLabs FT020T wireless weather station with USB\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 500,\n        .long_width  = 0,    // not used\n        .gap_limit   = 1200, // Not used\n        .reset_limit = 1200, // Packet gap is 5400 us.\n        .decode_fn   = &cotech_36_7959_decode,\n        .fields      = cotech_36_7959_output_fields,\n};\n"
  },
  {
    "path": "src/devices/current_cost.c",
    "content": "/** @file\n    CurrentCost TX, CurrentCost EnviR current sensors.\n\n    Copyright (C) 2015 Emmanuel Navarro <enavarro222@gmail.com>\n    CurrentCost EnviR added by Neil Cowburn <git@neilcowburn.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nCurrentCost TX, CurrentCost EnviR current sensors.\n\n@todo Documentation needed.\n*/\nstatic int current_cost_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitbuffer_t packet = {0};\n    uint8_t *b;\n    int is_envir = 0;\n    unsigned int start_pos;\n\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t init_pattern_classic[] = {0xcc, 0xcc, 0xcc, 0xce, 0x91, 0x5d}; // 45 bits (! last 3 bits is not init)\n\n    // The EnviR transmits 0x55 0x55 0x55 0x55 0x2D 0xD4\n    // which is a 4-byte preamble and a 2-byte syncword\n    // The init pattern is inverted and left-shifted by\n    // 1 bit so that the decoder starts with a high bit.\n    uint8_t init_pattern_envir[] = {0x55, 0x55, 0x55, 0x55, 0xa4, 0x57};\n\n    start_pos = bitbuffer_search(bitbuffer, 0, 0, init_pattern_envir, 48);\n\n    if (start_pos + 47 + 112 <= bitbuffer->bits_per_row[0]) {\n        is_envir = 1;\n        // bitbuffer_search matches patterns starting on a high bit, but the EnviR protocol\n        // starts with a low bit, so we have to adjust the offset by 1 to prevent the\n        // Manchester decoding from failing. This is perfectly safe though has the 47th bit\n        // is always 0 as it's the last bit of the 0x2DD4 syncword, i.e. 0010110111010100.\n        start_pos += 47;\n    }\n    else {\n        start_pos = bitbuffer_search(bitbuffer, 0, 0, init_pattern_classic, 45);\n\n        if (start_pos + 45 + 112 > bitbuffer->bits_per_row[0]) {\n            return DECODE_ABORT_EARLY;\n        }\n\n        start_pos += 45;\n    }\n\n    bitbuffer_manchester_decode(bitbuffer, 0, start_pos, &packet, 0);\n\n    if (packet.bits_per_row[0] < 64) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    b = packet.bb[0];\n    // Read data\n    // Meter (b[0] = 0000xxxx) bits 5 and 4 are \"unknown\", but always 0 to date.\n    if ((b[0] & 0xf0) == 0) {\n        uint16_t device_id = (b[0] & 0x0f) << 8 | b[1];\n        uint16_t watt0 = 0;\n        uint16_t watt1 = 0;\n        uint16_t watt2 = 0;\n        //Check the \"Data valid indicator\" bit is 1 before using the sensor values\n        if ((b[2] & 0x80) == 128)\n            watt0 = (b[2] & 0x7F) << 8 | b[3];\n        if ((b[4] & 0x80) == 128)\n            watt1 = (b[4] & 0x7F) << 8 | b[5];\n        if ((b[6] & 0x80) == 128)\n            watt2 = (b[6] & 0x7F) << 8 | b[7];\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",              DATA_COND, is_envir, DATA_STRING, \"CurrentCost-EnviR\", //TODO: it may have different CC Model ? any ref ?\n                \"model\",        \"\",              DATA_COND, !is_envir, DATA_STRING, \"CurrentCost-TX\", //TODO: it may have different CC Model ? any ref ?\n                //\"rc\",           \"Rolling Code\",  DATA_INT, rc, //TODO: add rolling code b[1] ? test needed\n                \"id\",           \"Device Id\",     DATA_FORMAT, \"%d\", DATA_INT, device_id,\n                \"power0_W\",     \"Power 0\",       DATA_FORMAT, \"%d W\", DATA_INT, watt0,\n                \"power1_W\",     \"Power 1\",       DATA_FORMAT, \"%d W\", DATA_INT, watt1,\n                \"power2_W\",     \"Power 2\",       DATA_FORMAT, \"%d W\", DATA_INT, watt2,\n                //\"battery_ok\",   \"Battery\",       DATA_INT,    !battery_low, //TODO is there some low battery indicator ?\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    // Counter (b[0] = 0100xxxx) bits 5 and 4 are \"unknown\", but always 0 to date.\n    else if ((b[0] & 0xf0) == 64) {\n        uint16_t device_id = (b[0] & 0x0f) << 8 | b[1];\n        // b[2] is \"Apparently unused\"\n        uint16_t sensor_type = b[3]; // Sensor type. Valid values are: 2-Electric, 3-Gas, 4-Water\n        uint32_t c_impulse   = (unsigned)b[4] << 24 | b[5] << 16 | b[6] << 8 | b[7];\n        /* clang-format off */\n        data = data_make(\n               \"model\",         \"\",              DATA_COND, is_envir, DATA_STRING, \"CurrentCost-EnviRCounter\", //TODO: it may have different CC Model ? any ref ?\n               \"model\",         \"\",              DATA_COND, !is_envir, DATA_STRING, \"CurrentCost-Counter\", //TODO: it may have different CC Model ? any ref ?\n               \"subtype\",       \"Sensor Id\",     DATA_FORMAT, \"%d\", DATA_INT, sensor_type, //Could \"friendly name\" this?\n               \"id\",            \"Device Id\",     DATA_FORMAT, \"%d\", DATA_INT, device_id,\n               //\"counter\",       \"Counter\",       DATA_FORMAT, \"%d\", DATA_INT, c_impulse,\n               \"power0\",        \"Counter\",       DATA_FORMAT, \"%d\", DATA_INT, c_impulse,\n               NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"subtype\",\n        \"power0_W\",\n        \"power1_W\",\n        \"power2_W\",\n        \"power0\",\n        NULL,\n};\n\nr_device const current_cost = {\n        .name        = \"CurrentCost Current Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 250,\n        .long_width  = 250, // NRZ\n        .reset_limit = 8000,\n        .decode_fn   = &current_cost_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/danfoss.c",
    "content": "/** @file\n    Danfoss CFR Thermostat sensor protocol.\n\n    Copyright (C) 2016 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nDanfoss CFR Thermostat sensor protocol.\n\nManual: http://na.heating.danfoss.com/PCMPDF/Vi.88.R1.22%20CFR%20Thrm.pdf\n\nNo protocol information found, so protocol is reverse engineered.\nSensor uses FSK modulation and Pulse Code Modulated (direct bit sequence) data.\n\nExample received raw data package:\n\n    bitbuffer:: Number of rows: 1\n    [00] {255} 2a aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa 36 5c a9 a6 93 6c 4d a6 a9 6a 6b 29 4f 19 72 b2\n\nThe package starts with a long (~128 bit) synchronization preamble (0xaa).\nSensor data consists of 21 nibbles of 4 bit, which are encoded with a 4b/6b encoder, resulting\nin an encoded sequence of 126 bits (~16 encoded bytes)\nThe package may end with a noise bit or two.\n\nExample:\n\n    Received bits                           | 6b/4b decoded nibbles\n    365C A9A6 936C 4DA6 A96A 6B29 4F19 72B2 | E02 111E C4 6616 7C14 B02C\n\nNibble content:\n\n- #0 -#2  -- Prefix - always 0xE02 (decoded)\n- #3 -#6  -- Sensor ID\n- #7      -- Message Count. Rolling counter incremented at each unique message.\n- #8      -- Switch setting -> 2=\"day\", 4=\"timer\", 8=\"night\"\n- #9 -#10 -- Temperature decimal: value/256\n- #11-#12 -- Temperature integer (in Celsius)\n- #13-#14 -- Set point decimal: value/256\n- #15-#16 -- Set point integer (in Celsius)\n- #17-#20 -- CRC16, poly 0x1021, includes nibble #1-#16\n\n */\n\n#include \"decoder.h\"\n\n#define NUM_BYTES 10    // Output contains 21 nibbles, but skip first nibble 0xE, as it is not part of CRC and to get byte alignment\nstatic const uint8_t HEADER[] = { 0x36, 0x5c }; // Encoded prefix. Full prefix is 3 nibbles => 18 bits (but checking 16 is ok)\n\n// Mapping from 6 bits to 4 bits\nstatic uint8_t danfoss_decode_nibble(uint8_t byte)\n{\n    uint8_t out = 0xFF; // Error\n    switch (byte) {\n    case 0x0B: out = 0xD; break;\n    case 0x0D: out = 0xE; break;\n    case 0x0E: out = 0x3; break;\n    case 0x13: out = 0x4; break;\n    case 0x15: out = 0xA; break;\n    case 0x16: out = 0xF; break;\n    case 0x19: out = 0x9; break;\n    case 0x1A: out = 0x6; break;\n    case 0x25: out = 0x0; break;\n    case 0x26: out = 0x7; break;\n    case 0x29: out = 0x1; break;\n    case 0x2A: out = 0x5; break;\n    case 0x2C: out = 0xC; break;\n    case 0x31: out = 0xB; break;\n    case 0x32: out = 0x2; break;\n    case 0x34: out = 0x8; break;\n    default: break; // Error\n    }\n    return out;\n}\n\nstatic int danfoss_cfr_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t bytes[NUM_BYTES]; // Decoded bytes with two 4 bit nibbles in each\n    data_t *data;\n\n    // Validate package\n    unsigned bits = bitbuffer->bits_per_row[0];\n    if (bits >= 246 && bits <= 260) { // Normal size is 255, but allow for some noise in preamble\n        // Find a package\n        unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 112, HEADER, sizeof(HEADER)*8);    // Normal index is 128, skip first 14 bytes to find faster\n        if (bits-bit_offset < 126) {    // Package should be at least 126 bits\n            decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"Danfoss: short package. Header index: %u\", bit_offset);\n            return DECODE_ABORT_LENGTH;\n        }\n        bit_offset += 6; // Skip first nibble 0xE to get byte alignment and remove from CRC calculation\n\n        // Decode input 6 bit nibbles to output 4 bit nibbles (packed in bytes)\n        for (unsigned n = 0; n < NUM_BYTES; ++n) {\n            uint8_t nibble_h = danfoss_decode_nibble(bitrow_get_byte(bitbuffer->bb[0], n * 12 + bit_offset) >> 2);\n            uint8_t nibble_l = danfoss_decode_nibble(bitrow_get_byte(bitbuffer->bb[0], n * 12 + bit_offset + 6) >> 2);\n            if (nibble_h > 0xF || nibble_l > 0xF) {\n                decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"Danfoss: 6b/4b decoding error\");\n                return DECODE_FAIL_SANITY;\n            }\n            bytes[n] = (nibble_h << 4) | nibble_l;\n        }\n\n        // Output raw decoded data for debug\n        decoder_log_bitrow(decoder, 1, __func__, bytes, NUM_BYTES * 8, \"Danfoss: Raw 6b/4b decoded\");\n\n        // Validate Prefix and CRC\n        uint16_t crc_calc = crc16(bytes, NUM_BYTES-2, 0x1021, 0x0000);\n        if (bytes[0] != 0x02        // Somewhat redundant to header search, but checks last bits\n         || crc_calc != (((uint16_t)bytes[8] << 8) | bytes[9])\n        ) {\n            decoder_log(decoder, 1, __func__, \"Danfoss: Prefix or CRC error.\");\n            return DECODE_FAIL_MIC;\n        }\n\n        // Decode data\n        unsigned id = (bytes[1] << 8) | bytes[2];\n\n        char const *str_sw;\n        switch (bytes[3] & 0x0F) {\n        case 2: str_sw = \"DAY\"; break;\n        case 4: str_sw = \"TIMER\"; break;\n        case 8: str_sw = \"NIGHT\"; break;\n        default: str_sw = \"ERROR\";\n        }\n\n        float temp_meas = (float)bytes[5] + (float)bytes[4] / 256.0f;\n        float temp_setp = (float)bytes[7] + (float)bytes[6] / 256.0f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Danfoss-CFR\",\n                \"id\",               \"ID\",           DATA_INT,    id,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_meas,\n                \"setpoint_C\",       \"Setpoint\",     DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_setp,\n                \"switch\",           \"Switch\",       DATA_STRING, str_sw,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    // TODO: move up instead of putting at bottom\n    return DECODE_ABORT_LENGTH;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"setpoint_C\",\n        \"switch\",\n        \"mic\",\n        NULL,\n};\n\nr_device const danfoss_CFR = {\n        .name        = \"Danfoss CFR Thermostat\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100, // NRZ decoding\n        .long_width  = 100, // Bit width\n        .reset_limit = 500, // Maximum run is 4 zeroes/ones\n        .decode_fn   = &danfoss_cfr_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/deltadore_x3d.c",
    "content": "/** @file\n    Decoder for DeltaDore X3D devices.\n\n    Copyright (C) 2021 Sven Fabricius <sven.fabricius@livediesel.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int deltadore_x3d_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nDecoder for DeltaDore X3D devices.\n\nNote: work in progress\n\n- Modulation: FSK PCM\n- Frequency: 868.95MHz\n- 25 us bit time\n- 40000 baud\n- based on Semtech SX1211\n- manual CRC\n\nPayload format:\n- Preamble          {32} 0xaaaaaaaa\n- Syncword          {32} 0x8169967e\n- Length            {8}\n- Header            {n}\n- Msg Payload       {n}\n- CRC16             {16}\n\nKnown Data:\n- Length            {8}\n- Unknown           {8}  always 0xff\n- Msg No.           {8}\n- Msg Type          {8}\n   - 0x00   sensor message (from window detector)\n   - 0x01   standard message\n   - 0x02   setup message\n   - 0x03   actor identification beacon (click/clack)\n- Header Len/Flag   {8}\n   - possible upper 3 bits are flags, if bit 5 (0x20) is set, then no message payload is attached\n   - lower 5 bits align with length of following header\n- Device ID         {32}  always the device ID of the thermostat, assigned switch actors send same id.\n- Unknown           {8}\n- Some Flags        {8}\n   - Msg type 0: 0x41 window was opened, 0x01 window was closed, 0x00 nothing changed\n- Some Flags        {8}\n   - 0x00           nothing followed\n   - 0x01\n     - unknown      {8} 0x00\n   - 0x08 then following temperature\n     - unknown      {8} 0x00\n     - temperature  {16} int little-endian multiplied by 100 -> 2050 = 20.5 °C\n- Msg Id            {16} some random value\n- Header Chk        {16} big-endian negated cross sum of the header part from device id on\n\nOptional message payload:\nThe payload extends by the count of connected actors and also the retry count and actor number\n- Retry Cnt         {8} used for msg retry ? and direction\n   - lower nibble and zero send from thermostat, count downwards\n   - upper nibble send from switch actor, count upwards\n- Act No.           {8} target actor number\n\nPayload standard message 0x01:\n- Unknown           {8}  always 0x00\n- Response          {8}  0x00 from Thermostat, 0x01 answer from actor\n- Unknown           {16}  0x0001 or 0x0000\n- Command?          {16}\n   - 0x0001        read register\n   - 0x0008        status?\n   - 0x0009        write register\n- Register No       {16}\n- Command resp      {8}  0x01 successful\n- Unknown           {8}  0x00\n- 1st Value         {8}\n- 2nd Value         {8}\n\nRegister address:\n- register area  {8}  x11, x15, x16, x18, x19, x1a\n- register no.   {8}\n\n  - 11-51: Unknown\n  - 15-21: Unknown\n\n  - 16-11: Current Target Temp and status\n    - Get current Target Temp  {8}\n    - Status                   {8}\n      0x10  Heater on\n      0x20  unknown\n      0x80  Window open\n\n  - 16-31:\n    - Set current Target Temp  {8}\n    - Enabled Modes?           {8}\n      Could be a Bitmask or enum:\n      00 = manual\n      02 = Freeze mode\n      07 = Auto mode\n      08 = Holiday/Party mode\n\n  - 16-41: on off state?\n    - 3907 = on, 3807 = off\n\n  - 16-61: {16}\n    - Party on time in minutes\n    - Holiday time in minutes starting from current time.\n      (Days - 1) * 1440 + Current Time in Minutes\n\n  - 16-81:\n    - Freeze Temp {8}\n    - unknown     {8}\n\n  - 16-91:\n    - Night Temp  {8}\n    - Day Temp    {8}\n\n  - 18-01: Unknown\n\n  - 19-10 (RO): {16} On time lsb in seconds\n  - 19-90 (RO): {16} On time msb\n    used to calculate energy consumption\n\n  - 1a-04: Unknown\n\n  The switch temperature is calculated in 0.5 °C steps.\n\nThe length including payload is whitened using CCITT whitening enabled in SX1211 chipset.\nThe payload contains some garbage at the end. The documentation of the SX1211 assume to\nexclude the length byte from length calculation, but the CRC16 checksum at the end is so placed,\nthat the length byte is included. Maybe someone read the docs wrong. The garbage after the\nchecksum contains data from previous larger messages.\n\nSo the last two bytes contains the CRC16(Poly=0x1021,Init=0x0000) value.\n\nTo get raw data:\n\n    ./rtl_433 -f 868.95M -X 'n=DeltaDore,m=FSK_PCM,s=25,l=25,r=800,preamble=aa8169967e'\n*/\n\n// ** DeltaDore X3D known message types\n#define DELTADORE_X3D_MSGTYPE_SENSOR          0x00\n#define DELTADORE_X3D_MSGTYPE_STANDARD        0x01\n#define DELTADORE_X3D_MSGTYPE_PAIRING         0x02\n#define DELTADORE_X3D_MSGTYPE_BEACON          0x03\n\n#define DELTADORE_X3D_HEADER_LENGTH_MASK      0x1f\n#define DELTADORE_X3D_HEADER_FLAGS_MASK       0xe0\n#define DELTADORE_X3D_HEADER_FLAG_NO_PAYLOAD  0x20\n#define DELTADORE_X3D_HEADER_FLAG3_EMPTY_BYTE 0x01\n#define DELTADORE_X3D_HEADER_FLAG3_TEMP       0x08\n#define DELTADORE_X3D_HEADER_FLAG2_WND_CLOSED 0x01\n#define DELTADORE_X3D_HEADER_FLAG2_WND_OPENED 0x41\n#define DELTADORE_X3D_HEADER_TEMP_INDOOR      0x00\n#define DELTADORE_X3D_HEADER_TEMP_OUTDOOR     0x01\n\n#define DELTADORE_X3D_MAX_PKT_LEN             (64U)\n\nstruct deltadore_x3d_message_header {\n    uint8_t number;\n    uint8_t type;\n    uint8_t header_len;\n    uint8_t header_flags;\n    uint32_t device_id;\n    uint8_t network;\n    uint8_t unknown_header_flags1;\n    uint8_t unknown_header_flags2;\n    uint8_t unknown_header_flags3;\n    uint8_t temp_type;\n    int16_t temperature;\n    uint16_t message_id;\n    int16_t header_check;\n};\n\nstruct deltadore_x3d_message_payload {\n    uint8_t retry;\n    uint16_t transfer;\n    uint16_t transfer_ack;\n    uint16_t target;\n    uint8_t action;\n    uint8_t register_high;\n    uint8_t register_low;\n    uint16_t target_ack;\n};\n\n/* clang-format off */\nstatic uint32_t deltadore_x3d_read_le_u24(uint8_t **buffer)\n{\n    uint32_t res = **buffer; (*buffer)++;\n    res |= **buffer << 8;    (*buffer)++;\n    res |= **buffer << 16;   (*buffer)++;\n    return res;\n}\n\nstatic uint16_t deltadore_x3d_read_le_u16(uint8_t **buffer)\n{\n    uint16_t res = **buffer; (*buffer)++;\n    res |= **buffer << 8;    (*buffer)++;\n    return res;\n}\n\nstatic uint16_t deltadore_x3d_read_be_u16(uint8_t **buffer)\n{\n    uint16_t res = **buffer << 8; (*buffer)++;\n    res |= **buffer;              (*buffer)++;\n    return res;\n}\n/* clang-format on */\n\nstatic uint8_t deltadore_x3d_parse_message_header(uint8_t *buffer, struct deltadore_x3d_message_header *out)\n{\n    uint8_t bytes_read         = 14;\n    out->number                = *buffer++;\n    out->type                  = *buffer++;\n    out->header_len            = *buffer & DELTADORE_X3D_HEADER_LENGTH_MASK;\n    out->header_flags          = *buffer++ & DELTADORE_X3D_HEADER_FLAGS_MASK;\n    out->device_id             = deltadore_x3d_read_le_u24(&buffer);\n    out->network               = *buffer++;\n    out->unknown_header_flags1 = *buffer++;\n    out->unknown_header_flags2 = *buffer++;\n    out->unknown_header_flags3 = *buffer++;\n    if (out->unknown_header_flags3 == DELTADORE_X3D_HEADER_FLAG3_EMPTY_BYTE) {\n        buffer++;\n        bytes_read++;\n    }\n    else if (out->unknown_header_flags3 == DELTADORE_X3D_HEADER_FLAG3_TEMP) {\n        out->temp_type   = *buffer++;\n        out->temperature = deltadore_x3d_read_le_u16(&buffer);\n        bytes_read += 3;\n    }\n    out->message_id   = deltadore_x3d_read_le_u16(&buffer);\n    out->header_check = deltadore_x3d_read_be_u16(&buffer);\n\n    return bytes_read;\n}\n\nstatic uint8_t deltadore_x3d_parse_message_payload(uint8_t *buffer, struct deltadore_x3d_message_payload *out)\n{\n    out->retry         = *buffer++;\n    out->transfer      = deltadore_x3d_read_le_u16(&buffer);\n    out->transfer_ack  = deltadore_x3d_read_le_u16(&buffer);\n    out->target        = deltadore_x3d_read_le_u16(&buffer);\n    out->action        = *buffer++;\n    out->register_high = *buffer++;\n    out->register_low  = *buffer++;\n    out->target_ack    = deltadore_x3d_read_le_u16(&buffer);\n    return 12;\n}\n\nstatic int deltadore_x3d_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {\n            /*0xaa, 0xaa, */ 0xaa, 0xaa, // preamble\n            0x81, 0x69, 0x96, 0x7e       // sync word\n    };\n\n    data_t *data;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int row = 0;\n    // Validate message and reject it as fast as possible : check for preamble\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, sizeof(preamble) * 8);\n\n    if (start_pos >= bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_EARLY; // no preamble detected\n    }\n\n    // start after preamble\n    start_pos += sizeof(preamble) * 8;\n\n    // check min length\n    if (bitbuffer->bits_per_row[row] < 10 * 8) { // preamble(4) + sync(4) + len(1) + data(1)\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // read length byte in advance\n    uint8_t len;\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos, &len, 8);\n\n    // dewhite length\n    ccitt_whitening(&len, 1);\n\n    if (len > DELTADORE_X3D_MAX_PKT_LEN) {\n        decoder_logf(decoder, 1, __func__, \"packet too large (%u bytes), dropping it\\n\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t frame[65] = {0};\n    // Get whole frame (len includes the length byte)\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos, frame, len * 8);\n\n    // dewhite the data\n    ccitt_whitening(frame, len);\n\n    decoder_log_bitrow(decoder, 2, __func__, frame, len * 8, \"frame data\");\n\n    const uint16_t crc        = crc16(frame, len - 2, 0x1021, 0x0000);\n    const uint16_t actual_crc = (frame[len - 2] << 8 | frame[len - 1]);\n\n    if (actual_crc != crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC invalid %04x != %04x\\n\", actual_crc, crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    struct deltadore_x3d_message_header head = {0};\n    uint8_t bytes_read                       = 2; // step over length and FF field\n    bytes_read += deltadore_x3d_parse_message_header(&frame[bytes_read], &head);\n    const char *class, *wnd_stat, *temp_type;\n\n    /* clang-format off */\n    switch (head.type) {\n        case DELTADORE_X3D_MSGTYPE_SENSOR:   class = \"Sensor\";   break;\n        case DELTADORE_X3D_MSGTYPE_STANDARD: class = \"Standard\"; break;\n        case DELTADORE_X3D_MSGTYPE_PAIRING:  class = \"Pairing\";  break;\n        case DELTADORE_X3D_MSGTYPE_BEACON:   class = \"Beacon\";   break;\n        default:                             class = \"Unknown\";  break;\n    }\n\n    switch (head.unknown_header_flags2) {\n        case DELTADORE_X3D_HEADER_FLAG2_WND_CLOSED: wnd_stat = \"Closed\"; break;\n        case DELTADORE_X3D_HEADER_FLAG2_WND_OPENED: wnd_stat = \"Opened\"; break;\n        default:                                    wnd_stat = \"\";       break;\n    }\n\n    switch (head.temp_type) {\n        case DELTADORE_X3D_HEADER_TEMP_INDOOR:  temp_type = \"indoor\";  break;\n        case DELTADORE_X3D_HEADER_TEMP_OUTDOOR: temp_type = \"outdoor\"; break;\n        default:                                temp_type = \"\";        break;\n    }\n    /* clang-format on */\n\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",   \"\",            DATA_STRING, \"DeltaDore-X3D\",\n            \"id\",      \"\",            DATA_INT,    head.device_id,\n            \"network\", \"Net\",         DATA_INT,    head.network,\n            \"subtype\", \"Class\",       DATA_FORMAT, \"%s\", DATA_STRING, class,\n            \"msg_id\",  \"Message Id\",  DATA_INT,    head.message_id,\n            \"msg_no\",  \"Message No.\", DATA_INT,    head.number,\n            \"mic\",     \"Integrity\",   DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    // message from thermostate\n    if (head.unknown_header_flags3 == DELTADORE_X3D_HEADER_FLAG3_TEMP) {\n        float temperature = head.temperature / 100.0f;\n        /* clang-format off */\n        data = data_dbl(data, \"temperature_C\",    \"Temperature\", \"%.1f C\", temperature);\n        data = data_str(data, \"temperature_type\", \"Temp Type\",   NULL,   temp_type);\n        /* clang-format on */\n    }\n\n    if (head.header_flags & DELTADORE_X3D_HEADER_FLAG_NO_PAYLOAD) {\n        // Window stat from window sensor\n        if (strlen(wnd_stat) > 0) {\n            data = data_str(data, \"wnd_stat\", \"Window Status\", NULL, wnd_stat);\n        }\n    }\n    else {\n        struct deltadore_x3d_message_payload body = {0};\n        bytes_read += deltadore_x3d_parse_message_payload(&frame[bytes_read], &body);\n\n        // Max hex string len is 2 * (maximum packet length - crc) + NUL character\n        char raw_str[2 * (DELTADORE_X3D_MAX_PKT_LEN - 2) + 1];\n\n        /* clang-format off */\n        data = data_int(data, \"retry\",         \"Retry\",             NULL, body.retry);\n        data = data_int(data, \"transfer\",      \"Transfer\",          NULL, body.transfer);\n        data = data_int(data, \"transfer_ack\",  \"Transfer Ack\",      NULL, body.transfer_ack);\n        data = data_int(data, \"target\",        \"Target\",            NULL, body.target);\n        data = data_int(data, \"target_ack\",    \"Target Ack\",        NULL, body.target_ack);\n        data = data_int(data, \"action\",        \"Action\",            NULL, body.action);\n        data = data_int(data, \"register_high\", \"Reg High\",          NULL, body.register_high);\n        data = data_int(data, \"register_low\",  \"Reg Low\",           NULL, body.register_low);\n        data = data_hex(data, \"raw_msg\",       \"Raw Register Data\", NULL, &frame[bytes_read], len - bytes_read - 2, raw_str);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic const char *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"network\",\n        \"subtype\",\n        \"msg_id\",\n        \"msg_no\",\n        \"temperature_C\",\n        \"temperature_type\",\n        \"wnd_stat\",\n        \"retry\",\n        \"transfer\",\n        \"transfer_ack\",\n        \"target\",\n        \"action\",\n        \"register_high\",\n        \"register_low\",\n        \"target_ack\",\n        \"raw_msg\",\n        \"mic\",\n        NULL,\n};\n\nr_device const deltadore_x3d = {\n        .name        = \"DeltaDore X3D devices\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 25,\n        .long_width  = 25,\n        .reset_limit = 800,\n        .decode_fn   = &deltadore_x3d_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/digitech_xc0324.c",
    "content": "/** @file\n    Decoder for Digitech XC-0324 temperature sensor.\n\n    Copyright (C) 2018 Geoff Lee\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for Digitech XC-0324 temperature sensor.\n\nAlso AmbientWeather FT005TH.\n\nNote: this decoder should be simplified further.\n\nThe encoding is pulse position modulation\n(i.e. gap width contains the modulation information)\n- pulse is about 400 us\n- short gap is (approx) 520 us\n- long gap is (approx) 1000 us\n\nDeciphered using two transmitters.\n\nA transmission package is 148 bits\n(plus or minus one or two due to demodulation or transmission errors).\n\nEach transmission contains 3 repeats of the 48 bit message,\nwith 2 zero bits separating each repetition.\n\nA 48 bit message consists of:\n- byte 0: preamble (for synchronisation), 0x5F\n- byte 1: device id\n- byte 2 and the first nibble of byte 3: encodes the temperature\n    as a 12 bit integer,\n    transmitted in least significant bit first order\n    in tenths of degree Celsius\n    offset from -40.0 degrees C (minimum temp spec of the device)\n- byte 4: Humidity in Percentage on newer units.\n- byte 5: a check byte (the XOR of bytes 0-4 inclusive)\n    each bit is effectively a parity bit for correspondingly positioned bit\n    in the real message\n\nThis decoder is associated with a tutorial entry in the\nrtl_433 wiki describing the way the transmissions were deciphered.\nSee https://github.com/merbanan/rtl_433/wiki/digitech_xc0324.README.md\n\nThe tutorial is \"by a newbie, for a newbie\", ie intended to assist newcomers\nwho wish to learn how to decipher a new device, and develop a rtl_433 device\ndecoder from scratch for the first time.\n\nTo illustrate stages in the deciphering process, this decoder includes some\ndebug style trace messages that would normally be removed. Specifically,\nrunning this decoder with debug level :\n- `-vvv` simulates what might be seen early in the deciphering process, when\n    only the modulation scheme and parameters have been discovered,\n- `-vv` simulates what might be seen once the synchronisation/preamble and\n    message length has been uncovered, and it is time to start work on\n    deciphering individual fields in the message,\n    with no debug flags set provides the final (production stage) results,\n    and\n- `-vvvv` is a special \"finished development\" output.  It provides a file of\n        reference values, to be included with the test data for future\n        regression test purposes.\n*/\n\n//#define XC0324_DEVICE_BITLEN      148\n#define XC0324_MESSAGE_BITLEN     48\n#define XC0324_MESSAGE_BYTELEN    (XC0324_MESSAGE_BITLEN + 7)/ 8\n//#define XC0324_DEVICE_MINREPEATS  3\n\nstatic int decode_xc0324_message(r_device *decoder, bitbuffer_t *bitbuffer,\n        unsigned row, uint16_t bitpos, data_t **data_out)\n{\n    // Extract the message\n    uint8_t b[XC0324_MESSAGE_BYTELEN];\n    bitbuffer_extract_bytes(bitbuffer, row, bitpos, b, XC0324_MESSAGE_BITLEN);\n\n    // Examine the chksum and bail out now if not OK to save time\n    // b[5] is a check byte, the XOR of bytes 0-4.\n    // ie a checksum where the sum is \"binary add no carry\"\n    // Effectively, each bit of b[5] is the parity of the bits in the\n    // corresponding position of b[0] to b[4]\n    // NB : b[0] ^ b[1] ^ b[2] ^ b[3] ^ b[4] ^ b[5] == 0x00 for a clean message\n    uint8_t chksum = xor_bytes(b, 6);\n    if (chksum != 0x00) {\n        // Log the \"bad\" message (only for message level deciphering!)\n        decoder_logf_bitrow(decoder, 2, __func__, b, XC0324_MESSAGE_BITLEN,\n                \"chksum = 0x%02X not 0x00, row %d bit %d\",\n                chksum, row, bitpos);\n        return DECODE_FAIL_MIC; // No message was able to be decoded\n    }\n\n    // Log good message rows\n    decoder_logf_bitrow(decoder, 2, __func__, b, XC0324_MESSAGE_BITLEN,\n            \"at row %03d bit %03d\", row, bitpos);\n\n    // Decode only once, skip if we already have data\n    if (*data_out == NULL) {\n        // Extract the id as hex string\n        char id[3] = {0};\n        snprintf(id, sizeof(id), \"%02X\", b[1]);\n\n        // Decode temperature (b[2]), plus 1st 4 bits b[3], LSB first order!\n        // Tenths of degrees C, offset from the minimum possible (-40.0 degrees)\n        int temp = ((uint16_t)(reverse8(b[3]) & 0x0f) << 8) | reverse8(b[2]);\n        float temperature   = (temp - 400) * 0.1f;\n\n        // Decode humiddity (b[4]), LSB first order!\n        // Whole Number Integers in Percentage\n        int humidity = reverse8(b[4]);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"Device Type\",      DATA_STRING, \"Digitech-XC0324\",\n                \"id\",               \"ID\",               DATA_STRING, id,\n                \"temperature_C\",    \"Temperature C\",    DATA_FORMAT, \"%.1f\", DATA_DOUBLE, temperature,\n                \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        *data_out = data;\n    }\n\n    return 1; // Message successfully decoded\n}\n\n/**\nDigitech XC-0324 device.\n@sa decode_xc0324_message()\n*/\nstatic int digitech_xc0324_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x5F};\n\n    int ret      = 0;\n    int events   = 0;\n    data_t *data = NULL;\n\n    // A clean XC0324 transmission contains 3 repeats of a message in a single row.\n    // But in case of transmission or demodulation glitches,\n    // loop over all rows and check for salvageable messages.\n    for (int r = 0; r < bitbuffer->num_rows; ++r) {\n        if (bitbuffer->bits_per_row[r] < XC0324_MESSAGE_BITLEN) {\n            // bail out of this \"too short\" row early\n            // Output the bad row, only for message level debug / deciphering.\n            decoder_logf_bitrow(decoder, 1, __func__, bitbuffer->bb[r], bitbuffer->bits_per_row[r],\n                    \"Bad message need %d bits got %d, row %d bit %d\",\n                    XC0324_MESSAGE_BITLEN, bitbuffer->bits_per_row[r], r, 0);\n            continue; // DECODE_ABORT_LENGTH\n        }\n        // We have enough bits so search for a message preamble followed by\n        // enough bits that it could be a complete message.\n        unsigned bitpos = 0;\n        while ((bitpos = bitbuffer_search(bitbuffer, r, bitpos, preamble_pattern, 8)) + XC0324_MESSAGE_BITLEN <=\n                bitbuffer->bits_per_row[r]) {\n            ret = decode_xc0324_message(decoder, bitbuffer, r, bitpos, &data);\n            if (ret > 0) {\n                events += ret;\n            }\n            bitpos += XC0324_MESSAGE_BITLEN;\n        }\n    }\n\n    if (events > 0) {\n        data = data_int(data, \"message_num\", \"Message repeat count\", NULL, events);\n        decoder_output_data(decoder, data);\n    }\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        \"message_num\",\n        NULL,\n};\n\nr_device const digitech_xc0324 = {\n        .name        = \"Digitech XC-0324 / AmbientWeather FT005TH temp/hum sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 520,  // = 130 * 4\n        .long_width  = 1000, // = 250 * 4\n        .reset_limit = 3000,\n        .decode_fn   = &digitech_xc0324_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/directv.c",
    "content": "/** @file\n    DirecTV RC66RX Remote Control decoder.\n\n    Copyright (C) 2019 Karl Lohner <klohner@thespill.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/** @fn int directv_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nDirecTV RC66RX Remote Control decoder.\n\nThe device uses FSK to transmit a PCM signal TRANSMISSION.  Its FSK signal\nseems to be centered around 433.92 MHz with its MARK and SPACE frequencies\neach +/- 50 kHz from that center point.\n\nA full signal TRANSMISSION consists of ROWS, which are collections of SYMBOLS.\nSYMBOLS, both the higher-frequency MARK (`1`) and lower-frequency SPACE\n(`0`), have a width of 600µs.  If there is more than one ROW in a single\nTRANSMISSION, there will be a GAP of 27,600µs of silence between each ROW.\n\nA TRANSMISSION may be generated in response to an EVENT on the remote.  Observed\nEVENTS that may trigger a TRANSMISSION seem limited to manual button presses.\n\nEach ROW in the TRANSMISSION consists of two ordered parts -- its SYNC and its\nMESSAGE.  Each ROW is expected to be complete; the device does not seem to ever\ntruncate a signal inside of a ROW.\n\nThe SYNC may be either a LONG SYNC or a SHORT SYNC. The LONG SYNC consists of\nSYMBOLS `000111111111100`.  It is used in each row to signify that the MESSAGE\nwhich follows will be the first time this unique MESSAGE will be seen in\nthis TRANSMISSION.\n\nHowever, if a unique MESSAGE is to be sent more than once in a\nTRANSMISSION, each subsequent ROW with this repeated MESSAGE will send a\nSHORT SYNC instead of a LONG SYNC.  A SHORT SYNC consists of SYMBOLS\n`0001111100`.\n\nROWS are typically repeated for the duration of the EVENT (a button push on the\nremote) and a ROW is allowed to finish sending even if the EVENT ends before the\nROW is completely sent.\n\nROWS in any single TRANSMISSION usually contain the same MESSAGE, however this\nis not always the case.  TRANSMISSIONS may be one ROW for some short EVENTS,\nalthough some specific EVENTS generate TRANSMISSIONS of three rows, regardless\nthe duration of the EVENT.  Single TRANSMISSIONS have been observed to switch\nfrom one MESSAGE to another.  This seems to happen for specific buttons, such as\nthe [SELECT] button, which sends a single ROW containing a LONG SYNC and a\nMESSAGE that encodes a new [SELECT RELEASE] MESSAGE.  Some buttons send one\nMESSAGE during the initial duration of the EVENT, but then switch to a new\nMESSAGE if the EVENT continues. Some TRANSMISSIONS stop sending ROWS after a\nduration even if the EVENT continues.\n\nLOGICAL DATA in the MESSAGE may be decoded from the ROW using some sort of\nDifferential Pulse Width Modulation (DPWM) method.  Between each SYMBOL\ntransition (both `1` to `0` and `0` to `1`) consider the number of SYMBOLS.  If\nthere is only one SYMBOL, the LOGICAL DATA bit is a `0`.  If there are two\nSYMBOLS, the LOGICAL DATA bit is a `1`.  If there are 3 or more SYMBOLS, this is\nnot DATA - it is a sync pulse.  If a sync pulse is found (and is followed by\nmore SYMBOLS i.e. the SYMBOL does not occur at the end of the ROW), both it and\nthe one or two contiguous SYMBOLS after it are ignored and LOGICAL DATA would\nresume decoding from that next transition.\n\nAfter decoding, there should be 40 bits (5 bytes) of LOGICAL DATA.\n\nLOGICAL DATA layout in nibbles:\n\nMM DD DD DB BC\n\n| Nibble # | Letter | Description                                                                |\n|----------|--------|-------------                                                               |\n| 0 - 1    | MM     | Model? Seems to always be 0x10                                             |\n| 2 - 6    | DDDDD  | Device ID. 0x00000 - 0xF423F are valid (000000 - 999999 in decimal)        |\n| 7 - 8    | BB     | Button Code. 0x00 - 0xFF maps to specific buttons or functions             |\n| 9        | C      | Checksum. Least Significant Nibble of sum of previous 9 nibbles, 0x0 - 0xF |\n\nFlex Spec to get ROW SYMBOLS:\n\n$ rtl_433 -R 0 -X '-X n=DirecTV,m=FSK_PCM,s=600,l=600,g=30000,r=80000'\n\n*/\n\n#include \"decoder.h\"\n\n#define ROW_BITLEN_MIN     44  // The shortest possible fragment that can possibly decode successfully\n#define ROW_BITLEN_MAX     99  // But even with a LONG SYNC and large MESSAGE value, won't be larger than this\n#define ROW_SYNC_SHORT_LEN 5   // A SYNC longer than this will be considered a LONG SYNC\n#define DTV_BITLEN_MAX     40  // Valid decoded data for this device will be exactly 40 bits in length\n\n// Provide a lookup between button ID codes and their names based on observations\nstatic const char *dtv_button_label[] = {\n    [0x01] = \"1\",\n    [0x02] = \"2\",\n    [0x03] = \"3\",\n    [0x04] = \"4\",\n    [0x05] = \"5\",\n    [0x06] = \"6\",\n    [0x07] = \"7\",\n    [0x08] = \"8\",\n    [0x09] = \"9\",\n    [0x0D] = \"CH UP\",\n    [0x0E] = \"CH DOWN\",\n    [0x0F] = \"CH PREV\",\n    [0x10] = \"PWR\",\n    [0x11] = \"0\",\n    [0x12] = \"DASH\",\n    [0x13] = \"ENTER\",\n    [0x14] = \"DASH REPEAT\",\n    [0x15] = \"ENTER REPEAT\",\n    [0x20] = \"MENU\",\n    [0x21] = \"UP\",\n    [0x22] = \"DOWN\",\n    [0x23] = \"LEFT\",\n    [0x24] = \"RIGHT\",\n    [0x25] = \"SELECT\",\n    [0x26] = \"EXIT\",\n    [0x27] = \"BACK\",\n    [0x28] = \"GUIDE\",\n    [0x29] = \"ACTIVE\",\n    [0x2A] = \"LIST\",\n    [0x2B] = \"LIST REPEAT\",\n    [0x2C] = \"INFO REPEAT\",\n    [0x2D] = \"GUIDE REPEAT\",\n    [0x2E] = \"INFO\",\n    [0x30] = \"VCR PLAY\",\n    [0x31] = \"VCR STOP\",\n    [0x32] = \"VCR PAUSE\",\n    [0x33] = \"VCR RWD\",\n    [0x34] = \"VCR FFD\",\n    [0x35] = \"VCR REC\",\n    [0x36] = \"VCR BACK\",\n    [0x37] = \"VCR SKIP\",\n    [0x38] = \"VCR SKIP REPEAT\",\n    [0x3A] = \"VCR PLAY REPEAT\",\n    [0x3B] = \"VCR PAUSE REPEAT\",\n    [0x3C] = \"VCR RWD REPEAT\",\n    [0x3D] = \"VCR FFD REPEAT\",\n    [0x3E] = \"VCR REC REPEAT\",\n    [0x3F] = \"VCR BACK REPEAT\",\n    [0x41] = \"RED\",\n    [0x42] = \"YELLOW\",\n    [0x43] = \"GREEN\",\n    [0x44] = \"BLUE\",\n    [0x45] = \"MENU REPEAT\",\n    [0x46] = \"ACTIVE REPEAT\",\n    [0x4A] = \"RED REPEAT\",\n    [0x4B] = \"YELLOW REPEAT\",\n    [0x4C] = \"GREEN REPEAT\",\n    [0x4D] = \"BLUE REPEAT\",\n    [0x51] = \"TV: VCR ALERT\",\n    [0x59] = \"VOLUME ALERT\",\n    [0x5A] = \"AV1/AV2/TV: IR ALERT 1\",\n    [0x5B] = \"DTV: IR ALERT\",\n    [0x5C] = \"AV1/AV2/TV: IR ALERT 2\",\n    [0x5D] = \"TV: DTV ALERT\",\n    [0x5E] = \"AV1: DTV ALERT\",\n    [0x5F] = \"AV2: DTV ALERT\",\n    [0x60] = \"0 REPEAT\",\n    [0x61] = \"1 REPEAT\",\n    [0x62] = \"2 REPEAT\",\n    [0x63] = \"3 REPEAT\",\n    [0x64] = \"4 REPEAT\",\n    [0x65] = \"5 REPEAT\",\n    [0x66] = \"6 REPEAT\",\n    [0x67] = \"7 REPEAT\",\n    [0x68] = \"8 REPEAT\",\n    [0x69] = \"9 REPEAT\",\n    [0x73] = \"FORMAT\",\n    [0x75] = \"FORMAT REPEAT\",\n    [0x80] = \"DTV: DTV&TV POWER ON\",\n    [0x81] = \"DTV: DTV&TV POWER OFF\",\n    [0xD6] = \"SELECT RELEASE\",\n    [0x100] = \"unknown\",\n};\n\nstatic const char *get_dtv_button_label(uint8_t button_id)\n{\n    const char *label = dtv_button_label[button_id];\n    if (!label) {\n        label = dtv_button_label[0x100];\n    }\n    return label;\n}\n\n/// Set a single bit in a bitrow at bit_idx position.  Assume success, no bounds checking, so be careful!\n/// Maybe this can graduate to bitbuffer.c someday?\nstatic void bitrow_set_bit(uint8_t *bitrow, unsigned bit_idx, unsigned bit_val)\n{\n    if (bit_val == 0) {\n        bitrow[bit_idx >> 3] &= ~(1 << (7 - (bit_idx & 7)));\n    }\n    else {\n        bitrow[bit_idx >> 3] |= (1 << (7 - (bit_idx & 7)));\n    }\n}\n\n/// This is a differential PWM decode and is essentially only looking at symbol\n/// transitions, not the symbols themselves.  An inverted bitstring would yield the\n/// same result.  Note that:\n///\n/// - Initial contiguous alike symbol(s) is not considered data, regardless of length.\n///   Essentially, the\n///\n/// - Any group of alike contiguous symbols with a length of 3 or more is considered\n///   a sync.  If this happens anywhere except at the end of bitrow, any data already\n///   decoded is discarded, the length and position of the sync is noted, and data\n///   decoding resumes.\n///\n/// - The one or two alike contiguous symbols immediately after a sync are not treated\n///   as data, they are essentially there to signify the end of the sync.\n///\n/// Return value is length of data decoded into bitrow_buf after last sync. If bitrow\n/// ends with a sync, that sync is ignored and returned data will be data before that\n/// sync.\n///\n/// Ensure that bitrow_buf is at least as big as bitrow or data overrun might occur.\n///\n/// Note that sync_pos and sync_len will be modified if a sync is found. If returned\n/// sync_pos is greater than start, it might mean there is data between start and\n/// sync_pos.  If desired, call again with bit_len = sync_pos to find this data.\n///\n/// Maybe this can graduate to bitbuffer.c someday?\nstatic unsigned bitrow_dpwm_decode(uint8_t const *bitrow, unsigned bit_len, unsigned start,\n        uint8_t *bitrow_buf, unsigned *sync_pos, unsigned *sync_len)\n{\n    unsigned bitrow_pos;\n    int bitrow_buf_pos          = -1;\n    unsigned cur_symbol_len     = -1;\n    *sync_pos                   = start;\n    *sync_len                   = 0;\n    unsigned sync_in_progress   = 1;\n    unsigned prev_bit           = 0xff;  // So it's always different than the first bit\n    unsigned this_bit;\n\n    for (bitrow_pos = start; bitrow_pos < bit_len; bitrow_pos++) {\n        this_bit = bitrow_get_bit(bitrow, bitrow_pos);\n        if (this_bit == prev_bit) {\n            if (++cur_symbol_len > 1) {\n                sync_in_progress = 1;\n            }\n        }\n        else {\n            if (sync_in_progress) {\n                *sync_len = cur_symbol_len + 1;\n                *sync_pos = bitrow_pos - cur_symbol_len - 1;\n                bitrow_buf_pos = -1;\n                sync_in_progress = 0;\n            }\n            else {\n                if (bitrow_buf_pos >= 0) {\n                    bitrow_set_bit(bitrow_buf, bitrow_buf_pos, cur_symbol_len);\n                }\n                bitrow_buf_pos++;\n            }\n            cur_symbol_len = 0;\n        }\n        prev_bit = this_bit;\n    }\n\n    // If a sync was started at the end of the row, ignore it and the previous decoded bit\n    if (sync_in_progress) {\n        bitrow_buf_pos -= 1;\n    }\n\n    // If bad decode, just send back an empty result string.\n    if (bitrow_buf_pos < 0) {\n        bitrow_buf_pos = 0;\n    }\n\n    return bitrow_buf_pos;\n}\n\nstatic int directv_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int r;                   // a row index\n    uint8_t bitrow[13];      // space for a possibly modified bitbuffer row, up to 99 bits\n    uint8_t bit_len;         // row length is variable, so need to keep track of this\n    uint8_t dtv_buf[13] = {0}; // decoded bitrow data, 40 bits (5 bytes)\n    unsigned dtv_bit_len;\n    unsigned row_sync_pos;\n    unsigned row_sync_len;\n\n    // Signal is reset by rtl_433 before recognizing rows, so in practice, there's only one row.\n    // It would be useful to catch rows in signal so that there'd only be one decoded row per\n    // signal and we could count repeats to report the length of the signal, but that's not\n    // supported yet.  It seems the gap between rows exceeds the ook_hysteresis threshold in\n    // pulse_detect.c:  int16_t const ook_hysteresis = ook_threshold / 8; // ±12%\n    // and changing this value isn't the right direction, nor does this even work for this signal.\n    // Grouping rows in a signal like this will need to be supported in some other way, and this\n    // support is not yet available in rtl_433.\n    // For now, we'll decode the signal in the bitbuffer assuming it is only one row.\n\n    r = 0;\n    bit_len = bitbuffer->bits_per_row[r];\n\n    if ((bit_len < ROW_BITLEN_MIN) || (bit_len > ROW_BITLEN_MAX)) {\n        decoder_logf(decoder, 2, __func__, \"incorrect number of bits in bitbuffer: %d (expected between %d and %d).\", bit_len, ROW_BITLEN_MIN, ROW_BITLEN_MAX);\n        return DECODE_FAIL_SANITY;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, r, 0, bitrow, bit_len);\n\n    // Decode the message symbols\n    dtv_bit_len = bitrow_dpwm_decode(bitrow, bit_len, 0, dtv_buf, &row_sync_pos, &row_sync_len);\n    decoder_logf_bitrow(decoder, 2, __func__, dtv_buf, dtv_bit_len, \"SYNC at pos:%u for %u symbols. DPWM Decoded Message\", row_sync_pos, row_sync_len);\n\n    // Make sure we have exactly 40 bits (DTV_BITLEN_MAX)\n    if (dtv_bit_len != DTV_BITLEN_MAX) {\n        decoder_logf(decoder, 2, __func__, \"Incorrect number of decoded bits: %u (should be %d).\", dtv_bit_len, DTV_BITLEN_MAX);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // First byte should be 0x10 (model number?)\n    if (dtv_buf[0] != 0x10) {\n        decoder_logf(decoder, 2, __func__, \"Incorrect Model ID number: 0x%02X (should be 0x10).\", dtv_buf[0]);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Validate Checksum\n    unsigned checksum_1;\n    unsigned checksum_2;\n    checksum_1 = ((dtv_buf[0] >> 4) + (dtv_buf[0] & 0x0F) + (dtv_buf[1] >> 4) + (dtv_buf[1] & 0x0F) +\n            (dtv_buf[2] >> 4) + (dtv_buf[2] & 0x0F) + (dtv_buf[3] >> 4) + (dtv_buf[3] & 0x0F) +\n            (dtv_buf[4] >> 4)) & 0x0F;\n    checksum_2 = dtv_buf[4] & 0x0F;\n    if (checksum_1 != checksum_2) {\n        decoder_logf(decoder, 2, __func__, \"Checksum failed: 0x%01X should match 0x%01X\", checksum_1, checksum_2);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Get Device ID\n    unsigned dtv_device_id;\n    dtv_device_id = dtv_buf[1] << 12 | dtv_buf[2] << 4 | dtv_buf[3] >> 4;\n    if (dtv_device_id > 999999) {\n        decoder_logf(decoder, 2, __func__, \"Bad Device ID: %u (should be between 000000 and 999999).\", dtv_device_id);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Get Button ID, assuming all byte values are valid.\n    uint8_t dtv_button_id;\n    dtv_button_id = dtv_buf[3] << 4 | dtv_buf[4] >> 4;\n\n    // Populate our return fields\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"DirecTV-RC66RX\",\n            \"id\",            \"\",            DATA_FORMAT, \"%06d\", DATA_INT, dtv_device_id,\n            \"button_id\",     \"\",            DATA_FORMAT, \"0x%02X\", DATA_INT, dtv_button_id,\n            \"button_name\",   \"\",            DATA_STRING, get_dtv_button_label(dtv_button_id),\n            \"event\",         \"\",            DATA_STRING, row_sync_len > ROW_SYNC_SHORT_LEN ? \"INITIAL\" : \"REPEAT\",\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"button_id\",\n        \"button_name\",\n        \"event\",\n        \"mic\",\n        NULL,\n};\n\nr_device const directv = {\n        .name        = \"DirecTV RC66RX Remote Control\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 600,  // 150 samples @250k\n        .long_width  = 600,  // 150 samples @250k\n        .gap_limit   = 30000, // gap is typically around 27,600µs, so long that rtl_433 resets\n                              // signal decoder before recognizing row repeats in signal\n        .reset_limit = 50000, // maximum gap size before End Of Row [µs]\n        .decode_fn   = &directv_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/dish_remote_6_3.c",
    "content": "/** @file\n    Decoder for UHF Dish Remote Control 6.3.\n\n    Copyright (C) 2018 David E. Tiller\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nDecoder for UHF Dish Remote Control 6.3.\n(tested with genuine Dish remote.)\n\nThe device uses PPM encoding,\n0 is encoded as 400 us pulse and 1692 uS gap,\n1 is encoded as 400 us pulse and 2812 uS gap.\nThe device sends 7 transmissions per button press approx 6000 uS apart.\nA transmission starts with a 400 uS start bit and a 6000 uS gap.\n\nEach packet is 16 bits in length.\nPacket bits: BBBBBB10 101X1XXX\nB = Button pressed, big-endian\nX = unknown, possibly channel\n*/\n\n#include \"decoder.h\"\n\n#define MYDEVICE_BITLEN      16\n#define MYDEVICE_MINREPEATS  3\n\nchar const *button_map[] = {\n/*  0 */ \"Undefined\",\n/*  1 */ \"Undefined\",\n/*  2 */ \"Swap\",\n/*  3 */ \"Undefined\",\n/*  4 */ \"Position\",\n/*  5 */ \"PIP\",\n/*  6 */ \"DVR\",\n/*  7 */ \"Undefined\",\n/*  8 */ \"Skip Forward\",\n/*  9 */ \"Skip Backward\",\n/* 10 */ \"Undefined\",\n/* 11 */ \"Dish Button\",\n/* 12 */ \"Undefined\",\n/* 13 */ \"Forward\",\n/* 14 */ \"Backward\",\n/* 15 */ \"TV Power\",\n/* 16 */ \"Reset\",\n/* 17 */ \"Undefined\",\n/* 18 */ \"Undefined\",\n/* 19 */ \"Undefined\",\n/* 20 */ \"Undefined\",\n/* 21 */ \"Undefined\",\n/* 22 */ \"SAT\",\n/* 23 */ \"Mute/Volume Up/Volume Down\",\n/* 24 */ \"Undefined\",\n/* 25 */ \"#/Search\",\n/* 26 */ \"*/Format\",\n/* 27 */ \"Undefined\",\n/* 28 */ \"Undefined\",\n/* 29 */ \"Undefined\",\n/* 30 */ \"Stop\",\n/* 31 */ \"Pause\",\n/* 32 */ \"Record\",\n/* 33 */ \"Channel Down\",\n/* 34 */ \"Undefined\",\n/* 35 */ \"Left\",\n/* 36 */ \"Recall\",\n/* 37 */ \"Channel Up\",\n/* 38 */ \"Undefined\",\n/* 39 */ \"Right\",\n/* 40 */ \"TV/Video\",\n/* 41 */ \"View/Live TV\",\n/* 42 */ \"Undefined\",\n/* 43 */ \"Guide\",\n/* 44 */ \"Undefined\",\n/* 45 */ \"Cancel\",\n/* 46 */ \"Digit 0\",\n/* 47 */ \"Select\",\n/* 48 */ \"Page Up\",\n/* 49 */ \"Digit 9\",\n/* 50 */ \"Digit 8\",\n/* 51 */ \"Digit 7\",\n/* 52 */ \"Menu\",\n/* 53 */ \"Digit 6\",\n/* 54 */ \"Digit 5\",\n/* 55 */ \"Digit 4\",\n/* 56 */ \"Page Down\",\n/* 57 */ \"Digit 3\",\n/* 58 */ \"Digit 2\",\n/* 59 */ \"Digit 1\",\n/* 60 */ \"Play\",\n/* 61 */ \"Dish Power\",\n/* 62 */ \"Undefined\",\n/* 63 */ \"Info\"\n};\n\nstatic int dish_remote_6_3_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int r; // a row index\n    uint8_t *b; // bits of a row\n    uint8_t button;\n    char const *button_string;\n\n    decoder_log_bitbuffer(decoder, 2, __func__, bitbuffer, \"\");\n\n    r = bitbuffer_find_repeated_row(bitbuffer, MYDEVICE_MINREPEATS, MYDEVICE_BITLEN);\n    if (r < 0 || bitbuffer->bits_per_row[r] > MYDEVICE_BITLEN) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = bitbuffer->bb[r];\n\n    /* Check fixed bits to prevent misreads */\n    if ((b[0] & 0x03) != 0x02 || (b[1] & 0xe8) != 0xa8) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    button = b[0] >> 2;\n    button_string = button_map[button];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",    \"\",     DATA_STRING, \"Dish-RC63\",\n            \"button\",   \"\",     DATA_STRING, button_string,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"button\",\n        NULL,\n};\n\nr_device const dish_remote_6_3 = {\n        .name        = \"Dish remote 6.3\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1692,\n        .long_width  = 2812,\n        .gap_limit   = 4500,\n        .reset_limit = 9000,\n        .decode_fn   = &dish_remote_6_3_callback,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/dsc.c",
    "content": "/** @file\n    DSC security contact sensors.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2015 Robert C. Terzi\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nDSC - Digital Security Controls 433 Mhz Wireless Security Contacts\ndoors, windows, smoke, CO2, water.\n\nProtocol Description available in this FCC Report for FCC ID F5300NB912\nhttps://apps.fcc.gov/eas/GetApplicationAttachment.html?id=100988\n\nGeneral Packet Description\n- Packets are 26.5 mS long\n- Packets start with 2.5 mS of constant modulation for most sensors\n  Smoke/CO2/Fire sensors start with 5.6 mS of constant modulation\n- The length of a bit is 500 uS, broken into two 250 uS segments.\n   A logic 0 is 500 uS (2 x 250 uS) of no signal.\n   A logic 1 is 250 uS of no signal followed by 250 uS of signal/keying\n- Then there are 4 sync logic 1 bits.\n- There is a sync/start 1 bit in between every 8 bits.\n- A zero byte would be 8 x 500 uS of no signal (plus the 250 uS of\n  silence for the first half of the next 1 bit) for a maximum total\n  of 4,250 uS (4.25 mS) of silence.\n- The last byte is a CRC with nothing after it, no stop/sync bit, so\n  if there was a CRC byte of 0, the packet would wind up being short\n  by 4 mS and up to 8 bits (48 bits total).\n- Note the WS4945 doubles the length of those timings.\n\nThere are 48 bits in the packet including the leading 4 sync 1 bits.\nThis makes the packet 48 x 500 uS bits long plus the 2.5 mS preamble\nfor a total packet length of 26.5 ms.  (smoke will be 3.1 ms longer)\n\nPacket Decoding\n\n    Check intermessage start / sync bits, every 8 bits\n    Byte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5\n    vvvv         v         v         v         v\n    SSSSdddd ddddSddd dddddSdd ddddddSd dddddddS cccccccc  Sync,data,crc\n    01234567 89012345 67890123 45678901 23456789 01234567  Received Bit No.\n    84218421 84218421 84218421 84218421 84218421 84218421  Received Bit Pos.\n\n    SSSS         S         S         S         S           Synb bit positions\n        ssss ssss ttt teeee ee eeeeee e eeeeeee  cccccccc  type\n        tttt tttt yyy y1111 22 223333 4 4445555  rrrrrrrr\n\n- Bits: 0,1,2,3,12,21,30,39 should == 1\n\n- Status (st) = 8 bits, open, closed, tamper, repeat\n- Type (ty)   = 4 bits, Sensor type, really first nybble of ESN\n- ESN (e1-5)  = 20 bits, Electronic Serial Number: Sensor ID.\n- CRC (cr)    = 8 bits, CRC, type/polynom to be determined\n\nThe ESN in practice is 24 bits, The type + remaining 5 nybbles.\nThe physical devices have all 6 digits printed in hex. Devices are enrolled\nby entering or recording the 6 hex digits.\n\nThe CRC is 8 bit, reflected (lsb first), Polynomial 0xf5, Initial value 0x3d\n\nStatus bit breakout:\n\nThe status byte contains a number of bits that indicate:\n-  open vs closed\n- event vs heartbeat\n- battery ok vs low\n- tamper\n- recent activity (for certain devices)\n\nThe majority of the DSC sensors use the status bits the same way.\nThere are some slight differences depending on who made the device.\n\n@todo - the status bits don't make sense for the one-way keyfob\nand should be broken out two indicate which buttons are pressed.\nThe keyfob can be detected by the type nybble.\n\nNotes:\n- The device type nybble isn't really useful other than for detecting\n  the keyfob. For example door/window contacts (Type 2) are used pretty\n  generically, so the same type can be used for burglar, flood, fire,\n  temperature limits, etc.  The device type is mildly informational\n  during testing and discovery. It can easily be seen as the  first digit\n  of the ESN, so it doesn't need to be broken out separately.\n- There seem to be two bits used inconsistently to indicate whether\n  the sensor is being tampered with (case opened, removed from the wall,\n  missing EOL resistor, etc.\n- The two-way devices wireless keypad and use an entirely different\n  modulation. They are supposed to be encrypted. A sampling rate\n  greater than 250 khz (1 mhz?) looks to be necessary.\n- Tested on EV-DW4927 door/glass break sensor, WS4975 door sensor,\n  WS4945 door sensor and WS4904P motion sensors.\n- The EV-DW4927 combined door / glass break sensor sends out two\n  separate signals. Glass break uses the original ESN as written on\n  the case and door sensor uses ESN with last digit +1.\n\n*/\n\n#include \"decoder.h\"\n\n#define DSC_CT_MSGLEN        5\n\nstatic int dsc_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int valid_cnt = 0;\n    uint8_t bytes[5];\n    uint8_t status, crc;\n    //int subtype;\n    uint32_t esn;\n    int s_closed, s_event, s_tamper, s_battery_low;\n    int s_xactivity, s_xtamper1, s_xtamper2, s_exception;\n\n    int result = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] > 0) {\n            decoder_logf(decoder, 2, __func__, \"row %d bit count %d\",\n                    row, bitbuffer->bits_per_row[row]);\n        }\n\n        // Number of bits in the packet should be 48 but due to the\n        // encoding of trailing zeros is a guess based on reset_limit /\n        // long_width (bit period).  With current values up to 10 zero\n        // bits could be added, so it is normal to get a 58 bit packet.\n        //\n        // If the limits are changed for some reason, the max number of bits\n        // will need to be changed as there may be more zero bit padding\n        if (bitbuffer->bits_per_row[row] < 48 ||\n            bitbuffer->bits_per_row[row] > 70) {  // should be 48 at most\n            if (bitbuffer->bits_per_row[row] > 0) {\n                decoder_logf(decoder, 2, __func__, \"row %d invalid bit count %d\",\n                        row, bitbuffer->bits_per_row[row]);\n            }\n            result = DECODE_ABORT_EARLY;\n            continue; // DECODE_ABORT_EARLY\n        }\n\n        b = bitbuffer->bb[row];\n        // Validate Sync/Start bits == 1 and are in the right position\n        if (!((b[0] & 0xF0) &&     // First 4 bits are start/sync bits\n              (b[1] & 0x08) &&    // Another sync/start bit between\n              (b[2] & 0x04) &&    // every 8 data bits\n              (b[3] & 0x02) &&\n              (b[4] & 0x01))) {\n            decoder_log_bitrow(decoder, 2, __func__, b, 40, \"Invalid start/sync bits \");\n            result = DECODE_ABORT_EARLY;\n            continue; // DECODE_ABORT_EARLY\n        }\n\n        bytes[0] = ((b[0] & 0x0F) << 4) | ((b[1] & 0xF0) >> 4);\n        bytes[1] = ((b[1] & 0x07) << 5) | ((b[2] & 0xF8) >> 3);\n        bytes[2] = ((b[2] & 0x03) << 6) | ((b[3] & 0xFC) >> 2);\n        bytes[3] = ((b[3] & 0x01) << 7) | ((b[4] & 0xFE) >> 1);\n        bytes[4] = ((b[5]));\n\n        // prevent false positive of: ff ff ff ff 00\n        if (bytes[0] == 0xff && bytes[1] == 0xff && bytes[2] == 0xff && bytes[3] == 0xff) {\n            result = DECODE_FAIL_SANITY;\n            continue; // DECODE_FAIL_SANITY\n        }\n\n        decoder_log_bitrow(decoder, 1, __func__, bytes, 40, \"Contact Raw Data\");\n\n        status = bytes[0];\n        //subtype = bytes[1] >> 4;  // @todo needed for detecting keyfob\n        esn = (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n        crc = bytes[4];\n\n        if (crc8le(bytes, DSC_CT_MSGLEN, 0xf5, 0x3d) != 0) {\n            decoder_logf(decoder, 1, __func__, \"Contact bad CRC: %06X, Status: %02X, CRC: %02X\",\n                        esn, status, crc);\n            result = DECODE_FAIL_MIC;\n            continue; // DECODE_FAIL_MIC\n        }\n\n        // Decode status bits:\n\n        // 0x02 = Closed/OK/Restored\n        s_closed = (status & 0x02) == 0x02;\n\n        // 0x40 = Heartbeat (not an open/close event)\n        s_event = ((status & 0x40) != 0x40);\n\n        // 0x08 Battery Low\n        s_battery_low = (status & 0x08) == 0x08;\n\n        // Tamper: 0x10 set or 0x01 unset indicate tamper\n        // 0x10 Set to tamper message type (more testing needed)\n        // 0x01 Cleared tamper status (seen during heartbeats)\n        s_tamper = ((status & 0x01) != 0x01) || ((status & 0x10) == 0x10);\n\n        // \"experimental\" (naming might change)\n        s_xactivity = (status & 0x20) == 0x20;\n\n        // Break out 2 tamper bits\n        s_xtamper1 = (status & 0x01) != 0x01; // 0x01 set: case closed/no tamper\n        s_xtamper2 = (status & 0x10) == 0x10; //tamper event or EOL problem\n\n        // exception/states not seen\n        // 0x80 is always set and 0x04 has never been set.\n        s_exception = ((status & 0x80) != 0x80) || ((status & 0x04) == 0x04);\n\n        char status_str[3];\n        snprintf(status_str, sizeof(status_str), \"%02x\", status);\n        char esn_str[7];\n        snprintf(esn_str, sizeof(esn_str), \"%06x\", esn);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"DSC-Security\",\n                \"id\",           \"\",             DATA_INT,    esn,\n                \"closed\",       \"\",             DATA_INT,    s_closed, // @todo make bool\n                \"event\",        \"\",             DATA_INT,    s_event, // @todo make bool\n                \"tamper\",       \"\",             DATA_INT,    s_tamper, // @todo make bool\n                \"battery_ok\",   \"Battery\",      DATA_INT,    !s_battery_low,\n                \"xactivity\",    \"\",             DATA_INT,    s_xactivity, // @todo make bool\n\n                // Note: the following may change or be removed\n                \"xtamper1\",     \"\",             DATA_INT,    s_xtamper1, // @todo make bool\n                \"xtamper2\",     \"\",             DATA_INT,    s_xtamper2, // @todo make bool\n                \"exception\",    \"\",             DATA_INT,    s_exception, // @todo make bool\n                \"esn\",          \"\",             DATA_STRING, esn_str, // to be removed - transitional\n                \"status\",       \"\",             DATA_INT,    status,\n                \"status_hex\",   \"\",             DATA_STRING, status_str, // to be removed - once bits are output\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n\n        valid_cnt++; // Have a valid packet.\n    }\n\n    if (valid_cnt) {\n        return 1;\n    }\n\n    // Only returns the latest result, but better than nothing.\n    return result;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"closed\",\n        \"event\",\n        \"tamper\",\n        \"status\",\n        \"battery_ok\",\n        \"esn\",\n        \"exception\",\n        \"status_hex\",\n        \"xactivity\",\n        \"xtamper1\",\n        \"xtamper2\",\n        \"mic\",\n        NULL,\n};\n\nr_device const dsc_security = {\n        .name        = \"DSC Security Contact\",\n        .modulation  = OOK_PULSE_RZ,\n        .short_width = 250,  // Pulse length, 250 µs\n        .long_width  = 500,  // Bit period, 500 µs\n        .reset_limit = 5000, // Max gap,\n        .decode_fn   = &dsc_callback,\n        .fields      = output_fields,\n};\n\nr_device const dsc_security_ws4945 = {\n        // Used for EV-DW4927, WS4975 and WS4945.\n        .name        = \"DSC Security Contact (WS4945)\",\n        .modulation  = OOK_PULSE_RZ,\n        .short_width = 536,  // Pulse length, 536 µs\n        .long_width  = 1072, // Bit period, 1072 µs\n        .reset_limit = 9000, // Max gap, based on 8 zero bits between sync bit\n        .decode_fn   = &dsc_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ec3k.c",
    "content": "/** @file\n    Decoder for Voltcraft EnergyCount 3000 (ec3k, sold by Conrad), tested with RT-110\n\n    Copyright (C) 2025 Michael Dreher <michael(a)5dot1.de>, nospam2000 at github.com\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n#define DECODED_PAKET_LEN_BYTES (41)\n#define PAKET_MIN_BITS (90)\n#define PAKET_MAX_BITS (PAKET_MIN_BITS * 5 / 2) // NRZ encoding, stuffing and noise\n\nstruct ec3k_decode_ctx {\n    int32_t packetpos;  // number of bytes collected for current packet\n    uint8_t packet;     // 0=not in packet, 1=in packet\n    uint8_t onecount;   // count of consecutive 1 bits\n    uint8_t recbyte;    // currently received byte\n    uint8_t recpos;     // number of bits received for current byte (0..7)\n};\nstatic inline void ec3k_resync_decoder(struct ec3k_decode_ctx *ctx)\n{\n    ctx->packetpos = 0;\n    ctx->packet    = 0;\n    ctx->onecount  = 0;\n    ctx->recbyte   = 0;\n    ctx->recpos    = 0;\n}\n\nstatic int ec3k_extract_fields(r_device *const decoder, const uint8_t *packetbuffer);\n\nstatic inline uint8_t bit_at(const uint8_t *bytes, int32_t bit)\n{\n    return (uint8_t)(bytes[bit >> 3] >> (7 - (bit & 7)) & 1);\n}\n\nstatic inline uint8_t symbol_at(const uint8_t *bytes, int32_t bit)\n{\n    // NRZI decoding\n    uint8_t bit0 = (bit > 0) ? bit_at(bytes, bit - 1) : 0;\n    uint8_t bit1 = bit_at(bytes, bit);\n    return (bit0 == bit1) ? 1 : 0;\n}\n\nstatic inline uint8_t descrambled_symbol_at(const uint8_t *bytes, int32_t bit)\n{\n    uint8_t out = symbol_at(bytes, bit);\n    if (bit > 17)\n        out = out ^ symbol_at(bytes, bit - 17);\n    if (bit > 12)\n        out = out ^ symbol_at(bytes, bit - 12);\n\n    return out;\n}\n\n// maximum 8 nibbles (32 bits)\nstatic inline uint32_t unpack_nibbles(const uint8_t *buf, int32_t start_nibble, int32_t num_nibbles)\n{\n    uint32_t val = 0;\n    for (int32_t i = 0; i < num_nibbles; i++) {\n        val = (val << 4) | ((buf[(start_nibble + i) / 2] >> ((1 - ((start_nibble + i) & 1)) * 4)) & 0x0F);\n    }\n    return val;\n}\n\n/**\nDecoder for Voltcraft EnergyCount 3000 (ec3k, sold by Conrad), tested with RT-110\n\nThe bit time is 50 us. The device transmits every 5 seconds (if there is a change in power consumption)\nor every 30 minutes (if there is no change). It uses BFSK modulation with two frequencies between\n30 and 80 kHz apart (e.g. 868.297 and 868.336 MHz).\n\nThe used chip is probably a AX5042 from On Semiconductor (formerly from Axsem), datasheet: https://www.onsemi.com/download/data-sheet/pdf/ax5042-d.pdf\nHDLC mode follows High−Level Data Link Control (HDLC, ISO 13239) protocol. HDLC Mode is the main framing mode of the AX5042.\nHDLC packets are delimited with flag sequences of content 0x7E. In AX5042 the meaning of address and control is user defined.\nThe Frame Check Sequence (FCS) can be programmed to be CRC−CCITT, CRC−16 or CRC−32.\nThe CRC is appended to the received data. There could be an optional flag byte after the CRC.\nThe packet length is 41 bytes (including the 16-bit CRC but excluding the two framing bytes and the optional flag byte).\nThe packet is NRZI encoded, with bit stuffing (a 0 is inserted after 5 consecutive 1 bits).\nThe packet is framed by 0x7E (01111110) bytes at start and end.\nThe CRC is calculated over the packet excluding the leading and trailing framing byte 0x7E and the crc-value itself.\nThe CRC bytes in the packet are in little-endian order (low byte first).\nThe CRC polynomial is 0x8408 (the reverse of the standard CRC-16-CCITT polynomial 0x1021),\nthe initial value is 0xFFFF, the CRC is inverted (XORed with 0xFFFF) before appending to the packet,\nand the CRC calculation is done on the bit-reflected input data. See also https://reveng.sourceforge.io/crc-catalogue/16.htm#crc.cat.crc-16-ibm-sdlc\n\nList of known compatible devices:\n- Voltcraft EnergyCount 3000 (\"ec3k\", Item No. 12 53 53, https://conrad-rus.ru/images/stories/virtuemart/media/125353-an-01-ml-TCRAFT_ENERGYC_3000_ENER_MESSG_de_en_nl.pdf)\n- Technoline Cost Control RT-110 (https://www.technotrade.de/produkt/technoline-cost-control-rt-110/), EAN 4029665006208\n- Velleman (type NETBESEM4)\n- La Crosse Techology Remote Cost Control Monitor” (type RS3620)\n\nThe following fields are decoded:\n- id -- 16-bit ID of the device\n- time_total -- time in seconds since last reset\n- time_on -- time in seconds since last reset with non-zero device power\n- energy -- total energy in kWh (transmitted in Ws (watt-seconds))\n- power_current -- current device power in watts (transmitted in 0.1 watt steps)\n- power_max -- maximum device power in watts (reset at unknown intervals, transmitted in 0.1 watt steps)\n- reset_counter -- total number of transmitter resets\n- device_on_flag -- true if device is currently drawing non-zero power\n- crc\n- some padding fields that are always zero\n\nDecoding works best with this params for a RTL28382U, you might need to tune the frequency offset to your devices, especially for 250k sample rate:\n    rtl_433 -f 868000k -s 1000k\n    rtl_433 -f 868300k -s 250k\n\n\\verbatim\nTo test with a file created by URH you can use this command:\n    cat Rad1o-20251001_112936-868_2MHz-2MSps-2MHz_single.complex16s | csdr convert_s8_f | csdr fir_decimate_cc 2 0.02 HAMMING | csdr convert_f_s8 | rtl_433 -R 282 -r CS8:- -f 868000k -s 1000k\n    fir_decimate_cc: taps_length = 201\n    rtl_433 version -128-NOTFOUND branch feat-ec3k at 202510042209 inputs file rtl_tcp RTL-SDR with TLS\n    New defaults active, use \"-Y classic -s 250k\" if you need the old defaults\n\n    [Input] Test mode active. Reading samples from file: <stdin>\n    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n    time      : @1.864588s\n    model     : Voltcraft-EC3k            id        : bb9b\n    Power     : 90.200       Energy    : 754.518       Energy 2  : 1.860         Integrity : CRC\n    Time total: 64942080     Time on   : 57501776      Power max : 186.500       Reset counter: 4          Flags     : 8\n    [pulse_slicer_pcm] Voltcraft EnergyCount 3000 (ec3k)\n    codes     : {550}d4018c7e67bf2e4b15f2b3b404fc2bdace27e30ba759a5be0edcbff0f5e2b070f59d89ec5459cef2a6cddb6adf8c4e487546309633d08e4a092fba1d16749519e5de63c5c0\n\\endverbatim\n\nCheck here for some example captures: https://github.com/merbanan/rtl_433_tests/tree/master/tests/ec3k/01\n\nDecoding info taken from these projects:\n- https://github.com/EmbedME/ec3k_decoder (using rtl_fm)\n- https://github.com/avian2/ec3k (using python and gnuradio)\n\nSome more info can be found here:\n- https://www.sevenwatt.com/main/rfm69-energy-count-3000-elv-cost-control/\n- https://batilanblog.wordpress.com/2015/01/11/getting-data-from-voltcraft-energy-count-3000-on-your-computer/\n- https://web.archive.org/web/20121019130917/http://forum.jeelabs.net:80/comment/4020\n */\nstatic int ec3k_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[0] < PAKET_MIN_BITS) {\n        decoder_logf(decoder, 3, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH; // Unrecognized data\n    }\n\n    int rc = DECODE_ABORT_EARLY;\n    uint8_t packetbuffer[DECODED_PAKET_LEN_BYTES];\n    struct ec3k_decode_ctx ctx;\n    ec3k_resync_decoder(&ctx);\n\n    for (int32_t bufferpos = 17; rc != 1 && bufferpos < bitbuffer->bits_per_row[0]; bufferpos++) {\n        uint8_t out = descrambled_symbol_at((const uint8_t *)bitbuffer->bb[0], bufferpos);\n        if (out) {\n            if ((ctx.onecount < 6) && (ctx.packetpos < DECODED_PAKET_LEN_BYTES)) {\n                ctx.onecount++;\n                ctx.recbyte = ctx.recbyte >> 1 | 0x80;\n                ctx.recpos++;\n                if ((ctx.recpos == 8) && (ctx.packet)) {\n                    ctx.recpos                    = 0;\n                    packetbuffer[ctx.packetpos++] = ctx.recbyte;\n                }\n            }\n            else {\n                ec3k_resync_decoder(&ctx);\n            }\n        }\n        else {\n            if ((ctx.onecount < 5) && (ctx.packetpos < DECODED_PAKET_LEN_BYTES)) {\n                // normal 0 bit\n                ctx.recbyte = ctx.recbyte >> 1;\n                ctx.recpos++;\n                if ((ctx.recpos == 8) && (ctx.packet)) {\n                    ctx.recpos                    = 0;\n                    packetbuffer[ctx.packetpos++] = ctx.recbyte;\n                }\n            }\n            else if (ctx.onecount == 5) {\n                // bit unstuffing: 0 after 5 ones is a stuffed 0, skip it\n            }\n            // start and end of packet is marked by 6 ones surrounded by zero (0x7e)\n            else if (ctx.onecount == 6) {\n                ctx.packet    = !ctx.packet;\n                ctx.packetpos = 0;\n                ctx.recpos    = 0;\n            }\n            else {\n                ec3k_resync_decoder(&ctx);\n            }\n\n            ctx.onecount = 0;\n        }\n\n        if (ctx.packetpos >= DECODED_PAKET_LEN_BYTES) {\n            rc = ec3k_extract_fields(decoder, packetbuffer);\n            ec3k_resync_decoder(&ctx);\n        }\n    }\n\n    return rc;\n}\n\n static int ec3k_extract_fields(r_device *const decoder, const uint8_t *packetbuffer)\n{\n    uint16_t id             = unpack_nibbles(packetbuffer, 1, 4);\n    uint16_t time_total_low = unpack_nibbles(packetbuffer, 5, 4);\n    uint16_t pad_1          = unpack_nibbles(packetbuffer, 9, 4);\n    uint16_t time_on_low    = unpack_nibbles(packetbuffer, 13, 4);\n    uint32_t pad_2          = unpack_nibbles(packetbuffer, 17, 7);\n    uint32_t energy_low     = unpack_nibbles(packetbuffer, 24, 7);\n    double power_current    = unpack_nibbles(packetbuffer, 31, 4) / 10.0;\n    double power_max        = unpack_nibbles(packetbuffer, 35, 4) / 10.0;\n    // unknown? (seems to be used for internal calculations)\n    uint32_t energy2 = unpack_nibbles(packetbuffer, 39, 6);\n    // \t\t\t\t\t\tnibbles[45:59]\n    uint16_t time_total_high = unpack_nibbles(packetbuffer, 59, 3);\n    uint32_t pad_3           = unpack_nibbles(packetbuffer, 62, 5);\n    uint64_t energy_high     = (uint64_t)unpack_nibbles(packetbuffer, 67, 4) << 28;\n    uint16_t time_on_high    = unpack_nibbles(packetbuffer, 71, 3);\n    uint8_t reset_counter    = unpack_nibbles(packetbuffer, 74, 2);\n    uint8_t flags            = unpack_nibbles(packetbuffer, 76, 1);\n    uint8_t pad_4            = unpack_nibbles(packetbuffer, 77, 1);\n    uint16_t received_crc    = 0xffff ^ (unpack_nibbles(packetbuffer, 78, 2) | (unpack_nibbles(packetbuffer, 80, 2) << 8)); // little-endian\n    uint16_t calculated_crc  = crc16lsb(packetbuffer, DECODED_PAKET_LEN_BYTES - 2, 0x8408, 0xffff);\n\n    // convert to common units\n    uint64_t energy_Ws       = energy_high | energy_low;\n    const double energy_kWh  = energy_Ws / (1000.0 * 3600.0); // Ws to kWh\n    const double energy2_kWh = energy2 / (1000.0 * 3600.0);   // Ws to kWh\n    uint32_t time_total      = (uint32_t)time_total_low | ((uint32_t)time_total_high << 16);\n    uint32_t time_on         = (uint32_t)time_on_low | ((uint32_t)time_on_high << 16);\n\n    if (pad_1 != 0 || pad_2 != 0 || pad_3 != 0 || pad_4 != 0) {\n        decoder_logf(decoder, 1, __func__, \"Warning: padding bits are not zero, pad_1=%u pad_2=%u pad_3=%u pad_4=%u\", pad_1, pad_2, pad_3, pad_4);\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (calculated_crc != received_crc) {\n        decoder_logf(decoder, 1, __func__, \"Warning: CRC error, calculated %04X but received %04X\", calculated_crc, received_crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",              DATA_STRING, \"Voltcraft-EC3k\",\n            \"id\",               \"\",              DATA_FORMAT, \"%04x\", DATA_INT, id,\n            \"power\",            \"Power\",         DATA_DOUBLE, power_current,\n            \"energy\",           \"Energy\",        DATA_DOUBLE, energy_kWh,\n            \"energy2\",          \"Energy 2\",      DATA_DOUBLE, energy2_kWh,\n            \"time_total\",       \"Time total\",    DATA_INT,    time_total,\n            \"time_on\",          \"Time on\",       DATA_INT,    time_on,\n            \"power_max\",        \"Power max\",     DATA_DOUBLE, power_max,\n            \"reset_counter\",    \"Reset counter\", DATA_INT,    reset_counter,\n            \"flags\",            \"Flags\",         DATA_INT,    flags,\n            \"mic\",              \"Integrity\",     DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n// originally from the ec3k python implementation at https://github.com/avian2/ec3k\n// this crc function is aquivalent to crc16lsb(buffer, len, 0x8408, 0xffff) but proably faster\n/*\nstatic inline uint16_t calc_ec3k_crc(const uint8_t *buffer, size_t len)\n{\n    uint16_t crc = 0xffff;\n    for (size_t i = 0; i < len; i++) {\n        uint8_t ch = buffer[i];\n        ch ^= crc & 0xff;\n        ch ^= (ch << 4) & 0xff;\n        crc = (((uint16_t)ch << 8) | (crc >> 8)) ^ ((uint16_t)ch >> 4) ^ ((uint16_t)ch << 3);\n    }\n\n    return crc;\n}\n*/\n\n/*\n * List of fields that may appear in the output\n *\n * Used to determine what fields will be output in what\n * order for this device when using -F csv.\n *\n */\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"power\",\n        \"energy\",\n        \"energy2\",\n        \"time_total\",\n        \"time_on\",\n        \"power_max\",\n        \"reset_counter\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ec3k = {\n        .name        = \"Voltcraft EnergyCount 3000 (ec3k)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 50, // in us\n        .long_width  = 50, // in us\n        .tolerance   = 5, //  in us ; there can be up to 5 consecutive 0 or 1 pulses and the sync word is 6 bits, so 15% would be max\n        .gap_limit   = 3000,            // some distance above long\n        .reset_limit = 5000,            // a bit longer than packet gap\n        .decode_fn   = &ec3k_decode,\n        .disabled    = 0,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ecodhome.c",
    "content": "/** @file\n    Decoder for EcoDHOME Smart Socket and MCEE Solar monitor.\n\n    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for EcoDHOME Smart Socket and MCEE Solar monitor.\n\n(the Smart Switch should be the same as the Smart Socket.)\n\nSmart Socket receives and implements the switching on/off instruction remotely from the Controller.\nThe Transmitters with sensor clamps collect home energy consumption data for the MCEE Solar monitor.\n\nsee https://github.com/merbanan/rtl_433/issues/1525\n\nThe transmission is FSK PCM with 250 us bit width.\n\n## PV Transmitter (P/N 01333-5847-00)\n\nExample data:\n\n    {144}aaaaaa 2dd4 8c74 d4b9 3eb3 223844 51 550000\n    {144}aaaaaa 2dd4 8c74 d4b9 3eb3 c53344 ef 550000\n    {144}aaaaaa 2dd4 8c76 d4b9 71b3 863363 04 550000 (every 71 seconds)\n\nOther device:\n\n    {144}aaaaaa 2dd4 8c74 12d6  Type: 3eb3 bc3544\n    {144}aaaaaa 2dd4 8c76 12d6  Type: 71b3 333363 (also 863363)\n\n- 3eb3 messages are a power reading of LL HH 0x44, LL and HH start at 0x33 (=0) and wrap up to 0x32 (=255)\n- 71b3 messages (arrive every 71 seconds)\n- 71b3 863363 04 550000 which might be some kind of status then and not a reading.\n\nThe checksum is: add all bytes after the sync word plus 0x35 (mod 0xff).\n\n## Smart Socket (P/N 01333-5840-00)\n\nExample data:\n\n    {155}2ad455555516ea2918ae353b802b2d3f8029a12\n    {154}55a8 aaaaaa 2dd4 5231 5c6a 7700 565a 7f00 53 42 4\n\nData Seen:\n\n    52315c6a 7700 565a 007f00\n    52315c6a 7700 565a 007e00\n    52315c6a 7700 565a 007d00\n    46315c6a 7700 414b 000000\n    52315c6a 7700 565a 008000\n    46315c6a 7700 5053 000000\n    52315c6a 7700 414b 000000\n    52315c6a 7700 565a 008100\n    52315c6a 7700 565a 008200\n    52315c6a 7700 565a 008300\n    52315c6a 7700 414b 003209\n    52315c6a 7700 414b 003d03\n    52315c6a 7700 565a 007c00\n    52315c6a 7700 565a 007b00\n    52315c6a 7700 565a 007a00\n\nRemoving the first 1 or 2 bits gives a prefix of 55a8aaaaaa2dd4, the leading bits are likely warm-up or garbage.\n\nThe next bytes of 5231 5c6a 7700 are likely a serial number (id).\n\nThen we have messages with 414b or 565a or 5053 which likely is a message type.\nOn 414b the two byte (little endian) power value follows. For the other types it is unknown, maybe kWh or state.\nLastly there is a fixed 53 (status? stop?) and a checksum byte.\n\nInteresting to note that 414b, 565a, and 53 are \"AK\", \"VZ\", and \"S\" which might not be a coincidence.\n\nThe checksum is: add all bytes after the sync word (mod 0xff).\n*/\n\nstatic int ecodhome_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0x2d, 0xd4};\n\n    data_t *data;\n    uint8_t msg[13];\n\n    if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[0] < 128) {\n        decoder_logf(decoder, 2, __func__, \"to few bits (%u)\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH; // unrecognized\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n    start_pos += sizeof(preamble_pattern) * 8;\n\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"preamble not found\");\n        return DECODE_ABORT_EARLY; // no preamble found\n    }\n    //if (start_pos + sizeof (msg) * 8 >= bitbuffer->bits_per_row[0]) {\n    if (start_pos + 12 * 8 >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 2, __func__, \"message too short (%u)\", bitbuffer->bits_per_row[0] - start_pos);\n        return DECODE_ABORT_LENGTH; // message too short\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);\n    decoder_log_bitrow(decoder, 2, __func__, msg, sizeof(msg) * 8, \"MSG\");\n\n    uint32_t id   = ((uint32_t)msg[0] << 24) | (msg[1] << 16) | (msg[2] << 8) | (msg[3]);\n    int m_type    = (msg[4] << 8) | (msg[5]);\n    int m_subtype = (msg[6] << 8) | (msg[7]); // only Smart Socket\n\n    if (m_type == 0x7700) {\n        int sum = add_bytes(msg, 11); // socket\n        if ((sum & 0xff) != msg[11]) {\n            decoder_logf(decoder, 2, __func__, \"checksum fail %02x vs %02x\", sum, msg[9]);\n            return DECODE_FAIL_MIC;\n        }\n        if (msg[10] != 0x53) {\n            decoder_logf(decoder, 2, __func__, \"wrong stop byte %02x\", msg[10]);\n            return DECODE_FAIL_SANITY;\n        }\n        int raw     = (msg[8] << 8) | (msg[9]);\n        int power_w = (msg[9] << 8) | (msg[8]);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"EcoDHOME-SmartSocket\",\n                \"id\",               \"\",                 DATA_FORMAT, \"%08x\",   DATA_INT, id,\n                \"message_type\",     \"Message Type\",     DATA_FORMAT, \"%04x\",   DATA_INT, m_type,\n                \"message_subtype\",  \"Message Subtype\",  DATA_FORMAT, \"%04x\",   DATA_INT, m_subtype,\n                \"power_W\",          \"Power\",            DATA_COND, m_subtype == 0x414b, DATA_FORMAT, \"%.1f W\",   DATA_DOUBLE, (double)power_w,\n                \"raw\",              \"Raw data\",         DATA_FORMAT, \"%06x\",   DATA_INT, raw,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n    }\n    else {\n        int sum = add_bytes(msg, 9) + 0x35; // transmitter\n        if ((sum & 0xff) != msg[9]) {\n            decoder_logf(decoder, 2, __func__, \"checksum fail %02x vs %02x\", sum, msg[9]);\n            return DECODE_FAIL_MIC;\n        }\n        if (msg[10] != 0x55) {\n            decoder_logf(decoder, 2, __func__, \"wrong stop byte %02x\", msg[10]);\n            return DECODE_FAIL_SANITY;\n        }\n        if (msg[11] != 0x00) {\n            decoder_logf(decoder, 2, __func__, \"wrong poststop byte %02x\", msg[11]);\n            return DECODE_FAIL_SANITY;\n        }\n        int raw     = (msg[6] << 16) | (msg[7] << 8) | (msg[8]);\n        int power_w = ((uint8_t)(msg[7] - 0x33) << 8) | (uint8_t)(msg[6] - 0x33);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"EcoDHOME-Transmitter\",\n                \"id\",               \"\",                 DATA_FORMAT, \"%08x\",   DATA_INT, id,\n                \"message_type\",     \"Message Type\",     DATA_FORMAT, \"%04x\",   DATA_INT, m_type,\n                \"power_W\",          \"Power\",            DATA_COND, m_type == 0x3eb3, DATA_FORMAT, \"%.1f W\",   DATA_DOUBLE, (double)power_w,\n                \"raw\",              \"Raw data\",         DATA_FORMAT, \"%06x\",   DATA_INT, raw,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"message_type\",\n        \"message_subtype\",\n        \"power_W\",\n        \"raw\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ecodhome = {\n        .name        = \"EcoDHOME Smart Socket and MCEE Solar monitor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 250,\n        .long_width  = 250,\n        .reset_limit = 6000,\n        .decode_fn   = &ecodhome_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ecowitt.c",
    "content": "/** @file\n    Ecowitt Wireless Outdoor Thermometer WH53/WH0280/WH0281A.\n\n    Copyright 2019 Google LLC.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nEcowitt Wireless Outdoor Thermometer WH53/WH0280/WH0281A.\n\n55-bit one-row data packet format (inclusive ranges, 0-indexed):\n\n|  0-6  | 7-bit header, ignored for checksum, always 1111111, not stable, could be 6 x 1 bit see #2933\n|  7-14 | Model code, 0x53\n| 15-22 | Sensor ID, randomly reinitialized on boot\n| 23-24 | Always 00\n| 25-26 | 2-bit sensor channel, selectable on back of sensor {00=1, 01=2, 10=3}\n| 27-28 | Always 00\n| 29-38 | 10-bit temperature in tenths of degrees C, starting from -40C. e.g. 0=-40C\n| 39-46 | Trailer, always 1111 1111\n| 47-54 | CRC-8 checksum poly 0x31 init 0x00 skipping first 7 bits\n*/\n\n#include \"decoder.h\"\n\nstatic int ecowitt_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {\n        0xf5, 0x30  // preamble and model code nominally 7+8 bit, look for 12 bit only #2933\n        };\n\n    // All Ecowitt packets have one row.\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 12);\n\n    // Preamble found ?\n    if (pos >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 2, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // 4 + 6*8 bit required\n    if ((bitbuffer->bits_per_row[0] - pos) < 52) {\n        decoder_logf(decoder, 2, __func__, \"Too short\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Byte-align the rest of the message by skipping the first 4 bit.\n    uint8_t b[6];\n    bitbuffer_extract_bytes(bitbuffer, 0, pos + 4 , b, sizeof(b) * 8); // Skip first 4 bit but keep model 0x53 needed for crc\n    decoder_log_bitrow(decoder, 2, __func__, b, sizeof(b) * 8, \"MSG\");\n\n    // check crc, poly 0x31, init 0x00\n    if (crc8(b, 6, 0x31, 0)) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // Randomly generated at boot time sensor ID.\n    int sensor_id = b[1];\n\n    int channel = b[2] >> 4; // First nybble.\n    channel++; // Convert 0-indexed wire protocol to 1-indexed channels on the device UI\n    if (channel > 3) {\n        return DECODE_FAIL_SANITY; // The switch only has 1-3.\n    }\n\n    // All Ecowitt packets have bits 27 and 28 set to 0\n    // Perhaps these are just an extra two high bits for temperature?\n    // The manual though says it only operates to 60C, which about matches 10 bits (1023/10-40C)=62.3C\n    // Above 60 is pretty hot - let's just check these are always zero.\n    if ((b[2] & (4 | 8)) != 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Temperature is next 10 bits\n    int temp_raw = ((b[2] & 0x3) << 8) | b[3];\n    float temp_c = (temp_raw - 400) * 0.1f;\n\n    // All Ecowitt observed packets have bits 39-48 set.\n    if (b[4] != 0xFF) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Ecowitt-WH53\",\n            \"id\",            \"Id\",          DATA_INT,    sensor_id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ecowitt = {\n        .name        = \"Ecowitt Wireless Outdoor Thermometer WH53/WH0280/WH0281A\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 500,  // 500 us nominal short pulse\n        .long_width  = 1480, // 1480 us nominal long pulse\n        .gap_limit   = 1500, // 960 us nominal fixed gap\n        .reset_limit = 2000, // 31 ms packet distance (too far apart)\n        .sync_width  = 0,\n        .decode_fn   = &ecowitt_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/efergy_e2_classic.c",
    "content": "/** @file\n    Efergy e2 classic (electricity meter).\n\n    Copyright (C) 2015 Robert Högberg <robert.hogberg@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nEfergy e2 classic (electricity meter).\n\nThis electricity meter periodically reports current power consumption\non frequency ~433.55 MHz. The data that is transmitted consists of 8\nbytes:\n\n- Byte 1: Start bits (00)\n- Byte 2-3: Device id\n- Byte 4: Learn mode, sending interval and battery status\n- Byte 5-7: Current power consumption\n  -  Byte 5: Integer value (High byte)\n  -  Byte 6: integer value (Low byte)\n  -  Byte 7: exponent (values between -3? and 4?)\n- Byte 8: Checksum\n\nPower calculations come from Nathaniel Elijah's program EfergyRPI_001.\n\nTest codes:\n- Current   4.64 A: {65}0cc055604a41030f8\n- Current 185.16 A: {65}0cc055605c9408798\n\n*/\n\n#include \"decoder.h\"\n\nstatic int efergy_e2_classic_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    unsigned num_bits = bitbuffer->bits_per_row[0];\n    uint8_t *bytes = bitbuffer->bb[0];\n    data_t *data;\n\n    if (num_bits < 64 || num_bits > 80) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // The bit buffer isn't always aligned to the transmitted data, so\n    // search for data start and shift out the bits which aren't part\n    // of the data. The data always starts with 0000 (or 1111 if\n    // gaps/pulses are mixed up).\n    while ((bytes[0] & 0xf0) != 0xf0 && (bytes[0] & 0xf0) != 0x00) {\n        num_bits -= 1;\n        if (num_bits < 64) {\n            return DECODE_FAIL_SANITY;\n        }\n\n        for (unsigned i = 0; i < (num_bits + 7) / 8; ++i) {\n            bytes[i] <<= 1;\n            bytes[i] |= (bytes[i + 1] & 0x80) >> 7;\n        }\n    }\n\n    // Sometimes pulses and gaps are mixed up. If this happens, invert\n    // all bytes to get correct interpretation.\n    if (bytes[0] & 0xf0) {\n        for (unsigned i = 0; i < 8; ++i) {\n            bytes[i] = ~bytes[i];\n        }\n    }\n\n    int zero_count = 0;\n    for (int i = 0; i < 8; i++) {\n        if (bytes[i] == 0)\n            zero_count++;\n    }\n    if (zero_count++ > 5)\n        return DECODE_FAIL_SANITY; // too many Null bytes\n\n    unsigned checksum = add_bytes(bytes, 7);\n\n    if (checksum == 0) {\n        return DECODE_FAIL_SANITY; // reduce false positives\n    }\n    if ((checksum & 0xff) != bytes[7]) {\n        return DECODE_FAIL_MIC;\n    }\n\n    uint16_t address = bytes[2] << 8 | bytes[1];\n    uint8_t learn    = (bytes[3] & 0x80) >> 7;\n    uint8_t interval = (((bytes[3] & 0x30) >> 4) + 1) * 6;\n    uint8_t battery  = (bytes[3] & 0x40) >> 6;\n    uint8_t fact     = -(int8_t)bytes[6] + 15;\n    if (fact < 7 || fact > 23) // full range unknown so far\n        return DECODE_FAIL_SANITY; // invalid exponent\n    float current_adc = (float)(bytes[4] << 8 | bytes[5]) / (1 << fact);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",                 DATA_STRING, \"Efergy-e2CT\",\n            \"id\",           \"Transmitter ID\",   DATA_INT,    address,\n            \"battery_ok\",   \"Battery\",          DATA_INT,    !!battery,\n            \"current\",      \"Current\",          DATA_FORMAT, \"%.2f A\", DATA_DOUBLE, current_adc,\n            \"interval\",     \"Interval\",         DATA_FORMAT, \"%ds\", DATA_INT, interval,\n            \"learn\",        \"Learning\",         DATA_STRING, learn ? \"YES\" : \"NO\",\n            \"mic\",          \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"current\",\n        \"interval\",\n        \"learn\",\n        \"mic\",\n        NULL,\n};\n\nr_device const efergy_e2_classic = {\n        .name        = \"Efergy e2 classic\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 64,\n        .long_width  = 136,\n        .sync_width  = 500,\n        .gap_limit   = 200,\n        .reset_limit = 400,\n        .decode_fn   = &efergy_e2_classic_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/efergy_optical.c",
    "content": "/** @file\n    Efergy IR Optical energy consumption meter.\n\n    Copyright (C) 2016 Adrian Stevenson <adrian_stevenson2002@yahoo.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nEfergy IR is a devices that periodically reports current energy consumption\non frequency ~433.55 MHz. The data that is transmitted consists of 8\nbytes:\n\n- Byte 0-2: Start bits (0000), then static data (probably device id)\n- Byte 3: seconds (64: 30s - red led; 80: 60s - orange led; 96: 90s - green led)\n- Byte 4-7: all zeros\n- Byte 8: Pulse Count\n- Byte 9: sample frequency (15 seconds)\n- Byte 10-11: bytes 0-9 crc16 xmodem XOR with FF\n\n    if pulse count <3 then energy = ((pulsecount/impulse-perkwh) * (3600/seconds))\n    else energy = ((pulsecount/n_imp) * (3600/seconds))\n\nTransmitter can operate in 3 modes (signaled in bytes[3]):\n- red led: information is sent every 30s\n- orange led: information is sent every 60s\n- green led: information is sent every 90s\n\nTo get the mode: short-push the physical button on transmitter.\nTo set the mode: long-push the physical button on transmitter.\n*/\n\n#include \"decoder.h\"\n\nstatic int efergy_optical_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    unsigned num_bits = bitbuffer->bits_per_row[0];\n    uint8_t *bytes = bitbuffer->bb[0];\n\n    if (num_bits < 96 || num_bits > 100)\n        return DECODE_ABORT_LENGTH;\n\n    // The bit buffer isn't always aligned to the transmitted data, so\n    // search for data start and shift out the bits which aren't part\n    // of the data. The data always starts with 0000 (or 1111 if\n    // gaps/pulses are mixed up).\n    while ((bytes[0] & 0xf0) != 0xf0 && (bytes[0] & 0xf0) != 0x00) {\n        num_bits -= 1;\n        if (num_bits < 96)\n            return DECODE_ABORT_EARLY;\n\n        for (unsigned i = 0; i < (num_bits + 7) / 8; ++i) {\n            bytes[i] <<= 1;\n            bytes[i] |= (bytes[i + 1] & 0x80) >> 7;\n        }\n    }\n\n    // Sometimes pulses and gaps are mixed up. If this happens, invert\n    // all bytes to get correct interpretation.\n    if (bytes[0] & 0xf0) {\n        for (unsigned i = 0; i < 12; ++i) {\n            bytes[i] = ~bytes[i];\n        }\n    }\n\n    // reject false positives\n    if ((bytes[8] == 0) && (bytes[9] == 0) && (bytes[10] == 0) && (bytes[11] == 0)) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Calculate checksum for bytes[0..9]\n    // crc16 xmodem with start value of 0x00 and polynomic of 0x1021 is same as CRC-CCITT (0x0000)\n    // start of data, length of data=10, polynomic=0x1021, init=0x0000\n\n    uint16_t csum1 = (bytes[10] << 8) | (bytes[11]);\n\n    uint16_t crc = crc16(bytes, 10, 0x1021, 0x0000);\n\n    if (crc != csum1) {\n        decoder_log(decoder, 1, __func__, \"CRC error.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    unsigned id = ((unsigned)bytes[0] << 16) | (bytes[1] << 8) | (bytes[2]);\n\n    // interval:\n    // - red led (every 30s):    bytes[3]=64 (0100 0000)\n    // - orange led (every 60s): bytes[3]=80 (0101 0000)\n    // - green led (every 90s):  bytes[3]=96 (0110 0000)\n    float seconds = (((bytes[3] & 0x30) >> 4) + 1) * 30.0f;\n\n    int pulsecount = bytes[8];\n\n    // this setting depends on your electricity meter's optical output\n    // New code for calculating various energy values for differing pulse-kwh values\n    const int imp_kwh[] = {4000, 3200, 2000, 1000, 500, 0};\n    for (unsigned i = 0; imp_kwh[i] != 0; ++i) {\n        float energy = (((float)pulsecount / imp_kwh[i]) * (3600 / seconds));\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"Model\",        DATA_STRING, \"Efergy-Optical\",\n                \"id\",           \"\",             DATA_INT,   id,\n                \"pulses\",       \"Pulse-rate\",   DATA_INT, imp_kwh[i],\n                \"pulsecount\",   \"Pulse-count\",  DATA_INT, pulsecount,\n                \"energy_kWh\",   \"Energy\",       DATA_FORMAT, \"%.3f kWh\", DATA_DOUBLE, energy,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n    }\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"pulses\",\n        \"pulsecount\",\n        \"energy_kWh\",\n        \"mic\",\n        NULL,\n};\n\nr_device const efergy_optical = {\n        .name        = \"Efergy Optical\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 64,\n        .long_width  = 136,\n        .sync_width  = 500,\n        .reset_limit = 400,\n        .decode_fn   = &efergy_optical_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/efth800.c",
    "content": "/** @file\n    Eurochron EFTH-800 temperature and humidity sensor.\n\n    Copyright (c) 2020 by Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nEurochron EFTH-800 temperature and humidity sensor.\n\nWakeup of short pulse, 4x 970 us gap, 990 us pulse,\npacket gap of 4900 us,\ntwo packets of each\n4x 750 us pulse, 720 us gap, then\n(1-bit) 500 us pulse, 230 us gap or\n(0-bit) 250 us pulse, 480 us gap.\n\nThere might be an alternative (longer) packet interleaved, e.g.:\n\n    {65} 2B 1E A9 90 AB D3 83 2A 8\n    {49} AB 1F B3 B7 B6 BE 80\n    {65} 2B 1E A9 90 AB D3 83 2A 8\n    {49} AB 1F B3 B7 B6 BE 8\n\nData layout:\n\n    ?ccc iiii  iiii iiii  bntt tttt  tttt ????  hhhh hhhh  xxxx xxxx\n\n- c:  3 bit channel valid channels are 0-7 (stands for channel 1-8)\n- i: 12 bit random id (changes on power-loss)\n- b:  1 bit battery indicator (0=>OK, 1=>LOW)\n- n:  1 bit temperature sign? (0=>negative, 1=>positive)\n- t: 10 bit signed temperature, scaled by 10\n- h:  8 bit relative humidity percentage (BCD)\n- x:  8 bit CRC-8, poly 0x31, init 0x00\n- ?: unknown (Bit 0, 28-31 always 0 ?)\n\nThe sensor sends messages at intervals of about 57-58 seconds.\n*/\n\n#include \"decoder.h\"\n\nstatic int eurochron_efth800_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_invert(bitbuffer);\n\n    /* Look for clock packet */\n    char dcf77_str[20] = {0}; // \"2064-16-32T32:64:64\"\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 65);\n    if (row > 0) {\n        uint8_t *b = bitbuffer->bb[row];\n\n        // 0         1      2       3       4       5    6         7\n        // ?1b CH:3d ID:12d 3b H?5d 2b M:6d 2b S:6d Y?7d D:5d M:4d CHK?8h 1x\n        // TODO: (b[2] >> 5) may have DST and/or TZ info ?\n        int dcf77_hour = (b[2] & 0x1f);\n        int dcf77_min  = (b[3] & 0x3f);\n        int dcf77_sec  = (b[4] & 0x3f);\n        int dcf77_year = (b[5] >> 1);\n        int dcf77_day  = ((b[5] & 0x01) << 4) | (b[6] & 0xf0) >> 4;\n        int dcf77_mth  = (b[6] & 0x0f);\n\n        if (!crc8(b, 8, 0x31, 0x00)) {\n            snprintf(dcf77_str, sizeof(dcf77_str), \"%4d-%02d-%02dT%02d:%02d:%02d\", dcf77_year + 2000, dcf77_mth, dcf77_day, dcf77_hour, dcf77_min, dcf77_sec);\n        }\n    }\n\n    // Remove long rows with unknown data\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        if (bitbuffer->bits_per_row[row] > 49) {\n            bitbuffer->bits_per_row[row] = 0; // cancel row\n        }\n    }\n\n    /* Validation checks */\n    row = bitbuffer_find_repeated_row(bitbuffer, 2, 48);\n\n    if (row < 0) // repeated rows?\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 49) // 48 bits per row?\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = bitbuffer->bb[row];\n\n    // No need to decode/extract values for simple test\n    if (b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x00 && b[4] == 0x00) {\n        // data has been inverted at this point, originally it was 0xff\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0xff\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (crc8(b, 6, 0x31, 0x00)) {\n        return DECODE_FAIL_MIC; // crc mismatch\n    }\n\n    /* Extract data */\n    int channel     = (b[0] & 0x70) >> 4;\n    int id          = ((b[0] & 0x0f) << 8) | b[1];\n    int battery_low = b[2] >> 7;\n    int temp_raw    = (int16_t)((b[2] & 0x3f) << 10) | ((b[3] & 0xf0) << 2); // sign-extend\n    float temp_c    = (temp_raw >> 6) * 0.1f;\n    int humidity    = (b[4] >> 4) * 10 + (b[4] & 0xf); // BCD\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Eurochron-EFTH800\",\n            \"id\",               \"\",             DATA_INT,    id,\n            \"channel\",          \"\",             DATA_INT,    channel + 1,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_INT,    humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            \"radio_clock\",      \"Radio Clock\",  DATA_COND,   *dcf77_str, DATA_STRING, dcf77_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        \"radio_clock\",\n        NULL,\n};\n\nr_device const eurochron_efth800 = {\n        .name        = \"Eurochron EFTH-800 temperature and humidity sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 250,\n        .long_width  = 500,\n        .sync_width  = 750,\n        .gap_limit   = 900,\n        .reset_limit = 5500,\n        .decode_fn   = &eurochron_efth800_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/elro_db286a.c",
    "content": "/** @file\n    Generic doorbell implementation for Elro DB286A devices.\n\n    Copyright (C) 2016 Fabian Zaremba <fabian@youremail.eu>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nGeneric doorbell implementation for Elro DB286A devices.\n\nNote that each device seems to have two codes, which alternate\nfor every other button press.\n\nshort is 456 us pulse, 1540 us gap\nlong is 1448 us pulse, 544 us gap\npacket gap is 7016 us\n\nExample code: 37f62a6c80\n*/\n\n#include \"decoder.h\"\n\nstatic int elro_db286a_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // 33 bits expected, 5 minimum packet repetitions (14 expected)\n    int row = bitbuffer_find_repeated_row(bitbuffer, 5, 33);\n\n    if (row < 0 || bitbuffer->bits_per_row[row] != 33)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = bitbuffer->bb[row];\n\n    // 32 bits, trailing bit is dropped\n    char id_str[4 * 2 + 1];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x%02x\", b[0], b[1], b[2], b[3]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",    \"\",        DATA_STRING, \"Elro-DB286A\",\n            \"id\",       \"ID\",      DATA_STRING, id_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        NULL,\n};\n\nr_device const elro_db286a = {\n        .name        = \"Elro DB286A Doorbell\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 456,\n        .long_width  = 1448,\n        .gap_limit   = 2000,\n        .reset_limit = 8000,\n        .decode_fn   = &elro_db286a_callback,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/elv.c",
    "content": "/** @file\n    ELV WS 2000.\n\n    KS200/KS300 addition Copyright (C) 2022 Jan Schmidt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\nstatic uint16_t AD_POP(uint8_t const *bb, uint8_t bits, uint8_t bit)\n{\n    uint16_t val = 0;\n    for (uint8_t i = 0; i < bits; i++) {\n        uint8_t byte_no = (bit + i) / 8;\n        uint8_t bit_no  = 7 - ((bit + i) % 8);\n        if (bb[byte_no] & (1 << bit_no)) {\n            val = val | (1 << i);\n        }\n    }\n    return val;\n}\n\n/**\nELV EM 1000 decoder.\n\nbased on fs20.c\n*/\nstatic int em1000_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t dec[10];\n    uint8_t bit = 18; // preamble\n    uint8_t bb_p[14];\n    //char* types[] = {\"EM 1000-S\", \"EM 1000-?\", \"EM 1000-GZ\"};\n    uint8_t checksum_calculated = 0;\n    uint8_t i;\n    uint8_t checksum_received;\n\n    // check and combine the 3 repetitions\n    for (i = 0; i < 14; i++) {\n        if (bb[0][i] == bb[1][i] || bb[0][i] == bb[2][i])\n            bb_p[i] = bb[0][i];\n        else if (bb[1][i] == bb[2][i])\n            bb_p[i] = bb[1][i];\n        else\n            return DECODE_ABORT_EARLY;\n    }\n\n    // read 9 bytes with stopbit ...\n    for (i = 0; i < 9; i++) {\n        dec[i] = AD_POP(bb_p, 8, bit);\n        bit += 8;\n        uint8_t stopbit = AD_POP(bb_p, 1, bit);\n        bit += 1;\n        if (!stopbit) {\n//            decoder_logf(decoder, 0, __func__, \"!stopbit: %d\", i);\n            return DECODE_ABORT_EARLY;\n        }\n        checksum_calculated ^= dec[i];\n    }\n\n    // Read checksum\n    checksum_received = AD_POP(bb_p, 8, bit); // bit+=8;\n    if (checksum_received != checksum_calculated) {\n//        decoder_logf(decoder, 0, __func__, \"checksum_received != checksum_calculated: %d %d\", checksum_received, checksum_calculated);\n        return DECODE_FAIL_MIC;\n    }\n\n//    decoder_log_bitrow(decoder, 0, __func__, dec, 9 * 8, \"\");\n\n    // based on 15_CUL_EM.pm\n    //char *subtype = dec[0] >= 1 && dec[0] <= 3 ? types[dec[0] - 1] : \"?\";\n    int code      = dec[1];\n    int seqno     = dec[2];\n    int total     = dec[3] | dec[4] << 8;\n    int current   = dec[5] | dec[6] << 8;\n    int peak      = dec[7] | dec[8] << 8;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",    \"\", DATA_STRING, \"ELV-EM1000\",\n            \"id\",       \"\", DATA_INT, code,\n            \"seq\",      \"\", DATA_INT, seqno,\n            \"total\",    \"\", DATA_INT, total,\n            \"current\",  \"\", DATA_INT, current,\n            \"peak\",     \"\", DATA_INT, peak,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const elv_em1000_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"seq\",\n        \"total\",\n        \"current\",\n        \"peak\",\n        NULL,\n};\n\nr_device const elv_em1000 = {\n        .name        = \"ELV EM 1000\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 500,  // guessed, no samples available\n        .long_width  = 1000, // guessed, no samples available\n        .gap_limit   = 7250,\n        .reset_limit = 30000,\n        .decode_fn   = &em1000_callback,\n        .disabled    = 1,\n        .fields      = elv_em1000_output_fields,\n};\n\n/**\nELV WS 2000.\n\nbased on http://www.dc3yc.privat.t-online.de/protocol.htm\n\n- added support for comob sensor (subtyp 7)\n- sensor 1 (Thermo/Hygro) and 4 (Thermo/Hygro/Baro) supported as well\n- other sensors could be detected, if the length is defined correct\n  but will not receive correct values\n\n- rain_count counts the ticks of a seesaw, the amount of water per\n  tick has to be calibrated. As shown in user manual the default is\n  295ml / m²\n\nProtocol version V1.2\n\nCoding of a bit:\n- the length of a bit is 1220.7s, corresponding to 819.2 Hz\n- it is derived from 32768 Hz : 40\n- the pulse:gap ratio is 7:3 (for logical 0) or 3:7 (for logical 1)\n- a logical 0 is represented by an HF carrier of 854.5s and 366.2s gap\n- a logical 1 is represented by a HF carrier of 366.2s and 854.5s gap\n- The preamble consists of 7 to 10 * 0 and 1 * 1.\n- The data is always transmitted as a 4-bit nibble. This is followed by a 1 bit.\n- The LSBit is transmitted first.\n\nThe checksums at the end are calculated as follows:\n- Check: all nibbles starting with the type up to Check are XORed, result is 0\n- Sum: all nibbles beginning with the type up to Check are summed up,\n  5 is added and the upper 4 bits are discarded\n\nThe type consists of 3 bits encoded as follows.\n- 0 Thermal (AS3)\n- 1 Thermo/Hygro (AS2000, ASH2000, S2000, S2001A, S2001IA, ASH2200, S300IA)\n- 2 Rain (S2000R)\n- 3 Wind (S2000W)\n- 4 Thermo/Hygro/Baro (S2001I, S2001ID)\n- 5 Brightness (S2500H)\n- 6 Pyrano (radiant power)\n- 7 Combo Sensor (KS200, KS300)\n\n    00000001  T1T2T3T41  A1A2A3A41  T11T12T13T141  T21T22T23T241  T31T32T33T341  F11F12F13F141  F21F22F23F241  W11W12W13W141  W21W22W23W241  W31W32W33W341  C11C12C13C141  C21C22C23C241  C31C32C33C341  B1B2B3B41  Q1Q2Q3Q41  S1S2S3S41\n    Preamble  ____7___1  1_R_0_V_1  ____0.1C____1  ____1C______1  _____10C____1  _____1%_____1  ____10%_____1  __0.1 km/h__1  ___1 km/h___1  ___10 km/h__1  ___R_LSN____1  ___R_MID____1  ____R_MSN___1  ___???__1  __Check_1  __Sum___1\n\n- R: Currently Raining (1 = rain)\n- V: Temperature sign (1 = negative)\n- W1x .. W3x : 3 * 4-bit wind speed km/h (BCD)\n- C1x .. C3x :    12-bit rain counter\n- T1x .. T3x : 3 * 4-bit temperature C (BCD)\n- F1x .. F2x : 2 * 4-bit humidity % (BCD)\n */\nstatic int ws2000_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t dec[16]= {0};\n    uint8_t nibbles=0;\n    uint8_t bit=11; // preamble\n    char const *const types[]={\"!AS3\", \"AS2000/ASH2000/S2000/S2001A/S2001IA/ASH2200/S300IA\", \"!S2000R\", \"!S2000W\", \"S2001I/S2001ID\", \"!S2500H\", \"!Pyrano\", \"KS200/KS300\"};\n    uint8_t const length[16] = {5, 8, 5, 8, 12, 9, 8, 14, 8};\n    uint8_t check_calculated=0, sum_calculated=0;\n    uint8_t i;\n    uint8_t stopbit;\n    uint8_t sum_received;\n\n    dec[0] = AD_POP(bb[0], 4, bit); bit+=4;\n    stopbit= AD_POP(bb[0], 1, bit); bit+=1;\n    if (!stopbit) {\n        decoder_log(decoder, 1, __func__, \"!stopbit\");\n        return DECODE_ABORT_EARLY;\n    }\n    check_calculated ^= dec[0];\n    sum_calculated   += dec[0];\n\n    // read nibbles with stopbit ...\n    for (i = 1; i <= length[dec[0]]; i++) {\n        dec[i] = AD_POP(bb[0], 4, bit); bit+=4;\n        stopbit= AD_POP(bb[0], 1, bit); bit+=1;\n        if (!stopbit) {\n            decoder_logf(decoder, 1, __func__, \"!stopbit %d\", bit);\n            return DECODE_ABORT_EARLY;\n        }\n        check_calculated ^= dec[i];\n        sum_calculated   += dec[i];\n        nibbles++;\n    }\n    decoder_log_bitrow(decoder, 1, __func__, dec, nibbles * 8, \"\");\n\n    if (check_calculated) {\n        decoder_logf(decoder, 1, __func__, \"check_calculated (%d) != 0\", check_calculated);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Read sum\n    sum_received = AD_POP(bb[0], 4, bit); // bit+=4;\n    sum_calculated+=5;\n    sum_calculated&=0xF;\n    if (sum_received != sum_calculated) {\n        decoder_logf(decoder, 1, __func__, \"sum_received (%d) != sum_calculated (%d)\", sum_received, sum_calculated);\n        return DECODE_FAIL_MIC;\n    }\n\n    char const *subtype  = (dec[0] <= 7) ? types[dec[0]] : \"?\";\n    int code       = dec[1] & 7;\n    float temp     = ((dec[1] & 8) ? -1.0f : 1.0f) * (dec[4] * 10 + dec[3] + dec[2] * 0.1f);\n    float humidity = dec[7] * 10 + dec[6] + dec[5] * 0.1f;\n    int pressure   = 0;\n\n    int is_ksx00 = 0;\n    int it_rains = 0;\n    float wind   = 0;\n    int rainsum  = 0;\n    int unknown  = 0;\n    if (dec[0] == 4) {\n        pressure = 200 + dec[10] * 100 + dec[9] * 10 + dec[8];\n    }\n    if (dec[0] == 7) {\n        is_ksx00 = 1;\n        it_rains = (dec[1] & 2) ? 1 : 0;\n        humidity = dec[6] * 10 + dec[5];\n        wind     = dec[9] * 10 + dec[8] + dec[7] * 0.1f;\n        rainsum  = (dec[12] << 8) + (dec[11] << 4) + dec[10];\n        unknown  = dec[13];\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\", DATA_STRING, \"ELV-WS2000\",\n            \"subtype\",          \"\", DATA_STRING, subtype,\n            \"id\",               \"\", DATA_INT,    code,\n            \"temperature_C\",    \"\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, (double)temp,\n            \"humidity\",         \"\", DATA_FORMAT, \"%.1f %%\", DATA_DOUBLE, (double)humidity,\n            \"pressure_hPa\",     \"\", DATA_COND, pressure, DATA_FORMAT, \"%d hPa\", DATA_INT, pressure,\n            //KS200 / KS300\n            \"wind_avg_km_h\",    \"\", DATA_COND, is_ksx00, DATA_FORMAT, \"%.1f km/h\", DATA_DOUBLE, (double)wind,\n            \"rain_count\",       \"\", DATA_COND, is_ksx00, DATA_FORMAT, \"%d\", DATA_INT, rainsum,\n            \"rain_mm\",          \"\", DATA_COND, is_ksx00, DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, (double)rainsum * 0.295,\n            \"is_raining\",       \"\", DATA_COND, is_ksx00, DATA_FORMAT, \"%d\", DATA_INT, it_rains,\n            \"unknown\",          \"\", DATA_COND, is_ksx00, DATA_FORMAT, \"%d\", DATA_INT, unknown,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const elv_ws2000_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"subtype\",\n        \"temperature_C\",\n        \"humidity\",\n        \"pressure_hPa\",\n        // KS200 / KS300\n        \"wind_avg_km_h\",\n        \"rain_count\",\n        \"rain_mm\",\n        \"is_raining\",\n        \"unknown\",\n        NULL,\n};\n\nr_device const elv_ws2000 = {\n        .name        = \"ELV WS 2000\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 366,  // 0 => 854us, 1 => 366us according to link in top\n        .long_width  = 854,  // no repetitions\n        .reset_limit = 1000, // Longest pause is 854us according to link\n        .decode_fn   = &ws2000_callback,\n        .disabled    = 1,\n        .fields      = elv_ws2000_output_fields,\n};\n"
  },
  {
    "path": "src/devices/emax.c",
    "content": "/** @file\n    First version was for Altronics X7064 temperature and humidity sensor.\n    Then updated by Profboc75 with Optex 990040 (Emax full Weather station rain gauge/wind speed/wind direction ... ref EM3390W6 )\n\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n    based on protocol decoding by Thomas White\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFuzhou Emax Electronic W6 Professional Weather Station.\nRebrand and devices decoded :\n- Emax W6 / WEC-W6 / 3390TX W6 / EM3390W6 / EM3551H\n- Altronics x7063/4\n- Altronics x7064A, #3463, different temp coding and another checksum\n- Optex 990040 / 990050 / 990051 / SM-040\n- Infactory FWS-1200\n- Newentor Q9\n- Otio Weather Station Pro La Surprenante 810025\n- Orium Pro Atlanta 13093, Helios 13123\n- Protmex PT3390A\n- Jula Marquant 014331 weather station /014332 temp hum sensor\n- TechniSat IMETEO X6 76-4924-00 weather station without UV/LUX/Gust  #2753\n- Auriol Weather Station With Multisensor HG11911\n\nS.a. issue #2000 #2299 #2326 #2375 PR #2300 #2346 #2374 #3463\n\n- Likely a rebranded device, sold by Altronics\n- Data length is 32 bytes with a preamble of 10 bytes (33 bytes for Rain/Wind Station)\n\nData Layout:\n\n    // That fits nicely: aaa16e95 a3 8a ae 2d is channel 1, id 6e95, temp 38e (=910, 1 F, -17.2 C), hum 2d (=45).\n\nTemp/Hum Sensor :\n\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33\n    Layout         AA MC II IF AT TA AT HH AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA SS AA 00\n                             |\n       Flag bit position  4 3 2 1\n                          B P M M\n\ndefault empty value = 0xAA\n\n- M: (4 bit) Sensor model type, = 0xA if Temp/Hum Sensor or = 0x0 if Weather Rain/Wind station\n- C: (4 bit) channel ( = 4 for Weather Rain/wind station)\n- I:(12 bit) ID\n- B: (1 bit) Battery low flag\n- P: (1 bit) TX pairing button pressed flag\n- M: (2 bit) subtype model flags,  0b01 = X7063/X7064/990040/SM-40, 0b11 = X7064A, 0x10 = Weather station\n- T:(12 bit) temperature, if subtype 0b01, in F, offset 900, scale 10, if subtype 0b11, in C, offset 500, scale 10\n- H: (8 bit) humidity %\n- A: (4 bit) fixed values of 0xA\n- S: (8 bit) checksum, sum of the 31 byte & 0xff, except subtype 0b11, same sum formula but offset 0x9A\n\nRaw data sample:\n\n    FF FF AA AA AA AA AA CA CA 54\n    AA A1 6E 95 A6 BA A5 3B AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA D4\n    AA 00 0\n\nFormat string:\n\n    12h CH:4h ID:12h FLAGS:4b TEMP:4x4h4h4x4x4h HUM:8d 184h CHKSUM:8h 8x\n\nDecoded example:\n\n    aaa CH:1 ID:6e9 FLAGS:0101 TEMP:6b5 HUM:059 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa CHKSUM:d4 000\n\n\nEmax Rain / Wind speed / Wind Direction / Temp / Hum / UV / Lux\n\nWeather Rain/Wind station : humidity not at same byte position as temp/hum sensor.\n- With UV Lux without Wind Gust\n    AA KC II IF 0T TT HH 0W WW 0D DD RR RR UU LL LL 04 05 06 07 08 09 10 11 12 13 14 15 16 17 xx SS yy\n- Without UV LUX Wind Gust\n    AA KC II IF 0T TT HH 0W WW 0D DD RR RR D9 01 01 04 05 06 07 08 09 10 11 12 13 14 15 16 17 xx SS yy\n- Without UV / Lux , with Wind Gust\n    AA KC II IF 0T TT HH 0W WW 0D DD RR RR ?0 01 01 GG 04 05 06 07 08 09 10 11 12 13 14 15 16 xx SS yy\n\ndefault empty/null = 0x01 => value = 0\n\n- K: (4 bit) Kind of device, = A if Temp/Hum Sensor or = 0 if Weather Rain/Wind station\n- C: (4 bit) channel ( = 4 for Weather Rain/wind station)\n- I: (12 bit) ID\n- F: (4 bit) Flags: B P M M, B = Bat Low, P = TX pairing pressed, MM = 2 bit Subtype model, 0b10 for Weather station\n- T: (12 bit) temperature in F, offset 900, scale 10\n- H: (8 bit) humidity %\n- R: (16) Rain\n- W: (12) Wind speed\n- D: (9 bit) Wind Direction\n- U: (5 bit) UV index\n- L: (1 + 15 bit) Lux value, if first bit = 1 , then x 10 the rest.\n- G: (8 bit) Wind Gust\n- ?: unknown\n- A: (4 bit) fixed values of 0xA\n- 0: (4 bit) fixed values of 0x0\n- xx: incremental value each tx\n- yy: incremental value each tx yy = xx + 1\n- S: (8 bit) checksum\n\nRaw Data:\n\n    ff ff 80 00 aa aa aa aa aa ca ca 54\n    aa 04 59 41 06 1f 42 01 01 01 81 01 16 01 01 01 04 05 06 07 08 09 10 11 12 13 14 15 16 17 9d ad 9e\n    0000\n\nFormat string:\n\n    8h K:4h CH:4h ID:12h Flags:4b 4h Temp:12h Hum:8h 4h Wind:12h 4h Direction: 12h Rain: 16h 4h UV:4h Lux:16h  112h xx:8d CHKSUM:8h\n\nDecoded example:\n\n    aa KD:0 CH:4 ID:594 FLAGS:0001 0 TEMP:61f (66.7F) HUM:42 (66%) Wind: 101 ( = 000 * 0.2 = 0 kmh) 0 Direction: 181 ( = 080 = 128°) Rain: 0116 ( 0015 * 0.2  = 4.2 mm) 0 UV: 1 (0 UV) Lux: 0101 (0 Lux) 04 05 ...16 17 xx:9d CHKSUM:ad yy:9e\n\n*/\n\n#define EMAX_MESSAGE_BITLEN     264   //33 * 8\n\nstatic int emax_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is ffffaaaaaaaaaacaca54\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xca, 0xca, 0x54};\n\n    // Because of a gap false positive if LUX at max for weather station, only single row to be analyzed with expected 3 repeats inside the data.\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int ret = 0;\n    int pos = 0;\n    while ((pos = bitbuffer_search(bitbuffer, 0, pos, preamble_pattern, sizeof(preamble_pattern) * 8)) + EMAX_MESSAGE_BITLEN <= bitbuffer->bits_per_row[0]) {\n\n        if (pos >= bitbuffer->bits_per_row[0]) {\n            decoder_log(decoder, 2, __func__, \"Preamble not found\");\n            ret = DECODE_ABORT_EARLY;\n            continue;\n        }\n        decoder_logf(decoder, 2, __func__, \"Found Emax preamble pos: %d\", pos);\n\n        pos += sizeof(preamble_pattern) * 8;\n        // we expect at least 32 bytes\n        if (pos + 32 * 8 > bitbuffer->bits_per_row[0]) {\n            decoder_log(decoder, 2, __func__, \"Length check fail\");\n            ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n        uint8_t b[32] = {0};\n        bitbuffer_extract_bytes(bitbuffer, 0, pos, b, sizeof(b) * 8);\n\n        // verify checksum, 2 formulas, depends on the subtype model\n        int subtype = b[3] & 0x03;\n        int checksum = add_bytes(b, 31);\n        if (subtype == 0x3) { // same formula but 0x9A offset for Altronics-X7064A model only\n            checksum -= 0x9A;\n        }\n        if ((checksum & 0xff) != b[31]) {\n            decoder_log(decoder, 2, __func__, \"Checksum fail\");\n            ret = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        int channel     = (b[1] & 0x0f);\n        int kind        = ((b[1] & 0xf0) >> 4);\n        int id          = (b[2] << 4) | (b[3] >> 4);\n        int battery_low = (b[3] & 0x08);\n        int pairing     = (b[3] & 0x04);\n\n        // depend if external temp/hum sensor or Weather rain/wind station the values are not decode the same\n        float temp_f = 0;\n        float temp_c = 0;\n\n        if (kind != 0) {  // if not Rain/Wind ... sensor\n\n            int temp_raw    = ((b[4] & 0x0f) << 8) | (b[5] & 0xf0) | (b[6] & 0x0f); // weird format\n            if (subtype == 0x1) {\n                temp_f    = (temp_raw - 900) * 0.1f;\n            }\n            if (subtype == 0x3) {\n                temp_c    = (temp_raw - 500) * 0.1f;\n            }\n            int humidity    = b[7];\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                 DATA_COND,   subtype == 0x1, DATA_STRING, \"Altronics-X7064\",\n                    \"model\",            \"\",                 DATA_COND,   subtype == 0x3, DATA_STRING, \"Altronics-X7064A\",\n                    \"id\",               \"\",                 DATA_FORMAT, \"%03x\", DATA_INT,    id,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery_OK\",       DATA_INT,    !battery_low,\n                    \"temperature_F\",    \"Temperature\",      DATA_COND,   subtype == 0x1, DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_f,\n                    \"temperature_C\",    \"Temperature\",      DATA_COND,   subtype == 0x3, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                    \"pairing\",          \"Pairing\",          DATA_COND,   pairing,   DATA_INT,    !!pairing,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        else {  // if Rain/Wind sensor\n\n            int temp_raw      = ((b[4] & 0x0f) << 8) | (b[5]); // weird format\n            temp_f      = (temp_raw - 900) * 0.1f;\n            int humidity      = b[6];\n            int wind_raw      = (((b[7] - 1) & 0xff) << 8) | ((b[8] - 1) & 0xff);   // need to remove 1 from byte , 0x01 - 1 = 0 , 0x02 - 1 = 1 ... 0xff -1 = 254 , 0x00 - 1 = 255.\n            float speed_kmh   = wind_raw * 0.2f;\n            int direction_deg = (((b[9] - 1) & 0x0f) << 8) | ((b[10] - 1) & 0xff);\n            int rain_raw      = (((b[11] - 1) & 0xff) << 8) | ((b[12] - 1) & 0xff);\n            float rain_mm     = rain_raw * 0.2f;\n\n            if (b[29] == 0x17) {                               // with UV/Lux, without Wind Gust\n                int uv_index      = (b[13] - 1) & 0x1f;\n                int lux_14        = (b[14] - 1) & 0xFF;\n                int lux_15        = (b[15] - 1) & 0xFF;\n                int lux_multi     = ((lux_14 & 0x80) >> 7);\n                int light_lux     = ((lux_14 & 0x7f) << 8) | (lux_15);\n                if (lux_multi == 1) {\n                    light_lux = light_lux * 10;\n                }\n                int tag           = ((b[13] - 1) & 0xC0)>>6;  // if tag = 3 = model IMETEO X6 without UV and LUX #2753\n                /* clang-format off */\n                data_t *data = data_make(\n                        \"model\",            \"\",                 DATA_COND, tag !=3, DATA_STRING, \"Emax-W6\",\n                        \"model\",            \"\",                 DATA_COND, tag ==3, DATA_STRING, \"IMETEO-X6\",\n                        \"id\",               \"\",                 DATA_FORMAT, \"%03x\", DATA_INT,    id,\n                        \"channel\",          \"Channel\",          DATA_INT,    channel,\n                        \"battery_ok\",       \"Battery_OK\",       DATA_INT,    !battery_low,\n                        \"temperature_F\",    \"Temperature\",      DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_f,\n                        \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",   DATA_INT,    humidity,\n                        \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n                        \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT,    direction_deg,\n                        \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",  DATA_DOUBLE, rain_mm,\n                        \"uvi\",              \"UV Index\",         DATA_COND,   tag !=3, DATA_FORMAT, \"%.0f\", DATA_DOUBLE, (double)uv_index,\n                        \"light_lux\",        \"Lux\",              DATA_COND,   tag !=3, DATA_FORMAT, \"%u\", DATA_INT, light_lux,\n                        \"pairing\",          \"Pairing?\",         DATA_COND,   pairing,    DATA_INT,    !!pairing,\n                        \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                        NULL);\n                /* clang-format on */\n\n                decoder_output_data(decoder, data);\n                return 1;\n            }\n            if (b[29] == 0x16) {                               //without UV/Lux with Wind Gust\n                float gust_kmh = b[16] / 1.5f;\n                /* clang-format off */\n                data_t *data = data_make(\n                        \"model\",            \"\",                 DATA_STRING, \"Emax-EM3551H\",\n                        \"id\",               \"\",                 DATA_FORMAT, \"%03x\", DATA_INT,    id,\n                        \"channel\",          \"Channel\",          DATA_INT,    channel,\n                        \"battery_ok\",       \"Battery_OK\",       DATA_INT,    !battery_low,\n                        \"temperature_F\",    \"Temperature\",      DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_f,\n                        \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",   DATA_INT,    humidity,\n                        \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n                        \"wind_max_km_h\",    \"Wind max speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, gust_kmh,\n                        \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT,    direction_deg,\n                        \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",  DATA_DOUBLE, rain_mm,\n                        \"pairing\",          \"Pairing?\",         DATA_COND,   pairing,    DATA_INT,    !!pairing,\n                        \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                        NULL);\n                /* clang-format on */\n\n                decoder_output_data(decoder, data);\n                return 1;\n            }\n        }\n        pos += EMAX_MESSAGE_BITLEN;\n    }\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_km_h\",\n        \"wind_max_km_h\",\n        \"rain_mm\",\n        \"wind_dir_deg\",\n        \"uvi\",\n        \"light_lux\",\n        \"pairing\",\n        \"mic\",\n        NULL,\n};\n\nr_device const emax = {\n        .name        = \"Emax W6, rebrand Altronics x7063/4/x7064A, Optex 990040/50/51, Orium 13093/13123, Infactory FWS-1200, Newentor Q9, Otio 810025, Protmex PT3390A, Jula Marquant 014331/32, TechniSat IMETEO X6 76-4924-00, Weather Station or temperature/humidity sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 90,\n        .long_width  = 90,\n        .reset_limit = 9000,\n        .decode_fn   = &emax_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/emontx.c",
    "content": "/** @file\n    OpenEnergyMonitor.org emonTx sensor protocol.\n\n    Copyright (C) 2016 Tommy Vestermark\n    Copyright (C) 2016 David Woodhouse\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n// We don't really *use* this because there's no endianness support\n// for just using le16_to_cpu(pkt.ct1) etc. A task for another day...\n#pragma pack(push, 1)\nstruct emontx {\n    uint8_t syn, group, node, len;\n    uint16_t ct1, ct2, ct3, ct4, Vrms, temp[6];\n    uint32_t pulse;\n    uint16_t crc;\n    uint8_t postamble;\n};\n#pragma pack(pop)\n\n/** @fn int emontx_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nOpenEnergyMonitor.org emonTx sensor protocol.\n\nThis is the JeeLibs RF12 packet format as described at\nhttp://jeelabs.org/2011/06/09/rf12-packet-format-and-design/\n\nThe RFM69 chip misses out the zero bit at the end of the\n0xAA 0xAA 0xAA preamble; the receivers only use it to set\nup the bit timing, and they look for the 0x2D at the start\nof the packet. So we'll do the same -- except since we're\nspecifically looking for emonTx packets, we can require a\nlittle bit more. We look for a group of 0xD2, and we\nexpect the CDA bits in the header to all be zero:\n*/\nstatic unsigned char const preamble[3] = { 0xaa, 0xaa, 0xaa };\nstatic unsigned char const pkt_hdr_inverted[3] = { 0xd2, 0x2d, 0xc0 };\nstatic unsigned char const pkt_hdr[3] = { 0x2d, 0xd2, 0x00 };\n\nstatic int emontx_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    unsigned bitpos = 0;\n    int events = 0;\n\n    // Search for only 22 bits to cope with inverted frames and\n    // the missing final preamble bit with RFM69 transmissions.\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos,\n                      preamble, 22)) < bitbuffer->bits_per_row[0]) {\n        int inverted = 0;\n        unsigned pkt_pos;\n        uint16_t crc;\n        data_t *data;\n        union {\n            struct emontx p;\n            uint8_t b[sizeof(struct emontx)];\n        } pkt;\n        uint16_t words[14];\n        float vrms;\n        unsigned i;\n\n        bitpos += 22;\n\n        // Eat any additional 101010 sequences (which might be attributed\n        // to noise at the start of the packet which coincidentally matches).\n        while (bitbuffer_search(bitbuffer, 0, bitpos, preamble, 2) == bitpos)\n            bitpos += 2;\n\n        // Account for RFM69 bug which drops a zero bit at the end of the\n        // preamble before the 0x2d SYN byte. And for inversion.\n        bitpos--;\n\n        // Check for non-inverted packet header...\n        pkt_pos = bitbuffer_search(bitbuffer, 0, bitpos,\n                       pkt_hdr, 11);\n\n        // And for inverted, if it's not found close enough...\n        if (pkt_pos > bitpos + 5) {\n            pkt_pos = bitbuffer_search(bitbuffer, 0, bitpos,\n                           pkt_hdr_inverted, 11);\n            if (pkt_pos > bitpos + 5)\n                continue; // DECODE_ABORT_EARLY\n            inverted = 1;\n        }\n\n        // Need enough data for a full packet (including postamble)\n        if (pkt_pos + sizeof(pkt)*8 > bitbuffer->bits_per_row[0])\n            break;\n\n        // Extract the group even though we matched on it; the CRC\n        // covers it too. And might as well have the 0x2d too for\n        // alignment.\n        bitbuffer_extract_bytes(bitbuffer, 0, pkt_pos,\n                    (uint8_t *)&pkt, sizeof(pkt) * 8);\n        if (inverted) {\n            for (i=0; i<sizeof(pkt); i++)\n                pkt.b[i] ^= 0xff;\n        }\n        if (pkt.p.len != 0x1a || pkt.p.postamble != 0xaa)\n            continue; // DECODE_ABORT_EARLY\n        crc = crc16lsb((uint8_t *)&pkt.p.group, 0x1d, 0xa001, 0xffff);\n\n        // Ick. If we could just do le16_to_cpu(pkt.p.ct1) we wouldn't need this.\n        for (i=0; i<14; i++)\n            words[i] = pkt.b[4 + (i * 2)] | (pkt.b[5 + i * 2] << 8);\n        if (crc != words[13])\n            continue; // DECODE_FAIL_MIC\n\n        vrms = (float)words[4] / 100.0f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"emonTx-Energy\",\n                \"node\",         \"\",             DATA_FORMAT, \"%02x\", DATA_INT, pkt.p.node & 0x1f,\n                \"ct1\",          \"\",             DATA_FORMAT, \"%d\", DATA_INT, (int16_t)words[0],\n                \"ct2\",          \"\",             DATA_FORMAT, \"%d\", DATA_INT, (int16_t)words[1],\n                \"ct3\",          \"\",             DATA_FORMAT, \"%d\", DATA_INT, (int16_t)words[2],\n                \"ct4\",          \"\",             DATA_FORMAT, \"%d\", DATA_INT, (int16_t)words[3],\n                \"batt_Vrms\",    \"\",             DATA_FORMAT, \"%.2f\", DATA_DOUBLE, vrms,\n                \"pulse\",        \"\",             DATA_FORMAT, \"%u\", DATA_INT, words[11] | ((uint32_t)words[12] << 16),\n                // Slightly horrid... a value of 300.0°C means 'no reading'. So omit them completely.\n                \"temp1_C\",      \"\",             DATA_COND, words[5] != 3000, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, words[5] * 0.1f,\n                \"temp2_C\",      \"\",             DATA_COND, words[6] != 3000, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, words[6] * 0.1f,\n                \"temp3_C\",      \"\",             DATA_COND, words[7] != 3000, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, words[7] * 0.1f,\n                \"temp4_C\",      \"\",             DATA_COND, words[8] != 3000, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, words[8] * 0.1f,\n                \"temp5_C\",      \"\",             DATA_COND, words[9] != 3000, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, words[9] * 0.1f,\n                \"temp6_C\",      \"\",             DATA_COND, words[10] != 3000, DATA_FORMAT, \"%.1f\", DATA_DOUBLE, words[10] * 0.1f,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        events++;\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"node\",\n        \"ct1\",\n        \"ct2\",\n        \"ct3\",\n        \"ct4\",\n        \"batt_Vrms\",\n        \"temp1_C\",\n        \"temp2_C\",\n        \"temp3_C\",\n        \"temp4_C\",\n        \"temp5_C\",\n        \"temp6_C\",\n        \"pulse\",\n        \"mic\",\n        NULL,\n};\n\nr_device const emontx = {\n        .name        = \"emonTx OpenEnergyMonitor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 2000000.0f / (49230 + 49261), // 49261kHz for RFM69, 49230kHz for RFM12B\n        .long_width  = 2000000.0f / (49230 + 49261),\n        .reset_limit = 1200, // 600 zeros...\n        .decode_fn   = &emontx_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/emos_e6016.c",
    "content": "/** @file\n    EMOS E6016 weatherstation with DCF77.\n\n    Copyright (C) 2022 Dirk Utke-Woehlke <kardinal26@mail.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nEMOS E6016 weatherstation with DCF77.\n\n- Manufacturer: EMOS\n- Transmit Interval: every ~61 s\n- Frequency: 433.92 MHz\n- Modulation: OOK PWM, INVERTED\n\nData Layout:\n\n    PP PP PP II ?K KK KK KK CT TT HH SS DF XX RR\n\n- P: (24 bit) preamble\n- I: (8 bit) ID\n- ?: (2 bit) unknown\n- K: (32 bit) datetime, fields are 6d-4d-5d 5d:6d:6d\n- C: (2 bit) channel\n- T: (12 bit) temperature, signed, scale 10\n- H: (8 bit) humidity\n- S: (8 bit) wind speed\n- D: (4 bit) wind direction\n- F: (4 bit) flags of (?B??), B is battery good indication\n- X: (8 bit) checksum\n- R: (8 bit) repeat counter\n\nRaw data:\n\n    [00] {120} 55 5a 7c 00 6a a5 60 e7 3f 36 da ff 5d 38 ff\n    [01] {120} 55 5a 7c 00 6a a5 60 e7 3f 36 da ff 5d 38 fe\n    [02] {120} 55 5a 7c 00 6a a5 60 e7 3f 36 da ff 5d 38 fd\n    [03] {120} 55 5a 7c 00 6a a5 60 e7 3f 36 da ff 5d 38 fc\n    [04] {120} 55 5a 7c 00 6a a5 60 e7 3f 36 da ff 5d 38 fb\n    [05] {120} 55 5a 7c 00 6a a5 60 e7 3f 36 da ff 5d 38 fa\n\nFormat string:\n\n    MODEL?:8h8h8h ID?:8d ?2b DT:6d-4d-5dT5d:6d:6d CH:2d TEMP:12d HUM?8d WSPEED:8d WINDIR:4d BAT:4b CHK:8h REPEAT:8h\n\nDecoded example:\n\n    MODEL?:aaa583 ID?:255 ?10 DT:21-05-21T07:49:35 CH:0 TEMP:0201 HUM?037 WSPEED:000 WINDIR:10 BAT:1101 CHK:c7 REPEAT:00\n\n*/\n\nstatic int emos_e6016_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row = bitbuffer_find_repeated_prefix(bitbuffer, 3, 120 - 8); // ignores the repeat byte\n    if (row < 0) {\n        decoder_log(decoder, 2, __func__, \"Repeated row fail\");\n        return DECODE_ABORT_EARLY;\n    }\n    decoder_logf(decoder, 2, __func__, \"Found row: %d\", row);\n\n    uint8_t *b = bitbuffer->bb[row];\n    // we expect 120 bits\n    if (bitbuffer->bits_per_row[row] != 120) {\n        decoder_log(decoder, 2, __func__, \"Length check fail\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // model check 55 5a 7c\n    if (b[0] != 0x55 || b[1] != 0x5a || b[2] != 0x7c) {\n        decoder_log(decoder, 2, __func__, \"Model check fail\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    bitbuffer_invert(bitbuffer);\n\n    // check checksum\n    if ((add_bytes(b, 13) & 0xff) != b[13]) {\n        decoder_log(decoder, 2, __func__, \"Checksum fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id         = b[3];\n    int battery    = ((b[12] >> 2) & 0x1);\n    unsigned dcf77 = ((b[4] & 0x3f) << 26) | (b[5] << 18) | (b[6] << 10) | (b[7] << 2) | (b[8] >> 6);\n    int dcf77_sec  = ((dcf77 >> 0) & 0x3f);\n    int dcf77_min  = ((dcf77 >> 6) & 0x3f);\n    int dcf77_hour = ((dcf77 >> 12) & 0x1f);\n    int dcf77_day  = ((dcf77 >> 17) & 0x1f);\n    int dcf77_mth  = ((dcf77 >> 22) & 0x0f);\n    int dcf77_year = ((dcf77 >> 26) & 0x3f);\n    int channel    = ((b[8] >> 4) & 0x3) + 1;\n    int temp_raw   = (int16_t)(((b[8] & 0x0f) << 12) | (b[9] << 4)); // use sign extend\n    float temp_c   = (temp_raw >> 4) * 0.1f;\n    int humidity   = b[10];\n    float speed_ms = b[11] * 0.295f;\n    int dir_raw    = (((b[12] & 0xf0) >> 4));\n    float dir_deg  = dir_raw * 22.5f;\n\n    char dcf77_str[20]; // \"2064-16-32T32:64:64\"\n    snprintf(dcf77_str, sizeof(dcf77_str), \"%4d-%02d-%02dT%02d:%02d:%02d\", dcf77_year + 2000, dcf77_mth, dcf77_day, dcf77_hour, dcf77_min, dcf77_sec);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"EMOS-E6016\",\n            \"id\",               \"House Code\",       DATA_INT,    id,\n            \"channel\",          \"Channel\",          DATA_INT,    channel,\n            \"battery_ok\",       \"Battery_OK\",       DATA_INT,    battery,\n            \"temperature_C\",    \"Temperature_C\",    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u\", DATA_INT, humidity,\n            \"wind_avg_m_s\",     \"WindSpeed m_s\",    DATA_FORMAT, \"%.1f m/s\",  DATA_DOUBLE, speed_ms,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_FORMAT, \"%.1f\",  DATA_DOUBLE, dir_deg,\n            \"radio_clock\",      \"Radio Clock\",      DATA_STRING, dcf77_str,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_m_s\",\n        \"wind_dir_deg\",\n        \"radio_clock\",\n        \"mic\",\n        NULL,\n};\n// n=EMOS-E6016,m=OOK_PWM,s=280,l=796,r=804,g=0,t=0,y=1836,rows>=3,bits=120\nr_device const emos_e6016 = {\n        .name        = \"EMOS E6016 weatherstation with DCF77\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 280,\n        .long_width  = 796,\n        .gap_limit   = 3000,\n        .reset_limit = 850,\n        .sync_width  = 1836,\n        .decode_fn   = &emos_e6016_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/emos_e6016_rain.c",
    "content": "/** @file\n    EMOS E6016 rain gauge.\n\n    Copyright (C) 2022 Dirk Utke-Woehlke <kardinal26@mail.de>\n    Copyright (C) 2022 Stefan Tomko <stefan.tomko@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nEMOS E6016 rain gauge.\n\n- Manufacturer: EMOS\n- Transmit Interval: every 85s\n- Frequency: 433.92 MHz\n- Modulation: OOK PWM, INVERTED\n\nData Layout:\n\n    PP PP PP II BU UU UR RR XX\n\n- P: (24 bit) preamble\n- I: (8 bit) ID\n- B: (2 bit) battery indication\n- U: (18 bit) Unknown\n- R: (12 bit) Rain\n- X: (8 bit) checksum\n\nRaw data:\n\n    {73} 55 5a 75 cb 13 cf ff ff d6 0\n\nAfter inversion\n\n         aa a5 8a 34 ec 30 0b b7 29 8\n\nFormat string:\n\n    MODEL?:8h8h8h ID?:8h BAT?:2b ?:6h8h4h RAIN:12d CHK:8h 8x\n\nDecoded example:\n\n   MODEL?:aaa58a ID?:34 BAT?:11 ?:2c300 RAIN:2999 CHK:29\n\n*/\n\nstatic int emos_e6016_rain_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 72);\n\n    if (r < 0) {\n        decoder_log(decoder, 2, __func__, \"Repeated row fail\");\n        return DECODE_ABORT_EARLY;\n    }\n    decoder_logf(decoder, 2, __func__, \"Found row: %d\", r);\n\n    uint8_t *b = bitbuffer->bb[r];\n    // we expect 73 bits\n    if (bitbuffer->bits_per_row[r] < 72 || bitbuffer->bits_per_row[r] > 73) {\n        decoder_log(decoder, 2, __func__, \"Length check fail\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // model check 55 5a 75\n    if (b[0] != 0x55 || b[1] != 0x5a || b[2] != 0x75) {\n        decoder_log(decoder, 2, __func__, \"Model check fail\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    bitbuffer_invert(bitbuffer);\n\n    // verify checksum\n    if ((add_bytes(b, 8) & 0xff) != b[8]) {\n        decoder_log(decoder, 2, __func__, \"Checksum fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id        = (b[3]);\n    int battery   = (b[4] >> 6);\n    int rain_raw  = (b[6] & 0x0f) << 8 | b[7];\n    float rain_mm = rain_raw * 0.7f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"EMOS-E6016R\",\n            \"id\",               \"House Code\",       DATA_INT,    id,\n            \"battery_ok\",       \"Battery_OK\",       DATA_INT,    !!battery,\n            \"rain_mm\",          \"Rain_mm\",          DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"rain_mm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const emos_e6016_rain = {\n        .name        = \"EMOS E6016 rain gauge\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 300,\n        .long_width  = 800,\n        .gap_limit   = 1000,\n        .reset_limit = 2500,\n        .decode_fn   = &emos_e6016_rain_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/enocean_erp1.c",
    "content": "/** @file\n    EnOcean ERP1.\n\n    Copyright (C) 2021 Christoph M. Wintersteiger <christoph@winterstiger.at>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int enocean_erp1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nEnOcean Radio Protocol 1.\n\n- 868.3Mhz ASK, 125kbps, inverted, 8/12 coding\n- Spec: https://www.enocean.com/erp1/\n*/\n\nstatic int decode_8of12(uint8_t const *b, int pos, int end, bitbuffer_t *out)\n{\n    if (pos + 12 > end)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_add_bit(out, bitrow_get_bit(b, pos + 0));\n    bitbuffer_add_bit(out, bitrow_get_bit(b, pos + 1));\n    uint8_t b2 = bitrow_get_bit(b, pos + 2);\n    bitbuffer_add_bit(out, b2);\n\n    if (b2 != !bitrow_get_bit(b, pos + 3))\n        return DECODE_FAIL_SANITY;\n\n    bitbuffer_add_bit(out, bitrow_get_bit(b, pos + 4));\n    bitbuffer_add_bit(out, bitrow_get_bit(b, pos + 5));\n    uint8_t b6 = bitrow_get_bit(b, pos + 6);\n    bitbuffer_add_bit(out, b6);\n\n    if (b6 != !bitrow_get_bit(b, pos + 7))\n        return DECODE_FAIL_SANITY;\n\n    bitbuffer_add_bit(out, bitrow_get_bit(b, pos + 8));\n    bitbuffer_add_bit(out, bitrow_get_bit(b, pos + 9));\n\n    return (bitrow_get_bit(b, pos + 10) << 1) | bitrow_get_bit(b, pos + 11);\n}\n\nstatic int enocean_erp1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_EARLY;\n\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t preamble[2] = {0x55, 0x20};\n    unsigned start      = bitbuffer_search(bitbuffer, 0, 0, preamble, 11);\n    if (start >= bitbuffer->bits_per_row[0])\n        return DECODE_FAIL_SANITY;\n\n    unsigned pos = start + 11;\n    unsigned len = bitbuffer->bits_per_row[0] - start;\n    unsigned end = start + len;\n\n    bitbuffer_t bytes = {0};\n    uint8_t more      = 0x01;\n    do {\n        more = decode_8of12(bitbuffer->bb[0], pos, end, &bytes);\n        pos += 12;\n    } while (pos < end && more == 0x01);\n\n    if (bytes.bits_per_row[0] < 16)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t chk = crc8(bytes.bb[0], (bytes.bits_per_row[0] - 1) / 8, 0x07, 0x00);\n    if (chk != bitrow_get_byte(bytes.bb[0], bytes.bits_per_row[0] - 8))\n        return DECODE_FAIL_MIC;\n\n    char tstr[256];\n    bitrow_snprint(bytes.bb[0], bytes.bits_per_row[0], tstr, sizeof(tstr));\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",    \"\",             DATA_STRING, \"EnOcean-ERP1\",\n            \"telegram\", \"\",             DATA_STRING, tstr,\n            \"mic\",      \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"telegram\",\n        \"mic\",\n        NULL,\n};\n\nr_device const enocean_erp1 = {\n        .name        = \"EnOcean ERP1\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 8,\n        .long_width  = 8,\n        .sync_width  = 0,\n        .tolerance   = 1,\n        .reset_limit = 800,\n        .decode_fn   = &enocean_erp1_decode,\n        .disabled    = 1, // default disabled because a high sample rate is needed\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ert_idm.c",
    "content": "/** @file\n    ERT Interval Data Message (IDM) and Interval Data Message (IDM) for Net Meters.\n\n    Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/*\nFreq 912600155\n\nRandom information:\n\nThis file contains supports callbacks for both IDM and NetIDM Given the similarities.\n\nCurrently the code is unable to differentiate between the the two\nsimilar protocols thus both will respond to the same packet. As\nof this time I am unable to find any documentation on how to\ndifferentiate IDM and NetIDM packets as both use identical use Sync\nID / Packet Type / length / App Version ID and CRC.\n\nEventually ert_idm_decode() and ert_netidm_decode() may be merged.\n\nhttps://github.com/bemasher/rtlamr/wiki/Protocol\nhttp://www.gridinsight.com/community/documentation/itron-ert-technology/\n*/\n\n#define IDM_PACKET_BYTES 92\n#define IDM_PACKET_BITLEN 720\n// 92 * 8\n\n// Least significant nibble of endpoint_type is equivalent to SCM's endpoint type field\n// id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters\nstatic char const *get_meter_type_name(uint8_t ERTType)\n{\n    switch (ERTType & 0x0f) {\n    case 4:\n    case 5:\n    case 7:\n    case 8:\n        return \"Electric\";\n    case 0:\n    case 1:\n    case 2:\n    case 9:\n    case 12:\n        return \"Gas\";\n    case 3:\n    case 11:\n    case 13:\n        return \"Water\";\n    default:\n        return \"unknown\";\n    }\n}\n\n/**\nERT Interval Data Message (IDM).\n\nIDM layout:\n\nField                 | Length | Offset/byte index\n--- | --- | ---\npream                 | 2      |\nSync Word             | 2      | 0\nPacket Type           | 1      | 2\nPacket Length         | 1      | 3\nHamming Code          | 1      | 4\nApplication Version   | 1      | 5\nEndpoint Type         | 1      | 6\nEndpoint ID           | 4      | 7\nConsumption Interval  | 1      | 11\nMod Programming State | 1      | 12\nTamper Count          | 6      | 13\nAsync Count           | 2      | 19\nPower Outage Flags    | 6      | 21\nLast Consumption      | 4      | 27\nDiff Consumption      | 53     | 31\nTransmit Time Offset  | 2      | 84\nMeter ID Checksum     | 2      | 86\nPacket Checksum       | 2      | 88\n*/\nstatic int ert_idm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[IDM_PACKET_BYTES];\n    data_t *data;\n    unsigned sync_index;\n    const uint8_t idm_frame_sync[] = {0x16, 0xA3, 0x1C};\n\n    uint8_t PacketTypeID;\n    char PacketTypeID_str[5];\n    uint8_t PacketLength;\n    // char    PacketLength_str[5];\n    //uint8_t HammingCode;\n    // char    HammingCode_str[5];\n    uint8_t ApplicationVersion;\n    // char    ApplicationVersion_str[5];\n    uint8_t ERTType;\n    // char    ERTType_str[5];\n    uint32_t ERTSerialNumber;\n    uint8_t ConsumptionIntervalCount;\n    uint8_t ModuleProgrammingState;\n    // char  ModuleProgrammingState_str[5];\n    // uint64_t TamperCounters = 0;  // 6 bytes\n    char TamperCounters_str[16];\n    uint16_t AsynchronousCounters;\n    // char AsynchronousCounters_str[8];\n    //uint64_t PowerOutageFlags = 0; // 6 bytes\n    char PowerOutageFlags_str[16];\n    uint32_t LastConsumptionCount;\n    uint32_t DifferentialConsumptionIntervals[47] = {0}; // 47 intervals of 9-bit unsigned integers\n    uint16_t TransmitTimeOffset;\n    uint16_t MeterIdCRC;\n    // char  MeterIdCRC_str[8];\n    uint16_t PacketCRC;\n    // char  PacketCRC_str[8];\n\n    if (bitbuffer->bits_per_row[0] > 600) {\n        decoder_logf(decoder, 1, __func__, \"rows=%hu, row0 len=%hu\", bitbuffer->num_rows, bitbuffer->bits_per_row[0]);\n    }\n\n    if (bitbuffer->bits_per_row[0] < IDM_PACKET_BITLEN) {\n        return (DECODE_ABORT_LENGTH);\n    }\n\n    sync_index = bitbuffer_search(bitbuffer, 0, 0, idm_frame_sync, 24);\n\n    decoder_logf(decoder, 1, __func__, \"sync_index=%u\", sync_index);\n\n    if (sync_index >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((bitbuffer->bits_per_row[0] - sync_index) < IDM_PACKET_BITLEN) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // bitbuffer_debug(bitbuffer);\n    bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, IDM_PACKET_BITLEN);\n    decoder_log_bitrow(decoder, 1, __func__, b, IDM_PACKET_BITLEN, \"\");\n\n    // uint32_t t_16; // temp vars\n    // uint32_t t_32;\n    // uint64_t t_64;\n    char *p;\n\n    uint16_t crc;\n    // memcpy(&t_16, &b[88], 2);\n    // pkt_checksum = ntohs(t_16);\n    // pkt_checksum = (b[88] << 8 | b[89]);\n    PacketCRC = (b[88] << 8 | b[89]);\n\n    crc = crc16(&b[2], 86, 0x1021, 0xD895);\n    if (crc != PacketCRC) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // snprintf(XX_str, sizeof(XX_str), \"0x%02X\", XX);\n\n    PacketTypeID = b[2];\n    snprintf(PacketTypeID_str, sizeof(PacketTypeID_str), \"0x%02X\", PacketTypeID);\n\n    PacketLength = b[3];\n    // snprintf(PacketLength_str, sizeof(PacketLength_str), \"0x%02X\", PacketLength);\n\n    //HammingCode = b[4];\n    // snprintf(HammingCode_str, sizeof(HammingCode_str), \"0x%02X\", HammingCode);\n\n    ApplicationVersion = b[5];\n    // snprintf(ApplicationVersion_str, sizeof(ApplicationVersion_str), \"0x%02X\", ApplicationVersion);\n\n    ERTType = b[6]; // & 0x0F;\n    // snprintf(ERTType_str, sizeof(ERTType_str), \"0x%02X\", ERTType);\n\n    // memcpy(&t_32, &b[7], 4);\n    // ERTSerialNumber = ntohl(t_32);\n    ERTSerialNumber = ((uint32_t)b[7] << 24) | (b[8] << 16) | (b[9] << 8) | (b[10]);\n\n    ConsumptionIntervalCount = b[11];\n\n    ModuleProgrammingState = b[12];\n    // snprintf(ModuleProgrammingState_str, sizeof(ModuleProgrammingState_str), \"0x%02X\", ModuleProgrammingState);\n\n    /*\n    http://davestech.blogspot.com/2008/02/itron-remote-read-electric-meter.html\n    SCM1 Counter1 Meter has been inverted\n    SCM1 Counter2 Meter has been removed\n    SCM2 Counter3 Meter detected a button-press demand reset\n    SCM2 Counter4 Meter has a low-battery/end-of-calendar warning\n    SCM3 Counter5 Meter has an error or a warning that can affect billing\n    SCM3 Counter6 Meter has a warning that may or may not require a site visit,\n    */\n    p = TamperCounters_str;\n    strncpy(p, \"0x\", sizeof(TamperCounters_str));\n    p += 2;\n    for (int j = 0; j < 6; j++) {\n        // GCC-14 is confused by sprintf()\n        p += snprintf(p, 3, \"%02X\", b[13 + j]);\n    }\n    decoder_logf_bitrow(decoder, 2, __func__, &b[13], 6 * 8, \"TamperCounters_str   %s\", TamperCounters_str);\n\n    AsynchronousCounters = (b[19] << 8 | b[20]);\n    // snprintf(AsynchronousCounters_str, sizeof(AsynchronousCounters_str), \"0x%04X\", AsynchronousCounters);\n\n    p = PowerOutageFlags_str;\n    strncpy(p, \"0x\", sizeof(PowerOutageFlags_str));\n    p += 2;\n    for (int j = 0; j < 6; j++) {\n        // GCC-14 is confused by sprintf()\n        p += snprintf(p, 3, \"%02X\", b[21 + j]);\n    }\n    decoder_logf_bitrow(decoder, 2, __func__, &b[21], 6 * 8, \"PowerOutageFlags_str %s\", PowerOutageFlags_str);\n\n    LastConsumptionCount = ((uint32_t)b[27] << 24) | (b[28] << 16) | (b[29] << 8) | (b[30]);\n    decoder_logf_bitrow(decoder, 1, __func__, &b[27], 32, \"LastConsumptionCount %d\", LastConsumptionCount);\n\n    // DifferentialConsumptionIntervals : 47 intervals of 9-bit unsigned integers\n    decoder_log_bitrow(decoder, 2, __func__, &b[31], 423, \"DifferentialConsumptionIntervals\");\n    unsigned pos = sync_index + (31 * 8);\n    for (int j = 0; j < 47; j++) {\n        uint8_t buffy[4] = {0};\n\n        bitbuffer_extract_bytes(bitbuffer, 0, pos, buffy, 9);\n        DifferentialConsumptionIntervals[j] = ((uint16_t)buffy[0] << 1) | (buffy[1] >> 7);\n        pos += 9;\n    }\n    if (decoder_verbose(decoder) > 1) {\n        decoder_log(decoder, 2, __func__, \"DifferentialConsumptionIntervals\");\n        for (int j = 0; j < 47; j++) {\n            decoder_logf(decoder, 2, __func__, \"%d\", DifferentialConsumptionIntervals[j]);\n        }\n    }\n\n    TransmitTimeOffset = (b[84] << 8 | b[85]);\n\n    MeterIdCRC = (b[86] << 8 | b[87]);\n    // snprintf(SerialNumberCRC_str, sizeof(MeterIdCRC_str), \"0x%04X\", MeterIdCRC);\n\n    //  snprintf(PacketCRC_str, sizeof(PacketCRC_str), \"0x%04X\", PacketCRC);\n\n    // Least significant nibble of endpoint_type is  equivalent to SCM's endpoint type field\n    // id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters\n\n    char const *meter_type = get_meter_type_name(ERTType);\n    // decoder_logf(decoder, 0, __func__, \"meter_type = %s\", meter_type);\n\n    /*\n        Field key names and format set to  match rtlamr field names\n\n        {\"Time\":\"2020-06-25T08:22:52.404629556-04:00\",\"Offset\":1835008,\"Length\":229376,\"Type\":\"IDM\",\"Message\":\n        {\"Preamble\":1431639715,\"PacketTypeID\":28,\"PacketLength\":92,\"HammingCode\":198,\"ApplicationVersion\":4,\"ERTType\":7,\n         \"ERTSerialNumber\":11278109,\"ConsumptionIntervalCount\":246,\"ModuleProgrammingState\":188,\n         \"TamperCounters\":\"QgUWry0H\",\"AsynchronousCounters\":0,\"PowerOutageFlags\":\"QUgmCEEF\",\"LastConsumptionCount\":339972,\n         \"DifferentialConsumptionIntervals\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0],\n         \"TransmitTimeOffset\":476,\"SerialNumberCRC\":60090,\"PacketCRC\":31799}}\n    */\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",                            \"\",    DATA_STRING, \"IDM\",\n            \"id\",                               \"\",     DATA_INT,       ERTSerialNumber,\n\n            // \"PacketTypeID\",             \"\",             DATA_FORMAT, \"0x%02X\", DATA_INT, PacketTypeID,\n            \"PacketTypeID\",                     \"\",    DATA_STRING,       PacketTypeID_str,\n            \"PacketLength\",                     \"\",    DATA_INT,       PacketLength,\n            // \"HammingCode\",              \"\",             DATA_INT,          HammingCode,\n            \"ApplicationVersion\",               \"\",     DATA_INT,       ApplicationVersion,\n            \"ERTType\",                          \"\",     DATA_FORMAT,  \"0x%02X\", DATA_INT,    ERTType,\n            // \"ERTType\",                          \"\",     DATA_INT,       ERTType,\n            \"ERTSerialNumber\",                  \"\",     DATA_INT,       ERTSerialNumber, // NOTE: this is also \"id\"\n            \"ConsumptionIntervalCount\",         \"\",     DATA_INT,       ConsumptionIntervalCount,\n            // \"ModuleProgrammingState\",           \"\",     DATA_FORMAT, \"0x%02X\", DATA_INT, ModuleProgrammingState,\n            \"ModuleProgrammingState\",           \"\",     DATA_FORMAT, \"0x%02X\", DATA_INT, ModuleProgrammingState,\n            // \"ModuleProgrammingState\",           \"\",     DATA_INT,      ModuleProgrammingState,\n            \"TamperCounters\",                   \"\",     DATA_STRING,       TamperCounters_str,\n            \"AsynchronousCounters\",             \"\",     DATA_FORMAT, \"0x%02X\", DATA_INT, AsynchronousCounters,\n            // \"AsynchronousCounters\",             \"\",     DATA_INT,    AsynchronousCounters,\n\n            \"PowerOutageFlags\",                 \"\",     DATA_STRING,       PowerOutageFlags_str ,\n            \"LastConsumptionCount\",             \"\",     DATA_INT,       LastConsumptionCount,\n            \"DifferentialConsumptionIntervals\", \"\",     DATA_ARRAY, data_array(47, DATA_INT, DifferentialConsumptionIntervals),\n            \"TransmitTimeOffset\",               \"\",     DATA_INT,       TransmitTimeOffset,\n            \"MeterIdCRC\",                       \"\",     DATA_FORMAT, \"0x%04X\", DATA_INT, MeterIdCRC,\n            \"PacketCRC\",                        \"\",     DATA_FORMAT, \"0x%04X\", DATA_INT, PacketCRC,\n\n            \"MeterType\",                        \"Meter_Type\",       DATA_STRING, meter_type,\n            \"mic\",                              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nInterval Data Message (IDM) for Net Meters.\n\nNetIDM layout:\n\nField                 | Length | Offset/byte index\n--- | --- | ---\nPreamble              | 2\nSync Word             | 2      | 0\nProtocol ID           | 1      | 2\nPacket Length         | 1      | 3\nHamming Code          | 1      | 4\nApplication Version   | 1      | 5\nEndpoint Type         | 1      | 6\nEndpoint ID           | 4      | 7\nConsumption Interval  | 1      | 11\nProgramming State     | 1      | 12\nTamper Count          | 6      | 13  - New\nUnknown_1             | 7      | 19  - New\nUnknown_1             | 13     | 13  - Old\nLast Generation Count | 3      | 26\nUnknown_2             | 3      | 29\nLast Consumption Count| 4      | 32\nDifferential Cons     | 48     | 36    27 intervals of 14-bit unsigned integers.\nTransmit Time Offset  | 2      | 84\nMeter ID Checksum     | 2      | 86    CRC-16-CCITT of Meter ID.\nPacket Checksum       | 2      | 88    CRC-16-CCITT of packet starting at Packet Type.\n*/\nstatic int ert_netidm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[IDM_PACKET_BYTES];\n    data_t *data;\n    unsigned sync_index;\n    const uint8_t idm_frame_sync[] = {0x16, 0xA3, 0x1C};\n\n    uint8_t PacketTypeID;\n    char PacketTypeID_str[5];\n    uint8_t PacketLength;\n    // char    PacketLength_str[5];\n    //uint8_t HammingCode;\n    // char    HammingCode_str[5];\n    uint8_t ApplicationVersion;\n    // char    ApplicationVersion_str[5];\n    uint8_t ERTType;\n    // char    ERTType_str[5];\n    uint32_t ERTSerialNumber;\n    uint8_t ConsumptionIntervalCount;\n    uint8_t ModuleProgrammingState;\n    // char  ModuleProgrammingState_str[5];\n\n    //uint8_t Unknown_field_1[13];\n    char Unknown_field_1_str[32];\n\n    uint32_t LastGenerationCount = 0;\n    //char LastGenerationCount_str[16];\n\n    //uint8_t Unknown_field_2[3];\n    char Unknown_field_2_str[9];\n\n    uint32_t LastConsumptionCount;\n    //char LastConsumptionCount_str[16];\n\n    // uint64_t TamperCounters = 0;  // 6 bytes\n    char TamperCounters_str[16];\n    // uint16_t AsynchronousCounters;\n    // char AsynchronousCounters_str[8];\n    // uint64_t PowerOutageFlags = 0;  // 6 bytes\n    // char  PowerOutageFlags_str[16];\n\n    uint32_t DifferentialConsumptionIntervals[27] = {0}; // 27 intervals of 14-bit unsigned integers\n\n    uint16_t TransmitTimeOffset;\n    uint16_t MeterIdCRC;\n    // char  MeterIdCRC_str[8];\n    uint16_t PacketCRC;\n    // char  PacketCRC_str[8];\n\n    if (bitbuffer->bits_per_row[0] > 600) {\n        decoder_logf(decoder, 1, __func__, \"rows=%d, row0 len=%hu\", bitbuffer->num_rows, bitbuffer->bits_per_row[0]);\n    }\n\n    if (bitbuffer->bits_per_row[0] < IDM_PACKET_BITLEN) {\n        return (DECODE_ABORT_LENGTH);\n    }\n\n    sync_index = bitbuffer_search(bitbuffer, 0, 0, idm_frame_sync, 24);\n\n    decoder_logf(decoder, 1, __func__, \"sync_index=%u\", sync_index);\n\n    if (sync_index >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((bitbuffer->bits_per_row[0] - sync_index) < IDM_PACKET_BITLEN) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, IDM_PACKET_BITLEN);\n    decoder_log_bitrow(decoder, 1, __func__, b, IDM_PACKET_BITLEN, \"\");\n\n    // uint32_t t_16; // temp vars\n    // uint32_t t_32;\n    // uint64_t t_64;\n    char *p;\n\n    uint16_t crc;\n    // memcpy(&t_16, &b[88], 2);\n    // pkt_checksum = ntohs(t_16);\n    // pkt_checksum = (b[88] << 8 | b[89]);\n    PacketCRC = (b[88] << 8 | b[89]);\n\n    crc = crc16(&b[2], 86, 0x1021, 0xD895);\n    if (crc != PacketCRC) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // snprintf(PacketCRC_str, sizeof(PacketCRC_str), \"0x%04X\", PacketCRC);\n\n    PacketTypeID = b[2];\n    snprintf(PacketTypeID_str, sizeof(PacketTypeID_str), \"0x%02X\", PacketTypeID);\n\n    PacketLength = b[3];\n    // snprintf(PacketLength_str, sizeof(PacketLength_str), \"0x%02X\", PacketLength);\n\n    //HammingCode = b[4];\n    // snprintf(HammingCode_str, sizeof(HammingCode_str), \"0x%02X\", HammingCode);\n\n    ApplicationVersion = b[5];\n    // snprintf(ApplicationVersion_str, sizeof(ApplicationVersion_str), \"0x%02X\", b[5]);\n\n    ERTType = b[6]; // & 0x0f;\n    // snprintf(ERTType_str, sizeof(ERTType_str), \"0x%02X\", ERTType);\n\n    // memcpy(&t_32, &b[7], 4);\n    // ERTSerialNumber = ntohl(t_32);\n    ERTSerialNumber = ((uint32_t)b[7] << 24) | (b[8] << 16) | (b[9] << 8) | (b[10]);\n\n    ConsumptionIntervalCount = b[11];\n\n    ModuleProgrammingState = b[12];\n    // snprintf(ModuleProgrammingState_str, sizeof(ModuleProgrammingState_str), \"0x%02X\", ModuleProgrammingState);\n\n    /*\n    http://davestech.blogspot.com/2008/02/itron-remote-read-electric-meter.html\n    SCM1 Counter1 Meter has been inverted\n    SCM1 Counter2 Meter has been removed\n    SCM2 Counter3 Meter detected a button-press demand reset\n    SCM2 Counter4 Meter has a low-battery/end-of-calendar warning\n    SCM3 Counter5 Meter has an error or a warning that can affect billing\n    SCM3 Counter6 Meter has a warning that may or may not require a site visit,\n    */\n    p = TamperCounters_str;\n    strncpy(p, \"0x\", sizeof(TamperCounters_str));\n    p += 2;\n    for (int j = 0; j < 6; j++) {\n        // GCC-14 is confused by sprintf()\n        p += snprintf(p, 3, \"%02X\", b[13 + j]);\n    }\n    decoder_logf_bitrow(decoder, 2, __func__, &b[13], 6 * 8, \"TamperCounters_str   %s\", TamperCounters_str);\n\n    //  should this be included ?\n    p = Unknown_field_1_str;\n    strncpy(p, \"0x\", sizeof(Unknown_field_1_str));\n    p += 2;\n    for (int j = 0; j < 7; j++) {\n        // GCC-14 is confused by sprintf()\n        p += snprintf(p, 3, \"%02X\", b[19 + j]);\n    }\n    decoder_logf_bitrow(decoder, 1, __func__, &b[19], 7 * 8, \"Unknown_field_1 %s\", Unknown_field_1_str);\n\n    // 3 bytes\n    LastGenerationCount = ((uint32_t)(b[26] << 16)) | (b[27] << 8) | (b[28]);\n\n    //  should this be included ?\n    p = Unknown_field_2_str;\n    strncpy(p, \"0x\", sizeof(Unknown_field_2_str));\n    p += 2;\n    for (int j = 0; j < 3; j++) {\n        // GCC-14 is confused by sprintf()\n        p += snprintf(p, 3, \"%02X\", b[29 + j]);\n    }\n    decoder_logf_bitrow(decoder, 1, __func__, &b[29], 3 * 8, \"Unknown_field_1 %s\", Unknown_field_2_str);\n\n    LastConsumptionCount = ((uint32_t)b[32] << 24) | (b[33] << 16) | (b[34] << 8) | (b[35]);\n\n    decoder_logf_bitrow(decoder, 1, __func__, &b[32], 32, \"LastConsumptionCount %d\", LastConsumptionCount);\n\n    // DifferentialConsumptionIntervals[] = 27 intervals of 14-bit unsigned integers.\n    unsigned pos = sync_index + (36 * 8);\n    decoder_log_bitrow(decoder, 1, __func__, &b[36], 48 * 8, \"DifferentialConsumptionIntervals\");\n    for (int j = 0; j < 27; j++) {\n        uint8_t buffy[4] = {0};\n\n        bitbuffer_extract_bytes(bitbuffer, 0, pos, buffy, 14);\n        DifferentialConsumptionIntervals[j] = ((uint16_t)buffy[0] << 6) | (buffy[1] >> 2);\n        // decoder_logf_bitrow(decoder, 0, __func__, buffy, 14, \"%d %d\", j, DifferentialConsumptionIntervals[j]);\n        pos += 14;\n    }\n    if (decoder_verbose(decoder)) {\n        decoder_log(decoder, 1, __func__, \"DifferentialConsumptionIntervals\");\n        for (int j = 0; j < 27; j++) {\n            decoder_logf(decoder, 1, __func__, \"%d\", DifferentialConsumptionIntervals[j]);\n        }\n    }\n\n    TransmitTimeOffset = (b[84] << 8 | b[85]);\n\n    MeterIdCRC = (b[86] << 8 | b[87]);\n    // snprintf(MeterIdCRC_str, sizeof(MeterIdCRC_str), \"0x%04X\", MeterIdCRC);\n\n    // Least significant nibble of endpoint_type is  equivalent to SCM's endpoint type field\n    // id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters\n    /*\n    char *meter_type =  get_meter_type_name(ERTType);\n    switch (ERTType & 0x0f) {\n    case 4:\n    case 5:\n    case 7:\n    case 8:\n        meter_type = \"Electric\";\n        break;\n    case 2:\n    case 9:\n    case 12:\n        meter_type = \"Gas\";\n        break;\n    case 11:\n    case 13:\n        meter_type = \"Water\";\n        break;\n    default:\n        meter_type = \"unknown\";\n        break;\n    }\n    */\n\n    char const *meter_type = get_meter_type_name(ERTType);\n\n    // decoder_logf(decoder, 0, __func__, \"meter_type = %s\", meter_type);\n\n    /*\n        Field key names and format set to  match rtlamr field names\n\n        {Time\":\"2020-06-25T08:22:08.569276915-04:00\",\"Offset\":1605632,\"Length\":229376,\"Type\":\"NetIDM\",\"Message\":\n        {\"Preamble\":1431639715,\"ProtocolID\":28,\"PacketLength\":92,\"HammingCode\":198,\"ApplicationVersion\":4,\"ERTType\":7,\n         \"ERTSerialNumber\":1550406067,\"ConsumptionIntervalCount\":30,\"ProgrammingState\":184,\"LastGeneration\":125,\n         \"LastConsumption\":0,\"LastConsumptionNet\":2223120656,\"DifferentialConsumptionIntervals\":\n          [7695,545,2086,1475,6240,2180,4240,4616,240,7191,609,7224,1603,96,2052,12464,6152,8480,9226,352,12312,833,10292,1795,4248,4613,8416],\n         \"TransmitTimeOffset\":2145,\"SerialNumberCRC\":61178,\"PacketCRC\":37271}}\n\n    */\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",                            \"\",     DATA_STRING, \"NETIDM\",\n            \"id\",                               \"\",     DATA_INT,       ERTSerialNumber,\n\n            \"PacketTypeID\",                     \"\",     DATA_STRING,       PacketTypeID_str,\n            \"PacketLength\",                     \"\",     DATA_INT,       PacketLength,\n            // \"HammingCode\",              \"\",             DATA_FORMAT, \"0x%02X\", DATA_INT, HammingCode,\n            \"ApplicationVersion\",               \"\",     DATA_INT,       ApplicationVersion,\n\n            \"ERTType\",                          \"\",     DATA_FORMAT,  \"0x%02X\", DATA_INT,    ERTType,\n            \"ERTSerialNumber\",                  \"\",     DATA_INT,       ERTSerialNumber, // NOTE: this is also \"id\"\n            \"ConsumptionIntervalCount\",         \"\",     DATA_INT,       ConsumptionIntervalCount,\n            \"ModuleProgrammingState\",           \"\",     DATA_FORMAT, \"0x%02X\", DATA_INT, ModuleProgrammingState,\n            // \"ModuleProgrammingState\",           \"\",     DATA_STRING,    ModuleProgrammingState_str,\n            \"TamperCounters\",                   \"\",     DATA_STRING,       TamperCounters_str,\n            // \"AsynchronousCounters\",             \"\",     DATA_FORMAT, \"0x%02X\", DATA_INT, AsynchronousCounters,\n            \"Unknown_field_1\",                  \"\",     DATA_STRING,    Unknown_field_1_str,\n            \"LastGenerationCount\",              \"\",     DATA_INT,       LastGenerationCount,\n            \"Unknown_field_2\",                  \"\",     DATA_STRING,    Unknown_field_2_str,\n\n            // \"AsynchronousCounters\",             \"\",     DATA_STRING,    AsynchronousCounters_str,\n\n            // \"PowerOutageFlags\",                 \"\",     DATA_STRING,       PowerOutageFlags_str ,\n            \"LastConsumptionCount\",             \"\",     DATA_INT,       LastConsumptionCount,\n            \"DifferentialConsumptionIntervals\", \"\",     DATA_ARRAY, data_array(27, DATA_INT, DifferentialConsumptionIntervals),\n            \"TransmitTimeOffset\",               \"\",     DATA_INT,       TransmitTimeOffset,\n            \"MeterIdCRC\",                       \"\",     DATA_FORMAT, \"0x%04X\", DATA_INT, MeterIdCRC,\n            \"PacketCRC\",                        \"\",     DATA_FORMAT, \"0x%04X\", DATA_INT, PacketCRC,\n\n            \"MeterType\",                        \"\",       DATA_STRING, meter_type,\n            \"mic\",                              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n\n        // Common fields\n        \"model\",\n        \"id\",\n        \"PacketTypeID\",\n        \"PacketLength\",\n        \"HammingCode\",\n        \"ApplicationVersion\",\n        \"ERTType\",\n        \"ERTSerialNumber\",\n        \"ConsumptionIntervalCount\",\n        \"ModuleProgrammingState\",\n\n        // NetIDM Only\n        \"Unknown_field_1\",\n        \"LastGenerationCount\",\n        \"Unknown_field_2\",\n\n        // IDM Only\n        \"TamperCounters\",\n        \"AsynchronousCounters\",\n        \"PowerOutageFlags\",\n\n        // Common fields\n        \"LastConsumptionCount\",\n        \"DifferentialConsumptionIntervals\",\n        \"TransmitTimeOffset\",\n        \"MeterIdCRC\",\n        \"PacketCRC\",\n        \"MeterType\",\n        \"mic\",\n        NULL,\n};\n\n//      Freq 912600155\n//     -X n=L58,m=OOK_MC_ZEROBIT,s=30,l=30,g=20000,r=20000,match={24}0x16a31e,preamble={1}0x00\n\nr_device const ert_idm = {\n        .name        = \"ERT Interval Data Message (IDM)\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 30,\n        .long_width  = 0, // not used\n        .gap_limit   = 20000,\n        .reset_limit = 20000,\n        // .gap_limit   = 2500,\n        // .reset_limit = 4000,\n        .decode_fn = &ert_idm_decode,\n        .fields    = output_fields,\n};\n\nr_device const ert_netidm = {\n        .name        = \"ERT Interval Data Message (IDM) for Net Meters\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 30,\n        .long_width  = 0, // not used\n        .gap_limit   = 20000,\n        .reset_limit = 20000,\n        // .gap_limit   = 2500,\n        // .reset_limit = 4000,\n        .decode_fn = &ert_netidm_decode,\n        .fields    = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ert_scm.c",
    "content": "/** @file\n    ERT Standard Consumption Message (SCM) sensors.\n\n    Copyright (C) 2020 Benjamin Larsson.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nERT Standard Consumption Message (SCM) sensors.\n\nRandom information:\n\nhttps://github.com/bemasher/rtlamr\n\nhttps://en.wikipedia.org/wiki/Encoder_receiver_transmitter\n\nhttps://patentimages.storage.googleapis.com/df/23/d3/f0c33d9b2543ff/WO2007030826A2.pdf\n\n96-bit Itron® Standard Consumption Message protocol\nhttps://www.smartmetereducationnetwork.com/uploads/how-to-tell-if-I-have-a-ami-dte-smart-advanced-meter/Itron%20Centron%20Meter%20Technical%20Guide1482163-201106090057150.pdf (page 28)\n\nData layout:\n\n    SAAA AAAA  AAAA AAAA  AAAA A\n    iiR PPTT TTEE CCCC CCCC CCCC  CCCC CCCC  CCCC IIII  IIII IIII  IIII IIII  IIII XXXX XXXX XXXX  XXXX\n\n- S - Sync bit\n- A - Preamble\n- i - ERT ID Most Significant bits\n- R - Reserved\n- P - Physical tamper\n- T - ERT Type (4 and 7 are mentioned in the pdf)\n- E - Encoder Tamper\n- C - Consumption data\n- I - ERT ID Least Significant bits\n- X - CRC (polynomial 0x6F63)\n\nhttps://web.archive.org/web/20090828043201/http://www.openamr.org/wiki/ItronERTModel45\n\n*/\n\nstatic int ert_scm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // TODO: Verify preamble\n    //static const uint8_t ERT_PREAMBLE[]  = {/*0xF*/ 0x2A, 0x60};\n    uint8_t *b;\n    uint8_t physical_tamper, ert_type, encoder_tamper;\n    uint32_t consumption_data, ert_id;\n    data_t *data;\n\n    if (bitbuffer->bits_per_row[0] != 96)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[0];\n\n    // No need to decode/extract values for simple test\n    // check id tamper type crc  value not all zero'ed\n    if (!b[0] && !b[1] && !b[2] && !b[3]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (crc16(&b[2], 10, 0x6F63, 0))\n        return DECODE_FAIL_MIC;\n\n    /* Instead of detecting the preamble we rely on the\n     * CRC and extract the parameters from the back */\n\n    /* Extract parameters */\n    physical_tamper = (b[3]&0xC0) >> 6;\n    ert_type = (b[3]>>2) & 0x0F;\n    encoder_tamper = b[3]&0x03;\n    consumption_data = (b[4]<<16) | (b[5]<<8) | b[6];\n    ert_id = ((b[2]&0x06)<<23) | (b[7]<<16) | (b[8]<<8) | b[9];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",           \"\",                 DATA_STRING, \"ERT-SCM\",\n            \"id\",              \"Id\",               DATA_INT,    ert_id,\n            \"physical_tamper\", \"Physical Tamper\",  DATA_INT, physical_tamper,\n            \"ert_type\",        \"ERT Type\",         DATA_INT, ert_type,\n            \"encoder_tamper\",  \"Encoder Tamper\",   DATA_INT, encoder_tamper,\n            \"consumption_data\",\"Consumption Data\", DATA_INT, consumption_data,\n            \"mic\",             \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"physical_tamper\",\n        \"ert_type\",\n        \"encoder_tamper\",\n        \"consumption_data\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ert_scm = {\n        .name        = \"ERT Standard Consumption Message (SCM)\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 30,\n        .long_width  = 0, // not used\n        .gap_limit   = 0,\n        .reset_limit = 64,\n        .decode_fn   = &ert_scm_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/esa.c",
    "content": "/** @file\n    ELV Energy Counter ESA 1000/2000.\n\n    Copyright (C) 2016 TylerDurden23, initial cleanup by Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n#define MAXMSG 40               // ESA messages\n\nstatic uint8_t decrypt_esa(uint8_t *b)\n{\n    uint8_t pos = 0;\n    uint8_t i = 0;\n    uint8_t salt = 0x89;\n    uint16_t crc = 0xf00f;\n    uint8_t byte;\n\n    for (i = 0; i < 15; i++) {\n        byte = b[pos];\n        crc += byte;\n        b[pos++] ^= salt;\n        salt = byte + 0x24;\n    }\n    byte = b[pos];\n    crc += byte;\n    b[pos++] ^= 0xff;\n\n    crc -= (b[pos] << 8) | b[pos + 1];\n    return crc;\n}\n\n/**\nELV Energy Counter ESA 1000/2000.\n\n@todo Documentation needed.\n*/\nstatic int esa_cost_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t b[MAXMSG];\n\n    unsigned is_retry, sequence_id, deviceid, impulses;\n    unsigned impulse_constant, impulses_val, impulses_total;\n    float energy_total_val, energy_impulse_val;\n\n    if (bitbuffer->bits_per_row[0] != 160 || bitbuffer->num_rows != 1)\n        return DECODE_ABORT_LENGTH;\n\n    // remove first two bytes?\n    bitbuffer_extract_bytes(bitbuffer, 0, 16, b, 160 - 16);\n\n    if (decrypt_esa(b))\n        return DECODE_FAIL_MIC; // checksum fail\n\n    is_retry           = (b[0] >> 7);\n    sequence_id        = (b[0] & 0x7f);\n    deviceid           = (b[1]);\n    impulses           = (b[3] << 8) | b[4];\n    impulse_constant   = ((b[14] << 8) | b[15]) ^ b[1];\n    impulses_total     = ((unsigned)b[5] << 24) | (b[6] << 16) | (b[7] << 8) | b[8];\n    impulses_val       = (b[9] << 8) | b[10];\n    energy_total_val   = 1.0f * impulses_total / impulse_constant;\n    energy_impulse_val = 1.0f * impulses_val / impulse_constant;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"Model\",            DATA_STRING, \"ESA-x000\",\n            \"id\",               \"Id\",               DATA_INT, deviceid,\n            \"impulses\",         \"Impulses\",          DATA_INT, impulses,\n            \"impulses_total\",   \"Impulses Total\",   DATA_INT, impulses_total,\n            \"impulse_constant\", \"Impulse Constant\", DATA_INT, impulse_constant,\n            \"total_kWh\",        \"Energy Total\",     DATA_DOUBLE, energy_total_val,\n            \"impulse_kWh\",      \"Energy Impulse\",   DATA_DOUBLE, energy_impulse_val,\n            \"sequence_id\",      \"Sequence ID\",      DATA_INT, sequence_id,\n            \"is_retry\",         \"Is Retry\",         DATA_INT, is_retry,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"impulses\",\n        \"impulses_total\",\n        \"impulse_constant\",\n        \"total_kWh\",\n        \"impulse_kWh\",\n        \"sequence_id\",\n        \"is_retry\",\n        \"mic\",\n        NULL,\n};\n\nr_device const esa_energy = {\n        .name        = \"ESA1000 / ESA2000 Energy Monitor\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 260,\n        .long_width  = 0,\n        .reset_limit = 3000,\n        .decode_fn   = &esa_cost_callback,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/esic_emt7110.c",
    "content": "/** @file\n    ESIC EMT7110 power meter (for EMR7370 receiver).\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n    Samples and analysis by Petter Reinholdtsen <pere@hungry.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nESIC EMT7110 power meter (for EMR7370 receiver).\n\n- Center Frequency: 868.28 MHz\n- Modulation: FSK\n- Deviation: +/- 90 kHz\n- Datarate: 9.579 kbit/s\n- Preamble: 0xAAAA\n- Sync-Word: 0x2DD4\n\nA transmission is two packets, 14 ms apart.\n\nData Layout:\n\n    II II II II FP PP CC CC VV UE EE XX\n\n- I: (32 bit) byte 0-3: Sender ID\n- F: (2 bit) byte 4 bit 7/6: Bit6 = power connected, Bit7 = Pairing mode\n- P: (14 bit) byte 4 bit 5-0, byte 5: Power in 0.5 W\n- C: (16 bit) byte 6-7: Current in mA\n- V: (8 bit) byte 8: Voltage in V, Scaled by 2, Offset by 128 V\n- U: (2 bit) byte 9 bit 7/6: unknown\n- E: (14 bit) byte 9 bit 5-0, byte 10 Energyusage, total, in 10 Wh (0.01 kWh)\n- X: (8 bit) byte 11: Sum of all 11 data bytes plus CHK is 0 (mod 256)\n\nA message is ca 131-132 bits including preamble.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int esic_emt7110_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4};\n\n    data_t *data;\n    uint8_t b[12];\n\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_EARLY;\n    if ((bitbuffer->bits_per_row[0] < 120) || (bitbuffer->bits_per_row[0] > 140))\n        return DECODE_ABORT_LENGTH;\n\n    unsigned offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof (preamble) * 8);\n    offset += sizeof(preamble) * 8; // skip preamble\n    if (offset > bitbuffer->bits_per_row[0])\n        return DECODE_ABORT_EARLY;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 96);\n\n    int chk = add_bytes(b, 12);\n    if (chk & 0xff)\n        return DECODE_FAIL_MIC;\n\n    uint32_t id      = ((unsigned)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int pairing      = (b[4] & 0x80) >> 7;\n    int connected    = (b[4] & 0x40) >> 6;\n    int power_raw    = ((b[4] & 0x3f) << 8) | (b[5]);\n    float power_w    = power_raw * 0.5f;\n    int current_ma   = (b[6] << 8) | (b[7]);\n    float current_a   = current_ma * 0.001f;\n    float voltage_v  = (b[8] + 256) * 0.5f;\n    int energy_raw   = ((b[9] & 0x3f) << 8) | (b[10]);\n    float energy_kwh = energy_raw * 0.01f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"ESIC-EMT7110\",\n            \"id\",           \"Sensor ID\",    DATA_FORMAT, \"%08x\",     DATA_INT,    id,\n            \"power_W\",      \"Power\",        DATA_FORMAT, \"%.1f W\",   DATA_DOUBLE, power_w,\n            \"current_A\",    \"Current\",      DATA_FORMAT, \"%.3f A\",   DATA_DOUBLE, current_a,\n            \"voltage_V\",    \"Voltage\",      DATA_FORMAT, \"%.1f V\",   DATA_DOUBLE, voltage_v,\n            \"energy_kWh\",   \"Energy\",       DATA_FORMAT, \"%.2f kWh\", DATA_DOUBLE, energy_kwh,\n            \"pairing\",      \"Pairing?\",     DATA_INT,    pairing,\n            \"connected\",    \"Connected?\",   DATA_INT,    connected,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"power_W\",\n        \"current_A\",\n        \"voltage_V\",\n        \"energy_kWh\",\n        \"pairing\",\n        \"connected\",\n        \"mic\",\n        NULL,\n};\n\nr_device const esic_emt7110 = {\n        .name        = \"ESIC EMT7110 power meter\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 104,\n        .long_width  = 104,\n        .reset_limit = 10000,\n        .decode_fn   = &esic_emt7110_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/esperanza_ews.c",
    "content": "/** @file\n    Esperanza EWS-103 sensor on 433.92Mhz.\n\n    Copyright (C) 2015 Alberts Saulitis\n    Enhanced (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLargely the same as kedsum, s3318p.\n@sa kedsum.c s3318p.c\n\nList of known supported devices:\n- JYWDJ-009\n      * Known voltage operating range 1.7V - 3.8V\n      * Low-batt flag is raised when supply voltage goes below 2.75V\n\nFrame structure:\n\n    Byte:      0        1        2        3        4\n    Nibble:    1   2    3   4    5   6    7   8    9   10\n    Type:   00 IIIIIIII ??CCTTTT TTTTTTTT HHHHHHHH FFFFXXXX\n\n- 0: Preamble\n- I: Random device ID\n- C: Channel (1-3)\n- T: Temperature (Little-endian)\n- H: Humidity (Little-endian)\n- F: Flags (unknown low-batt unknown unknown)\n- X: CRC-4 poly 0x3 init 0x0 xor last 4 bits\n\nFlags (bbbb)\n3: Unknown\n2: low-batt Flag is raised when supply voltage drops below threshold.\n1: Unknown\n0: Unknown\n\nSample Data:\n\n    Esperanze EWS: TemperatureF=55.5 TemperatureC=13.1 Humidity=74 Device_id=0 Channel=1\n\n    bitbuffer:: Number of rows: 14\n    [00] {0} :\n    [01] {0} :\n    [02] {42} 00 53 e5 69 02 00 : 00000000 01010011 11100101 01101001 00000010 00\n    [03] {0} :\n    [04] {42} 00 53 e5 69 02 00 : 00000000 01010011 11100101 01101001 00000010 00\n    [05] {0} :\n    [06] {42} 00 53 e5 69 02 00 : 00000000 01010011 11100101 01101001 00000010 00\n    [07] {0} :\n    [08] {42} 00 53 e5 69 02 00 : 00000000 01010011 11100101 01101001 00000010 00\n    [09] {0} :\n    [10] {42} 00 53 e5 69 02 00 : 00000000 01010011 11100101 01101001 00000010 00\n    [11] {0} :\n    [12] {42} 00 53 e5 69 02 00 : 00000000 01010011 11100101 01101001 00000010 00\n    [13] {0} :\n*/\n\n#include \"decoder.h\"\n\nstatic int esperanza_ews_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[5];\n    data_t *data;\n\n    // require two leading sync pulses\n    if (bitbuffer->bits_per_row[0] != 0 || bitbuffer->bits_per_row[1] != 0)\n        return DECODE_FAIL_SANITY;\n\n    if (bitbuffer->num_rows != 14)\n        return DECODE_ABORT_LENGTH;\n\n    for (int row = 2; row < bitbuffer->num_rows - 3; row += 2) {\n        if (memcmp(bitbuffer->bb[row], bitbuffer->bb[row + 2], sizeof(bitbuffer->bb[row])) != 0\n                || bitbuffer->bits_per_row[row] != 42)\n            return DECODE_FAIL_SANITY;\n    }\n    int r = 2;\n\n    // remove the two leading 0-bits and align the data\n    bitbuffer_extract_bytes(bitbuffer, r, 2, b, 40);\n\n    // CRC-4 poly 0x3, init 0x0 over 32 bits then XOR the next 4 bits\n    int crc = crc4(b, 4, 0x3, 0x0) ^ (b[4] >> 4);\n    if (crc != (b[4] & 0xf))\n        return DECODE_FAIL_MIC;\n\n    int device_id = b[0];\n    int channel   = ((b[1] & 0x30) >> 4) + 1;\n    // Battery status is the 7th bit 0x40. 0 = normal, 1 = low\n    unsigned char const battery_low = (b[4] & 0x40) == 0x40;\n    int temp_raw  = ((b[2] & 0x0f) << 8) | (b[2] & 0xf0) | (b[1] & 0x0f);\n    float temp_f  = (temp_raw - 900) * 0.1f;\n    int humidity  = ((b[3] & 0x0f) << 4) | ((b[3] & 0xf0) >> 4);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Esperanza-EWS\",\n            \"id\",               \"ID\",           DATA_INT, device_id,\n            \"channel\",          \"Channel\",      DATA_INT, channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT, !battery_low,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const esperanza_ews = {\n        .name        = \"Esperanza EWS\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 4400,\n        .reset_limit = 9400,\n        .decode_fn   = &esperanza_ews_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/eurochron.c",
    "content": "/** @file\n    Eurochron temperature and humidity sensor.\n\n    Copyright (c) 2019 by Oliver Weyhmüller\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nEurochron temperature and humidity sensor.\n\nDatagram format:\n\n    IIIIIIII B00P0000 HHHHHHHH TTTTTTTT TTTT\n\n- I: ID (new ID will be generated at battery change!)\n- B: Battery low\n- P: TX-Button pressed\n- H: Humidity (%)\n- T: Temperature (°C10)\n- 0: Unknown / always zero\n\nDevice type identification is only possible by datagram length\nand some zero bits. Therefore this device is disabled\nby default (as it could easily trigger false alarms).\n\nObserved update intervals:\n- transmission time slot every 12 seconds\n- at least once within 120 seconds (with stable values)\n- down to 12 seconds (with rapidly changing values)\n*/\n\n#include \"decoder.h\"\n\nstatic int eurochron_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int row;\n    uint8_t *b;\n    int temp_raw, humidity, device, battery_low, button;\n    float temp_c;\n\n    /* Validation checks */\n    row = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n\n    if (row < 0) // repeated rows?\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 36) // 36 bits per row?\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[row];\n\n    if (b[1] & 0x0F) // is lower nibble of second byte zero?\n        return DECODE_FAIL_SANITY;\n\n    /* Extract data */\n    device = b[0];\n\n    temp_raw = (int16_t)((b[3] << 8) | (b[4] & 0xf0));\n    temp_c  = (temp_raw >> 4) * 0.1f;\n\n    humidity = b[2];\n\n    battery_low = b[1] >> 7;\n\n    button = (b[1] & 0x10) >> 4;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Eurochron-TH\",\n            \"id\",               \"\",             DATA_INT,    device,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_INT,    humidity,\n            \"button\",           \"Button\",       DATA_INT,    button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        NULL,\n};\n\nr_device const eurochron = {\n        .name        = \"Eurochron temperature and humidity sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1016,\n        .long_width  = 2024,\n        .gap_limit   = 2100,\n        .reset_limit = 8200,\n        .decode_fn   = &eurochron_decode,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset.c",
    "content": "/** @file\n    Fine Offset Electronics sensor protocol.\n\n    Copyright (C) 2017 Tommy Vestermark\n    Enhanced (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n    Added WH51 Soil Moisture Sensor (C) 2019 Marco Di Leo\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\nr_device const fineoffset_WH2;\n\nstatic r_device *fineoffset_WH2_create(char *arg)\n{\n    if (arg && !strcmp(arg, \"no-wh5\")) {\n        r_device *r_dev = decoder_create(&fineoffset_WH2, sizeof(int));\n        if (!r_dev) {\n            return NULL; // NOTE: returns NULL on alloc failure.\n        }\n\n        int *quirk = decoder_user_data(r_dev);\n        *quirk = 1;\n        return r_dev;\n    }\n    else {\n        return decoder_create(&fineoffset_WH2, 0); // NOTE: returns NULL on alloc failure.\n    }\n}\n\n/**\nFine Offset Electronics WH2 Temperature/Humidity sensor protocol,\nalso Agimex Rosenborg 66796 (sold in Denmark), collides with WH5,\nalso ClimeMET CM9088 (Sold in UK),\nalso TFA Dostmann/Wertheim 30.3157 (Temperature only!) (sold in Germany).\n\nThe sensor sends two identical packages of 48 bits each ~48s. The bits are PWM modulated with On Off Keying.\n\nThe data is grouped in 6 bytes / 12 nibbles.\n\n    [pre] [pre] [type] [id] [id] [temp] [temp] [temp] [humi] [humi] [crc] [crc]\n\nThere is an extra checksum byte (after the CRC) in WH2A messages.\nTODO: maybe add the checksum for WH2A, e.g. b[5] == b[0]+b[1]+b[2]+b[3]+b[4]\n\n- pre is always 0xFF\n- type is always 0x4 (may be different for different sensor type?)\n- id is a random id that is generated when the sensor starts\n- temp is 12 bit signed magnitude scaled by 10 celsius\n- humi is 8 bit relative humidity percentage\n\nBased on reverse engineering with gnu-radio and the nice article here:\nhttp://lucsmall.com/2012/04/29/weather-station-hacking-part-2/\n*/\n#define MODEL_WH2 2\n#define MODEL_WH2A 3\n#define MODEL_WH5 5\n#define MODEL_RB 6\n#define MODEL_TP 7\nstatic int fineoffset_WH2_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    void *user_data = decoder_user_data(decoder);\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t b[6] = {0};\n    data_t *data;\n\n    int model_num;\n    int type;\n    uint8_t id;\n    int16_t temp;\n    float temperature;\n    uint8_t humidity;\n\n    if (bitbuffer->bits_per_row[0] == 48 &&\n            bb[0][0] == 0xFF) { // WH2\n        bitbuffer_extract_bytes(bitbuffer, 0, 8, b, 40);\n        model_num = MODEL_WH2;\n\n    } else if (bitbuffer->bits_per_row[0] == 55 &&\n            bb[0][0] == 0xFE) { // WH2A\n        bitbuffer_extract_bytes(bitbuffer, 0, 7, b, 48);\n        model_num = MODEL_WH2A;\n\n    } else if (bitbuffer->bits_per_row[0] == 47 &&\n            bb[0][0] == 0xFE) { // WH5\n        bitbuffer_extract_bytes(bitbuffer, 0, 7, b, 40);\n        model_num = MODEL_WH5;\n        if (user_data) // don't care for the actual value\n            model_num = MODEL_RB;\n\n    } else if (bitbuffer->bits_per_row[0] == 49 &&\n            bb[0][0] == 0xFF && (bb[0][1]&0x80) == 0x80) { // Telldus\n        bitbuffer_extract_bytes(bitbuffer, 0, 9, b, 40);\n        model_num = MODEL_TP;\n\n    } else\n        return DECODE_ABORT_LENGTH;\n\n    // Validate package\n    if (b[4] != crc8(&b[0], 4, 0x31, 0)) // x8 + x5 + x4 + 1 (x8 is implicit)\n        return DECODE_FAIL_MIC;\n\n    // Nibble 2 contains type, must be 0x04 -- or is this a (battery) flag maybe? please report.\n    type = b[0] >> 4;\n    if (type != 4) {\n        decoder_logf(decoder, 1, __func__, \"Unknown type: (%d) %d\", model_num, type);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Nibble 3,4 contains id\n    id = ((b[0]&0x0F) << 4) | ((b[1]&0xF0) >> 4);\n\n    // Nibble 5,6,7 contains 12 bits of temperature\n    temp = ((b[1] & 0x0F) << 8) | b[2];\n    if (bitbuffer->bits_per_row[0] != 47 || user_data) { // WH2, Telldus, WH2A\n        // The temperature is signed magnitude and scaled by 10\n        if (temp & 0x800) {\n            temp &= 0x7FF; // remove sign bit\n            temp = -temp; // reverse magnitude\n        }\n    } else { // WH5\n        // The temperature is unsigned offset by 40 C and scaled by 10\n        temp -= 400;\n    }\n    temperature = temp * 0.1f;\n\n    // Nibble 8,9 contains humidity\n    humidity = b[3];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_COND, model_num == MODEL_WH2,  DATA_STRING, \"Fineoffset-WH2\",\n            \"model\",            \"\",             DATA_COND, model_num == MODEL_WH2A, DATA_STRING, \"Fineoffset-WH2A\",\n            \"model\",            \"\",             DATA_COND, model_num == MODEL_WH5,  DATA_STRING, \"Fineoffset-WH5\",\n            \"model\",            \"\",             DATA_COND, model_num == MODEL_RB,   DATA_STRING, \"Rosenborg-66796\",\n            \"model\",            \"\",             DATA_COND, model_num == MODEL_TP,   DATA_STRING, \"Fineoffset-TelldusProove\",\n            \"id\",               \"ID\",           DATA_INT, id,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"humidity\",         \"Humidity\",     DATA_COND, humidity != 0xff, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFine Offset Electronics WH24, WH65B, HP1000 and derivatives Temperature/Humidity/Pressure sensor protocol.\n\nAlso: Misol WS2320 (rebranded WH65B, 433MHz)\n\nThe sensor sends a package each ~16 s with a width of ~11 ms. The bits are PCM modulated with Frequency Shift Keying.\n\nExample:\n\n         [00] {196} d5 55 55 55 55 16 ea 12 5f 85 71 03 27 04 01 00 25 00 00 80 00 00 47 83 9\n      aligned {199} 1aa aa aa aa aa 2d d4 24 bf 0a e2 06 4e 08 02 00 4a 00 01 00 00 00 8f 07 2\n    Payload:                              FF II DD VT TT HH WW GG RR RR UU UU LL LL LL CC BB\n    Reading: id: 191, temp: 11.8 C, humidity: 78 %, wind_dir 266 deg, wind_speed: 1.12 m/s, gust_speed 2.24 m/s, rainfall: 22.2 mm\n\nThe WH65B sends the same data with a slightly longer preamble and postamble:\n\n            {209} 55 55 55 55 55 51 6e a1 22 83 3f 14 3a 08 00 00 00 08 00 10 00 00 04 60 a1 00 8\n    aligned  {208} a aa aa aa aa aa 2d d4 24 50 67 e2 87 41 00 00 00 01 00 02 00 00 00 8c 14 20 1\n    Payload:                              FF II DD VT TT HH WW GG RR RR UU UU LL LL LL CC BB\n\n- Preamble:  aa aa aa aa aa\n- Sync word: 2d d4\n- Payload:   FF II DD VT TT HH WW GG RR RR UU UU LL LL LL CC BB\n\n- F: 8 bit Family Code, fixed 0x24\n- I: 8 bit Sensor ID, set on battery change\n- D: 8 bit Wind direction\n- V: 4 bit Various bits, D11S, wind dir 8th bit, wind speed 8th bit\n- B: 1 bit low battery indicator\n- T: 11 bit Temperature (+40*10), top bit is low battery flag\n- H: 8 bit Humidity\n- W: 8 bit Wind speed\n- G: 8 bit Gust speed\n- R: 16 bit rainfall counter\n- U: 16 bit UV value\n- L: 24 bit light value\n- C: 8 bit CRC checksum of the 15 data bytes\n- B: 8 bit Bitsum (sum without carry, XOR) of the 16 data bytes\n\nThe WS69 sends the same data with additional 8 bytes (6 bytes plus CRC and SUM):\n\n    {263}aaaaaaaaaaa 2dd4 241c8801ca63000000330000000000 acd5 01ff ffff 002f 835a 0\n    {263}aaaaaaaaaaa 2dd4 241c5981b06304010033000000001e 8e11 01ff ffff 002f 96e5 0\n    {263}aaaaaaaaaaa 2dd4 241c5981b06304000033000000001e c749 01ff ffff 002f 9857 0\n    {263}aaaaaaaaaaa 2dd4 241c5981b06303000033000000001e 9f20 01ff ffff 002f df4c 0\n    {263}aaaaaaaaaaa 2dd4 241c5981b06302000033000000001e 2aaa 01ff ffff 002f 5fe0 0\n    {263}aaaaaaaaaaa 2dd4 241c5981b063000000330000000014 aa1e 01ff ffff 002f b41a 0\n\nThe WS69 also seems to sends another type of packet, 100 us FSK PCM, that has not been analyzed:\n\n    5555 5555 d395 d395 d90a ba0b 5cb7 1d\n\n    rtl_433 -f 868.2M -s 1000k -Y minmax -R 0 -X 'n=name,m=FSK_PCM,s=100,l=100,r=3000,preamble=d395d395'\n */\n#define MODEL_WH24 24 /* internal identifier for model WH24, family code is always 0x24 */\n#define MODEL_WH65B 65 /* internal identifier for model WH65B, family code is always 0x24 */\n#define MODEL_WS69 69 /* internal identifier for model WS69, family code is always 0x24 */\nstatic int fineoffset_WH24_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4}; // part of preamble and sync word\n    uint8_t b[17]; // aligned packet data, would need 25 bytes for WS69\n    unsigned bit_offset;\n    int type;\n\n    // Validate package, WH24 nominal size is 196 bit periods, WH65b is 209 bit periods, WS69 is 260 bits.\n    if (bitbuffer->bits_per_row[0] < 190 || bitbuffer->bits_per_row[0] > 268) {\n        decoder_logf(decoder, 1, __func__, \"wrong package size %u\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a data package and extract data buffer\n    bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"short package. Header index: %u\", bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Classification heuristics\n    if (bitbuffer->bits_per_row[0] - bit_offset - sizeof(b) * 8 < 8) {\n        if (bit_offset < 61) {\n            type = MODEL_WH24; // nominal 3 bits postamble\n        }\n        else {\n            type = MODEL_WH65B;\n        }\n    }\n    else {\n        type = MODEL_WH65B; // nominal 12 bits postamble\n    }\n    if (bitbuffer->bits_per_row[0] > 215) { // minimum 27*8=216\n        // we could also check the extra 6 bytes plus crc and sum.\n        type = MODEL_WS69; // nominal 260 bits\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    decoder_logf_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, \"Raw @ bit_offset [%u]\", bit_offset);\n\n    if (b[0] != 0x24) { // Check for family code 0x24\n        decoder_logf(decoder, 1, __func__, \"unknown family code: 0x%02x\", b[0]);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Verify checksum, same as other FO Stations: Reverse 1Wire CRC (poly 0x131)\n    uint8_t crc = crc8(b, 15, 0x31, 0x00);\n    uint8_t checksum = 0;\n    for (unsigned n = 0; n < 16; ++n) {\n        checksum += b[n];\n    }\n    if (crc != b[15] || checksum != b[16]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x\", crc, checksum);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Decode data\n    int id              = b[1];                      // changes on battery change\n    int wind_dir        = b[2] | (b[3] & 0x80) << 1; // range 0-359 deg, 0x1ff if invalid\n    int low_battery     = (b[3] & 0x08) >> 3;\n    int temp_raw        = (b[3] & 0x07) << 8 | b[4]; // 0x7ff if invalid\n    float temperature   = (temp_raw - 400) * 0.1f; // range -40.0-60.0 C\n    int humidity        = b[5];                      // 0xff if invalid\n    int wind_speed_raw  = b[6] | (b[3] & 0x10) << 4; // 0x1ff if invalid\n    float wind_speed_factor, rain_cup_count;\n    // Wind speed factor is 1.12 m/s (1.19 per specs?) for WH24, 0.51 m/s for WH65B\n    // Rain cup each count is 0.3mm for WH24, 0.01inch (0.254mm) for WH65B\n    if (type == MODEL_WH24) { // WH24\n        wind_speed_factor = 1.12f;\n        rain_cup_count = 0.3f;\n    } else { // WH65B\n        wind_speed_factor = 0.51f;\n        rain_cup_count = 0.254f;\n    }\n    // Wind speed is scaled by 8, wind speed = raw / 8 * 1.12 m/s (0.51 for WH65B)\n    float wind_speed_ms = wind_speed_raw * 0.125f * wind_speed_factor;\n    int gust_speed_raw  = b[7];             // 0xff if invalid\n    // Wind gust is unscaled, multiply by wind speed factor 1.12 m/s\n    float gust_speed_ms = gust_speed_raw * wind_speed_factor;\n    int rainfall_raw    = b[8] << 8 | b[9]; // rain tip counter\n    float rainfall_mm   = rainfall_raw * rain_cup_count; // each tip is 0.3mm / 0.254mm\n    int uv_raw          = b[10] << 8 | b[11];               // range 0-20000, 0xffff if invalid\n    int light_raw       = b[12] << 16 | b[13] << 8 | b[14]; // 0xffffff if invalid\n    double light_lux     = light_raw * 0.1; // range 0.0-300000.0lux\n    // Light = value/10 ; Watts/m Sqr. = Light/683 ;  Lux to W/m2 = Lux/126\n\n    // UV value   UVI\n    // 0-432      0\n    // 433-851    1\n    // 852-1210   2\n    // 1211-1570  3\n    // 1571-2017  4\n    // 2018-2450  5\n    // 2451-2761  6\n    // 2762-3100  7\n    // 3101-3512  8\n    // 3513-3918  9\n    // 3919-4277  10\n    // 4278-4650  11\n    // 4651-5029  12\n    // >=5230     13\n    int uvi_upper[] = {432, 851, 1210, 1570, 2017, 2450, 2761, 3100, 3512, 3918, 4277, 4650, 5029};\n    int uv_index   = 0;\n    while (uv_index < 13 && uvi_upper[uv_index] < uv_raw) ++uv_index;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_COND, type == MODEL_WH24,  DATA_STRING, \"Fineoffset-WH24\",\n            \"model\",            \"\",                 DATA_COND, type == MODEL_WH65B, DATA_STRING, \"Fineoffset-WH65B\",\n            \"model\",            \"\",                 DATA_COND, type == MODEL_WS69,  DATA_STRING, \"Fineoffset-WS69\",\n            \"id\",               \"ID\",               DATA_INT,  id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,  !low_battery,\n            \"temperature_C\",    \"Temperature\",      DATA_COND, temp_raw != 0x7ff, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"humidity\",         \"Humidity\",         DATA_COND, humidity != 0xff, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_COND, wind_dir != 0x1ff, DATA_INT, wind_dir,\n            \"wind_avg_m_s\",     \"Wind speed\",       DATA_COND, wind_speed_raw != 0x1ff, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_speed_ms,\n            \"wind_max_m_s\",     \"Gust speed\",       DATA_COND, gust_speed_raw != 0xff, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, gust_speed_ms,\n            \"rain_mm\",          \"Rainfall\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rainfall_mm,\n            \"uv\",               \"UV\",               DATA_COND, uv_raw != 0xffff, DATA_INT, uv_raw,\n            \"uvi\",              \"UV Index\",         DATA_COND, uv_raw != 0xffff, DATA_FORMAT, \"%.0f\", DATA_DOUBLE, (double)uv_index,\n            \"light_lux\",        \"Light\",            DATA_COND, light_raw != 0xffffff, DATA_FORMAT, \"%.1f lux\", DATA_DOUBLE, light_lux,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFine Offset Electronics WH0290 Wireless Air Quality Monitor\nAlso: Ambient Weather PM25\nAlso: Misol PM25\nAlso: Ecowitt WH0290, Ecowitt WH41\n\nThe sensor sends a data burst every 10 minutes.  The bits are PCM\nmodulated with Frequency Shift Keying.\n\nEcowitt advertises this device as a PM2.5 sensor.  It contains a\nHoneywell PM2.5 sensor:\n\n  https://sensing.honeywell.com/honeywell-sensing-particulate-hpm-series-datasheet-32322550.pdf\n\nHowever, the Honeywell datasheet says that it also has a PM10 output\nwhich is \"calculated from\" the PM2.5 reading.  While there is an\naccuracy spec for PM2.5, there is no specification of an kind from\nPM10.  The datasheet does not explain the calculation, and does not\ngive references to papers in the scientific literature.\n\nNote that PM2.5 is the mass of particles <= 2.5 microns in 1 m^3 of\nair, and PM10 is the mass of particles <= 10 microns.  Therefore the\ndifference in those measurements is the mass of particles > 2.5\nmicrons and <= 10 microns, sometimes called PM2.5-10.  By definition\nthese particles are not included in the PM2.5 measurement, so\n\"calculating\" doesn't make sense.  Rather, this appears an assumption\nabout correlation, meaning how much mass of larger particles is likely\nto be present based on the mass of the smaller particles.\n\nThe serial stream from the sensor has fields for PM2.5 and PM10 and\nthese fields have been verified to appear in the transmitted signal by\ncross-comparing the internal serial lines and data received via\nrtl_433.\n\nThe Ecowitt displays show only PM2.5, and Ecowitt confirmed that the\nsecond field is the PM10 output of the sensor but said the value is\nnot accurate so they have not adopted it.\n\nBy observation of an Ecowitt WH41, the formula is pm10 = pm2.5 +\nincrement(pm2.5), where the increment is by ranges from the following\ntable (with gaps when no samples have been observed).  It is left as\nfuture work to compare with an actual PM10 sensor.\n\n0 to 24     | 1\n25 to 106   | 2\n109 to 185  | 3\n190 to 222  | 4\n311         | 5\n390         | 6\n\n\nData layout:\n             41 c7 41 ae 01 c2 f9 b3 00000, Ecowitt 41\n    aa 2d d4 42 cc 41 9a 41 ae c1 99 9\n             FF DD ?P PP ?A AA CC BB\n\n- F: 8 bit Family Code?\n- D: 8 bit device id (corresponds to sticker on device in hex)\n- ?: 1 bit?\n- b: 1 bit MSB of battery bars out of 5\n- P: 14 bit PM2.5 reading in ug/m3\n- b: 2 bits LSBs of battery bars out of 5\n- A: 14 bit PM10.0 reading in ug/m3\n- C: 8 bit CRC checksum of the previous 6 bytes\n- B: 8 bit Bitsum (sum without carry, XOR) of the previous 7 bytes\n\nBitBench Examples\n{129} 55 55 55 55 55 51 6e a2 0c ba 02 d0 03 25 13 c0 00 [pm2=9 pm10=10 id=151 0x97 battery 4/5bars]\n{128} 55 55 55 55 55 51 6e a2 0c ba 03 70 03 c3 43 30 [pm2=11 pm10=12 id=151 0x97 battery 4/5bars]\n{129} 55 55 55 55 55 51 6e a2 0c b8 01 46 01 94 9c 2c 00 [pm2=4 pm10=5 id=151 0x97 3/5 bars]\nPreamble: aa2dd4\nFAM:8d ID: 8h 1b Bat_MSB:1d PMTWO:14d Bat_LSB:2d PMTEN:14d CRC:8h BITSIM:8h bbbbb\n*/\nstatic int fineoffset_WH0290_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4};\n    uint8_t b[8];\n    unsigned bit_offset;\n\n    bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"short package. Row length: %u. Header index: %u\", bitbuffer->bits_per_row[0], bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    // Verify checksum, same as other FO Stations: Reverse 1Wire CRC (poly 0x131)\n    uint8_t crc = crc8(b, 6, 0x31, 0x00);\n    uint8_t checksum = 0;\n    for (unsigned n = 0; n < 7; ++n) {\n        checksum += b[n];\n    }\n    if (crc != b[6] || checksum != b[7]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x\", crc, checksum);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Decode data\n    uint8_t family    = b[0];\n    uint8_t id        = b[1];\n    uint8_t unknown1  = (b[2] & 0x80) ? 1 : 0;\n    int pm25          = (b[2] & 0x3f) << 8 | b[3];\n    int pm100         = (b[4] & 0x3f) << 8 | b[5];\n    int battery_bars  = (b[2] & 0x40) >> 4 | (b[4] & 0xC0) >> 6; //out of 5\n    float battery_ok  = battery_bars * 0.2f; //convert out of 5 bars to 0 (0 bars) to 1 (5 bars)\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Fineoffset-WH0290\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"battery_ok\",       \"Battery level\",  DATA_FORMAT, \"%.1f\", DATA_DOUBLE, battery_ok,\n            \"pm2_5_ug_m3\",      \"2.5um Fine Particulate Matter\",  DATA_FORMAT, \"%d ug/m3\", DATA_INT, pm25/10,\n            \"estimated_pm10_0_ug_m3\",     \"Estimate of 10um Coarse Particulate Matter\",  DATA_FORMAT, \"%d ug/m3\", DATA_INT, pm100/10,\n            \"family\",           \"FAMILY\",       DATA_INT,    family,\n            \"unknown1\",         \"UNKNOWN1\",     DATA_INT,    unknown1,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFine Offset Electronics WH25 / WH32 / WH32B / WN32B Temperature/Humidity/Pressure sensor protocol.\n\nThe sensor sends a package each ~64 s with a width of ~28 ms. The bits are PCM modulated with Frequency Shift Keying.\n\nExample: 22.6 C, 40 %, 1001.7 hPa\n\n    [00] {500} 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 2a aa aa aa aa aa 8b 75 39 40 9c 8a 09 c8 72 6e ea aa aa 80 10\n\nData layout:\n\n    aa 2d d4 e5 02 72 28 27 21 c9 bb aa\n             MI IT TT HH PP PP CC XX\n\n- M: 4 bit Model code, 0xd: old model, 0xe: new model.\n- I: 8 bit Sensor ID (based on 2 different sensors). Does not change at battery change.\n- B: 1 bit low battery indicator\n- F: 1 bit invalid reading indicator\n- T: 10 bit Temperature (+40*10), top two bits are flags\n- H: 8 bit Humidity\n- P: 16 bit Pressure (*10)\n- C: 8 bit Checksum of previous 6 bytes (binary sum truncated to 8 bit)\n- X: 8 bit Bitsum (XOR) of the 6 data bytes (high and low nibble exchanged)\n\nWH32B is the same as WH25 but two packets in one transmission of {971} and XOR sum missing.\n\n    TYPE:4h ID:8d FLAGS:2b TEMP_C:10d HUM:8d HPA:16d CHK:8h\n\n*/\nstatic int fineoffset_WH25_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4};\n    uint8_t b[8];\n    int type = 25;\n    unsigned bit_offset;\n\n    // Validate package\n    if (bitbuffer->bits_per_row[0] < 160) {\n        // Nominal length of WH0290 is 129 bits\n        return fineoffset_WH0290_callback(decoder, bitbuffer); // abort and try WH0290\n    }\n    else if (bitbuffer->bits_per_row[0] < 190) {\n        // Nominal length of WN32B is 173 bits\n        type = 32; // new WN32B\n    }\n    else if (bitbuffer->bits_per_row[0] < 440) {             // Nominal size is 488 bit periods\n        return fineoffset_WH24_callback(decoder, bitbuffer); // abort and try WH24, WH65B, HP1000\n    }\n\n    if (bitbuffer->bits_per_row[0] > 510) { // WH32B has nominal size of 971 bit periods\n        type = 32;\n    }\n\n    // Find a data package and extract data payload\n    // Nominal index of WH25 is 367, and 123, 570 for WH32B\n    bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"short package. Header index: %u\", bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n    decoder_log_bitrow(decoder, 2, __func__, b, sizeof(b) * 8, \"Packet\");\n\n    // Verify type code\n    int msg_type = b[0] & 0xf0;\n    if (type == 32 && msg_type == 0xd0) {\n        // this is an older \"WH32\", does not have a barometric sensor\n        type = 31;\n    }\n    else if (msg_type != 0xe0) {\n        decoder_logf(decoder, 1, __func__, \"Msg type unknown: 0x%02x\", b[0]);\n        if (b[0] == 0x41) {\n            return fineoffset_WH0290_callback(decoder, bitbuffer); // abort and try WH0290\n        }\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Verify checksum\n    int sum = (add_bytes(b, 6) & 0xff) - b[6];\n    if (sum) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Verify xor-sum\n    int bitsum = xor_bytes(b, 6);\n    bitsum = ((bitsum & 0x0f) << 4) | (bitsum >> 4); // Swap nibbles\n    if (type == 25 && bitsum != b[7]) { // only check for WH25\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Bitsum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Decode data\n    uint8_t id        = ((b[0]&0x0f) << 4) | (b[1] >> 4);\n    int low_battery   = (b[1] & 0x08) >> 3;\n    //int invalid_flag  = (b[1] & 0x04) >> 2;\n    int temp_raw      = (b[1] & 0x03) << 8 | b[2]; // 0x7ff if invalid\n    float temperature = (temp_raw - 400) * 0.1f;    // range -40.0-60.0 C\n    uint8_t humidity  = b[3];\n    int pressure_raw  = (b[4] << 8 | b[5]);\n    float pressure    = pressure_raw * 0.1f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_COND, type == 31, DATA_STRING, \"Fineoffset-WH32\",\n            \"model\",            \"\",             DATA_COND, type == 32, DATA_STRING, \"Fineoffset-WH32B\",\n            \"model\",            \"\",             DATA_COND, type == 25, DATA_STRING, \"Fineoffset-WH25\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !low_battery,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"pressure_hPa\",     \"Pressure\",     DATA_COND,   pressure_raw != 0xffff, DATA_FORMAT, \"%.1f hPa\", DATA_DOUBLE, pressure,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFine Offset WH51, Ecowitt WH51, WN31, MISOL/1 Soil Moisture Sensor.\n\nAlso: SwitchDoc Labs SM23 Soil Moisture Sensor.\nAlso: Ecowitt WN31 temperature humidity sensor.\n\nTest decoding with: rtl_433 -f 433920000  -X \"n=soil_sensor,m=FSK_PCM,s=58,l=58,t=5,r=5000,g=4000,preamble=aa2dd4\"\n\nNote: for WH51 at 915MHz: try also \"-Y classic\" i.e. : rtl_433 -f 915M -Y classic -- see https://github.com/merbanan/rtl_433/issues/2235\n\nData format:\n\n                   00 01 02 03 04 05 06 07 08 09 10 11 12 13\n    aa aa aa 2d d4 51 00 6b 58 6e 7f 24 f8 d2 ff ff ff 3c 28 8\n                   FF II II II TB YY MM ZA AA XX XX XX CC SS\n\n- Sync:     aa aa aa ...\n- Preamble: 2d d4\n- FF:       Family code 0x51 (Ecowitt/FineOffset WH51)\n- IIIIII:   ID (3 bytes)\n- T:        Transmission period boost: highest 3 bits set to 111 on moisture change and decremented each transmission;\n-           if T = 0 period is 70 sec, if T > 0 period is 10 sec\n- B:        Battery voltage: lowest 5 bits are battery voltage * 10 (e.g. 0x0c = 12 = 1.2V). Transmitter works down to 0.7V (0x07)\n- YY:       ? Fixed: 0x7f\n- MM:       Moisture percentage 0%-100% (0x00-0x64) MM = (AD - 70) / (450 - 70)\n- Z:        ? Fixed: leftmost 7 bit 1111 100\n- AAA:      9 bit AD value MSB byte[07] & 0x01, LSB byte[08]\n- XXXXXX:   ? Fixed: 0xff 0xff 0xff\n- CC:       CRC of the preceding 12 bytes (Polynomial 0x31, Initial value 0x00, Input not reflected, Result not reflected)\n- SS:       Sum of the preceding 13 bytes % 256\n\nSee http://www.ecowitt.com/upfile/201904/WH51%20Manual.pdf for relationship between AD and moisture %\n\nShort explanation:\n- Soil Moisture Percentage = (Moisture AD - 0%AD) / (100%AD - 0%AD) * 100\n- 0%AD = 70\n- 100%AD = 450 (manual states 500, but sensor internal computation are closer to 450)\n- If sensor-calculated moisture percentage are inaccurate at low/high values, use the AD value and the above formaula\n  changing 0%AD and 100%AD to cover the full scale from dry to damp\n*/\nstatic int fineoffset_WH51_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4};\n    uint8_t b[14];\n    unsigned bit_offset;\n\n    // Validate package\n    if (bitbuffer->bits_per_row[0] < 120) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a data package and extract data payload\n    bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"short package. Header index: %u\", bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    // Verify family code\n    if (b[0] != 0x51) {\n        decoder_logf(decoder, 1, __func__, \"Msg family unknown: 0x%02x\", b[0]);\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Verify checksum\n    if ((add_bytes(b, 13) & 0xff) != b[13]) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Verify crc\n    if (crc8(b, 12, 0x31, 0) != b[12]) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Bitsum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Decode data\n    char id[7];\n    snprintf(id, sizeof(id), \"%02x%02x%02x\", b[1], b[2], b[3]);\n    int boost           = (b[4] & 0xe0) >> 5;\n    int battery_mv      = (b[4] & 0x1f) * 100;\n    float battery_level = (battery_mv - 700) / 900.0f; // assume 1.6V (100%) to 0.7V (0%) range\n    int ad_raw          = (((int)b[7] & 0x01) << 8) | (int)b[8];\n    int moisture        = b[6];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Fineoffset-WH51\",\n            \"id\",               \"ID\",               DATA_STRING, id,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery_level,\n            \"battery_mV\",       \"Battery\",          DATA_FORMAT, \"%d mV\", DATA_INT, battery_mv,\n            \"moisture\",         \"Moisture\",         DATA_FORMAT, \"%u %%\", DATA_INT, moisture,\n            \"boost\",            \"Transmission boost\", DATA_INT, boost,\n            \"ad_raw\",           \"AD raw\",           DATA_INT, ad_raw,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n\n/**\nAlecto WS-1200 V1.0 decoder by Christian Zuckschwerdt, documentation by Andreas Untergasser, help by curlyel.\n\nA Thermometer with clock and wireless rain unit with temperature sensor.\n\nManual available at\nhttps://www.alecto.nl/media/blfa_files/WS-1200_manual_NL-FR-DE-GB_V2.2_8712412532964.pdf\n\nData layout:\n\n    1111111 FFFFIIII IIIIB?TT TTTTTTTT RRRRRRRR RRRRRRRR 11111111 CCCCCCCC\n\n- 1: 7 bit preamble of 1's\n- F: 4 bit fixed message type (0x3)\n- I: 8 bit random sensor ID, changes at battery change\n- B: 1 bit low battery indicator\n- T: 10 bit temperature in Celsius offset 40 scaled by 10\n- R: 16 bit (little endian) rain count in 0.3 mm steps, absolute with wrap around at 65536\n- C: 8 bit CRC-8 poly 0x31 init 0x0 for 7 bytes\n\nFormat string:\n\n    PRE:7b TYPE:4b ID:8b BATT:1b ?:1b T:10d R:<16d ?:8h CRC:8h\n*/\nstatic int alecto_ws1200v1_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t b[7];\n\n    // Validate package\n    if (bitbuffer->bits_per_row[0] != 63 // Match exact length to avoid false positives\n            || (bb[0][0] >> 1) != 0x7F   // Check preamble (7 bits)\n            || (bb[0][1] >> 5) != 0x3)   // Check message type (4 bits)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, 7, b, sizeof (b) * 8); // Skip first 7 bits\n\n    // Verify checksum\n    int crc = crc8(b, 7, 0x31, 0);\n    if (crc) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Alecto WS-1200 v1.0: CRC error \");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id            = ((b[0] & 0x0f) << 4) | (b[1] >> 4);\n    int battery_low   = (b[1] >> 3) & 0x1;\n    int temp_raw      = (b[1] & 0x7) << 8 | b[2];\n    float temperature = (temp_raw - 400) * 0.1f;\n    int rainfall_raw  = b[4] << 8 | b[3];   // rain tip counter\n    float rainfall    = rainfall_raw * 0.3f; // each tip is 0.3mm\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Alecto-WS1200v1\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rainfall,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nAlecto WS-1200 V2.0 DCF77 decoder by Christian Zuckschwerdt, documentation by Andreas Untergasser, help by curlyel.\n\nA Thermometer with clock and wireless rain unit with temperature sensor.\n\nManual available at\nhttps://www.alecto.nl/media/blfa_files/WS-1200_manual_NL-FR-DE-GB_V2.2_8712412532964.pdf\n\nData layout:\n\n    1111111 FFFFFFFF IIIIIIII B??????? ..YY..YY ..MM..MM ..DD..DD ..HH..HH ..MM..MM ..SS..SS CCCCCCCC AAAAAAAA\n\n- 1: 7 bit preamble of 1's\n- F: 8 bit fixed message type (0x52)\n- I: 8 bit random sensor ID, changes at battery change\n- B: 1 bit low battery indicator\n- ?: 7 bit unknown\n\n- T: 10 bit temperature in Celsius offset 40 scaled by 10\n- R: 16 bit (little endian) rain count in 0.3 mm steps, absolute with wrap around at 65536\n- C: 8 bit CRC-8 poly 0x31 init 0x0 for 10 bytes\n- A: 8 bit checksum (addition)\n\nFormat string:\n\n    PRE:7b TYPE:8b ID:8b BATT:1b ?:1b ?:8b YY:4d YY:4d MM:4d MM:4d DD:4d DD:4d HH:4d HH:4d MM:4d MM:4d SS:4d SS:4d ?:16b\n\n*/\nstatic int alecto_ws1200v2_dcf_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t b[11];\n\n    // Validate package\n    if (bitbuffer->bits_per_row[0] != 95 // Match exact length to avoid false positives\n            || (bb[0][0] >> 1) != 0x7F   // Check preamble (7 bits)\n            || (bb[0][1] >> 1) != 0x52)   // Check message type (8 bits)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, 7, b, sizeof (b) * 8); // Skip first 7 bits\n\n    // Verify CRC\n    int crc = crc8(b, 10, 0x31, 0);\n    if (crc) {\n        //decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Alecto WS-1200 v2.0 DCF77: CRC error \");\n        return DECODE_FAIL_MIC;\n    }\n    // Verify checksum\n    int sum = add_bytes(b, 10) - b[10];\n    if (sum & 0xff) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Alecto WS-1200 v2.0 DCF77: Checksum error \");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id          = (b[1]);\n    int battery_low = (b[2] >> 7) & 0x1;\n    // date/time fields are actually bcd, just print as hex.\n    // TODO: the seconds fields sometimes has values like: 0xb8, 0x3c?\n    int date_y      = b[4] + 0x2000; // (b[4] >> 4) * 10 + (b[4] & 0x0f) + 2000;\n    int date_m      = b[5]; // (b[5] >> 4) * 10 + (b[5] & 0x0f);\n    int date_d      = b[6]; // (b[6] >> 4) * 10 + (b[6] & 0x0f);\n    int time_h      = b[7]; // (b[7] >> 4) * 10 + (b[7] & 0x0f);\n    int time_m      = b[8]; // (b[8] >> 4) * 10 + (b[8] & 0x0f);\n    int time_s      = b[9]; // (b[9] >> 4) * 10 + (b[9] & 0x0f);\n    char clock_str[32];\n    snprintf(clock_str, sizeof(clock_str), \"%04x-%02x-%02xT%02x:%02x:%02x\",\n            date_y, date_m, date_d, time_h, time_m, time_s);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Alecto-WS1200v2\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"radio_clock\",      \"Radio Clock\",  DATA_STRING, clock_str,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nAlecto WS-1200 V2.0 decoder by Christian Zuckschwerdt, documentation by Andreas Untergasser, help by curlyel.\n\nA Thermometer with clock and wireless rain unit with temperature sensor.\n\nManual available at\nhttps://www.alecto.nl/media/blfa_files/WS-1200_manual_NL-FR-DE-GB_V2.2_8712412532964.pdf\n\nData layout:\n\n    1111111 FFFFIIII IIIIB?TT TTTTTTTT RRRRRRRR RRRRRRRR 11111111 CCCCCCCC AAAAAAAA DDDDDDDD DDDDDDDD DDDDDDDD\n\n- 1: 7 bit preamble of 1's\n- F: 4 bit fixed message type (0x3)\n- I: 8 bit random sensor ID, changes at battery change\n- B: 1 bit low battery indicator\n- T: 10 bit temperature in Celsius offset 40 scaled by 10\n- R: 16 bit (little endian) rain count in 0.3 mm steps, absolute with wrap around at 65536\n- C: 8 bit CRC-8 poly 0x31 init 0x0 for 7 bytes\n- A: 8 bit checksum (addition)\n- D: 24 bit DCF77 time, all 0 while training for the station connection\n\nFormat string:\n\n    PRE:7b TYPE:4b ID:8b BATT:1b ?:1b T:10d R:<16d ?:8h CRC:8h MAC:8h DATE:24b\n*/\nstatic int alecto_ws1200v2_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t b[11];\n\n    // Validate package\n    if (bitbuffer->bits_per_row[0] != 95 // Match exact length to avoid false positives\n            || (bb[0][0] >> 1) != 0x7F   // Check preamble (7 bits)\n            || (bb[0][1] >> 5) != 0x3)   // Check message type (8 bits)\n        return alecto_ws1200v2_dcf_callback(decoder, bitbuffer);\n\n    bitbuffer_extract_bytes(bitbuffer, 0, 7, b, sizeof (b) * 8); // Skip first 7 bits\n\n    // Verify CRC\n    int crc = crc8(b, 7, 0x31, 0);\n    if (crc) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Alecto WS-1200 v2.0: CRC error \");\n        return DECODE_FAIL_MIC;\n    }\n    // Verify checksum\n    int sum = add_bytes(b, 7) - b[7];\n    if (sum & 0xff) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Alecto WS-1200 v2.0: Checksum error \");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id            = ((b[0] & 0x0f) << 4) | (b[1] >> 4);\n    int battery_low   = (b[1] >> 3) & 0x1;\n    int temp_raw      = (b[1] & 0x7) << 8 | b[2];\n    float temperature = (temp_raw - 400) * 0.1f;\n    int rainfall_raw  = b[4] << 8 | b[3];   // rain tip counter\n    float rainfall    = rainfall_raw * 0.3f; // each tip is 0.3mm\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Alecto-WS1200v2\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rainfall,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFine Offset Electronics WH0530 Temperature/Rain sensor protocol,\nalso Agimex Rosenborg 35926 (sold in Denmark).\n\nThe sensor sends two identical packages of 71 bits each ~48s. The bits are PWM modulated with On Off Keying.\nData consists of 7 bit preamble and 8 bytes.\n\nData layout:\n    38 a2 8f 02 00 ff e7 51\n    FI IT TT RR RR ?? CC AA\n\n- F: 4 bit fixed message type (0x3)\n- I: 8 bit Sensor ID (guess). Does not change at battery change.\n- B: 1 bit low battery indicator\n- T: 11 bit Temperature (+40*10) (Upper bit is Battery Low indicator)\n- R: 16 bit (little endian) rain count in 0.3 mm steps, absolute with wrap around at 65536\n- ?: 8 bit Always 0xFF (maybe reserved for humidity?)\n- C: 8 bit CRC-8 with poly 0x31 init 0x00\n- A: 8 bit Checksum of previous 7 bytes (addition truncated to 8 bit)\n*/\nstatic int fineoffset_WH0530_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t b[8];\n\n    // try Alecto WS-1200 (v1, v2, DCF)\n    if (bitbuffer->bits_per_row[0] == 63)\n        return alecto_ws1200v1_callback(decoder, bitbuffer);\n    if (bitbuffer->bits_per_row[0] == 95)\n        return alecto_ws1200v2_callback(decoder, bitbuffer);\n\n    // Validate package\n    if (bitbuffer->bits_per_row[0] != 71) // Match exact length to avoid false positives\n        return DECODE_ABORT_LENGTH;\n\n    if ((bb[0][0] >> 1) != 0x7F   // Check preamble (7 bits)\n            || (bb[0][1] >> 5) != 0x3)   // Check message type (8 bits)\n        return DECODE_ABORT_EARLY;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, 7, b, sizeof(b) * 8); // Skip first 7 bits\n\n    // Verify checksum\n    int crc = crc8(b, 7, 0x31, 0);\n    int sum = (add_bytes(b, 7) & 0xff) - b[7];\n\n    if (crc || sum) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"Checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int id            = ((b[0] & 0x0f) << 4) | (b[1] >> 4);\n    int battery_low   = (b[1] >> 3) & 0x1;\n    int temp_raw      = (b[1] & 0x7) << 8 | b[2];\n    float temperature = (temp_raw - 400) * 0.1f;\n    int rainfall_raw  = b[4] << 8 | b[3];   // rain tip counter\n    float rainfall    = rainfall_raw * 0.3f; // each tip is 0.3mm\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Fineoffset-WH0530\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rainfall,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const output_fields_WH25[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"pressure_hPa\",\n        // WH24\n        \"wind_dir_deg\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"rain_mm\",\n        \"uv\",\n        \"uvi\",\n        \"light_lux\",\n        //WH0290\n        \"pm2_5_ug_m3\",\n        \"estimated_pm10_0_ug_m3\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const output_fields_WH51[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_mV\",\n        \"moisture\",\n        \"boost\",\n        \"ad_raw\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const output_fields_WH0530[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"rain_mm\",\n        \"radio_clock\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_WH2 = {\n        .name        = \"Fine Offset Electronics, WH2, WH5, Telldus Temperature/Humidity/Rain Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 500,  // Short pulse 544µs, long pulse 1524µs, fixed gap 1036µs\n        .long_width  = 1500, // Maximum pulse period (long pulse + fixed gap)\n        .reset_limit = 1200, // We just want 1 package\n        .tolerance   = 160,  // us\n        .decode_fn   = &fineoffset_WH2_callback,\n        .create_fn   = &fineoffset_WH2_create,\n        .fields      = output_fields,\n};\n\nr_device const fineoffset_WH25 = {\n        .name        = \"Fine Offset Electronics, WH25, WH32, WH32B, WN32B, WH24, WH65B, HP1000, Misol WS2320 Temperature/Humidity/Pressure Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,    // Bit width = 58µs (measured across 580 samples / 40 bits / 250 kHz)\n        .long_width  = 58,    // NRZ encoding (bit width = pulse width)\n        .reset_limit = 20000, // Package starts with a huge gap of ~18900 us\n        .decode_fn   = &fineoffset_WH25_callback,\n        .fields      = output_fields_WH25,\n};\n\nr_device const fineoffset_WH51 = {\n        .name        = \"Fine Offset Electronics/Ecowitt WH51, WN31, SwitchDoc Labs SM23 Soil Moisture Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58, // Bit width = 58µs (measured across 580 samples / 40 bits / 250 kHz)\n        .long_width  = 58, // NRZ encoding (bit width = pulse width)\n        .reset_limit = 5000,\n        .decode_fn   = &fineoffset_WH51_callback,\n        .fields      = output_fields_WH51,\n};\n\nr_device const fineoffset_WH0530 = {\n        .name        = \"Fine Offset Electronics, WH0530 Temperature/Rain Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 504,  // Short pulse 504µs\n        .long_width  = 1480, // Long pulse 1480µs\n        .reset_limit = 1200, // Fixed gap 960µs (We just want 1 package)\n        .sync_width  = 0,    // No sync bit used\n        .tolerance   = 160,  // us\n        .decode_fn   = &fineoffset_WH0530_callback,\n        .fields      = output_fields_WH0530,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh1050.c",
    "content": "/** @file\n    Fine Offset WH1050 and TFA 30.3151 Weather Station.\n\n    2016 Nicola Quiriti ('ovrheat')\n    Modifications 2016 by Don More\n    2023 Bruno OCTAU (ProfBoc75) for TFA 30.3151 FSK\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n */\n\n#include \"decoder.h\"\n#define TYPE_OOK 1\n#define TYPE_FSK 2\n\n/** @fn in fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned bitpos, int type)\nFine Offset WH1050 and TFA 30.3151 Weather Station.\n\nThis module is a cut-down version of the WH1080 decoder.\nThe WH1050 sensor unit is like the WH1080 unit except it has no\nwind direction sensor or time receiver.\nOther than omitting the unused code, the differences are the message length\nand the location of the battery-low bit.\n\nThis weather station is based on an indoor touchscreen receiver, and on a 5+1 outdoor wireless sensors group\n(rain, wind speed, temperature, humidity.\nSee the product page here: http://www.foshk.com/Weather_Professional/WH1070.html (The 1050 model has no radio clock).\n\nPlease note that the pressure sensor (barometer) is enclosed in the indoor console unit, NOT in the outdoor\nwireless sensors group.\nThat's why it's NOT possible to get pressure data by wireless communication. If you need pressure data you should try\nan Arduino/Raspberry solution wired with a BMP180 or BMP085 sensor.\n\nData is transmitted every 48 seconds, alternating between sending a single packet and sending two packets in quick succession\n(almost always identical, but clearly generated separately because during e.g. heavy rainfall different values have been observed).\nI.e., data packet, wait 48 seconds, two data packets, wait 48 seconds, data packet, wait 48 seconds, two data packets, ... .\n\nThe 'Total rainfall' field is a cumulative counter, increased by 0.3 millimeters of rain each step.\n\nThe station is also known as TFA STRATOS 35.1077\nSee the product page here: https://www.tfa-dostmann.de/en/product/wireless-weather-station-with-wind-and-rain-gauge-stratos-35-1077/\nThis model seems also capable to decode the DCF77 time signal sent by the time signal decoder (which is enclosed on the sensor tx):\naround the minute 59 of the even hours the sensor's TX stops sending weather data, probably to receive (and sync with) DCF77 signals.\nAfter around 3-4 minutes of silence it starts to send just time data for some minute, then it starts again with\nweather data as usual.\n\nTFA 30.3151 Sensor is FSK version and decodes here. See issue #2538: Preamble is aaaa2dd4 and Temperature is not offset and rain gauge is 0.5 mm by pulse.\n\nNote there is a collision with WH55 which starts with `aa aa aa 2d d4 55`\n\nTo recognize which message is received (weather or time) you can use the 'msg_type' field on json output:\n- msg_type 5 = weather data\n- msg_type 6 = time data\n\nWeather data - Message layout and example:\n\n     Preamble{8}   : 0xFF - OOK Version\n  or Preamble{40}  : 0xAAAAAA2DD4 - FSK Version\n\n     Byte Position : 00 01 02 03 04 05 06 07 08\n     Payload{72}   : BC CD DD EE FF GG HH HH II\n     Sample{72}    : 5f 51 93 48 00 00 12 46 aa\n\n- B :  4 bits : Msg Type - seems to be 0x5 for whether data, 0x6 for time data\n- C :  8 bits : Id, changes when reset (e.g., 0xF5)\n- D :  1 bit  : Temperature-Sign, only for FSK version\n- D :  1 bit  : Battery, 0 = ok, 1 = low (e.g, OK)\n- D : 10 bits : Temperature in Celsius, [offset 400 only for OOK Version], scaled by 10 (e.g., 0.3 degrees C)\n- E :  8 bits : Relative humidity, percent (e.g., 72%)\n- F :  8 bits : Wind speed average in m/s, scaled by 1/0.34 (e.g., 0 m/s)\n- G :  8 bits : Wind speed gust in m/s, scaled by 1/0.34 (e.g., 0 m/s)\n- H : 16 bits : Total rainfall in units of 0.3mm (OOK version) or 0.5mm (FSK version), since reset (e.g., 1403.4 mm)\n- I :  8 bits : CRC, poly 0x31, init 0x00 (excluding preamble)\n\nTime data - Message layout and example:\n\n     Preamble{8}   : 0xFF - OOK Version\n  or Preamble{40}  : 0xAAAAAA2DD4 - FSK Version\n\n     Byte Position : 00 01 02 03 04 05 06 07 08\n     Payload{72}   : BC CD DE FG HI JK LM NO PP\n     Sample{72}    : 69 0a 96 02 41 23 43 27 df\n\n- B :  4 bits : Msg Type - seems to be 0x5 for whether data, 0x6 for time data\n- C :  8 bits : Id, changes when reset (e.g., 0x90)\n- D :  1 bit  : Unknown (always 1?)\n- D :  1 bit  : Battery, 0 = ok, 1 = low (e.g, OK)\n- D :  4 bits : Unknown (always 0?)\n- D :  2 bits : hour BCD coded (*10)\n- E :  4 bits : hour BCD coded (*1)\n- F :  4 bits : minute BCD coded (*10)\n- G :  4 bits : minute BCD coded (*1)\n- H :  4 bits : second BCD coded (*10)\n- I :  4 bits : second BCD coded (*1)\n- J :  4 bits : year BCD coded (*10), counted from 2000\n- K :  4 bits : year BCD coded (*1), counted from 2000\n- L :  3 bits : Unknown\n- L :  1 bits : month BCD coded (*10)\n- M :  4 bits : month BCD coded (*1)\n- N :  4 bits : day BCD coded (*10)\n- O :  4 bits : day BCD coded (*1)\n- P :  8 bits : CRC, poly 0x31, init 0x00 (excluding preamble)\n\n*/\nstatic int fineoffset_wh1050_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned bitpos, int type)\n{\n    data_t *data;\n    uint8_t br[9];\n    float temperature, rain;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, bitpos, br, 9 * 8);\n\n    if (crc8(br, 9, 0x31, 0x00)) {\n        return 0; // DECODE_FAIL_MIC;\n    }\n\n    // GETTING MESSAGE TYPE\n    int msg_type = (br[0] >> 4);\n\n    if (msg_type == 5) {\n        // GETTING WEATHER SENSORS DATA\n        int temp_sign     = (br[1] & 0x08) >> 3; // only FSK version\n        int temp_raw      = ((br[1] & 0x03) << 8) | br[2];\n        int rain_raw      = (br[6] << 8) | br[7];\n        if (type == TYPE_OOK) {\n            temperature = (temp_raw - 400) * 0.1f;\n            rain        = rain_raw * 0.3f;\n        }\n        else {\n            temperature = temp_raw * 0.1f;\n            rain        = rain_raw * 0.5f;\n            if (temp_sign) {\n                temperature = -temperature;\n            }\n        }\n        int humidity      = br[3];\n        float speed       = (br[4] * 0.34f) * 3.6f; // m/s -> km/h\n        float gust        = (br[5] * 0.34f) * 3.6f; // m/s -> km/h\n        int device_id     = (br[0] << 4 & 0xf0) | (br[1] >> 4);\n        int battery_low   = br[1] & 0x04;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_COND, type == TYPE_OOK, DATA_STRING, \"Fineoffset-WH1050\",\n                \"model\",            \"\",                 DATA_COND, type == TYPE_FSK, DATA_STRING, \"TFA-303151\",\n                \"id\",               \"Station ID\",       DATA_FORMAT, \"%02X\",    DATA_INT,    device_id,\n                \"msg_type\",         \"Msg type\",         DATA_INT,    msg_type,\n                \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",   DATA_INT,    humidity,\n                \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.2f km/h\",   DATA_DOUBLE, speed,\n                \"wind_max_km_h\",    \"Wind gust\",        DATA_FORMAT, \"%.2f km/h \",   DATA_DOUBLE, gust,\n                \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",   DATA_DOUBLE, rain,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n    else if (msg_type == 6) {\n        // GETTING TIME DATA\n        int device_id   = (br[0] << 4 & 0xf0) | (br[1] >> 4);\n        int battery_low = br[1] & 0x04;\n        int hours       = ((br[2] & 0x30) >> 4) * 10 + (br[2] & 0x0F);\n        int minutes     = ((br[3] & 0xF0) >> 4) * 10 + (br[3] & 0x0F);\n        int seconds     = ((br[4] & 0xF0) >> 4) * 10 + (br[4] & 0x0F);\n        int year        = ((br[5] & 0xF0) >> 4) * 10 + (br[5] & 0x0F) + 2000;\n        int month       = ((br[6] & 0x10) >> 4) * 10 + (br[6] & 0x0F);\n        int day         = ((br[7] & 0xF0) >> 4) * 10 + (br[7] & 0x0F);\n\n        char clock_str[23];\n        snprintf(clock_str, sizeof(clock_str), \"%04d-%02d-%02dT%02d:%02d:%02d\",\n                year, month, day, hours, minutes, seconds);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_COND, type == TYPE_OOK, DATA_STRING, \"Fineoffset-WH1050\",\n                \"model\",            \"\",                 DATA_COND, type == TYPE_FSK, DATA_STRING, \"TFA-303151\",\n                \"id\",               \"Station ID\",       DATA_FORMAT, \"%02X\",    DATA_INT,    device_id,\n                \"msg_type\",         \"Msg type\",         DATA_INT,       msg_type,\n                \"battery_ok\",       \"Battery\",          DATA_INT,       !battery_low,\n                \"radio_clock\",      \"Radio Clock\",      DATA_STRING,    clock_str,\n                \"mic\",              \"Integrity\",        DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n    else {\n        decoder_logf(decoder, 1, __func__, \"Unknown msg type %x\", msg_type);\n        return 0; // DECODE_FAIL_MIC;\n    }\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFineoffset or TFA OOK/FSK protocol.\n@sa fineoffset_wh1050_decode()\n*/\nstatic int fineoffset_wh1050_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    unsigned bitpos = 0;\n    int events      = 0;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    /* The normal preamble for WH1050 is 8 1s (0xFF) followed by 4 0s\n       for a total 80 bit message.\n       (The 4 0s is not confirmed to be preamble but seems to be zero on most devices)\n\n       Digitech XC0346 (and possibly other models) only sends 7 1 bits not 8 (0xFE)\n       for some reason (maybe transmitter module is slow to wake up), for a total\n       79 bit message.\n\n       In both cases, we extract the 72 bits after the preamble.\n\n       For FSK version TFA 30.3151 the preamble is aaaaaa2dd4 and message payload is 6 times repeats (gap, preamble, message, gap, ... ) in one row and 754 bits.\n       gap is 11 bits long, preamble need to be searched into a while loop to get the repeated message\n    */\n\n    unsigned bits = bitbuffer->bits_per_row[0];\n    uint8_t preamble_byte = bitbuffer->bb[0][0]; // for OOK\n    uint8_t const preamble_fsk[] = {0xAA, 0x2D, 0xD4}; // part of preamble and sync word for FSK\n    if (bits == 79 && preamble_byte == 0xfe) {\n        fineoffset_wh1050_decode(decoder, bitbuffer, 7, TYPE_OOK);\n    } else if (bits == 80 && preamble_byte == 0xff) {\n        fineoffset_wh1050_decode(decoder, bitbuffer, 8, TYPE_OOK);\n    } else if (bits > 112 && bits < 760) {\n        while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_fsk, sizeof(preamble_fsk) * 8)) + 72 <=\n                bitbuffer->bits_per_row[0]) {\n            events += fineoffset_wh1050_decode(decoder, bitbuffer, bitpos + sizeof(preamble_fsk) * 8, TYPE_FSK);\n            bitpos += 123;\n        }\n    } else {\n        return DECODE_ABORT_LENGTH;\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"msg_type\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_km_h\",\n        \"wind_max_km_h\",\n        \"rain_mm\",\n        \"radio_clock\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh1050 = {\n        .name        = \"Fine Offset WH1050 Weather Station\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 544,\n        .long_width  = 1524,\n        .reset_limit = 10520,\n        .decode_fn   = &fineoffset_wh1050_callback,\n        .fields      = output_fields,\n};\n\nr_device const tfa_303151 = {\n        .name        = \"TFA 30.3151 Weather Station\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 60,\n        .long_width  = 60,\n        .reset_limit = 2500,\n        .decode_fn   = &fineoffset_wh1050_callback,\n        .priority    = 10, // Eliminate false positives by letting Fineoffset/Ecowitt WH55 go earlier\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh1080.c",
    "content": "/** @file\n    Fine Offset WH1080/WH3080 Weather Station.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/** @fn int fineoffset_wh1080_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nFine Offset WH1080/WH3080 Weather Station.\n\nThis module is based on Stanisław Pitucha ('viraptor' https://github.com/viraptor) code stub for the Digitech XC0348\nWeather Station, which seems to be a rebranded Fine Offset WH1080 Weather Station.\n\nSome info and code derived from Kevin Sangelee's page:\nhttp://www.susa.net/wordpress/2012/08/raspberry-pi-reading-wh1081-weather-sensors-using-an-rfm01-and-rfm12b/ .\n\nSee also Frank 'SevenW' page https://www.sevenwatt.com/main/wh1080-protocol-v2-fsk/ for some other useful info.\n\nFor the WH1080 part I mostly have re-elaborated and merged their works. Credits (and kudos) should go to them all\n(and to many others too).\n\nTwo packets are sent 31 ms apart. Reports 1 row, 88 pulses.\n\nData layout:\n\n    ff FI IT TT HH SS GG ?R RR BD CC\n\n- F: 4 bit fixed message format\n- I: 8 bit device id\n- T: 12 bit temperature, offset 40 scale 10, i.e. 0.1C steps -40C (top 2 bits are sign, discard)\n- H: 8 bit humidity percent\n- S: 8 bit wind speed, 0.34m/s steps\n- G: 8 bit gust speed, 0.34m/s steps\n- R: 12 bit? rain, 0.3mm steps\n- B: 4 bit flags, 0x1 is battery_low\n- D: 8 bit wind direction: 00 is N, 02 is NE, 04 is E, etc. up to 0F is seems\n- C: 8 bit checksum\n\n\n## WH1080\n\n(aka Watson W-8681)\n(aka Digitech XC0348 Weather Station)\n(aka PCE-FWS 20)\n(aka Elecsa AstroTouch 6975)\n(aka Froggit WH1080)\n(aka .....)\n\nThis weather station is based on an indoor touchscreen receiver, and on a 5+1 outdoor wireless sensors group\n(rain, wind speed, wind direction, temperature, humidity, plus a DCF77 time signal decoder, maybe capable to decode\nsome other time signal standard).\nSee the product page here: http://www.foshk.com/weather_professional/wh1080.htm .\nIt's a very popular weather station, you can easily find it on eBay or Amazon (just do a search for 'WH1080').\n\nThe module works fine, decoding all of the data as read into the original console (there is some minimal difference\nsometime on the decimals due to the different architecture of the console processor, which is a little less precise).\n\nPlease note that the pressure sensor (barometer) is enclosed in the indoor console unit, NOT in the outdoor\nwireless sensors group.\nThat's why it's NOT possible to get pressure data by wireless communication. If you need pressure data you should try\nan Arduino/Raspberry solution wired with a BMP180/280 or BMP085 sensor.\n\nData are transmitted in a 48 seconds cycle (data packet, then wait 48 seconds, then data packet...).\n\nThis module is also capable to decode the DCF77/WWVB time signal sent by the time signal decoder\n(which is enclosed on the sensor tx): around the minute 59 of the even hours the sensor's TX stops sending weather data,\nprobably to receive (and sync with) DCF77/WWVB signals.\nAfter around 3-4 minutes of silence it starts to send just time data for some minute, then it starts again with\nweather data as usual.\n\nBy living in Europe I can only test DCF77 time decoding, so if you live outside Europe and you find garbage instead\nof correct time, you should disable/ignore time decoding\n(or, better, try to implement a more complete time decoding system :) ).\n\nTo recognize message type (weather or time) you can use the 'msg_type' field on json output:\n- msg_type 0 = weather data\n- msg_type 1 = time data\n\nThe 'Total rainfall' field is a cumulative counter, increased by 0.3 millimeters of rain at once.\n\nThe station comes in three TX operating frequency versions: 433, 868.3 and 915 Mhz.\nThe module is tested with a 'Froggit WH1080' on 868.3 Mhz, using '-f 868140000' as frequency parameter and\nit works fine (compiled in x86, RaspberryPi 1 (v2), Raspberry Pi2 and Pi3, and also on a BananaPi platform. Everything is OK).\nI don't know if it works also with ALL of the rebranded versions/models of this weather station.\nI guess it *should* do... Just give it a try! :)\n\n## WH3080\n\nThe WH3080 Weather Station seems to be basically a WH1080 with the addition of UV/Light sensors onboard.\nThe weather/datetime radio protocol used for both is identical, the only difference is for the addition in the WH3080\nof the UV/Light part.\nUV/Light radio messages are disjointed from (and shorter than) weather/datetime radio messages and are transmitted\nin a 'once-every-60-seconds' cycle.\n\nThe module is able to decode all kind of data coming from the WH3080: weather, datetime, UV and light plus some\nerror/status code.\n\nTo recognize message type (weather, datetime or UV/light) you can refer to the 'msg_type' field on json output:\n- msg_type 0 = weather data\n- msg_type 1 = datetime data\n- msg_type 2 = UV/light data\n\nWhile the LCD console seems to truncate/round values in order to best fit to its display, this module keeps entire values\nas received from externals sensors (exception made for some rounding while converting values from lux to watts/m and fc),\nso you can see -sometimes- some little difference between module's output and LCD console's values.\n\n2016-2017 Nicola Quiriti ('ovrheat' - 'seven')\n*/\n\n#include \"decoder.h\"\n\nstatic int const wind_dir_degr[]= {0, 23, 45, 68, 90, 113, 135, 158, 180, 203, 225, 248, 270, 293, 315, 338};\n\n// The transmission differences are 8 preamble bits (EPB) and 7 preamble bits (SPB)\n#define EPB 8\n#define SPB 7\n\n#define TYPE_OOK 1\n#define TYPE_FSK 2\n\nstatic int fineoffset_wh1080_callback(r_device *decoder, bitbuffer_t *bitbuffer, int type)\n{\n    data_t *data;\n    uint8_t *br;\n    int msg_type;      // 0=Weather 1=Datetime 2=UV/Light\n    int sens_msg = 10; // 10=Weather/Time sensor  7=UV/Light sensor\n    uint8_t bbuf[11];  // max 8 / 11 bytes needed\n    int preamble;         // 7 or 8 preamble bits\n    int temp_raw;\n    float temperature;\n    uint8_t const fsk_preamble[] = {0xAA, 0x2D, 0xD4};\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (type == TYPE_FSK) {\n        int bit_offset = bitbuffer_search(bitbuffer, 0, 0, fsk_preamble, sizeof(fsk_preamble) * 8) + sizeof(fsk_preamble) * 8;\n        if (bit_offset + sizeof(bbuf) * 8 > bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n            decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"short package. Header index: %u\", bit_offset);\n            return DECODE_ABORT_LENGTH;\n        }\n        bitbuffer_extract_bytes(bitbuffer, 0, bit_offset-8, bbuf, sizeof(bbuf) * 8);\n        br = bbuf;\n        br[0] = 0xFF; // Emulate OOK payload\n        preamble = EPB;\n    }\n    else if (bitbuffer->bits_per_row[0] >= 88 && bitbuffer->bits_per_row[0] < 100) { // FineOffset WH1080/3080 Weather data msg\n        preamble = EPB;\n        sens_msg = 10;\n        br = bitbuffer->bb[0];\n    }\n    else if (bitbuffer->bits_per_row[0] == 87) { // FineOffset WH1080/3080 Weather data msg (different version (newest?))\n        preamble = SPB;\n        sens_msg = 10;\n        /* 7 bits of preamble, bit shift the whole buffer and fix the bytestream */\n        bitbuffer_extract_bytes(bitbuffer, 0, 7, bbuf + 1, 10 * 8);\n        bbuf[0] = (bitbuffer->bb[0][0] >> 1) | 0x80;\n        br      = bbuf;\n    }\n    else if (bitbuffer->bits_per_row[0] == 64) {  // FineOffset WH3080 UV/Light data msg\n        preamble = EPB;\n        sens_msg = 7;\n        br = bitbuffer->bb[0];\n    }\n    else if (bitbuffer->bits_per_row[0] == 63) { // FineOffset WH3080 UV/Light data msg (different version (newest?))\n        preamble = SPB;\n        sens_msg = 7;\n        /* 7 bits of preamble, bit shift the whole buffer and fix the bytestream */\n        bitbuffer_extract_bytes(bitbuffer, 0, 7, bbuf + 1, 7 * 8);\n        bbuf[0] = (bitbuffer->bb[0][0] >> 1) | 0x80;\n        br      = bbuf;\n    }\n    else {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, br, sens_msg * 8, \"Fine Offset WH1080 data \");\n\n    if (br[0] != 0xff) {\n        return DECODE_FAIL_SANITY; // preamble missing\n    }\n\n    if (sens_msg == 10) {\n        if (crc8(br, 11, 0x31, 0xff)) { // init is 0 if we skip the preamble\n            return DECODE_FAIL_MIC; // crc mismatch\n        }\n    }\n    else {\n        if (crc8(br, 8, 0x31, 0xff)) { // init is 0 if we skip the preamble\n            return DECODE_FAIL_MIC; // crc mismatch\n        }\n    }\n\n    if ((br[1] >> 4) == 0x0a) {\n        msg_type = 0; // WH1080/3080 Weather msg\n    }\n    else if ((br[1] >> 4) == 0x0b) {\n        msg_type = 1; // WH1080/3080 Datetime msg\n    }\n    else if ((br[1] >> 4) == 0x07) {\n        msg_type = 2; // WH3080 UV/Light msg\n    }\n    else {\n        // 0x03 is WH0530, Alecto WS-1200\n        // 0x05 is Alecto WS-1200 DCF77\n        return DECODE_FAIL_SANITY;\n    }\n\n    // GETTING WEATHER SENSORS DATA\n    if (type == TYPE_OOK) {\n        temp_raw      = ((br[2] & 0x03) << 8) | br[3]; // only 10 bits, discard top bits\n        temperature  = (temp_raw - 400) * 0.1f;\n    }\n    else {\n        temp_raw      = ((br[2] & 0x0F) << 8) | br[3];\n        if (temp_raw & 0x800) {\n            temp_raw &= 0x7FF; // remove sign bit\n            temp_raw = -temp_raw; // reverse magnitude\n        }\n        temperature = (temp_raw) * 0.1f;\n    }\n    int humidity      = br[4];\n    int direction_deg = wind_dir_degr[br[9] & 0x0f];\n    float speed       = (br[5] * 0.34f) * 3.6f; // m/s -> km/h\n    float gust        = (br[6] * 0.34f) * 3.6f; // m/s -> km/h\n    int rain_raw      = ((br[7] & 0x0f) << 8) | br[8];\n    float rain        = rain_raw * 0.3f;\n    int device_id     = (br[1] << 4 & 0xf0) | (br[2] >> 4);\n    int battery_low   = (br[9] >> 4) == 1;\n\n    // GETTING UV DATA\n    int uv_sensor_id = (br[1] << 4 & 0xf0) | (br[2] >> 4);\n    int uv_status_ok = br[3] == 85;\n    int uv_index     = br[2] & 0x0F;\n\n    // GETTING LIGHT DATA\n    int light = (br[4] << 16) | (br[5] << 8) | br[6];\n    double lux = light * 0.1;\n    float wm;\n    if (preamble == SPB)\n        wm = (light / 1265.8f);\n    else //EPB\n        wm = (light / 6830.0f);\n\n    // GETTING TIME DATA\n    int signal_type       = ((br[2] & 0x0F) == 10);\n    char const *signal_type_str = signal_type ? \"DCF77\" : \"WWVB/MSF\";\n\n    int hours   = ((br[3] & 0x30) >> 4) * 10 + (br[3] & 0x0F);\n    int minutes = ((br[4] & 0xF0) >> 4) * 10 + (br[4] & 0x0F);\n    int seconds = ((br[5] & 0xF0) >> 4) * 10 + (br[5] & 0x0F);\n    int year    = ((br[6] & 0xF0) >> 4) * 10 + (br[6] & 0x0F) + 2000;\n    int month   = ((br[7] & 0x10) >> 4) * 10 + (br[7] & 0x0F);\n    int day     = ((br[8] & 0xF0) >> 4) * 10 + (br[8] & 0x0F);\n\n    // PRESENTING DATA\n    if (msg_type == 0) {\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING,    \"Fineoffset-WHx080\",\n                \"subtype\",          \"Msg type\",         DATA_INT,       msg_type,\n                \"id\",               \"Station ID\",       DATA_INT,       device_id,\n                \"battery_ok\",       \"Battery\",          DATA_INT,       !battery_low,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT,    \"%.1f C\",  DATA_DOUBLE,    temperature,\n                \"humidity\",         \"Humidity\",         DATA_FORMAT,    \"%u %%\",    DATA_INT,       humidity,\n                \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT, direction_deg,\n                \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT,    \"%.2f km/h\",    DATA_DOUBLE,    speed,\n                \"wind_max_km_h\",    \"Wind gust\",        DATA_FORMAT,    \"%.2f km/h\",    DATA_DOUBLE,    gust,\n                \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT,    \"%.1f mm\",    DATA_DOUBLE,    rain,\n                \"mic\",              \"Integrity\",        DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n    else if (msg_type == 1) {\n        char clock_str[23];\n        snprintf(clock_str, sizeof(clock_str), \"%04d-%02d-%02dT%02d:%02d:%02d\",\n                year, month, day, hours, minutes, seconds);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING,    \"Fineoffset-WHx080\",\n                \"subtype\",          \"Msg type\",         DATA_INT,       msg_type,\n                \"id\",               \"Station ID\",       DATA_INT,       device_id,\n                \"signal\",           \"Signal Type\",      DATA_STRING,    signal_type_str,\n                \"radio_clock\",      \"Radio Clock\",      DATA_STRING,    clock_str,\n                \"mic\",              \"Integrity\",        DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n    else {\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING,    \"Fineoffset-WHx080\",\n                \"subtype\",          \"Msg type\",         DATA_INT,       msg_type,\n                \"uv_sensor_id\",     \"UV Sensor ID\",     DATA_INT,       uv_sensor_id,\n                \"uv_status\",        \"Sensor Status\",    DATA_STRING,    uv_status_ok ? \"OK\" : \"ERROR\",\n                \"uv_index\",         \"UV Index\",         DATA_INT,       uv_index,\n                \"lux\",              \"Lux\",              DATA_FORMAT,    \"%.1f\",     DATA_DOUBLE,    lux,\n                \"wm\",               \"Watts/m\",          DATA_FORMAT,    \"%.2f\",     DATA_DOUBLE,    wm,\n                \"mic\",              \"Integrity\",        DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nFine Offset WH1080/WH3080 Weather Station.\n@sa fineoffset_wh1080_callback()\n*/\nstatic int fineoffset_wh1080_callback_ook(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    return fineoffset_wh1080_callback(decoder, bitbuffer, TYPE_OOK);\n}\n\n/**\nFine Offset WH1080/WH3080 Weather Station.\n@sa fineoffset_wh1080_callback()\n*/\nstatic int fineoffset_wh1080_callback_fsk(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    return fineoffset_wh1080_callback(decoder, bitbuffer, TYPE_FSK);\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"subtype\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_dir_deg\",\n        \"wind_avg_km_h\",\n        \"wind_max_km_h\",\n        \"rain_mm\",\n        \"signal\",\n        \"radio_clock\",\n        \"sensor_code\",\n        \"uv_sensor_id\",\n        \"uv_status\",\n        \"uv_index\",\n        \"lux\",\n        \"wm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh1080 = {\n        .name        = \"Fine Offset Electronics WH1080/WH3080 Weather Station\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 544,  // Short pulse 544µs, long pulse 1524µs, fixed gap 1036µs\n        .long_width  = 1524, // Maximum pulse period (long pulse + fixed gap)\n        .reset_limit = 2800, // We just want 1 package\n        .decode_fn   = &fineoffset_wh1080_callback_ook,\n        .fields      = output_fields,\n};\n\nr_device const fineoffset_wh1080_fsk = {\n        .name        = \"Fine Offset Electronics WH1080/WH3080 Weather Station (FSK)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 5800,\n        .decode_fn   = &fineoffset_wh1080_callback_fsk,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh31l.c",
    "content": "/** @file\n    Ambient Weather (Fine Offset) WH31L protocol.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n    based on protocol analysis by \\@MksRasp.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nAmbient Weather (Fine Offset) WH31L protocol.\n915 MHz FSK PCM Lightning-Strike sensor, based on AS3935 Franklin lightning sensor (FCC ID WA5WH57E).\n\nAlso: FineOffset WH57 lighting sensor.\n\nNote that Ambient Weather is likely rebranded Fine Offset products.\n\n56 us bit length with a preamble of 40 bit flips (0xaaaaaaaaaa) and a 0x2dd4 sync-word.\nA transmission contains a single packet.\n\nIn the back of this device are 4 DIP switches\n- sensitivity:  2 switches, 4 possible combinations\n- short or long antenna 1 switch\n- indoor or outdoor 1 switch\n\nNone of these DIP switches make any difference to the data.\n\nData layout:\n\n    YY SI II II FF KK CC XX AA ?? ?\n\n- Y: 8 bit fixed Type Code of 0x57\n- S: 4 bit state indicator: 0: start-up, 1: interference, 4: noise, 8: strike\n- I: 20 bit device ID\n- F: 10 bit flags: (battery low seems to be the 1+2-bit on the first byte)\n- K: 6 bit estimated distance to front of storm, 1 to 25 miles / 1 to 40 km, 63 is invalid/no strike\n- C: 8 bit lightning strike count\n- X: 8 bit CRC-8, poly 0x31, init 0x00\n- A: 8 bit SUM-8\n\nState field:\n\n- 8: lightning strike detected\n- 4: EMP noise\n- 1: detection of interference\n- 0: battery change / reboot\n\nFlags:\n\n    0000 0BB1 ??\n\nWith battery (B) readings of\n\n- 2 at 3.2V\n- 1 at 2.6V\n- 0 at 2.3V\n\nExample packets:\n\n    {141} aa aa aa aa aa a2 dd 45 78 10 5c 80 58 10 1d f0 b8 10\n    {140} aa aa aa aa aa a2 dd 45 78 10 5c 80 58 10 1d f0 b8 20\n    {142} aa aa aa aa aa a2 dd 45 74 10 5c 80 5b f0 19 ac 44 08\n    {143} aa aa aa aa aa a2 dd 45 74 10 5c 80 5b f0 19 ac 40 04\n\nSome payloads:\n\n    57 0 105c8 05 bf 00 dd c6\n    57 8 105c8 05 81 01 df 0b\n    57 4 105c8 05 bf 01 9a c4\n    57 0 105c8 05 bf 00\n    57 8 105c8 05 85 01\n    57 8 20b90 0b 0a 02\n    57 8 105c8 05 81 02\n\nRaw flex decoder and BitBench format:\n\n    rtl_433 -c 0 -R 0 -X \"n=WH31L,m=FSK_PCM,s=56,l=56,r=1500,preamble=2dd4\" -f 915M\n\n    TYPE:8h STATE:4h ID:20h FLAGS:8b2b KM:6d COUNT:8d CRC:8h ADD:8h 16x\n\n*/\n\n#include \"decoder.h\"\n\nstatic int fineoffset_wh31l_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // (partial) preamble and sync word\n\n    int row = 0;\n    // Search for preamble and sync-word\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, 24);\n    // No preamble detected\n    if (start_pos == bitbuffer->bits_per_row[row])\n        return DECODE_ABORT_EARLY;\n    decoder_logf(decoder, 1, __func__, \"WH31L detected, buffer is %d bits length\", bitbuffer->bits_per_row[row]);\n\n    // Remove preamble and sync word, keep whole payload\n    uint8_t b[9];\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos + 24, b, 9 * 8);\n\n    // Check type code\n    if (b[0] != 0x57) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Validate checksums\n    uint8_t c_crc = crc8(b, 8, 0x31, 0x00);\n    if (c_crc) {\n        decoder_log(decoder, 1, __func__, \"bad CRC\");\n        return DECODE_FAIL_MIC;\n    }\n    uint8_t c_sum = add_bytes(b, 8) - b[8];\n    if (c_sum) {\n        decoder_log(decoder, 1, __func__, \"bad SUM\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int state      = (b[1] >> 4);\n    int id         = ((b[1] & 0xf) << 16) | (b[2] << 8) | (b[3]);\n    int flags      = (state << 12) | (b[4] << 4) | (b[5] >> 4);\n    int battery_ok = (b[4] & 0x06) >> 1; // 0 to 2\n    int s_dist     = (b[5] & 0x3f);\n    int s_count    = (b[6]);\n\n    char const *state_str;\n    if (state == 0)\n        state_str = \"reset\";\n    else if (state == 1)\n        state_str = \"interference\";\n    else if (state == 4)\n        state_str = \"noise\";\n    else if (state == 8)\n        state_str = \"strike\";\n    else\n        state_str = \"unknown\";\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"FineOffset-WH31L\",\n            \"id\",               \"\",                 DATA_INT,    id,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery_ok * 0.5f,\n            \"state\",            \"State\",            DATA_STRING, state_str,\n            \"flags\",            \"Flags\",            DATA_FORMAT, \"%04x\", DATA_INT,    flags,\n            \"storm_dist_km\",    \"Storm Distance\",   DATA_COND, s_dist != 63, DATA_FORMAT, \"%d km\", DATA_INT,    s_dist,\n            \"strike_count\",     \"Strike Count\",     DATA_INT,    s_count,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"state\",\n        \"flags\",\n        \"storm_dist_km\",\n        \"strike_count\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh31l = {\n        .name        = \"Ambient Weather WH31L (FineOffset WH57) Lightning-Strike sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 56,\n        .long_width  = 56,\n        .reset_limit = 1000,\n        .decode_fn   = &fineoffset_wh31l_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh43.c",
    "content": "/** @file\n    Fine Offset Electronics WH43 air quality sensor.\n\n    Analysis by \\@andrewjmcginnis\n    Copyright (C) 2025 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WH43 air quality sensor.\n\nS.a. the draft in #3179\n\nThe sensor sends a data burst every 10 minutes.  The bits are PCM\nmodulated with Frequency Shift Keying.\n\nEcowitt advertises this device as a PM2.5 sensor.  It contains a\nHoneywell PM2.5 sensor:\n\nhttps://sensing.honeywell.com/honeywell-sensing-particulate-hpm-series-datasheet-32322550.pdf\n\nHowever, the Honeywell datasheet says that it also has a PM10 output\nwhich is \"calculated from\" the PM2.5 reading.  While there is an\naccuracy spec for PM2.5, there is no specification of an kind from\nPM10.  The datasheet does not explain the calculation, and does not\ngive references to papers in the scientific literature.\n\nNote that PM2.5 is the mass of particles <= 2.5 microns in 1 m^3 of\nair, and PM10 is the mass of particles <= 10 microns.  Therefore the\ndifference in those measurements is the mass of particles > 2.5\nmicrons and <= 10 microns, sometimes called PM2.5-10.  By definition\nthese particles are not included in the PM2.5 measurement, so\n\"calculating\" doesn't make sense.  Rather, this appears an assumption\nabout correlation, meaning how much mass of larger particles is likely\nto be present based on the mass of the smaller particles.\n\nThe serial stream from the sensor has fields for PM2.5 and PM10 and\nthese fields have been verified to appear in the transmitted signal by\ncross-comparing the internal serial lines and data received via\nrtl_433.\n\nThe Ecowitt displays show only PM2.5, and Ecowitt confirmed that the\nsecond field is the PM10 output of the sensor but said the value is\nnot accurate so they have not adopted it.\n\nBy observation of an Ecowitt WH41, the formula is pm10 = pm2.5 +\nincrement(pm2.5), where the increment is by ranges from the following\ntable (with gaps when no samples have been observed).  It is left as\nfuture work to compare with an actual PM10 sensor.\n\n    0 to 24     | 1\n    25 to 106   | 2\n    109 to 185  | 3\n    190 to 222  | 4\n    311         | 5\n    390         | 6\n\nThis code is similar to the Fine Offset/Ecowitt WH0290/WH41/PM25 devices.\nThe WH43 uses a longer packet, due in part to the 24-bit ID (vs 8-bit for the WH41),\nwhich then offsets the location of the battery, PM2.5/10, CRC, and Checksum bits.\n\nData layout:\n    aa 2d d4 43 cc cc cc 41 9a 41 ae c1 99 9\n             FF II II II ?P PP ?A AA CC BB\n\n- F: 8 bit Family Code?\n- I: 8 bit device id (corresponds to sticker on device in hex)\n- ?: 1 bit?\n- b: 1 bit MSB of battery bars out of 5\n- P: 14 bit PM2.5 reading in ug/m3\n- b: 2 bits LSBs of battery bars out of 5\n- A: 14 bit PM10.0 reading in ug/m3\n- C: 8 bit CRC checksum of the previous 6 bytes\n- B: 8 bit Bitsum (sum without carry, XOR) of the previous 7 bytes\n\nPreamble: aa2dd4\n    FAM:8d ID: 24h 1b Bat_MSB:1d PMTWO:14d Bat_LSB:2d PMTEN:14d CRC:8h SUM:8h bbbbb\n*/\nstatic int fineoffset_wh43_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4};\n\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    uint8_t b[10];\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"short package. Row length: %u. Header index: %u\", bitbuffer->bits_per_row[0], bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    // Check first byte for our type code\n    if (b[0] != 0x43) {\n        decoder_logf(decoder, 1, __func__, \"Not our device type: %02x\", b[0]);\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, sizeof (b) * 8, \"Payload data\");\n\n    int crc = crc8(b, 8, 0x31, 0x00);  // Compute CRC over first 8 bytes.\n    int sum = add_bytes(b, 9) & 0xff;\n    if (crc != b[8] || sum != b[9]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x\", crc, sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    // int family     = b[0];\n    int id         = (b[1] << 16) | (b[2] << 8) | b[3]; // 24-bit ID\n    int pm25       = ((b[4] & 0x3F) << 8) | b[5];\n    int pm100      = ((b[6] & 0x3F) << 8) | b[7];\n    int batt_bars  = ((b[4] & 0x40) >> 4) | ((b[6] & 0xC0) >> 6);\n    int ext_power  = batt_bars == 6;\n    float batt_lvl = MIN(batt_bars * 0.2f, 1.0f); // cap for external power\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                    \"\",                         DATA_STRING, \"Fineoffset-WH43\",\n            \"id\",                       \"ID\",                       DATA_FORMAT, \"%06x\", DATA_INT, id,\n            \"battery_ok\",               \"Battery\",      \t        DATA_INT,    batt_bars > 1, // Level 1 means \"Low\"\n    \t\t\"battery_pct\",              \"Battery level\",            DATA_INT,    100 * batt_lvl, // Note: this might change with #3103\n            \"ext_power\",                \"External Power\",           DATA_INT,    ext_power,\n            \"pm2_5_ug_m3\",              \"2.5um Fine PM\",            DATA_FORMAT, \"%d ug/m3\", DATA_INT, pm25 / 10,\n            \"estimated_pm10_0_ug_m3\",   \"Estimate of 10um Coarse PM\", DATA_FORMAT, \"%d ug/m3\", DATA_INT, pm100 / 10,\n            \"mic\",                      \"Integrity\",                DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_pct\",\n        \"ext_power\",\n        \"pm2_5_ug_m3\",\n        \"estimated_pm10_0_ug_m3\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh43 = {\n        .name        = \"Fine Offset Electronics WH43 air quality sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 2500,\n        .decode_fn   = &fineoffset_wh43_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh45.c",
    "content": "/** @file\n    Fine Offset Electronics WH45 air quality sensor.\n\n    Copyright (C) 2022 \\@anthyz\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WH45 air quality sensor,\n\n- also Ecowitt WH45, Ecowitt WH0295\n- also Froggit DP250\n- also Ambient Weather AQIN\n\nPreamble is aaaa aaaa, sync word is 2dd4.\n\nPacket layout:\n\n     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14\n    YY II II II 0T TT HH Bp pp BP PP CC CC XX AA\n    45 00 36 60 02 7e 36 40 23 00 29 02 29 07 4f\n\n- Y: 8 bit fixed sensor type 0x45\n- I: 24 bit device ID\n- T: 11 bit temperature, offset 40, scale 10\n- H: 8 bit humidity\n- B: 1 bit MSB of battery bars out of 5 (a value of 6 indicates external power via USB)\n- p: 14 bit PM2.5 reading in ug/m3 * 10\n- B: 2 bits LSBs of battery bars out of 5\n- P: 14 bit PM10 reading in ug/m3 * 10\n- C: 16 bit CO2 reading in ppm\n- X: 8 bit CRC\n- A: 8 bit checksum\n\nOlder air quality sensors (WH0290/WH41/WH43) from Fine Offset use a\nparticulate sensor from Honeywell that crudely estimates PM10 values\nfrom PM2.5 measurements. Though Ecowitt and other displays only show\nPM2.5, the rtl_433 WH0290 decoder includes the estimated PM10 value.\nSee the WH0290 decoder for more details.\n\nThe WH45 uses a Sensirion SPS30 sensor for PM2.5/PM10 and a\nSensirion SCD30 for CO2.\n\nTechnical documents for the SPS30 are here:\n\nhttps://sensirion.com/products/catalog/SPS30\n\nThe sensor specification statement states that PM10 values are estimated\nfrom distribution profiles of PM0.5, PM1.0, and PM2.5 measurements, but\nthe datasheet does a specify a degree of accuracy for the values unlike\nthe Honeywell sensor.\n\nTechnical documents for the SCD30 are here:\n\nhttps://sensirion.com/products/catalog/SCD30/\n*/\n\nstatic int fineoffset_wh45_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // 24 bit, part of preamble and sync word\n    uint8_t b[15];\n\n    // bit counts have been observed between 187 and 222\n    if (bitbuffer->bits_per_row[0] < 170 || bitbuffer->bits_per_row[0] > 240) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a data package and extract data buffer\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, 24) + 24;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"short package at %u\", bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Extract package data\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    if (b[0] != 0x45) // Check for family code 0x45\n        return DECODE_ABORT_EARLY;\n\n    decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"\");\n\n    // Verify checksum and CRC\n    uint8_t crc = crc8(b, 13, 0x31, 0x00);\n    uint8_t chk = add_bytes(b, 14);\n    if (crc != b[13] || chk != b[14]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x\", crc, chk);\n        return DECODE_FAIL_MIC;\n    }\n\n    int id            = (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int temp_raw      = (b[4] & 0x7) << 8 | b[5];\n    float temp_c      = (temp_raw - 400) * 0.1f;    // range -40.0-60.0 C\n    int humidity      = b[6];\n    int battery_bars  = (b[7] & 0x40) >> 4 | (b[9] & 0xC0) >> 6;\n    // A battery bars value of 6 means the sensor is powered via USB (the Ecowitt WS View app shows 'DC')\n    int ext_power     = battery_bars == 6 ? 1 : 0;\n    //  Battery level is indicated with 5 bars. Convert to 0 (0 bars) to 1 (5 or 6 bars)\n    float battery_ok  = MIN(battery_bars * 0.2f, 1.0f);\n    int pm2_5_raw     = (b[7] & 0x3f) << 8 | b[8];\n    float pm2_5       = pm2_5_raw * 0.1f;\n    int pm10_raw      = (b[9] & 0x3f) << 8 | b[10];\n    float pm10        = pm10_raw * 0.1f;\n    int co2           = (b[11] << 8) | b[12];\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Fineoffset-WH45\",\n            \"id\",               \"ID\",           DATA_FORMAT, \"%06x\", DATA_INT, id,\n            \"battery_ok\",       \"Battery level\",  DATA_FORMAT, \"%.1f\", DATA_DOUBLE, battery_ok,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"pm2_5_ug_m3\",      \"2.5um Fine Particulate Matter\",  DATA_FORMAT, \"%.1f ug/m3\", DATA_DOUBLE, pm2_5,\n            \"pm10_ug_m3\",       \"10um Coarse Particulate Matter\",  DATA_FORMAT, \"%.1f ug/m3\", DATA_DOUBLE, pm10,\n            \"co2_ppm\",          \"Carbon Dioxide\", DATA_FORMAT, \"%d ppm\", DATA_INT, co2,\n            \"ext_power\",        \"External Power\", DATA_INT, ext_power,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"pm2_5_ug_m3\",\n        \"pm10_0_ug_m3\",\n        \"co2_ppm\",\n        \"ext_power\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh45 = {\n        .name        = \"Fine Offset Electronics WH45 air quality sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 2500,\n        .decode_fn   = &fineoffset_wh45_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh46.c",
    "content": "/** @file\n    Fine Offset Electronics WH46 air quality sensor.\n\n    Based on fineoffset_wh45 from \\@anthyz\n    Copyright (C) 2024 \\@joanma747\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WH46 air quality sensor,\n\n- also Ecowitt WH46\n\nPreamble is aaaa aaaa, sync word is 2dd4.\n\nPacket layout:\n\n     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20\n    YY II II II 0T TT HH Bp pp BP PP CC CC qq qq QQ QQ ?? ?? XX AA\n    46 00 27 f1 02 b5 33 40 32 40 39 03 0b 00 2a 00 36 01 90 e4 16\n\n- Y: 8 bit fixed sensor type 0x46\n- I: 24 bit device ID\n- T: 11 bit temperature, offset 40, scale 10\n- H: 8 bit humidity\n- B: 1 bit MSB of battery bars out of 5 (a value of 6 indicates external power via USB)\n- p: 14 bit PM2.5 reading in ug/m3 * 10\n- B: 2 bits LSBs of battery bars out of 5\n- P: 14 bit PM10 reading in ug/m3 * 10\n- C: 16 bit CO2 reading in ppm\n- q: 14 bit PM1 reading in ug/m3 * 10\n- Q: 14 bit PM4 reading in ug/m3 * 10\n- ?: Constant value 0190. Might be version of a firmware or so.\n- X: 8 bit CRC\n- A: 8 bit checksum\n\nThe WH46 uses a Sensirion SPS30 sensor for PM1/PM2.5/PM4/PM10 and a\nSensirion SCD30 for CO2.\n\nTechnical documents for the SPS30 are here:\n\nhttps://sensirion.com/products/catalog/SPS30\n\nThe sensor specification statement states that PM10 values are estimated\nfrom distribution profiles of PM0.5, PM1.0, and PM2.5 measurements, but\nthe datasheet does a specify a degree of accuracy for the values unlike\nthe Honeywell sensor.\n\nTechnical documents for the SCD30 are here:\n\nhttps://sensirion.com/products/catalog/SCD30/\n*/\n\nstatic int fineoffset_wh46_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // 24 bit, part of preamble and sync word\n\n    // Find a data package and extract data buffer\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    uint8_t b[21];\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"short package at %u\", bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Extract package data\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    if (b[0] != 0x46) { // Check for family code 0x46\n        decoder_logf(decoder, 1, __func__, \"Not our device type: %02x\", b[0]);\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, sizeof (b) * 8, \"Payload data\");\n\n    // Verify checksum and CRC\n    uint8_t crc = crc8(b, 19, 0x31, 0x00);\n    uint8_t sum = add_bytes(b, 20);\n    if (crc != b[19] || sum != b[20]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x\", crc, sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    // int family        = b[0];\n    int id            = (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int temp_raw      = (b[4] & 0x7) << 8 | b[5];\n    float temp_c      = (temp_raw - 400) * 0.1f;    // range -40.0-60.0 C\n    int humidity      = b[6];\n    int battery_bars  = (b[7] & 0x40) >> 4 | (b[9] & 0xC0) >> 6;\n    // A battery bars value of 6 means the sensor is powered via USB (the Ecowitt WS View app shows 'DC')\n    int ext_power     = battery_bars == 6;\n    //  Battery level is indicated with 5 bars. Convert to 0 (0 bars) to 1 (5 or 6 bars)\n    float batt_lvl    = MIN(battery_bars * 0.2f, 1.0f);\n    int pm2_5_raw     = (b[7] & 0x3f) << 8 | b[8];\n    float pm2_5       = pm2_5_raw * 0.1f;\n    int pm10_raw      = (b[9] & 0x3f) << 8 | b[10];\n    float pm10        = pm10_raw * 0.1f;\n    int co2           = (b[11] << 8) | b[12];\n    int pm1_raw       = (b[13] << 8) | b[14];\n    float pm1         = pm1_raw * 0.1f;\n    int pm4_raw       = (b[15] << 8) | b[16];\n    float pm4         = pm4_raw * 0.1f;\n    int unknown       = (b[17] << 8) | b[18];\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Fineoffset-WH46\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%06x\", DATA_INT, id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    battery_bars > 1, // Level 1 means \"Low\"\n            \"battery_pct\",      \"Battery level\",    DATA_DOUBLE, 100 * batt_lvl, // Note: this might change with #3103\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"pm1_ug_m3\",        \"1um Fine PM\",      DATA_FORMAT, \"%.1f ug/m3\", DATA_DOUBLE, pm1,\n            \"pm2_5_ug_m3\",      \"2.5um Fine PM\",    DATA_FORMAT, \"%.1f ug/m3\", DATA_DOUBLE, pm2_5,\n            \"pm4_ug_m3\",        \"4um Coarse PM\",    DATA_FORMAT, \"%.1f ug/m3\", DATA_DOUBLE, pm4,\n            \"pm10_ug_m3\",       \"10um Coarse PM\",   DATA_FORMAT, \"%.1f ug/m3\", DATA_DOUBLE, pm10,\n            \"co2_ppm\",          \"Carbon Dioxide\",   DATA_FORMAT, \"%d ppm\", DATA_INT, co2,\n            \"unknown\",          \"Do not know\",      DATA_FORMAT, \"%d ?\", DATA_INT, unknown,\n            \"ext_power\",        \"External Power\",   DATA_INT,    ext_power,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_pct\",\n        \"temperature_C\",\n        \"humidity\",\n        \"pm1_ug_m3\",\n        \"pm2_5_ug_m3\",\n        \"pm4_ug_m3\",\n        \"pm10_ug_m3\",\n        \"co2_ppm\",\n        \"unknown\",\n        \"ext_power\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh46 = {\n        .name        = \"Fine Offset Electronics WH46 air quality sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 2500,\n        .decode_fn   = &fineoffset_wh46_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wh55.c",
    "content": "/** @file\n    Fine Offset / Ecowitt WH55 water leak sensor.\n\n    Copyright (C) 2023 Christian W. Zuckschwerdt <zany@triq.net>\n    Protocol analysis by \\@cdavis289, test data by \\@AhrBee\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n#include \"decoder.h\"\n\n/**\nFine Offset / Ecowitt WH55 water leak sensor.\n\nTest decoding with: rtl_433 -f 433.92M  -X 'n=wh55,m=FSK_PCM,s=60,l=60,g=1000,r=2500'\n\nNote there is a collision with Fine Offset WH1050 / TFA 30.3151 weather station which starts with `aa aa aa 2d d4 5`\n\nData format:\n\n                   00 01 02 03 04 05 06 07 08 09 10 11\n    aa aa aa 2d d4 55 30 cf 55 04 02 89 be ae a4 20 10\n                   MM FI II II BB VV VV AD XX ?? ?? ??\n\n- Preamble: aa aa aa\n- Sync: 2d d4\n- M: 8 bit Family code 0x55 (ECOWITT/FineOffset WH55)\n- F: 4 bit Flags, Channel (1 byte): (0=CH1, 1 = CH2, 2 = CH3, 3 = CH4)\n- I: 20 bit ID, shown with leading channel in Ecowitt Web App\n- B: 8 bit Battery (1 byte): 0x01 = 20%, 0x02 = 40%, 0x03 = 60%, 0x04 = 80%, 0x05 = 100%\n- V: 16 bit Raw sensor measurement\n- A: 2 bit Sensitivity and Alarm Setting: Left bit, 1 = High Sensitivity, 0 = Low Sensitivity, Right Bit: 1 = Alarm On, 0 = Alarm Off\n- D: 6 bit Unknown?\n- X: 8 bit CRC poly 0x31, init 0\n- ?: 24 bit Unknown?\n\nFormat string:\n\n    TYPE:8h FLAGS?2b CH:2d ID:20h BATT:8d RAW:16h SENS:b ALARM:b ?:6b CRC:8h ?:hh hh hh\n\n*/\n\nstatic int fineoffset_wh55_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4, 0x55}; // part of preamble, sync word, and message type\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY; // We expect a single row\n    }\n\n    unsigned bitpos = bitbuffer_search(bitbuffer, 0, 0, preamble, 32);\n    bitpos += 24; // Start at message type\n    if (bitpos + 9 * 8 > bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY; // No full message found\n    }\n\n    uint8_t b[12];\n    bitbuffer_extract_bytes(bitbuffer, 0, bitpos, b, 12 * 8);\n\n    if (crc8(b, 9, 0x31, 0x00)) {\n        return 0; // DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, b, 12*8, \"Message data\");\n\n    // GETTING MESSAGE TYPE\n    // int msg_type  = b[0];\n    // int flags     = (b[1] & 0xf);\n    int channel   = (b[1] >> 4) + 1;\n    int device_id = (b[2] << 8) | b[3];\n    float battery = b[4] * 0.2f; // 0x01 = 20%, 0x02 = 40%, 0x03 = 60%, 0x04 = 80%, 0x05 = 100%\n    int raw_value = (b[5] << 8) | b[6];\n\n    // Left bit, 1 = High Sensitivity, 0 = Low Sensitivity, Right Bit: 1 = Alarm On, 0 = Alarm Off\n    // int settings = (b[7] >> 4);\n    int sensitivity = (b[7] >> 7) & 1;\n    int alarm = (b[7] >> 6) & 1;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Fineoffset-WH55\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%05X\",    DATA_INT,    device_id,\n            \"channel\",          \"Channel\",          DATA_INT,    channel,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery,\n            \"raw_value\",        \"Raw Value\",        DATA_INT, raw_value,\n            \"sensitivity\",      \"Sensitivity\",      DATA_INT, sensitivity,\n            \"alarm\",            \"Alarm\",            DATA_INT, alarm,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"raw_value\",\n        \"sensitivity\",\n        \"alarm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wh55 = {\n        .name        = \"Fine Offset / Ecowitt WH55 water leak sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 60,\n        .long_width  = 60,\n        .reset_limit = 2500,\n        .decode_fn   = &fineoffset_wh55_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_wn34.c",
    "content": "/** @file\n    Fine Offset Electronics WN34 Temperature Sensor.\n\n    Copyright (C) 2022 \\@anthyz\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WN34 Temperature Sensor.\n\n- also Ecowitt WN34S (soil), WN34L (water), range is -40~60 °C (-40~140 °F)\n- also Ecowitt WN34D (water), range is -55~125 °C (-67~257 °F)\n- also Froggit DP150 (soil), DP35 (water)\n\nPreamble is aaaa aaaa, sync word is 2dd4.\n\nPacket layout:\n\n     0  1  2  3  4  5  6  7  8  9 10\n    YY II II II ST TT BB XX AA ZZ ZZ\n    34 00 29 65 02 85 44 66 f3 20 10\n\n- Y:{8}  fixed sensor type 0x34\n- I:{24} device ID\n- S:{4}  sub type, 0 = WN34L, 0x4 = WN34D\n- T:{12} temperature, offset 40 (except WN34D), scale 10\n- B:{7}  battery level (unit of 20 mV)\n- X:{8}  bit CRC\n- A:{8}  bit checksum\n- Z:{13} trail byte, not used.\n\n*/\n\nstatic int fineoffset_wn34_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t const preamble[] = {0xAA, 0x2D, 0xD4};\n    uint8_t b[9];\n    unsigned bit_offset;\n    float temperature;\n\n    bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof(preamble) * 8) + sizeof(preamble) * 8;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"short package. Row length: %u. Header index: %u\", bitbuffer->bits_per_row[0], bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"\");\n\n    // Verify family code\n    if (b[0] != 0x34) {\n        decoder_logf(decoder, 2, __func__, \"Msg family unknown: %02x\", b[0]);\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"Row length(bits_per_row[0]): %u\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Verify checksum, same as other FO Stations: Reverse 1Wire CRC (poly 0x131)\n    uint8_t crc = crc8(b, 7, 0x31, 0x00);\n    uint8_t chk = add_bytes(b, 8);\n\n    if (crc != b[7] || chk != b[8]) {\n        decoder_logf(decoder, 2, __func__, \"Checksum error: %02x %02x\", crc, chk);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Decode data\n    int id          = (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int temp_raw    = (int16_t)((b[4] & 0x0F) << 12 | b[5] << 4); // use sign extend\n    int sub_type    = (b[4] & 0xF0) >> 4;\n    decoder_logf(decoder, 1, __func__, \"subtype : %d\", sub_type);\n\n    if (sub_type == 4) // WN34D\n        temperature = (temp_raw >> 4) * 0.1f;    // scale by 10 only.\n    else // WN34L/WN34S ...\n        temperature = ((temp_raw >> 4) * 0.1f) - 40;    // scale by 10, offset 40\n\n    int battery_mv  = (b[6] & 0x7f) * 20;         // mV\n\n    /*\n     * A 5 bar battery indicator is shown in the Ecowitt WS View app.\n     * Through observation of battery_mv values and the app indicator,\n     * it was determined that battery_mv maps non-linearly to the number\n     * of bars. Set battery_ok by mapping battery bars from 0 to 1 where\n     * 1 bar = 0 and 5 bars = 1.\n     */\n    int battery_bars;\n\n    if (battery_mv > 1440)\n        battery_bars = 5;\n    else if (battery_mv > 1380)\n        battery_bars = 4;\n    else if (battery_mv > 1300)\n        battery_bars = 3;\n    else if (battery_mv > 1200)\n        battery_bars = 2;\n    else\n        battery_bars = 1;\n\n    float battery_ok  = (battery_bars - 1) * 0.25f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",                DATA_COND, sub_type != 4, DATA_STRING, \"Fineoffset-WN34\",\n            \"model\",         \"\",                DATA_COND, sub_type == 4, DATA_STRING, \"Fineoffset-WN34D\",\n            \"id\",            \"ID\",              DATA_FORMAT, \"%x\",     DATA_INT,    id,\n            \"battery_ok\",    \"Battery level\",   DATA_FORMAT, \"%.1f\",   DATA_DOUBLE, battery_ok,\n            \"battery_mV\",    \"Battery Voltage\", DATA_FORMAT, \"%d mV\",  DATA_INT,    battery_mv,\n            \"temperature_C\", \"Temperature\",     DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"mic\",           \"Integrity\",       DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_mV\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_wn34 = {\n        .name        = \"Fine Offset Electronics WN34S/L/D and Froggit DP150/D35 temperature sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 2500,\n        .decode_fn   = &fineoffset_wn34_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_ws80.c",
    "content": "/** @file\n    Fine Offset Electronics WS80 weather station.\n\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n    Protocol description by \\@davidefa\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WS80 weather station.\n\nAlso sold by EcoWitt, used with the weather station GW1000.\n\nPreamble is aaaa aaaa aaaa, sync word is 2dd4.\n\nPacket layout:\n\n     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17\n    YY II II II LL LL BB FF TT HH WW DD GG VV UU UU AA XX\n    80 0a 00 3b 00 00 88 8a 59 38 18 6d 1c 00 ff ff d8 df\n\n- Y = fixed sensor type 0x80\n- I = device ID, might be less than 24 bit?\n- L = light value, unit of 10 Lux (or 0.078925 W/m2)\n- B = battery voltage, unit of 20 mV, we assume a range of 3.0V to 1.4V\n- F = flags and MSBs, 0x03: temp MSB, 0x10: wind MSB, 0x20: bearing MSB, 0x40: gust MSB\n      0x80 or 0x08: maybe battery good? seems to be always 0x88\n- T = temperature, lowest 8 bits of temperature, offset 40, scale 10\n- H = humidity\n- W = wind speed, lowest 8 bits of wind speed, m/s, scale 10\n- D = wind bearing, lowest 8 bits of wind bearing, range 0-359 deg, 0x1ff if invalid\n- G = wind gust, lowest 8 bits of wind gust, m/s, scale 10\n- V = uv index, scale 10\n- U = unknown, might be rain option\n- A = checksum\n- X = CRC\n\n*/\n\nstatic int fineoffset_ws80_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0x2d, 0xd4}; // 24 bit, part of preamble and sync word\n    uint8_t b[18];\n\n    // Validate package, WS80 nominal size is 219 bit periods\n    if (bitbuffer->bits_per_row[0] < 168 || bitbuffer->bits_per_row[0] > 240) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a data package and extract data buffer\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, 24) + 24;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"short package at %u\", bit_offset);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Extract package data\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    if (b[0] != 0x80) // Check for family code 0x80\n        return DECODE_ABORT_EARLY;\n\n    // Verify checksum and CRC\n    uint8_t crc = crc8(b, 17, 0x31, 0x00);\n    uint8_t chk = add_bytes(b, 17);\n    if (crc != 0 || chk != b[17]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x\", crc, chk);\n        return DECODE_FAIL_MIC;\n    }\n\n    int id          = (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int light_raw   = (b[4] << 8) | (b[5]);\n    float light_lux = light_raw * 10;        // Lux\n    //float light_wm2 = light_raw * 0.078925f; // W/m2\n    int battery_mv  = (b[6] * 20);            // mV\n    int battery_lvl = battery_mv < 1400 ? 0 : (battery_mv - 1400) / 16; // 1.4V-3.0V is 0-100\n    int flags       = b[7]; // to find the wind msb\n    int temp_raw    = ((b[7] & 0x03) << 8) | (b[8]);\n    float temp_c    = (temp_raw - 400) * 0.1f;\n    int humidity    = (b[9]);\n    int wind_avg    = ((b[7] & 0x10) << 4) | (b[10]);\n    int wind_dir    = ((b[7] & 0x20) << 3) | (b[11]);\n    int wind_max    = ((b[7] & 0x40) << 2) | (b[12]);\n    int uv_index    = (b[13]);\n    int unknown     = (b[14] << 8) | (b[15]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Fineoffset-WS80\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%06x\", DATA_INT,    id,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery_lvl * 0.01f,\n            \"battery_mV\",       \"Battery Voltage\",  DATA_FORMAT, \"%d mV\", DATA_INT,    battery_mv,\n            \"temperature_C\",    \"Temperature\",      DATA_COND, temp_raw != 0x3ff,   DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_COND, humidity != 0xff,    DATA_FORMAT, \"%u %%\",    DATA_INT, humidity,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_COND, wind_dir != 0x1ff,   DATA_INT, wind_dir,\n            \"wind_avg_m_s\",     \"Wind speed\",       DATA_COND, wind_avg != 0x1ff,   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_avg * 0.1f,\n            \"wind_max_m_s\",     \"Gust speed\",       DATA_COND, wind_max != 0x1ff,   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_max * 0.1f,\n            \"uvi\",              \"UV Index\",         DATA_COND, uv_index != 0xff,    DATA_FORMAT, \"%.1f\",     DATA_DOUBLE, uv_index * 0.1f,\n            \"light_lux\",        \"Light\",            DATA_COND, light_raw != 0xffff, DATA_FORMAT, \"%.1f lux\", DATA_DOUBLE, (double)light_lux,\n            \"flags\",            \"Flags\",            DATA_FORMAT, \"%02x\", DATA_INT, flags,\n            \"unknown\",          \"Unknown\",          DATA_COND, unknown != 0x3fff, DATA_INT, unknown,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_mV\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_dir_deg\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"uvi\",\n        \"light_lux\",\n        \"flags\",\n        \"unknown\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_ws80 = {\n        .name        = \"Fine Offset Electronics WS80 weather station\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 1500,\n        .decode_fn   = &fineoffset_ws80_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_ws85.c",
    "content": "/** @file\n    Fine Offset Electronics WS85 weather station.\n\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n    Protocol description by \\@davidefa\n\n    Copy of fineoffset_ws90.c with changes made to support Fine Offset WS85\n    sensor array.  Changes made by John Pochmara <john@zoiedog.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WS85 weather station.\n\nThe WS85 is a WS90 with the removal of temperature, humidity, lux and uv.\nData bytes 1-13 are the same between the two models.  The new rain data\nis in bytes 16-20, with bytes 19 and 20 reporting total rain.  Bytes\n17 and 18 are affected by rain, but it is unknown what they report.  Byte\n17 reports the voltage of the super cap. And the checksum and CRC\nhave been moved to bytes 27 and 26.  What is reported in the other\nbytes is unknown at this time.\n\nAlso sold by EcoWitt.\n\nPreamble is aaaa aaaa aaaa, sync word is 2dd4.\n\nPacket layout:\n\n     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31\n    YY II II II BB FF UU WW DD GG UU UU RS UU UU R1 R2 SS UU UU UU UU UU UU UU UU XX AA\n    85 00 28 EB 87 82 6F 00 83 00 3F FF 00 00 00 00 00 0B 00 00 FF EF FD 00 00 6B DD 0F 00 00 00\n\n- Y = fixed sensor type 0x85\n- I = device ID, might be less than 24 bit?\n- B = battery voltage, unit of 20 mV, we assume a range of 3.0V to 1.4V\n- F = flags and MSBs, 0x03: temp MSB, 0x10: wind MSB, 0x20: bearing MSB, 0x40: gust MSB\n      0x80 or 0x08: maybe battery good? seems to be always 0x88\n- W = wind speed, lowest 8 bits of wind speed, m/s, scale 10\n- D = wind bearing, lowest 8 bits of wind bearing, range 0-359 deg, 0x1ff if invalid\n- G = wind gust, lowest 8 bits of wind gust, m/s, scale 10\n- U = unknown\n- R = rain total (R3 << 8 | R4) * 0.1 mm\n- RS = rain start dection ((R1 & 0x10) >>4), 1 = raining, 0 = not raining\n- S = super cap voltage, unit of 0.1V, lower 6 bits, mask 0x3f\n- Z = Firmware version. 0x82 = 130 = 1.3.0\n- A = checksum\n- X = CRC\n\nRain start info:\nStatus 1 will be reset to 0 when:\n- Once the top is dry\n- After the amount of water on the top has remained unchanged for two hours.\n\n*/\n\nstatic int fineoffset_ws85_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0xaa, 0x2d, 0xd4}; // 32 bit, part of preamble and sync word\n    uint8_t b[32];\n\n    // Validate package, WS85 nominal size is 345 bit periods\n    if (bitbuffer->bits_per_row[0] < 168 || bitbuffer->bits_per_row[0] > 500) {\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"abort length\" );\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a data package and extract data buffer\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, 32) + 32;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"short package at %u (%u)\", bit_offset, bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Extract package data\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    if (b[0] != 0x85) // Check for family code 0x85\n        return DECODE_ABORT_EARLY;\n\n    decoder_logf(decoder, 1, __func__, \"WS85 detected, buffer is %u bits length\", bitbuffer->bits_per_row[0]);\n\n    // Verify checksum and CRC\n    uint8_t crc = crc8(b, 26, 0x31, 0x00);\n    uint8_t chk = add_bytes(b, 27);\n    if (crc != b[26] || chk != b[27]) {\n        decoder_logf(decoder, 1, __func__,\n            \"Checksum error: CRC=%02x (Expected=%02x) CHK=%02x (Expected=%02x)\",\n            crc, b[26], chk, b[27]);\n        return DECODE_FAIL_MIC;\n    }\n\n    int id          = (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int battery_mv  = (b[4] * 20); // mV\n    int flags       = b[5]; // to find the wind msb\n    int wind_avg    = ((b[5] & 0x10) << 4) | (b[7]);\n    int wind_dir    = ((b[5] & 0x20) << 3) | (b[8]);\n    int wind_max    = ((b[5] & 0x40) << 2) | (b[9]);\n    int rain_start  = (b[12] & 0x10) >> 4;\n    int rain_raw    = (b[15] << 8) | b[16]; // Extract the 16-bit raw value\n    int supercap_V  = (b[17] & 0x3f);\n    int firmware    = b[25];\n\n    int battery_ok = 0;\n    if (battery_mv > 2400) {\n        battery_ok = 1;\n    }\n\n    int battery_lvl = battery_mv < 1400 ? 0 : (battery_mv - 1400) / 16; // 1.4V-3.0V is 0-100\n    if (battery_lvl > 100) {\n        battery_lvl = 100;\n    }\n\n    char extra[31];\n    snprintf(extra, sizeof(extra), \"%02x%02x---%02x%02x%02x%02x%02x%02x%02x---%02x\", b[13], b[14], /* b[15] b[16] is rain_raw, b[17] is supercap_V */ b[18], b[19], b[20], b[21], b[22], b[23], b[24], b[28]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Fineoffset-WS85\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%06x\", DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",          DATA_INT, battery_ok, // 0–6 bars\n            \"battery_pct\",      \"Battery level\",    DATA_INT, battery_lvl, // 0–100%\n            \"battery_mV\",       \"Battery Voltage\",  DATA_FORMAT, \"%d mV\", DATA_INT,    battery_mv,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_COND, wind_dir != 0x1ff,   DATA_INT, wind_dir,\n            \"wind_avg_m_s\",     \"Wind speed\",       DATA_COND, wind_avg != 0x1ff,   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_avg * 0.1f,\n            \"wind_max_m_s\",     \"Gust speed\",       DATA_COND, wind_max != 0x1ff,   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_max * 0.1f,\n            \"flags\",            \"Flags\",            DATA_FORMAT, \"%02x\", DATA_INT, flags,\n            \"rain_mm\",          \"Total Rain\",       DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_raw * 0.1f,\n            \"rain_start\",       \"Rain Start\",       DATA_INT, rain_start,\n            \"supercap_V\",       \"Supercap Voltage\", DATA_COND, supercap_V != 0xff, DATA_FORMAT, \"%.1f V\", DATA_DOUBLE, supercap_V * 0.1f,\n            \"firmware\",         \"Firmware Version\", DATA_INT, firmware,\n            \"data\",             \"Extra Data\",       DATA_STRING, extra,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_pct\",\n        \"battery_mV\",\n        \"wind_dir_deg\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"flags\",\n        \"unknown\",\n        \"rain_mm\",\n        \"rain_start\",\n        \"supercap_V\",\n        \"firmware\",\n        \"data\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_ws85 = {\n        .name        = \"Fine Offset Electronics WS85 weather station\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 3000,\n        .decode_fn   = &fineoffset_ws85_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fineoffset_ws90.c",
    "content": "/** @file\n    Fine Offset Electronics WS90 weather station.\n\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n    Protocol description by \\@davidefa\n\n    Copy of fineoffset_ws80.c with changes made to support Fine Offset WS90\n    sensor array.  Changes made by John Pochmara <john@zoiedog.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFine Offset Electronics WS90 weather station.\n\nThe WS90 is a WS80 with the addition of a piezoelectric rain gauge.\nData bytes 1-13 are the same between the two models.  The new rain data\nis in bytes 16-20, with bytes 19 and 20 reporting total rain.  Bytes\n17 and 18 are affected by rain, but it is unknown what they report.  Byte\n21 reports the voltage of the super cap. And the checksum and CRC\nhave been moved to bytes 30 and 31.  What is reported in the other\nbytes is unknown at this time.\n\nAlso sold by EcoWitt.\n\nPreamble is aaaa aaaa aaaa, sync word is 2dd4.\n\nPacket layout:\n\n     0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31\n    YY II II II LL LL BB FF TT HH WW DD GG VV PP PP R0 R1 R2 R3 R4 SS UU UU UU UU UU UU UU ZZ AA XX\n    90 00 34 2b 00 77 a4 82 62 39 00 3e 00 00 3f ff 20 00 ba 00 00 26 02 00 ff 9f f8 00 00 82 92 4f\n\n- Y = fixed sensor type 0x90\n- I = device ID, might be less than 24 bit?\n- L = light value, unit of 10 lux\n- B = battery voltage, unit of 20 mV, we assume a range of 3.0V to 1.4V\n- F = flags and MSBs, 0x03: temp MSB, 0x10: wind MSB, 0x20: bearing MSB, 0x40: gust MSB\n      0x80 or 0x08: maybe battery good? seems to be always 0x88\n- T = temperature, lowest 8 bits of temperature, offset 40, scale 10\n- H = humidity\n- W = wind speed, lowest 8 bits of wind speed, m/s, scale 10\n- D = wind bearing, lowest 8 bits of wind bearing, range 0-359 deg, 0x1ff if invalid\n- G = wind gust, lowest 8 bits of wind gust, m/s, scale 10\n- V = uv index, scale 10\n- P = ambient pressure or 3f ff for invalid\n- U = unknown\n- R = rain total (R3 << 8 | R4) * 0.1 mm\n- RS = rain start dection ((R1 & 0x10) >>4), 1 = raining, 0 = not raining\n- S = super cap voltage, unit of 0.1V, lower 6 bits, mask 0x3f\n- Z = Firmware version. 0x82 = 130 = 1.3.0\n- A = checksum\n- X = CRC\n\nRain start info:\nStatus 1 will be reset to 0 when:\n- Once the top is dry\n- After the amount of water on the top has remained unchanged for two hours.\n\n*/\n\nstatic int fineoffset_ws90_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0xaa, 0x2d, 0xd4}; // 32 bit, part of preamble and sync word\n    uint8_t b[32];\n\n    // Validate package, WS90 nominal size is 345 bit periods\n    if (bitbuffer->bits_per_row[0] < 168 || bitbuffer->bits_per_row[0] > 500) {\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"abort length\" );\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a data package and extract data buffer\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble, 32) + 32;\n    if (bit_offset + sizeof(b) * 8 > bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"short package at %u (%u)\", bit_offset, bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Extract package data\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, sizeof(b) * 8);\n\n    if (b[0] != 0x90) // Check for family code 0x90\n        return DECODE_ABORT_EARLY;\n\n    decoder_logf(decoder, 1, __func__, \"WS90 detected, buffer is %u bits length\", bitbuffer->bits_per_row[0]);\n\n    // Verify checksum and CRC\n    uint8_t crc = crc8(b, 31, 0x31, 0x00);\n    uint8_t chk = add_bytes(b, 31);\n    if (crc != 0 || chk != b[31]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error: %02x %02x (%02x)\", crc, chk, b[31]);\n        return DECODE_FAIL_MIC;\n    }\n\n    int id          = (b[1] << 16) | (b[2] << 8) | (b[3]);\n    int light_raw   = (b[4] << 8) | (b[5]);\n    float light_lux = light_raw * 10;        // Lux\n    //float light_wm2 = light_raw * 0.078925f; // W/m2\n    int battery_mv  = (b[6] * 20);            // mV\n    int battery_lvl = battery_mv < 1400 ? 0 : (battery_mv - 1400) / 16; // 1.4V-3.0V is 0-100\n    int flags       = b[7]; // to find the wind msb\n    int temp_raw    = ((b[7] & 0x03) << 8) | (b[8]);\n    float temp_c    = (temp_raw - 400) * 0.1f;\n    int humidity    = (b[9]);\n    int wind_avg    = ((b[7] & 0x10) << 4) | (b[10]);\n    int wind_dir    = ((b[7] & 0x20) << 3) | (b[11]);\n    int wind_max    = ((b[7] & 0x40) << 2) | (b[12]);\n    int uv_index    = (b[13]);\n    int pressure    = (b[14] << 8) | (b[15]);\n    int rain_raw    = (b[19] << 8 ) | (b[20]);\n    int rain_start  = (b[16] & 0x10) >> 4;\n    int supercap_V  = (b[21] & 0x3f);\n    int firmware    = b[29];\n\n    if (battery_lvl > 100) // More then 100%?\n        battery_lvl = 100;\n\n    char extra[31];\n    snprintf(extra, sizeof(extra), \"%02x%02x%02x%02x%02x------%02x%02x%02x%02x%02x%02x%02x\", b[14], b[15], b[16], b[17], b[18], /* b[19,20] is the rain sensor, b[21] is supercap_V */ b[22], b[23], b[24], b[25], b[26], b[27], b[28]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Fineoffset-WS90\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%06x\", DATA_INT,    id,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery_lvl * 0.01f,\n            \"battery_mV\",       \"Battery Voltage\",  DATA_FORMAT, \"%d mV\", DATA_INT,    battery_mv,\n            \"temperature_C\",    \"Temperature\",      DATA_COND, temp_raw != 0x3ff,   DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_COND, humidity != 0xff,    DATA_FORMAT, \"%u %%\",    DATA_INT, humidity,\n            \"pressure_hPa\",     \"Pressure\",         DATA_COND, pressure != 0x3fff, DATA_FORMAT, \"%.1f hPa\", DATA_DOUBLE, (double)pressure,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_COND, wind_dir != 0x1ff,   DATA_INT, wind_dir,\n            \"wind_avg_m_s\",     \"Wind speed\",       DATA_COND, wind_avg != 0x1ff,   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_avg * 0.1f,\n            \"wind_max_m_s\",     \"Gust speed\",       DATA_COND, wind_max != 0x1ff,   DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_max * 0.1f,\n            \"uvi\",              \"UV Index\",         DATA_COND, uv_index != 0xff,    DATA_FORMAT, \"%.1f\",     DATA_DOUBLE, uv_index * 0.1f,\n            \"light_lux\",        \"Light\",            DATA_COND, light_raw != 0xffff, DATA_FORMAT, \"%.1f lux\", DATA_DOUBLE, (double)light_lux,\n            \"flags\",            \"Flags\",            DATA_FORMAT, \"%02x\", DATA_INT, flags,\n            \"rain_mm\",          \"Total Rain\",       DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_raw * 0.1f,\n            \"rain_start\",       \"Rain Start\",       DATA_INT, rain_start,\n            \"supercap_V\",       \"Supercap Voltage\", DATA_COND, supercap_V != 0xff, DATA_FORMAT, \"%.1f V\", DATA_DOUBLE, supercap_V * 0.1f,\n            \"firmware\",         \"Firmware Version\", DATA_INT, firmware,\n            \"data\",             \"Extra Data\",       DATA_STRING, extra,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_mV\",\n        \"temperature_C\",\n        \"humidity\",\n        \"pressure_hPa\",\n        \"wind_dir_deg\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"uvi\",\n        \"light_lux\",\n        \"flags\",\n        \"unknown\",\n        \"rain_mm\",\n        \"rain_start\",\n        \"supercap_V\",\n        \"firmware\",\n        \"data\",\n        \"mic\",\n        NULL,\n};\n\nr_device const fineoffset_ws90 = {\n        .name        = \"Fine Offset Electronics WS90 weather station\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 3000,\n        .decode_fn   = &fineoffset_ws90_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/flex.c",
    "content": "/** @file\n    Flexible general purpose decoder.\n\n    Copyright (C) 2017 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n#include \"optparse.h\"\n#include \"fatal.h\"\n#include <stdlib.h>\n\nstatic inline int bit(const uint8_t *bytes, unsigned b)\n{\n    return bytes[b >> 3] >> (7 - (b & 7)) & 1;\n}\n\n/// extract all mask bits skipping unmasked bits of a number up to 32/64 bits\nstatic unsigned long compact_number(uint8_t *data, unsigned bit_offset, unsigned long mask)\n{\n    // clz (fls) is not worth the trouble\n    int top_bit = 0;\n    while (mask >> top_bit)\n        top_bit++;\n    unsigned long val = 0;\n    for (int b = top_bit - 1; b >= 0; --b) {\n        if (mask & (1 << b)) {\n            val <<= 1;\n            val |= bit(data, bit_offset);\n        }\n        bit_offset++;\n    }\n    return val;\n}\n\n/// extract a number up to 32/64 bits from given offset with given bit length\nstatic unsigned long extract_number(uint8_t *data, unsigned bit_offset, unsigned bit_count)\n{\n    unsigned pos = bit_offset / 8;            // the first byte we need\n    unsigned shl = bit_offset - pos * 8;      // shift left we need to align\n    unsigned len = (shl + bit_count + 7) / 8; // number of bytes we need\n    unsigned shr = 8 * len - shl - bit_count; // actual shift right\n//    fprintf(stderr, \"pos: %d, shl: %d, len: %d, shr: %d\\n\", pos, shl, len, shr);\n    unsigned long val = data[pos];\n    val = (uint8_t)(val << shl) >> shl; // mask off top bits\n    for (unsigned i = 1; i < len - 1; ++i) {\n        val = val << 8 | data[pos + i];\n    }\n    // shift down and add the last bits, so we don't potentially loose the top bits\n    if (len > 1)\n        val = (val << (8 - shr)) | (data[pos + len - 1] >> shr);\n    else\n        val >>= shr;\n    return val;\n}\n\nstruct flex_map {\n    unsigned key;\n    const char *val;\n};\n\n#define GETTER_MAP_SLOTS 16\n\nstruct flex_get {\n    unsigned bit_offset;\n    unsigned bit_count;\n    unsigned long mask;\n    const char *name;\n    struct flex_map map[GETTER_MAP_SLOTS];\n    const char *format;\n};\n\n#define GETTER_SLOTS 12\n\nstruct flex_params {\n    char *name;\n    unsigned min_rows;\n    unsigned max_rows;\n    unsigned min_bits;\n    unsigned max_bits;\n    unsigned min_repeats;\n    unsigned max_repeats;\n    unsigned invert;\n    unsigned reflect;\n    unsigned unique;\n    unsigned count_only;\n    unsigned match_len;\n    uint8_t match_bits[128];\n    unsigned preamble_len;\n    uint8_t preamble_bits[128];\n    uint32_t symbol_zero;\n    uint32_t symbol_one;\n    uint32_t symbol_sync;\n    struct flex_get getter[GETTER_SLOTS];\n    unsigned decode_uart;\n    unsigned decode_dm;\n    unsigned decode_mc;\n    char const *fields[7 + GETTER_SLOTS + 1]; // NOTE: needs to match output_fields\n};\n\nstatic void print_row_bytes(char *row_bytes, uint8_t *bits, int num_bits)\n{\n    row_bytes[0] = '\\0';\n    // print byte-wide\n    for (int col = 0; col < (num_bits + 7) / 8; ++col) {\n        sprintf(&row_bytes[2 * col], \"%02x\", bits[col]);\n    }\n    // remove last nibble if needed\n    row_bytes[2 * (num_bits + 3) / 8] = '\\0';\n}\n\nstatic void render_getters(data_t *data, uint8_t *bits, struct flex_params *params)\n{\n    // add a data line for each getter\n    for (int g = 0; g < GETTER_SLOTS && params->getter[g].bit_count > 0; ++g) {\n        struct flex_get *getter = &params->getter[g];\n        unsigned long val;\n        if (getter->mask)\n            val = compact_number(bits, getter->bit_offset, getter->mask);\n        else\n            val = extract_number(bits, getter->bit_offset, getter->bit_count);\n        int m;\n        for (m = 0; getter->map[m].val; m++) {\n            if (getter->map[m].key == val) {\n                data_str(data, getter->name, \"\", NULL, getter->map[m].val);\n                break;\n            }\n        }\n        if (!getter->map[m].val) {\n            data_int(data, getter->name, \"\", getter->format, val);\n        }\n    }\n}\n\n/**\nGeneric flex decoder.\n*/\nstatic int flex_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int i;\n    int match_count = 0;\n    data_t *data;\n    data_t *row_data[BITBUF_ROWS];\n    char *row_codes[BITBUF_ROWS];\n    char row_bytes[BITBUF_ROWS * BITBUF_COLS * 2 + 1]; // TODO: this is a lot of stack\n\n    struct flex_params *params = decoder_user_data(decoder);\n\n    // discard short / unwanted bitbuffers\n    if ((bitbuffer->num_rows < params->min_rows)\n            || (params->max_rows && bitbuffer->num_rows > params->max_rows))\n        return DECODE_ABORT_LENGTH;\n\n    for (i = 0; i < bitbuffer->num_rows; i++) {\n        if ((bitbuffer->bits_per_row[i] >= params->min_bits)\n                && (!params->max_bits || bitbuffer->bits_per_row[i] <= params->max_bits))\n            match_count++;\n    }\n    if (!match_count)\n        return DECODE_ABORT_LENGTH;\n\n    // discard unless min_repeats, min_bits\n    // TODO: check max_repeats, max_bits\n    int r = bitbuffer_find_repeated_row(bitbuffer, params->min_repeats, params->min_bits);\n    if (r < 0)\n        return DECODE_ABORT_EARLY;\n    // TODO: set match_count to count of repeated rows\n\n    if (params->invert) {\n        bitbuffer_invert(bitbuffer);\n    }\n\n    if (params->reflect) {\n        // TODO: refactor to utils\n        for (i = 0; i < bitbuffer->num_rows; ++i) {\n            reflect_bytes(bitbuffer->bb[i], (bitbuffer->bits_per_row[i] + 7) / 8);\n        }\n    }\n\n    // discard unless match\n    if (params->match_len) {\n        r = -1;\n        match_count = 0;\n        for (i = 0; i < bitbuffer->num_rows; i++) {\n            if (bitbuffer_search(bitbuffer, i, 0, params->match_bits, params->match_len) < bitbuffer->bits_per_row[i]) {\n                if (r < 0)\n                    r = i;\n                match_count++;\n            }\n        }\n        if (!match_count)\n            return DECODE_FAIL_SANITY;\n    }\n\n    // discard unless match, this should be an AND condition\n    if (params->preamble_len) {\n        r = -1;\n        match_count = 0;\n        for (i = 0; i < bitbuffer->num_rows; i++) {\n            unsigned pos = bitbuffer_search(bitbuffer, i, 0, params->preamble_bits, params->preamble_len);\n            if (pos < bitbuffer->bits_per_row[i]) {\n                if (r < 0)\n                    r = i;\n                match_count++;\n                pos += params->preamble_len;\n                // TODO: refactor to bitbuffer_shift_row()\n                unsigned len = bitbuffer->bits_per_row[i] - pos;\n                bitbuffer_t tmp = {0};\n                bitbuffer_extract_bytes(bitbuffer, i, pos, tmp.bb[0], len);\n                memcpy(bitbuffer->bb[i], tmp.bb[0], (len + 7) / 8);\n                bitbuffer->bits_per_row[i] = len;\n            }\n        }\n        if (!match_count)\n            return DECODE_FAIL_SANITY;\n    }\n\n    if (params->symbol_zero) {\n        uint32_t zero = params->symbol_zero;\n        uint32_t one  = params->symbol_one;\n        uint32_t sync = params->symbol_sync;\n\n        for (i = 0; i < bitbuffer->num_rows; i++) {\n            // TODO: refactor to bitbuffer_decode_symbol_row()\n            unsigned len    = bitbuffer->bits_per_row[i];\n            bitbuffer_t tmp = {0};\n            len             = extract_bits_symbols(bitbuffer->bb[i], 0, len, zero, one, sync, tmp.bb[0]);\n            memcpy(bitbuffer->bb[i], tmp.bb[0], len); // safe to write over: can only be shorter\n            bitbuffer->bits_per_row[i] = len;\n        }\n        // TODO: apply min_bits, max_bits check\n    }\n\n    if (params->decode_uart) {\n        for (i = 0; i < bitbuffer->num_rows; i++) {\n            // TODO: refactor to bitbuffer_decode_uart_row()\n            unsigned len = bitbuffer->bits_per_row[i];\n            bitbuffer_t tmp = {0};\n            len = extract_bytes_uart(bitbuffer->bb[i], 0, len, tmp.bb[0]);\n            memcpy(bitbuffer->bb[i], tmp.bb[0], len); // safe to write over: can only be shorter\n            bitbuffer->bits_per_row[i] = len * 8;\n        }\n    }\n\n    if (params->decode_dm) {\n        for (i = 0; i < bitbuffer->num_rows; i++) {\n            // TODO: refactor to bitbuffer_decode_dm_row()\n            unsigned len = bitbuffer->bits_per_row[i];\n            bitbuffer_t tmp = {0};\n            bitbuffer_differential_manchester_decode(bitbuffer, i, 0, &tmp, len);\n            len = tmp.bits_per_row[0];\n            memcpy(bitbuffer->bb[i], tmp.bb[0], (len + 7) / 8); // safe to write over: can only be shorter\n            bitbuffer->bits_per_row[i] = len;\n        }\n    }\n\n    // IEEE 802.3 MC, may need G.E.Thomas option (bitbuffer_invert_row())\n    if (params->decode_mc) {\n        for (i = 0; i < bitbuffer->num_rows; i++) {\n            // TODO: refactor to bitbuffer_decode_mc_row()\n            unsigned len = bitbuffer->bits_per_row[i];\n            bitbuffer_t tmp = {0};\n            bitbuffer_manchester_decode(bitbuffer, i, 0, &tmp, len);\n            len = tmp.bits_per_row[0];\n            memcpy(bitbuffer->bb[i], tmp.bb[0], (len + 7) / 8); // safe to write over: can only be shorter\n            bitbuffer->bits_per_row[i] = len;\n        }\n    }\n\n    decoder_log_bitbuffer(decoder, 1, params->name, bitbuffer, \"\");\n\n    // discard duplicates\n    if (params->unique) {\n        print_row_bytes(row_bytes, bitbuffer->bb[r], bitbuffer->bits_per_row[r]);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\", \"\", DATA_STRING, params->name, // \"User-defined\"\n                \"count\", \"\", DATA_INT, match_count,\n                \"num_rows\", \"\", DATA_INT, bitbuffer->num_rows,\n                \"len\", \"\", DATA_INT, bitbuffer->bits_per_row[r],\n                \"data\", \"\", DATA_STRING, row_bytes,\n                NULL);\n        /* clang-format on */\n\n        // add a data line for each getter\n        render_getters(data, bitbuffer->bb[r], params);\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    if (params->count_only) {\n        /* clang-format off */\n        data = data_make(\n                \"model\", \"\", DATA_STRING, params->name, // \"User-defined\"\n                \"count\", \"\", DATA_INT, match_count,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    for (i = 0; i < bitbuffer->num_rows; i++) {\n        print_row_bytes(row_bytes, bitbuffer->bb[i], bitbuffer->bits_per_row[i]);\n\n        /* clang-format off */\n        data = data_make(\n                \"len\", \"\", DATA_INT, bitbuffer->bits_per_row[i],\n                \"data\", \"\", DATA_STRING, row_bytes,\n                NULL);\n        /* clang-format on */\n        row_data[i] = data;\n\n        // add a data line for each getter\n        render_getters(row_data[i], bitbuffer->bb[i], params);\n\n        // print at least one '0'\n        if (row_bytes[0] == '\\0') {\n            snprintf(row_bytes, sizeof(row_bytes), \"0\");\n        }\n\n        // a simpler representation for csv output\n        row_codes[i] = malloc(8 + bitbuffer->bits_per_row[i] / 4 + 1); // \"{nnnn}..\\0\"\n        if (!row_codes[i])\n            WARN_MALLOC(\"flex_decode()\");\n        else // NOTE: skipped on alloc failure.\n            sprintf(row_codes[i], \"{%d}%s\", bitbuffer->bits_per_row[i], row_bytes);\n    }\n    /* clang-format off */\n    data = data_make(\n            \"model\", \"\", DATA_STRING, params->name, // \"User-defined\"\n            \"count\", \"\", DATA_INT, match_count,\n            \"num_rows\", \"\", DATA_INT, bitbuffer->num_rows,\n            \"rows\", \"\", DATA_ARRAY, data_array(bitbuffer->num_rows, DATA_DATA, row_data),\n            \"codes\", \"\", DATA_ARRAY, data_array(bitbuffer->num_rows, DATA_STRING, row_codes),\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    for (i = 0; i < bitbuffer->num_rows; i++) {\n        free(row_codes[i]);\n    }\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"count\",\n        \"num_rows\",\n        \"rows\",\n        \"codes\",\n        // \"len\", // unique only\n        // \"data\", // unique only\n        NULL,\n};\n\nstatic void usage(void)\n{\n    fprintf(stderr,\n            \"Use -X <spec> to add a general purpose decoder. For usage use -X help\\n\");\n    exit(1);\n}\n\nstatic void help(void)\n{\n    fprintf(stderr,\n            \"\\t\\t= Flex decoder spec =\\n\"\n            \"Use -X <spec> to add a flexible general purpose decoder.\\n\\n\"\n            \"<spec> is \\\"key=value[,key=value...]\\\"\\n\"\n            \"Common keys are:\\n\"\n            \"\\tname=<name> (or: n=<name>)\\n\"\n            \"\\tmodulation=<modulation> (or: m=<modulation>)\\n\"\n            \"\\tshort=<short> (or: s=<short>)\\n\"\n            \"\\tlong=<long> (or: l=<long>)\\n\"\n            \"\\tsync=<sync> (or: y=<sync>)\\n\"\n            \"\\treset=<reset> (or: r=<reset>)\\n\"\n            \"\\tgap=<gap> (or: g=<gap>)\\n\"\n            \"\\ttolerance=<tolerance> (or: t=<tolerance>)\\n\"\n            \"\\tpriority=<n> : run decoder only as fallback\\n\"\n            \"where:\\n\"\n            \"<name> can be any descriptive name tag you need in the output\\n\"\n            \"<modulation> is one of:\\n\"\n            \"\\tOOK_MC_ZEROBIT :  Manchester Code with fixed leading zero bit\\n\"\n            \"\\tOOK_PCM :         Non Return to Zero coding (Pulse Code)\\n\"\n            \"\\tOOK_RZ :          Return to Zero coding (Pulse Code)\\n\"\n            \"\\tOOK_PPM :         Pulse Position Modulation\\n\"\n            \"\\tOOK_PWM :         Pulse Width Modulation\\n\"\n            \"\\tOOK_DMC :         Differential Manchester Code\\n\"\n            \"\\tOOK_PIWM_RAW :    Raw Pulse Interval and Width Modulation\\n\"\n            \"\\tOOK_PIWM_DC :     Differential Pulse Interval and Width Modulation\\n\"\n            \"\\tOOK_MC_OSV1 :     Manchester Code for OSv1 devices\\n\"\n            \"\\tFSK_PCM :         FSK Pulse Code Modulation\\n\"\n            \"\\tFSK_PWM :         FSK Pulse Width Modulation\\n\"\n            \"\\tFSK_MC_ZEROBIT :  Manchester Code with fixed leading zero bit\\n\"\n            \"<short>, <long>, <sync> are nominal modulation timings in us,\\n\"\n            \"<reset>, <gap>, <tolerance> are maximum modulation timings in us:\\n\"\n            \"PCM/RZ  short: Nominal width of pulse [us]\\n\"\n            \"         long: Nominal width of bit period [us]\\n\"\n            \"PPM     short: Nominal width of '0' gap [us]\\n\"\n            \"         long: Nominal width of '1' gap [us]\\n\"\n            \"PWM     short: Nominal width of '1' pulse [us]\\n\"\n            \"         long: Nominal width of '0' pulse [us]\\n\"\n            \"         sync: Nominal width of sync pulse [us] (optional)\\n\"\n            \"common    gap: Maximum gap size before new row of bits [us]\\n\"\n            \"        reset: Maximum gap size before End Of Message [us]\\n\"\n            \"    tolerance: Maximum pulse deviation [us] (optional).\\n\"\n            \"Available options are:\\n\"\n            \"\\tbits=<n> : only match if at least one row has <n> bits\\n\"\n            \"\\trows=<n> : only match if there are <n> rows\\n\"\n            \"\\trepeats=<n> : only match if some row is repeated <n> times\\n\"\n            \"\\t\\tuse opt>=n to match at least <n> and opt<=n to match at most <n>\\n\"\n            \"\\tinvert : invert all bits\\n\"\n            \"\\treflect : reflect each byte (MSB first to MSB last)\\n\"\n            \"\\tdecode_uart : UART 8n1 (10-to-8) decode\\n\"\n            \"\\tdecode_dm : Differential Manchester decode\\n\"\n            \"\\tdecode_mc : Manchester decode\\n\"\n            \"\\tmatch=<bits> : only match if the <bits> are found\\n\"\n            \"\\tpreamble=<bits> : match and align at the <bits> preamble\\n\"\n            \"\\t\\t<bits> is a row spec of {<bit count>}<bits as hex number>\\n\"\n            \"\\tunique : suppress duplicate row output\\n\\n\"\n            \"\\tcountonly : suppress detailed row output\\n\\n\"\n            \"E.g. -X \\\"n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repeats>=3\\\"\\n\\n\");\n    exit(0);\n}\n\nstatic float parse_atoiv(char const *str, int def, char const *error_hint)\n{\n    if (!str) {\n        return def;\n    }\n\n    if (!*str) {\n        return def;\n    }\n\n    char *endptr;\n    int val = strtol(str, &endptr, 10);\n\n    if (str == endptr) {\n        fprintf(stderr, \"%sinvalid number argument (%s)\\n\", error_hint, str);\n        exit(1);\n    }\n\n    return val;\n}\n\nstatic float parse_float(char const *str, char const *error_hint)\n{\n    if (!str) {\n        fprintf(stderr, \"%smissing number argument\\n\", error_hint);\n        exit(1);\n    }\n\n    if (!*str) {\n        fprintf(stderr, \"%sempty number argument\\n\", error_hint);\n        exit(1);\n    }\n\n    char *endptr;\n    double val = strtod(str, &endptr);\n\n    if (str == endptr) {\n        fprintf(stderr, \"%sinvalid number argument (%s)\\n\", error_hint, str);\n        exit(1);\n    }\n\n    if (*endptr != '\\0') {\n        fprintf(stderr, \"%strailing characters in number argument (%s)\\n\", error_hint, str);\n        exit(1);\n    }\n\n    return val;\n\n}\n\nstatic unsigned parse_modulation(char const *str)\n{\n    if (!strcasecmp(str, \"OOK_MC_ZEROBIT\"))\n        return OOK_PULSE_MANCHESTER_ZEROBIT;\n    else if (!strcasecmp(str, \"OOK_PCM\"))\n        return OOK_PULSE_PCM;\n    else if (!strcasecmp(str, \"OOK_RZ\"))\n        return OOK_PULSE_RZ;\n    else if (!strcasecmp(str, \"OOK_PPM\"))\n        return OOK_PULSE_PPM;\n    else if (!strcasecmp(str, \"OOK_PWM\"))\n        return OOK_PULSE_PWM;\n    else if (!strcasecmp(str, \"OOK_DMC\"))\n        return OOK_PULSE_DMC;\n    else if (!strcasecmp(str, \"OOK_PIWM_RAW\"))\n        return OOK_PULSE_PIWM_RAW;\n    else if (!strcasecmp(str, \"OOK_PIWM_DC\"))\n        return OOK_PULSE_PIWM_DC;\n    else if (!strcasecmp(str, \"OOK_MC_OSV1\"))\n        return OOK_PULSE_PWM_OSV1;\n    else if (!strcasecmp(str, \"FSK_PCM\"))\n        return FSK_PULSE_PCM;\n    else if (!strcasecmp(str, \"FSK_PWM\"))\n        return FSK_PULSE_PWM;\n    else if (!strcasecmp(str, \"FSK_MC_ZEROBIT\"))\n        return FSK_PULSE_MANCHESTER_ZEROBIT;\n    else {\n        fprintf(stderr, \"Bad flex spec, unknown modulation!\\n\");\n        usage();\n    }\n    return 0;\n}\n\n// used for match, preamble, getter, limited to 1024 bits (128 byte).\nstatic unsigned parse_bits(const char *code, uint8_t *bitrow)\n{\n    bitbuffer_t bits = {0};\n    bitbuffer_parse(&bits, code);\n    if (bits.num_rows != 1) {\n        fprintf(stderr, \"Bad flex spec, \\\"match\\\", \\\"preamble\\\", and getter mask need exactly one bit row (%d found)!\\n\", bits.num_rows);\n        usage();\n    }\n    unsigned len = bits.bits_per_row[0];\n    if (len > 1024) {\n        fprintf(stderr, \"Bad flex spec, \\\"match\\\", \\\"preamble\\\", and getter mask may have up to 1024 bits (%u found)!\\n\", len);\n        usage();\n    }\n    memcpy(bitrow, bits.bb[0], (len + 7) / 8);\n    return len;\n}\n\n// used for symbol decode, limited to 27 bits (32 - 5).\nstatic uint32_t parse_symbol(const char *code)\n{\n    bitbuffer_t bits = {0};\n    bitbuffer_parse(&bits, code);\n    if (bits.num_rows != 1) {\n        fprintf(stderr, \"Bad flex spec, \\\"symbol\\\" needs exactly one bit row (%d found)!\\n\", bits.num_rows);\n        usage();\n    }\n    unsigned len = bits.bits_per_row[0];\n    if (len > 27) {\n        fprintf(stderr, \"Bad flex spec, \\\"symbol\\\" may have up to 27 bits (%u found)!\\n\", len);\n        usage();\n    }\n    uint8_t *b = bits.bb[0];\n    return ((uint32_t)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | (b[3] << 0) | len;\n}\n\nstatic const char *parse_map(const char *arg, struct flex_get *getter)\n{\n    const char *c = arg;\n    int i = 0;\n\n    while (*c == ' ') c++;\n    if (*c == '[') c++;\n\n    while (*c) {\n        unsigned long key;\n        char *val;\n\n        while (*c == ' ') c++;\n        if (*c == ']') return c + 1;\n\n        // first parse a number\n        key = strtol(c, (char **)&c, 0); // hex, oct, or dec\n\n        while (*c == ' ') c++;\n        if (*c == ':') c++;\n        while (*c == ' ') c++;\n\n        // then parse a string\n        const char *e = c;\n        while (*e && *e != ' ' && *e != ']') e++;\n        val = malloc(e - c + 1);\n        if (!val)\n            WARN_MALLOC(\"parse_map()\");\n        else { // NOTE: skipped on alloc failure.\n            memcpy(val, c, e - c);\n            val[e - c] = '\\0';\n        }\n        c = e;\n\n        // store result\n        getter->map[i].key = key;\n        getter->map[i].val = val;\n        i++;\n    }\n    return c;\n}\n\nstatic void parse_getter(const char *arg, struct flex_get *getter)\n{\n    uint8_t bitrow[128];\n    while (arg && *arg) {\n        if (*arg == '[') {\n            arg = parse_map(arg, getter);\n            continue;\n        }\n        char *p = strchr(arg, ':');\n        if (p)\n            *p++ = '\\0';\n        if (*arg == '@')\n            getter->bit_offset = strtol(++arg, NULL, 0);\n        else if (*arg == '{' || (*arg >= '0' && *arg <= '9')) {\n            getter->bit_count = parse_bits(arg, bitrow);\n            getter->mask = extract_number(bitrow, 0, getter->bit_count);\n        }\n        else if (*arg == '%') {\n            getter->format = strdup(arg);\n            if (!getter->format)\n                FATAL_STRDUP(\"parse_getter()\");\n        }\n        else {\n            getter->name = strdup(arg);\n            if (!getter->name)\n                FATAL_STRDUP(\"parse_getter()\");\n        }\n        arg = p;\n    }\n    if (!getter->name) {\n        fprintf(stderr, \"Bad flex spec, \\\"get\\\" missing name!\\n\");\n        usage();\n    }\n    /*\n        fprintf(stderr, \"parse_getter() bit_offset: %d bit_count: %d mask: %lx name: %s\\n\",\n                getter->bit_offset, getter->bit_count, getter->mask, getter->name);\n    */\n}\n\n// NOTE: this is declared in rtl_433.c also.\nr_device *flex_create_device(char *spec);\n\nr_device *flex_create_device(char *spec)\n{\n    if (!spec || !*spec || *spec == '?' || !strncasecmp(spec, \"help\", strlen(spec))) {\n        help();\n    }\n\n    r_device *dev = decoder_create(NULL, sizeof(struct flex_params));\n    if (!dev) {\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n    struct flex_params *params = decoder_user_data(dev);\n    int get_count = 0;\n\n    spec = strdup(spec);\n    if (!spec)\n        FATAL_STRDUP(\"flex_create_device()\");\n\n    dev->decode_fn = flex_callback;\n    dev->fields = output_fields;\n\n    char *key, *val;\n    while (getkwargs(&spec, &key, &val)) {\n        key = remove_ws(key);\n        val = trim_ws(val);\n\n        if (!key || !*key)\n            continue;\n        else if (!strcasecmp(key, \"n\") || !strcasecmp(key, \"name\")) {\n            params->name = strdup(val);\n            if (!params->name)\n                FATAL_STRDUP(\"flex_create_device()\");\n            int name_size = strlen(val) + 27;\n            char* flex_name = malloc(name_size);\n            if (!flex_name)\n                FATAL_MALLOC(\"flex_create_device()\");\n            snprintf(flex_name, name_size, \"General purpose decoder '%s'\", val);\n            dev->name = flex_name;\n        }\n\n        else if (!strcasecmp(key, \"m\") || !strcasecmp(key, \"modulation\"))\n            dev->modulation = parse_modulation(val);\n        else if (!strcasecmp(key, \"s\") || !strcasecmp(key, \"short\"))\n            dev->short_width = parse_float(val, \"short: \");\n        else if (!strcasecmp(key, \"l\") || !strcasecmp(key, \"long\"))\n            dev->long_width = parse_float(val, \"long: \");\n        else if (!strcasecmp(key, \"y\") || !strcasecmp(key, \"sync\"))\n            dev->sync_width = parse_float(val, \"sync: \");\n        else if (!strcasecmp(key, \"g\") || !strcasecmp(key, \"gap\"))\n            dev->gap_limit = parse_float(val, \"gap: \");\n        else if (!strcasecmp(key, \"r\") || !strcasecmp(key, \"reset\"))\n            dev->reset_limit = parse_float(val, \"reset: \");\n        else if (!strcasecmp(key, \"t\") || !strcasecmp(key, \"tolerance\"))\n            dev->tolerance = parse_float(val, \"tolerance: \");\n        else if (!strcasecmp(key, \"prio\") || !strcasecmp(key, \"priority\"))\n            dev->priority = parse_atoiv(val, 0, \"priority: \");\n\n        else if (!strcasecmp(key, \"bits>\"))\n            params->min_bits = parse_atoiv(val, 0, \"bits: \");\n        else if (!strcasecmp(key, \"bits<\"))\n            params->max_bits = parse_atoiv(val, 0, \"bits: \");\n        else if (!strcasecmp(key, \"bits\"))\n            params->min_bits = params->max_bits = parse_atoiv(val, 0, \"bits:\");\n\n        else if (!strcasecmp(key, \"rows>\"))\n            params->min_rows = parse_atoiv(val, 0, \"rows: \");\n        else if (!strcasecmp(key, \"rows<\"))\n            params->max_rows = parse_atoiv(val, 0, \"rows: \");\n        else if (!strcasecmp(key, \"rows\"))\n            params->min_rows = params->max_rows = parse_atoiv(val, 0, \"rows: \");\n\n        else if (!strcasecmp(key, \"repeats>\"))\n            params->min_repeats = parse_atoiv(val, 0, \"repeats: \");\n        else if (!strcasecmp(key, \"repeats<\"))\n            params->max_repeats = parse_atoiv(val, 0, \"repeats: \");\n        else if (!strcasecmp(key, \"repeats\"))\n            params->min_repeats = params->max_repeats = parse_atoiv(val, 0, \"repeats: \");\n\n        else if (!strcasecmp(key, \"invert\"))\n            params->invert = parse_atoiv(val, 1, \"invert: \");\n        else if (!strcasecmp(key, \"reflect\"))\n            params->reflect = parse_atoiv(val, 1, \"reflect: \");\n\n        else if (!strcasecmp(key, \"match\"))\n            params->match_len = parse_bits(val, params->match_bits);\n\n        else if (!strcasecmp(key, \"preamble\"))\n            params->preamble_len = parse_bits(val, params->preamble_bits);\n\n        else if (!strcasecmp(key, \"countonly\"))\n            params->count_only = parse_atoiv(val, 1, \"countonly: \");\n\n        else if (!strcasecmp(key, \"unique\"))\n            params->unique = parse_atoiv(val, 1, \"unique: \");\n\n        else if (!strcasecmp(key, \"decode_uart\"))\n            params->decode_uart = parse_atoiv(val, 1, \"decode_uart: \");\n        else if (!strcasecmp(key, \"decode_dm\"))\n            params->decode_dm = parse_atoiv(val, 1, \"decode_dm: \");\n        else if (!strcasecmp(key, \"decode_mc\"))\n            params->decode_mc = parse_atoiv(val, 1, \"decode_mc: \");\n\n        else if (!strcasecmp(key, \"symbol_zero\"))\n            params->symbol_zero = parse_symbol(val);\n        else if (!strcasecmp(key, \"symbol_one\"))\n            params->symbol_one = parse_symbol(val);\n        else if (!strcasecmp(key, \"symbol_sync\"))\n            params->symbol_sync = parse_symbol(val);\n\n        else if (!strcasecmp(key, \"get\")) {\n            if (get_count < GETTER_SLOTS)\n                parse_getter(val, &params->getter[get_count++]);\n            else {\n                fprintf(stderr, \"Maximum getter slots exceeded (%d)!\\n\", GETTER_SLOTS);\n                usage();\n            }\n\n        } else {\n            fprintf(stderr, \"Bad flex spec, unknown keyword (%s)!\\n\", key);\n            usage();\n        }\n    }\n\n    if (params->min_bits < params->match_len)\n        params->min_bits = params->match_len;\n\n    if (params->min_bits > 0 && params->min_repeats < 1)\n        params->min_repeats = 1;\n\n    // add getter fields if unique requested\n    if (params->unique) {\n        int i = 0;\n        for (int f = 0; output_fields[f]; ++f) {\n            params->fields[i++] = output_fields[f];\n        }\n        params->fields[i++] = \"len\";\n        params->fields[i++] = \"data\";\n        for (int g = 0; g < GETTER_SLOTS && params->getter[g].name; ++g) {\n            params->fields[i++] = params->getter[g].name;\n        }\n        dev->fields = params->fields;\n    }\n\n    // sanity checks\n\n    if (!params->name || !*params->name) {\n        fprintf(stderr, \"Bad flex spec, missing name!\\n\");\n        usage();\n    }\n\n    if (!dev->modulation) {\n        fprintf(stderr, \"Bad flex spec, missing modulation!\\n\");\n        usage();\n    }\n\n    if (!dev->short_width) {\n        fprintf(stderr, \"Bad flex spec, missing short width!\\n\");\n        usage();\n    }\n\n    if (dev->modulation != OOK_PULSE_MANCHESTER_ZEROBIT\n            && dev->modulation != FSK_PULSE_MANCHESTER_ZEROBIT) {\n        if (!dev->long_width) {\n            fprintf(stderr, \"Bad flex spec, missing long width!\\n\");\n            usage();\n        }\n    }\n\n    if (!dev->reset_limit) {\n        fprintf(stderr, \"Bad flex spec, missing reset limit!\\n\");\n        usage();\n    }\n\n    if (dev->modulation == OOK_PULSE_DMC\n            || dev->modulation == OOK_PULSE_PIWM_RAW\n            || dev->modulation == OOK_PULSE_PIWM_DC) {\n        if (!dev->tolerance) {\n            fprintf(stderr, \"Bad flex spec, missing tolerance limit!\\n\");\n            usage();\n        }\n    }\n\n    if (params->symbol_zero && !params->symbol_one) {\n        fprintf(stderr, \"Bad flex spec, symbol-one missing!\\n\");\n        usage();\n    }\n    if (params->symbol_one && !params->symbol_zero) {\n        fprintf(stderr, \"Bad flex spec, symbol-zero missing!\\n\");\n        usage();\n    }\n\n    /*\n        fprintf(stderr, \"Adding flex decoder \\\"%s\\\"\\n\", params->name);\n        fprintf(stderr, \"\\tmodulation=%u, short_width=%.0f, long_width=%.0f, reset_limit=%.0f\\n\",\n                dev->modulation, dev->short_width, dev->long_width, dev->reset_limit);\n        fprintf(stderr, \"\\tmin_rows=%u, min_bits=%u, min_repeats=%u, invert=%u, reflect=%u, match_len=%u, preamble_len=%u\\n\",\n                params->min_rows, params->min_bits, params->min_repeats, params->invert, params->reflect, params->match_len, params->preamble_len);\n    */\n\n    free(spec);\n    return dev;\n}\n"
  },
  {
    "path": "src/devices/flowis.c",
    "content": "/** @file\n    Flowis water meter.\n\n    Copyright (C) 2023 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    Heavily based on marlec_solar.c\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n*/\n\n/**\nFlowis water meter.\n\nThere are several different message types with different message lengths.\nAll signals are transmitted with a preamble (0xA or 0x5) and then the\nsyncword d391 d391. This is a popular syncword used by the CC110x transceiver\nfamily.\n\n\nMessage layout type 1 (0x15 bytes of length), 4-bit nibble:\n\n               0  1  2 3 4 5  6  7 8 9 A  B  C  ....... 0x15\n    SSSS SSSS LL YY IIIIIIII ?? TTTTTTTT AA BB ???????? CC\n\n- S 32b: 2 x 16 bit sync words d391 d391 (32 bits)\n- L  8b: message length, different message types have different lengths\n- Y  8b: message type (1 and 2 has been observed)\n- I 32b: meter id, visible on the actual meter\n- ?  8b: unknown\n- T 32b: timestamp, bitpacked\n- V 32b: Volume in m3\n- A  8b: Alarm\n- B  8b: Backflow\n- ?  xb: unknown\n- C 16b: CRC-16 with poly=0x8005 and init=0xFFFF over data after sync\n\nMessage type 2 uses same message syntax, length type, payload and checksum.\n\nType 2 messages usually contain long runs of zeros that might cause bitstream desyncs.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int flowis_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {\n            /*0xaa, 0xaa, */ 0xaa, 0xaa, // preamble\n            0xd3, 0x91, 0xd3, 0x91       // sync word\n    };\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int row = 0;\n    // Validate message and reject it as fast as possible : check for preamble\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, sizeof (preamble) * 8);\n\n    if (start_pos == bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_EARLY; // no preamble detected\n    }\n\n    uint8_t len;\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos + sizeof (preamble) * 8, &len, 8);\n\n\n    uint8_t frame[256+2+1] = {0}; // uint8_t max bytes + 2 bytes crc + 1 length byte\n    frame[0] = len;\n    // Get frame (len don't include the length byte and the crc16 bytes)\n    bitbuffer_extract_bytes(bitbuffer, row,\n            start_pos + (sizeof (preamble) + 1) * 8,\n            &frame[1], (len + 2) * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, frame, (len + 1) * 8, \"frame data\");\n\n    uint16_t crc = crc16(frame, len + 1, 0x8005, 0xffff);\n\n    if ((frame[len + 1] << 8 | frame[len + 2]) != crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC invalid %04x != %04x\",\n                frame[len + 1] << 8 | frame[len + 2], crc);\n        return DECODE_FAIL_MIC;\n    }\n    uint8_t* b = frame;\n    int type = b[1];\n\n    /* Only type 1 decoding is supported */\n    if (type != 1) return DECODE_ABORT_EARLY;\n\n    int id   = b[5] << 24 | b[4] << 16 | b[3] << 8 | b[2];\n    int volume = b[13] << 16 | b[12] << 8 | b[11];\n\n    int fts_year = b[10] >> 2;\n    int fts_mth  = ((b[9]>>6) | (b[10]&3)<<2);\n    int fts_day  = (b[9]&0x3E) >> 1;\n    int fts_hour = (b[8]>>4) | ((b[9]&1)<<4);\n    int fts_min  = ((b[8]&0xF)<<2) | ((b[7]&0xC0)>>6);\n    int fts_sec  = b[7]&0x3F;\n    char fts_str[20];\n    snprintf(fts_str, sizeof(fts_str), \"%4d-%02d-%02dT%02d:%02d:%02d\", fts_year + 2000, fts_mth, fts_day, fts_hour, fts_min, fts_sec);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"Flowis\",\n            \"id\",           \"Meter id\",     DATA_INT,    id,\n            \"msg_type\",     \"Message Type\", DATA_INT,    type,\n            \"volume_m3\",    \"Volume\",       DATA_FORMAT, \"%.3f m3\", DATA_DOUBLE, volume/1000.0,\n            \"device_time\",  \"Device time\",  DATA_STRING, fts_str,\n            \"alarm\",        \"Alarm\",        DATA_INT,    b[15],\n            \"backflow\",     \"Backflow\",     DATA_INT,    b[14],\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"msg_type\",\n        \"volume_m3\",\n        \"device_time\",\n        \"alarm\",\n        \"backflow\",\n        \"mic\",\n        NULL,\n};\n\nr_device const flowis = {\n        .name        = \"Flowis flow meters\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 10,\n        .long_width  = 10,\n        .reset_limit = 5000,\n        .decode_fn   = &flowis_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fordremote.c",
    "content": "/** @file\n    Ford Car Key.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nFord Car Key.\n\nIdentifies event, but does not attempt to decrypt rolling code...\nNote: this used to have a broken PWM decoding, but is now proper DMC.\nThe output changed and the fields are very likely not as intended.\n\n    [00] {1} 80 : 1\n    [01] {9} 00 80 : 00000000 1\n    [02] {1} 80 : 1\n    [03] {78} 03 e0 01 e4 e0 90 52 97 39 60\n\n*/\n\n#include \"decoder.h\"\n\nstatic int fordremote_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *bytes;\n    int found = 0;\n    int device_id, code;\n\n    // expect {1} {9} {1} preamble\n    for (int i = 3; i < bitbuffer->num_rows; i++) {\n        if (bitbuffer->bits_per_row[i] < 78) {\n            continue; // DECODE_ABORT_LENGTH\n        }\n\n        // Validate preamble\n        if (bitbuffer->bits_per_row[i - 3] != 1 || bitbuffer->bits_per_row[i - 1] != 1\n                || bitbuffer->bits_per_row[i - 2] != 9 || bitbuffer->bb[i - 2][0] != 0) {\n            continue; // DECODE_ABORT_EARLY\n        }\n\n        decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"\");\n\n        bytes     = bitbuffer->bb[i];\n        device_id = (bytes[0] << 16) | (bytes[1] << 8) | bytes[2];\n        code      = bytes[7];\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",    \"model\",        DATA_STRING, \"Ford-CarRemote\",\n                \"id\",       \"device-id\",    DATA_INT,    device_id,\n                \"code\",     \"data\",         DATA_INT,    code,\n                NULL);\n        decoder_output_data(decoder, data);\n        /* clang-format on */\n\n        found++;\n    }\n    return found;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"code\",\n        NULL,\n};\n\nr_device const fordremote = {\n        .name        = \"Ford Car Key\",\n        .modulation  = OOK_PULSE_DMC,\n        .short_width = 250,  // half-bit width is 250 us\n        .long_width  = 500,  // bit width is 500 us\n        .reset_limit = 4000, // sync gap is 3500 us, preamble gap is 38400 us, packet gap is 52000 us\n        .tolerance   = 50,\n        .decode_fn   = &fordremote_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/fs20.c",
    "content": "/** @file\n    Simple FS20 remote decoder.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n    original implementation 2019 Dominik Pusch <dominik.pusch@koeln.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int fs20_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nSimple FS20 remote decoder.\n\nFrequency: use rtl_433 -f 868.35M\n\nfs20 protocol frame info from http://www.fhz4linux.info/tiki-index.php?page=FS20+Protocol\n\n    preamble  hc1    parity  hc2    parity  address  parity  cmd    parity  chksum  parity  eot\n    13 bit    8 bit  1 bit   8 bit  1 bit   8 bit    1 bit   8 bit  1 bit   8 bit   1 bit   1 bit\n\nwith extended commands\n\n    preamble  hc1    parity  hc2    parity  address  parity  cmd    parity  ext    parity  chksum  parity  eot\n    13 bit    8 bit  1 bit   8 bit  1 bit   8 bit    1 bit   8 bit  1 bit   8 bit  1 bit   8 bit   1 bit   1 bit\n\nchecksum and parity are not checked by this decoder.\nCommand extensions are also not decoded. feel free to improve!\n*/\n\nstatic int fs20_find_preamble(bitbuffer_t *bitbuffer, int bitpos)\n{\n    // Preamble is 12 x '0' | '1', but we ignore the first preamble bit\n    // Last bit ('1') is at position (pattern[1] >> 4 & 1)\n    uint8_t const preamble_pattern[2] = {0x00, 0x10};\n    uint8_t const min_packet_length   = 4 * (8 + 1);\n\n    // fast scan for 8 consecutive '0' bits\n    uint8_t *bits = bitbuffer->bb[0];\n    while ((bitpos + 12 + min_packet_length < bitbuffer->bits_per_row[0])\n        && ((bits[(bitpos / 8) + 1] == 0) || (bits[(bitpos / 8)] != 0))) {\n        bitpos += 8;\n    }\n    if (bitpos) {\n        bitpos--;\n        bitpos &= ~0x3;\n    }\n\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 12)) < bitbuffer->bits_per_row[0]) {\n        if (bitpos + min_packet_length >= bitbuffer->bits_per_row[0]) {\n            return DECODE_ABORT_LENGTH;\n        }\n\n        return bitpos + 12;\n    }\n\n    // preamble not found\n    return DECODE_FAIL_SANITY;\n}\n\nstruct parity_byte {\n    uint8_t data;\n    uint8_t err;\n};\n\nstatic struct parity_byte get_byte(uint8_t *bits, unsigned pos)\n{\n    uint16_t word = (bits[pos / 8] << 8) | bits[(pos / 8) + 1];\n    struct parity_byte res;\n\n    word <<= pos & 7;\n    res.data = word >> 8;\n    // parity8 returns odd parity, bit 9 is even parity\n    res.err = parity8(res.data) != (word >> 7 & 1);\n\n    return res;\n}\n\nstatic int fs20_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static char const *const cmd_tab[] = {\n            \"off\",\n            \"on, 6.25%\",\n            \"on, 12.5%\",\n            \"on, 18.75%\",\n            \"on, 25%\",\n            \"on, 31.25%\",\n            \"on, 37.5%\",\n            \"on, 43.75%\",\n            \"on, 50%\",\n            \"on, 56.25%\",\n            \"on, 62.5%\",\n            \"on, 68.75%\",\n            \"on, 75%\",\n            \"on, 81.25%\",\n            \"on, 87.5%\",\n            \"on, 93.75%\",\n            \"on, 100%\",\n            \"on, last value\",\n            \"toggle on/off\",\n            \"dim up\",\n            \"dim down\",\n            \"dim up/down\",\n            \"set timer\",\n            \"status request\",\n            \"off, timer\",\n            \"on, timer\",\n            \"last value, timer\",\n            \"reset to default\",\n            \"unused\",\n            \"unused\",\n            \"unused\",\n            \"unused\",\n    };\n    static char const *const flags_tab[8] = {\n            \"(none)\",\n            \"Extended\",\n            \"BiDir\",\n            \"Extended | BiDir\",\n            \"Response\",\n            \"Response | Extended\",\n            \"Response | BiDir\",\n            \"Response | Extended | BiDir\",\n    };\n    static char const *const fht_cmd_tab[16] = {\n            \"end-of-sync\",\n            \"valve open\",\n            \"valve close\",\n            \"? (0x3)\",\n            \"? (0x4)\",\n            \"? (0x5)\",\n            \"valve open <ext>%\",\n            \"? (0x7)\",\n            \"offset adjust\",\n            \"? (0x9)\",\n            \"valve de-scale\",\n            \"? (0x11)\",\n            \"sync countdown\",\n            \"? (0x13)\",\n            \"beep\",\n            \"pairing?\",\n    };\n    static char const *const fht_flags_tab[8] = {\n            \"(none)\",\n            \"Extended\",\n            \"BS?\",\n            \"Extended | BS?\",\n            \"Repeat\",\n            \"Repeat | Extended\",\n            \"Repeat | BS?\",\n            \"Repeat | Extended | BS?\",\n    };\n\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t *bits = bitbuffer->bb[0];\n    uint8_t cmd;\n    uint16_t hc;\n    uint8_t address;\n    uint8_t ext = 0;\n    uint8_t sum;\n\n    data_t *data;\n    uint16_t ad_b4 = 0;\n    uint32_t hc_b4 = 0;\n\n    int rc     = DECODE_FAIL_MIC;\n    int bitpos = 0;\n\n    while ((bitpos = fs20_find_preamble(bitbuffer, bitpos)) >= 0) {\n        decoder_logf(decoder, 2, __func__, \"Found preamble at %d\", bitpos);\n\n        struct parity_byte res;\n\n        res = get_byte(bits, bitpos);\n        if (res.err)\n            continue;\n        hc = res.data << 8;\n\n        res = get_byte(bits, bitpos + 9);\n        if (res.err)\n            continue;\n        hc |= res.data;\n\n        res = get_byte(bits, bitpos + 18);\n        if (res.err)\n            continue;\n        address = res.data;\n\n        res = get_byte(bits, bitpos + 27);\n        if (res.err)\n            continue;\n        cmd = res.data;\n\n        res = get_byte(bits, bitpos + 36);\n        if (res.err)\n            continue;\n\n        if (cmd & 0x20) {\n            ext = res.data;\n            if (bitpos + 45 + 9 > bitbuffer->bits_per_row[0])\n                break;\n\n            res = get_byte(bits, bitpos + 45);\n            if (res.err)\n                continue;\n        }\n        sum = res.data;\n\n        rc = 1;\n        break;\n    }\n\n    // propagate MIC\n    if (rc <= 0) {\n        return rc;\n    }\n\n    if (bitpos < 0) {\n        return bitpos;\n    }\n\n    // Sum is (HC1 + HC2 + Addr + Cmd [+ Ext] + Type + Repeater-Hopcount\n    // Type is either 6 for regular FS20 devices (switches, dimmers, ...)\n    // or 0xC for FHT (radiator valves)\n    sum -= hc >> 8;\n    sum -= hc & 0xff;\n    sum -= address;\n    sum -= cmd;\n    sum -= ext;\n\n    if ((sum < 6) || (sum > 0xC + 2)) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // convert address to fs20 format (base4+1)\n    for (uint8_t i = 0; i < 4; i++) {\n        ad_b4 += (address % 4 + 1) << i * 4;\n        address /= 4;\n    }\n\n    // convert housecode to fs20 format (base4+1)\n    for (uint8_t i = 0; i < 8; i++) {\n        hc_b4 += ((hc % 4) + 1) << i * 4;\n        hc /= 4;\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\", DATA_COND,  (sum < 0xc),    DATA_STRING,    \"FS20\",\n            \"model\",        \"\", DATA_COND, !(sum < 0xc),    DATA_STRING,    \"FHT\",\n            \"housecode\",    \"\", DATA_FORMAT, \"%x\", DATA_INT, hc_b4,\n            \"address\",      \"\", DATA_FORMAT, \"%x\", DATA_INT, ad_b4,\n            \"command\",      \"\", DATA_STRING, (sum < 0xc) ? cmd_tab[cmd & 0x1f] : fht_cmd_tab[cmd & 0xf],\n            \"flags\",        \"\", DATA_STRING, (sum < 0xc) ? flags_tab[cmd >> 5] : fht_flags_tab[cmd >> 5],\n            \"ext\",          \"\", DATA_FORMAT, \"%x\", DATA_INT, ext,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"housecode\",\n        \"address\",\n        \"command\",\n        \"flags\",\n        \"ext\",\n        NULL,\n};\n\nr_device const fs20 = {\n        .name        = \"FS20 / FHT\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 400,\n        .long_width  = 600,\n        .reset_limit = 9000,\n        .decode_fn   = &fs20_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ft004b.c",
    "content": "/** @file\n    FT-004-B Temperature Sensor.\n\n    Copyright (C) 2017 George Hopkins <george-hopkins@null.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nFT-004-B Temperature Sensor.\n\nThe sensor sends a packet every 60 seconds. Each frame of 46 bits\nis sent 3 times without padding/pauses.\n\n    Format: FFFFFFFF ???????? ???????? tttttttt TTT????? ??????\n       Fixed type code: 0xf4, Temperature (t=lsb, T=msb), Unknown (?)\n\n    {137} 2f cf 24 78 21 c8 bf 3c 91 e0 87 22 fc f2 47 82 1c 80\n    {137} 2f ce 24 72 a1 70 bf 38 91 ca 85 c2 fc e2 47 2a 17 00\n\nAligning at [..] (insert 2 bits) we get:\n\n         2f cf 24 78 21 c8 [..] 2f cf 24 78 21 c8 [..] 2f cf 24 78 21 c8\n         2f ce 24 72 a1 70 [..] 2f ce 24 72 a1 70 [..] 2f ce 24 72 a1 70\n\n*/\n\n#include \"decoder.h\"\n\nstatic int ft004b_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *msg;\n    float temperature;\n    data_t *data;\n\n    if (bitbuffer->bits_per_row[0] != 137 && bitbuffer->bits_per_row[0] != 138) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    /* take the majority of all 46 bits (pattern is sent 3 times) and reverse them */\n    msg = bitbuffer->bb[0];\n    for (int i = 0; i < (46 + 7) / 8; i++) {\n        uint8_t a = bitrow_get_byte(msg, i * 8);\n        uint8_t b = bitrow_get_byte(msg, i * 8 + 46);\n        uint8_t c = bitrow_get_byte(msg, i * 8 + 46 * 2);\n        msg[i]    = reverse8((a & b) | (b & c) | (a & c));\n    }\n\n    if (msg[0] != 0xf4)\n        return DECODE_FAIL_SANITY;\n\n    int temp_raw = ((msg[4] & 0x7) << 8) | msg[3];\n    temperature  = (temp_raw * 0.05f) - 40.0f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"FT-004B\",\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"temperature_C\",\n        NULL,\n};\n\nr_device const ft004b = {\n        .name        = \"FT-004-B Temperature Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1956,\n        .long_width  = 3900,\n        .gap_limit   = 4000,\n        .reset_limit = 4000,\n        .decode_fn   = &ft004b_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/funkbus.c",
    "content": "/** @file\n    Funkbus / Instafunk.\n\n    Copyright (C) 2021 Markus Sattler\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int funkbus_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nFunkbus / Instafunk.\n\nUsed by Berker, Gira, Jung and may more\ndeveloped by Insta GmbH.\n\n- Frequency: 433.42MHz\n- Preamble: 4000us\n- Short: 500us\n- Long: 1000us\n- Encoding: Differential Manchester Biphase-Mark (BP-M)\n\n      __ __       __    __ __    __\n     |     |     |  |  |     |  |  |\n    _|     |__ __|  |__|     |__|  |__.....\n     |  0  |  0  |  1  |  0  |  1  |\n\n- Mic: parity + lfsr with 8bit mask 0x8C shifted left by 2 bit\n- Bits: 48\n- Endian: LSB\n\nData layout:\n\n    TS II II IF FA AX\n\n- T: 4 bit type, there are multiple types\n- S: 4 bit subtype\n- I: 20 bit serial number\n- F: 2 bit r1, unknown\n- F: 1 bit bat, 1 == battery low\n- F: 2 bit r2,  // unknown\n- F: 3 bit command, button on the remote\n- A: 2 bit group, remote channel group 0-2 (A-C) are switches, 3 == light scene\n- A: 1 bit r3, unknown\n- A: 2 bit action, STOP, OFF, ON, SCENE\n- A: 1 bit repeat, 1 == not first send of packet\n- A: 1 bit longpress, longpress of button for (dim up/down, scene learning)\n- A: 1 bit parity, parity over all bits before\n- X: 4 bit check, LFSR with 8 bit mask 0x8C shifted left by 2 each bit\n\nSome details can be found by searching  \"instafunk RX/TX-Modul pdf\".\n*/\n\nstatic uint32_t get_bits_reflect(uint8_t const *bitrow, unsigned start, unsigned len)\n{\n    unsigned end = start + len - 1;\n    uint32_t result = 0;\n    uint32_t mask   = 1;\n    result          = 0;\n    for (; start <= end; mask <<= 1)\n        if (bitrow_get_bit(bitrow, start++) != 0)\n            result |= mask;\n    return result;\n}\n\nstatic uint8_t calc_checksum(uint8_t const *bitrow, unsigned len)\n{\n    const uint8_t full_bytes = len / 8;\n    const uint8_t bits_left  = len % 8;\n\n    uint8_t xor_byte = xor_bytes(bitrow, full_bytes);\n    // Mask out all unused lower bits from the last (partial) byte\n    uint8_t mask = 0xff << (8 - bits_left);\n    xor_byte ^= bitrow[full_bytes] & mask;\n\n    const uint8_t xor_nibble = ((xor_byte & 0xF0) >> 4) ^ (xor_byte & 0x0F);\n\n    uint8_t result = 0;\n    if (xor_nibble & 0x8) {\n        result ^= 0x8C;\n    }\n    if (xor_nibble & 0x4) {\n        result ^= 0x32;\n    }\n    if (xor_nibble & 0x2) {\n        result ^= 0xC8;\n    }\n    if (xor_nibble & 0x01) {\n        result ^= 0x23;\n    }\n\n    result = result & 0xF;\n    result |= (parity8(xor_byte) << 4);\n\n    return result;\n}\n\nstatic int funkbus_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int events = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] < 48) {\n            return DECODE_ABORT_LENGTH;\n        }\n\n        uint8_t *b = bitbuffer->bb[row];\n\n        int typ    = get_bits_reflect(b, 0, 4);\n        int subtyp = get_bits_reflect(b, 4, 4);\n\n        // only handle packet typ for remotes\n        if (typ != 0x4 || subtyp != 0x3) {\n            return DECODE_ABORT_EARLY;\n        }\n\n        int sn        = get_bits_reflect(b, 8, 20);\n        // int r1        = get_bits_reflect(b, 28, 2); // unknown\n        int bat       = get_bits_reflect(b, 30, 1); // 1 == battery low\n        // int r2        = get_bits_reflect(b, 31, 2); // unknown\n        int command   = get_bits_reflect(b, 33, 3); // button on the remote\n        int group     = get_bits_reflect(b, 36, 2); // remote channel group 0-2 (A-C) are switches, 3 == light scene\n        // int r3        = get_bits_reflect(b, 38, 1); // unknown\n        int action    = get_bits_reflect(b, 39, 2); // STOP, OFF, ON, SCENE\n        int repeat    = get_bits_reflect(b, 41, 1); // 1 == not first send of packet\n        int longpress = get_bits_reflect(b, 42, 1); // longpress of button for (dim up/down, scene learning)\n        int parity    = get_bits_reflect(b, 43, 1); // parity over all bits before\n        int check     = get_bits_reflect(b, 44, 4); // lfsr with 8bit mask 0x8C shifted left by 2 each bit\n\n        uint8_t checksum = calc_checksum(b, 43);\n        if (check != reflect4(checksum & 0xF) ||\n                parity != (checksum >> 4)) {\n            return DECODE_FAIL_MIC;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"\",                DATA_STRING, \"Funkbus-Remote\",\n                \"id\",           \"Serial number\",   DATA_INT, sn,\n                \"battery_ok\",   \"Battery\",         DATA_INT, bat ? 0 : 1,\n                \"command\",      \"Switch\",          DATA_INT, command,\n                \"group\",        \"Group\",           DATA_INT, group,\n                \"action\",       \"Action\",          DATA_INT, action,\n                \"repeat\",       \"Repeat\",          DATA_INT, repeat,\n                \"longpress\",    \"Longpress\",       DATA_INT, longpress,\n                \"mic\",          \"Integrity\",       DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        events++;\n    }\n\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"command\",\n        \"group\",\n        \"action\",\n        \"repeat\",\n        \"longpress\",\n        \"mic\",\n        NULL,\n};\n\nr_device const funkbus_remote = {\n        .name        = \"Funkbus / Instafunk (Berker, Gira, Jung)\",\n        .modulation  = OOK_PULSE_DMC,\n        .short_width = 500,\n        .long_width  = 1000,\n        .reset_limit = 2000,\n        .gap_limit   = 1500,\n        .sync_width  = 4000,\n        .tolerance   = 300, // us\n        .decode_fn   = &funkbus_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/gasmate_ba1008.c",
    "content": "/** @file\n    Gasmate BA1008 meat thermometer.\n\n    Copyright (C) 2023 Christian W. Zuckschwerdt <zany@triq.net>\n    based on protocol analysis by Lucy Winters\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nGasmate BA1008 meat thermometer.\n\nNotably this protocol does not feature ID or CHANNEL information.\n\nS.a. #2324\n\nData Layout:\n\n    PF TT ?? ?A\n\n- P: (4 bit) preamble/model/type? fixed 0xf\n- F: (4 bit) Unknown bit; Sign bit; 2-bit temperature 100ths (BCD)\n- T: (8 bit) temperature 10ths and 1ths (BCD)\n- ?: (12 bit) unknown value\n- A: (4 bit) checksum, nibble-wide add with carry\n\nRaw data:\n\n    F4040BFB [-04C]\n    F4060BF9 [-06C]\n    F4100BEF [-10C]\n    f0030ffc [+03C]\n    F0230FDC [+23C]\n    F0310FCE [+31C]\n\nFormat string:\n\n    PREAMBLE?h ?b SIGN:b TEMP:2hhhC ?hhh CHK:h\n\n*/\n\nstatic int gasmate_ba1008_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 1) {\n        decoder_log(decoder, 2, __func__, \"Row check fail\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int row = 0;\n    uint8_t *b = bitbuffer->bb[row];\n    // we expect 32 bits\n    if (bitbuffer->bits_per_row[row] != 32) {\n        decoder_log(decoder, 2, __func__, \"Length check fail\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // preamble/model/type and first flag bit check\n    if ((b[0] & 0xf8) != 0xf0) {\n        decoder_log(decoder, 2, __func__, \"Model check fail\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // check checksum\n    if ((add_nibbles(b, 4) & 0x0f) != 0x0c) {\n        decoder_log(decoder, 2, __func__, \"Checksum fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int sign     = (b[0] & 0x04) >> 2;\n    int huns     = (b[0] & 0x03);\n    int tens     = (b[1] & 0xf0) >> 4;\n    int ones     = (b[1] & 0x0f);\n    int temp_raw = huns * 100 + tens * 10 + ones;\n    int temp_c   = sign ? -temp_raw : temp_raw;\n    int unknown1 = (b[2] << 4) | (b[3] >> 4);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Gasmate-BA1008\",\n            \"temperature_C\",    \"Temperature_C\",    DATA_FORMAT, \"%d C\", DATA_INT, temp_c,\n            \"unknown_1\",        \"Unknown Value\",    DATA_FORMAT, \"%03x\", DATA_INT,    unknown1,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"temperature_C\",\n        \"unknown_1\",\n        \"mic\",\n        NULL,\n};\n\nr_device const gasmate_ba1008 = {\n        .name        = \"Gasmate BA1008 meat thermometer\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 536,\n        .long_width  = 1668,\n        .reset_limit = 2000,\n        .decode_fn   = &gasmate_ba1008_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ge_coloreffects.c",
    "content": "/** @file\n    GE Color Effects Remote.\n\n    Copyright (C) 2017 Luke Cyca <me@lukecyca.com>, Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/** @fn int ge_coloreffects_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned start_pos)\nGE Color Effects Remote.\n\nPrevious work decoding this device:\n- https://lukecyca.com/2013/g35-rf-remote.html\n- http://www.deepdarc.com/2010/11/27/hacking-christmas-lights/\n*/\n\n#include \"decoder.h\"\n\n// Helper to access single bit (copied from bitbuffer.c)\nstatic inline int bit(const uint8_t *bytes, unsigned b)\n{\n    return bytes[b >> 3] >> (7 - (b & 7)) & 1;\n}\n\n/**\nDecodes the following encoding scheme:\n- 10 = 0\n- 1100 = 1\n*/\nstatic unsigned ge_decode(bitbuffer_t *inbuf, unsigned row, unsigned start, bitbuffer_t *outbuf)\n{\n    uint8_t *bits = inbuf->bb[row];\n    unsigned int len = inbuf->bits_per_row[row];\n    unsigned int ipos = start;\n\n    while (ipos < len) {\n        uint8_t bit1 = bit(bits, ipos++);\n        uint8_t bit2 = bit(bits, ipos++);\n\n        if (bit1 == 1 && bit2 == 0) {\n            bitbuffer_add_bit(outbuf, 0);\n        } else if (bit1 == 1 && bit2 == 1) {\n            // Get two more bits\n            bit1 = bit(bits, ipos++);\n            bit2 = bit(bits, ipos++);\n            if (bit1 == 0 && bit2 == 0) {\n                bitbuffer_add_bit(outbuf, 1);\n            } else {\n                break;\n            }\n        } else {\n            break;\n        }\n    }\n\n    return ipos;\n}\n\nstatic int ge_coloreffects_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned start_pos)\n{\n    data_t *data;\n    bitbuffer_t packet_bits = {0};\n\n    ge_decode(bitbuffer, row, start_pos, &packet_bits);\n    //decoder_log_bitbuffer(decoder, 0, __func__, &packet_bits, \"\");\n\n    /* From http://www.deepdarc.com/2010/11/27/hacking-christmas-lights/\n     * Decoded frame format is:\n     *   Preamble\n     *   Two zero bits\n     *   6-bit Device ID (Can be modified by adding R15-R20 on the large PCB)\n     *   8-bit Command\n     *   One zero bit\n     */\n\n    // Frame should be 17 decoded bits (not including preamble)\n    if (packet_bits.bits_per_row[0] != 17)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = packet_bits.bb[0];\n\n    // First two bits must be 0\n    if (b[0] & 0xc0)\n        return DECODE_FAIL_SANITY;\n\n    // Last bit must be 0\n    if (b[2] & 0x80)\n        return DECODE_FAIL_SANITY;\n\n    // Extract device ID\n    // We want bits [2..8]. Since the first two bits are zero, we'll just take the entire first byte\n    int device_id = b[0];\n\n    // Extract command from the second byte\n    uint8_t command = b[1];\n\n    char cmd[7];\n    switch (command) {\n    case 0x5a: snprintf(cmd, sizeof(cmd), \"change\"); break;\n    case 0xaa: snprintf(cmd, sizeof(cmd), \"on\"); break;\n    case 0x55: snprintf(cmd, sizeof(cmd), \"off\"); break;\n    default:\n        snprintf(cmd, sizeof(cmd), \"0x%x\", command);\n        break;\n    }\n\n    // Format data\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",     DATA_STRING, \"GE-ColorEffects\",\n            \"id\",           \"\",     DATA_FORMAT, \"0x%x\", DATA_INT, device_id,\n            \"command\",      \"\",     DATA_STRING, cmd,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nGE Color Effects Remote.\n@sa ge_coloreffects_decode()\n*/\nstatic int ge_coloreffects_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Frame preamble:\n    // 11001100 11001100 11001100 11001100 11001100 11111111 00000000\n    // c   c    c   c    c   c    c   c    c   c    f   f    0   0\n    uint8_t const preamble_pattern[3] = {0xcc, 0xff, 0x00};\n    // Sync pulse/gap might be sliced short\n    uint8_t const preamble_pattern2[3] = {0xcc, 0xfe, 0x00};\n\n    unsigned bitpos = 0;\n    unsigned found  = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    // (if the device id and command were all zeros)\n    while ((found = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 24) + 24) + 33 <=\n                    bitbuffer->bits_per_row[0]\n            || (found = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 23) + 23) + 33 <=\n                    bitbuffer->bits_per_row[0]\n            || (found = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern2, 23) + 23) + 33 <=\n                    bitbuffer->bits_per_row[0]\n            || (found = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern2, 22) + 22) + 33 <=\n                    bitbuffer->bits_per_row[0]) {\n        bitpos = found;\n        ret = ge_coloreffects_decode(decoder, bitbuffer, 0, bitpos);\n        if (ret > 0)\n            events += ret;\n        bitpos++;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"command\",\n        NULL,\n};\n\nr_device const ge_coloreffects = {\n        .name        = \"GE Color Effects\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 450, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &ge_coloreffects_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/geevon.c",
    "content": "/** @file\n    Geevon TX16-3 Remote Outdoor Sensor with LCD Display.\n\n    Contributed by Matt Falcon <falcon4@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nGeevon TX16-3 Remote Outdoor Sensor with LCD Display.\n\nNote that Geevon TX16-3 and Geevon TX19-1 are identical except for the checksum.\n\nThis device is a simple temperature/humidity transmitter with a small LCD display for local viewing.\n\nThe test packet represents:\n- channel 1\n- battery OK\n- temperature of 62.6 Fahrenheit or 17 Celsius\n- 43% relative humidity.\n\nData layout:\n\n    Byte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6   Byte 7   Byte 8\n    IIIIIIII BxCCxxxx TTTTTTTT TTTT0000 HHHHHHHH FFFFFFFF FFFFFFFF FFFFFFFF CCCCCCCC\n       87       00       29       e0       2b       aa       55       aa       e8\n\n- I: ID?\n- B: Battery low status (0 = good, 1 = low battery)\n- C: Channel (0, 1, 2 as channels 1, 2, 3)\n- T: Temperature - represented as ((degrees C * 10) + 500)\n- H: Relative humidity - represented as percentage %\n- F: Integrity check - 3 bytes are always 0xAA 0x55 0xAA\n- X: CRC checksum (CRC-8 poly 0x31 init=0x7b)\n\nFormat string:\n\n    ID:8h BATT:b ?:b CHAN:2h FLAGS:4h TEMP_C:12d PAD:4h HUM:8d FIX:24h CRC:8h 1x\n\nExample packets:\n\n    f4002ac039aa55aa11\n    f4002ab039aa55aa54\n    f4002aa039aa55aa28\n    f4002a9039aa55aaac\n\n*/\n\nstatic int geevon_tx16_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // invert all the bits\n    bitbuffer_invert(bitbuffer);\n\n    // find the most common row, nominal we expect 5 packets\n    int r = bitbuffer_find_repeated_prefix(bitbuffer, bitbuffer->num_rows > 5 ? 5 : 3, 72);\n    if (r < 0) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // work with the best/most repeated capture\n    uint8_t *b = bitbuffer->bb[r];\n\n    // Check if the packet has the correct number of bits\n    if (bitbuffer->bits_per_row[r] != 73) {\n        decoder_log(decoder, 1, __func__, \"Bit length did NOT match.\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Check if the fixed bytes are correct\n    if (b[5] != 0xAA || b[6] != 0x55 || b[7] != 0xAA) {\n        decoder_log(decoder, 1, __func__, \"Fixed bytes did NOT match.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Verify CRC checksum\n    uint8_t chk = crc8(b, 9, 0x31, 0x7b);\n    if (chk) {\n        decoder_log(decoder, 1, __func__, \"Checksum did NOT match.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Extract the data from the packet\n    int battery_low = (b[1] >> 7);              // 0x00: battery good, 0x80: battery low\n    int channel     = ((b[1] & 0x30) >> 4) + 1; // channel: 1, 2, 3\n    int temp_raw    = (b[2] << 4) | (b[3] >> 4);\n    float temp_c    = (temp_raw - 500) * 0.1f; // temperature is ((degrees c + 500) * 10)\n    int humidity    = b[4];\n\n    // Store the decoded data\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Geevon-TX163\",\n            \"id\",               \"\",             DATA_INT,    b[0],\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT,     humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"battery\",\n        \"channel\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const geevon_tx16 = {\n        .name        = \"Geevon TX16-3 outdoor sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 250,\n        .long_width  = 500,\n        .sync_width  = 750,  // sync pulse is 728 us + 728 us gap\n        .gap_limit   = 625,  // long gap (with short pulse) is ~472 us, sync gap is ~728 us\n        .reset_limit = 1700, // maximum gap is 1250 us (long gap + longer sync gap on last repeat)\n        .decode_fn   = &geevon_tx16_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/geevon_tx19.c",
    "content": "/** @file\n    Geevon TX19-1 Remote Outdoor Sensor with LCD Display.\n\n    Contributed by Matt Falcon <falcon4@gmail.com>\n    Analyzed by \\@mattigins\n    Copyright (C) 2026 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nGeevon TX19-1 Remote Outdoor Sensor with LCD Display.\n\nNote that Geevon TX16-3 and Geevon TX19-1 are identical except for the checksum.\n\nThis device is a simple temperature/humidity transmitter with a small LCD display for local viewing.\n\nThe test packet represents:\n- id 138\n- channel 1\n- battery OK\n- temperature of 25.5 Celsius\n- 81% relative humidity.\n\nData layout:\n\n    Byte 0   Byte 1   Byte 2   Byte 3   Byte 4   Byte 5   Byte 6   Byte 7   Byte 8\n    IIIIIIII BxCCxxxx TTTTTTTT TTTT0000 HHHHHHHH FFFFFFFF FFFFFFFF FFFFFFFF CCCCCCCC\n       8a       00       2f       30       51       aa       55       aa       b3\n\n- I: ID?\n- B: Battery low status (0 = good, 1 = low battery)\n- C: Channel (0, 1, 2 as channels 1, 2, 3)\n- T: Temperature - represented as ((degrees C * 10) + 500)\n- H: Relative humidity - represented as percentage %\n- F: Integrity check - 3 bytes are always 0xAA 0x55 0xAA\n- X: LFSR checksum (Galois bit reflected, generator 0x98 key 0x25)\n\nFormat string:\n\n    ID:8h BATT:b ?:b CHAN:2h FLAGS:4h TEMP_C:12d PAD:4h HUM:8d FIX:24h CRC:8h 1x\n\nExample packets:\n\n    {73}75ffd0cfae55aa554c8\n    {73}75ffd20fac55aa55978\n    {73}75ffd28fa955aa551e8\n    {73}75ffd31fa755aa55538\n    {73}75ffd32fa455aa552e8\n    {73}75ffd2efa555aa55908\n    {73}75ffd2cfa555aa55688\n*/\n\nstatic int geevon_tx19_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // invert all the bits\n    bitbuffer_invert(bitbuffer);\n\n    // find the most common row, nominal we expect 5 packets\n    int r = bitbuffer_find_repeated_prefix(bitbuffer, bitbuffer->num_rows > 5 ? 5 : 3, 72);\n    if (r < 0) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // work with the best/most repeated capture\n    uint8_t *b = bitbuffer->bb[r];\n\n    // Check if the packet has the correct number of bits\n    if (bitbuffer->bits_per_row[r] != 73) {\n        decoder_log(decoder, 1, __func__, \"Bit length did NOT match.\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Check if the fixed bytes are correct\n    if (b[5] != 0xAA || b[6] != 0x55 || b[7] != 0xAA) {\n        decoder_log(decoder, 1, __func__, \"Fixed bytes did NOT match.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Verify LFSR checksum\n    uint8_t chk = lfsr_digest8_reverse(b, 8, 0x98, 0x25);\n    if (chk != b[8]) {\n        decoder_log(decoder, 1, __func__, \"Checksum did NOT match.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Extract the data from the packet\n    int battery_low = (b[1] >> 7);              // 0x00: battery good, 0x80: battery low\n    int channel     = ((b[1] & 0x30) >> 4) + 1; // channel: 1, 2, 3\n    int temp_raw    = (b[2] << 4) | (b[3] >> 4);\n    float temp_c    = (temp_raw - 500) * 0.1f; // temperature is ((degrees c + 500) * 10)\n    int humidity    = b[4];\n\n    // Store the decoded data\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Geevon-TX191\",\n            \"id\",               \"\",             DATA_INT,    b[0],\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT,     humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"battery\",\n        \"channel\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const geevon_tx19 = {\n        .name        = \"Geevon TX19-1 outdoor sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 250,\n        .long_width  = 500,\n        .sync_width  = 750,  // sync pulse is 728 us + 728 us gap\n        .gap_limit   = 625,  // long gap (with short pulse) is ~472 us, sync gap is ~728 us\n        .reset_limit = 1700, // maximum gap is 1250 us (long gap + longer sync gap on last repeat)\n        .decode_fn   = &geevon_tx19_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/generic_motion.c",
    "content": "/** @file\n    Generic off-brand wireless motion sensor and alarm system on 433.3MHz.\n\n    Copyright (C) 2015 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGeneric off-brand wireless motion sensor and alarm system on 433.3MHz.\n\nExample codes are: 80042 Arm alarm, 80002 Disarm alarm,\n80008 System ping (every 15 minutes), 800a2, 800c2, 800e2 Motion event\n(following motion detection the sensor will blackout for 90 seconds).\n\n2315 baud on/off rate and alternating 579 baud bit rate and 463 baud bit rate\nEach transmission has a warm-up of 17 to 32 pulse widths then 8 packets with\nalternating 1:3 / 2:2 or 1:4 / 2:3 gap:pulse ratio for 0/1 bit in the packet\nwith a repeat gap of 4 pulse widths, i.e.:\n- 6704 us to 13092 us warm-up pulse, 1672 us gap,\n- 0: 472 us gap, 1332 us pulse\n- 1: 920 us gap, 888 us pulse\n- 1672 us repeat gap,\n- 0: 472 us gap, 1784 us pulse\n- 1: 920 us gap, 1332 us pulse\n- ...\n*/\n\n#include \"decoder.h\"\n\nstatic int generic_motion_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    for (int i = 0; i < bitbuffer->num_rows; ++i) {\n        uint8_t *b = bitbuffer->bb[i];\n        // strictly validate package as there is no checksum\n        if ((bitbuffer->bits_per_row[i] != 20)\n                || ((b[1] == 0) && (b[2] == 0))\n                || ((b[1] == 0xff) && (b[2] == 0xf0))\n                || bitbuffer_count_repeats(bitbuffer, i, 0) < 3)\n            continue; // DECODE_ABORT_EARLY\n\n        int code = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4);\n        char code_str[6];\n        snprintf(code_str, sizeof(code_str), \"%05x\", code);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",    \"\",  DATA_STRING, \"Generic-Motion\",\n                \"code\",     \"\",  DATA_STRING, code_str,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return DECODE_ABORT_EARLY;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"code\",\n        NULL,\n};\n\nr_device const generic_motion = {\n        .name        = \"Generic wireless motion sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 888,\n        .long_width  = (1332 + 1784) / 2,\n        .sync_width  = 1784 + 670,\n        .gap_limit   = 1200,\n        .reset_limit = 2724 * 1.5,\n        .decode_fn   = &generic_motion_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/generic_remote.c",
    "content": "/** @file\n    Generic remotes and sensors using PT2260/PT2262 SC2260/SC2262 EV1527 protocol.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2015 nebman\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGeneric remotes and sensors using PT2260/PT2262 SC2260/SC2262 EV1527 protocol.\n\nTested devices:\n- SC2260\n- EV1527\n*/\n\n#include \"decoder.h\"\n\nstatic int generic_remote_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[0];\n    char tristate[23];\n    char *p = tristate;\n\n    //invert bits, short pulse is 0, long pulse is 1\n    b[0] = ~b[0];\n    b[1] = ~b[1];\n    b[2] = ~b[2];\n\n    unsigned bits = bitbuffer->bits_per_row[0];\n\n    // Validate package\n    if ((bits != 25)\n            || (b[3] & 0x80) == 0 // Last bit (MSB here) is always 1\n            || (b[0] == 0 && b[1] == 0) // Reduce false positives. ID 0x0000 not supported\n            || (b[2] == 0)) // Reduce false positives. CMD 0x00 not supported\n        return DECODE_ABORT_LENGTH;\n\n    int id_16b = b[0] << 8 | b[1];\n    int cmd_8b = b[2];\n\n    // output tristate coding\n    uint32_t full = b[0] << 16 | b[1] << 8 | b[2];\n\n    for (int i = 22; i >= 0; i -= 2) {\n        switch ((full >> i) & 0x03) {\n        case 0x00: *p++ = '0'; break;\n        case 0x01: *p++ = 'Z'; break; // floating / \"open\"\n        case 0x02: *p++ = 'X'; break; // tristate 10 is invalid code for SC226x but valid in EV1527\n        case 0x03: *p++ = '1'; break;\n        default:   *p++ = '?'; break; // not possible anyway\n        }\n    }\n    *p = '\\0';\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"Generic-Remote\",\n            \"id\",           \"House Code\",   DATA_INT, id_16b,\n            \"cmd\",          \"Command\",      DATA_INT, cmd_8b,\n            \"tristate\",     \"Tri-State\",    DATA_STRING, tristate,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"cmd\",\n        \"tristate\",\n        NULL,\n};\n\nr_device const generic_remote = {\n        .name        = \"Generic Remote SC226x EV1527\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 464,\n        .long_width  = 1404,\n        .reset_limit = 1800,\n        .sync_width  = 0,   // No sync bit used\n        .tolerance   = 200, // us\n        .decode_fn   = &generic_remote_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/generic_temperature_sensor.c",
    "content": "/** @file\n    Generic temperature sensor 1.\n\n    Copyright (C) 2015 Alexandre Coffignal\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGeneric temperature sensor 1.\n\n10 24 bits frames:\n\n    IIIIIIII BBTTTTTT TTTTTTTT\n\n- I: 8 bit ID\n- B: 2 bit? Battery ?\n- T: 12 bit Temp\n*/\n\n#include \"decoder.h\"\n\nstatic int generic_temperature_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[1];\n    int i, device, battery, temp_raw;\n    float temp_f;\n\n    for (i = 1; i < 10; i++) {\n        if (bitbuffer->bits_per_row[i] != 24) {\n            /*10 24 bits frame*/\n            return DECODE_ABORT_LENGTH;\n        }\n    }\n\n    // reduce false positives\n    if ((b[0] == 0 && b[1] == 0 && b[2] == 0)\n            || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff)) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    device  = (b[0]);\n    battery = (b[1] & 0xC0) >> 6;\n    temp_raw = (int16_t)(((b[1] & 0x3f) << 10) | (b[2] << 2));\n    temp_f  = (temp_raw >> 4) * 0.1f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING,    \"Generic-Temperature\",\n            \"id\",               \"Id\",           DATA_INT,       device,\n            \"battery_ok\",       \"Battery?\",     DATA_INT,       battery,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT,    \"%.2f C\",  DATA_DOUBLE,    temp_f,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        NULL,\n};\n\nr_device const generic_temperature_sensor = {\n        .name        = \"Generic temperature sensor 1\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 4800,\n        .reset_limit = 10000,\n        .decode_fn   = &generic_temperature_sensor_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/geo_minim.c",
    "content": "/** @file\n    GEO mimim+ energy monitor.\n\n    Copyright (C) 2022 Lawrence Rust, lvr at softsystem dot co dot uk\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/** @fn int minim_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nGEO mimim+ energy monitor.\n\n@warning This decoder depends on `mktime()` formatting.\n\n@sa geo_minim_ct_sensor_decode()\n@sa geo_minim_display_decode()\n\nThe GEO minim+ energy monitor comprises a sensor unit and a display unit.\nhttps://assets.geotogether.com/sites/4/20170719152420/Minim-Data-sheet.pdf\n\nThe sensor unit is supplied with a detachable current transformer that is\nclipped around the live wire feeding the monitored device. The sensor unit\nis powered by 3x AA batteries that provide for ~2 years of operation. It\ntransmits a short (5mS) data packet every ~3 seconds.\n\nFrequency 868.29 MHz, bit period 25 microseconds (40kbps), modulation FSK_PCM\n\nThe display unit requires a 5V supply, provided by the supplied mains/USB\nadapter. The display and sensor units are paired during initial power on\nor as follows:\n\n1. On the display, hold down the <- and +> buttons together for 3 seconds.\n2. At the next screen, hold down the middle button for 3 seconds until the\n   display shows \"Pair?\"\n3. On the sensor, press and hold the pair button (next to the red light)\n   until the red LED light illuminates.\n4. Release the pair button and the LED flashes as the transmitter pairs.\n5. The display should now read \"Paired CT\"\n\nWhen paired the display listens for sensor packets and then transmits a\nsummary packet using the same protocol.\n\nPacket types:\n\nThe first three header bytes are not identified but should be related to\nmessage type, session ID from pairing. Seen so far:\n\n    3f 06 29 05 // GEO minim+ current sensor\n    fb 06 81 05 // GEO minim+ current sensor\n    ea 01 35 2a // GEO minim+ display\n    da c1 35 2a // GEO minim+ display\n\nThen a byte of packet length 0x05 or 0x2a follows.\n\nThe following Flex decoder will capture the raw data:\n\n    rtl_433 -f 868.29M -s 1024k -Y classic -X 'n=minim+,m=FSK_PCM,s=24,l=24,r=3000,preamble=0x7bb9'\n*/\n\n#include <time.h>\n\n#include \"decoder.h\"\n\n/**\nGEO minim+ current sensor.\n\nPacket layout:\n\n- 24 bit preamble of alternating 0s and 1s\n- 2 sync bytes: 0x7b 0xb9\n- 3 byte header: contents unknown so far\n- 1 byte packet length: 0x05\n- 5 data bytes\n- CRC16\n\nThe following Flex decoder will capture the raw sensor data:\n\n    rtl_433 -f 868.29M -s 1024k -Y classic -X 'n=minim+ sensor,m=FSK_PCM,s=24,l=24,r=3000,preamble=0x7bb93f'\n\nData format string:\n\n    ID:24h VA:13d 3x UP:24d CRC:16h\n\n    VA: Big endian power x10VA, bit14 = 5VA\n    UP: Big endian uptime x9 seconds\n*/\nstatic int geo_minim_ct_sensor_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const buf[], unsigned len)\n{\n    (void)bitbuffer;\n\n    if (buf[3] != 5) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf, 8 * len,\n                \"Incorrect length. Expected payload of 5 got %u bytes\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if (len != 11) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf, 8 * len,\n            \"Incorrect length. Expected packet 11 got %u bytes\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    char id[7];\n    snprintf(id, sizeof(id), \"%02X%02X%02X\", buf[0], buf[1], buf[2]);\n\n    // Uptime in ~8 second intervals\n    unsigned uptime_raw = (buf[6] << 16) + (buf[7] << 8) + buf[8];\n    unsigned uptime_s = 8 * uptime_raw;\n\n    // Bytes 4 & 5 appear to be the instantaneous VA x10.\n    // When scaled by the 'Fine Tune' setting (power factor [0.88]) set on the\n    // display unit it matches the Watts value in display messages.\n    unsigned va = 10 * (buf[5] + ((buf[4] & 0x0f) << 8));\n    if (buf[4] & 0x40)\n        va += 5;\n\n    // TODO: what are the flag bits in buf[4] (0x30)? Battery OK, Fault?\n    unsigned flags4 = buf[4] & ~0x4f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"GEO-minimCT\",\n            \"id\",           \"\",             DATA_STRING, id,\n            \"power_VA\",     \"Power\",        DATA_FORMAT, \"%u VA\", DATA_INT, va,\n            \"flags4\",       \"Flags\",        DATA_COND, flags4 != 0x30, DATA_FORMAT, \"%#x\", DATA_INT, flags4,\n            \"uptime_s\",     \"Uptime\",       DATA_INT, uptime_s,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1; // Message successfully decoded\n}\n\n/**\nGEO minim+ display.\n\nPacket layout:\n\n- 24 bit preamble of alternating 0s and 1s\n- 2 sync bytes: 0x7b 0xb9\n- 3 byte header: contents unknown so far\n- 1 byte packet length: 0x2a (=42)\n- 42 data bytes\n- CRC16\n\nThe following Flex decoder will capture the raw display data:\n\n    rtl_433 -f 868.29M -s 1024k -Y classic -X 'n=minim+ display,m=FSK_PCM,s=24,l=24,r=3000,preamble=0x7bb9ea'\n\nData format string:\n\n    ID:24h PWR:15d 1x 64x WH:11d 5x 64x 48x MIN:8d HRS:8d DAYS:16d 96x CRC:16h\n\n    PWR: Instantaneous power, little endian\n    WH: Watt-hours in last 15 minutes, little endian\n    MIN,HRS,DAYs since 1/1/2007, little endian\n*/\nstatic int geo_minim_display_decode(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t const buf[], unsigned len)\n{\n    (void)bitbuffer;\n\n    uint8_t const zeroes[8] = { 0 };\n    uint8_t const aaes[5] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa };\n    uint8_t const trailer[12] = { 0xaa, 0xff, 0xff, 0, 0, 0, 0, 0xaa, 0xff, 0xaa, 0xaa, 0 };\n\n    if (buf[3] != 42) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf, 8 * len,\n                \"Incorrect length. Expected payload of 42 got %u bytes\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if (len != 48) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf, 8 * len,\n                \"Incorrect length. Expected 48, got %u bytes\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Report unexpected values\n    if (memcmp(zeroes, buf + 6, sizeof(zeroes))) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf + 6, 8 * sizeof(zeroes),\n                \"Nonzero @6\");\n        //return DECODE_FAIL_SANITY;\n    }\n\n    if (memcmp(zeroes, buf + 16, sizeof(zeroes))) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf + 16, 8 * sizeof(zeroes),\n                \"Nonzero @16\");\n        //return DECODE_FAIL_SANITY;\n    }\n\n    if (memcmp(aaes, buf + 24, sizeof(aaes))) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf + 24, 8 * sizeof(aaes),\n                \"Not 0xaa @24\");\n        //return DECODE_FAIL_SANITY;\n    }\n\n    if (buf[29] != 0x00) {\n        decoder_logf(decoder, 1, __func__,\n                \"Expected 0x00 but got %#x @29\", buf[29]);\n        //return DECODE_FAIL_SANITY;\n    }\n\n    if (memcmp(trailer, buf + 34, sizeof(trailer))) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf + 34, 8 * sizeof(trailer),\n                \"Bad trailer @34\");\n        //return DECODE_FAIL_SANITY;\n    }\n\n    char id[7];\n    snprintf(id, sizeof(id), \"%02X%02X%02X\", buf[0], buf[1], buf[2]);\n\n    // Instantaneous power: 300W => 60: 1 = 5W\n    unsigned watts = 5 * (buf[4] + ((buf[5] & 0x7f) << 8));\n    // TODO: what is bit7?\n    unsigned flags5 = buf[5] & ~0x7f;\n\n    // Energy: 480W => 8/min: 1 = 0.06kWm = 0.001kWh\n    unsigned wh = buf[14] + ((buf[15] & 0x7) << 8);\n    // TODO: what are bits 3..7 ? 0x40 normally, Battery OK, Fault?\n    unsigned flags15 = buf[15] & ~0x7;\n\n    struct tm t = {0};\n    // Date/time @30..33\n    t.tm_sec = 0;\n    t.tm_min = buf[33] & 0x3f;\n    t.tm_hour = buf[32] & 0x1f;\n    // Day 0 = 1/1/2007\n    t.tm_mday = 1 + buf[30] + (buf[31] << 8);\n    t.tm_mon = 1 - 1;\n    t.tm_year = 2007 - 1900;\n    t.tm_isdst = -1;\n    // Normalise the date\n    mktime(&t);\n    char now[64];\n    snprintf(now, sizeof(now), \"%04d-%02d-%02d %02d:%02d\",\n            1900 + t.tm_year, 1 + t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"GEO-minimDP\",\n            \"id\",           \"\",             DATA_STRING, id,\n            \"power_W\",      \"Power\",        DATA_FORMAT, \"%u W\", DATA_INT, watts,\n            \"energy_kWh\",   \"Energy\",       DATA_FORMAT, \"%.3f kWh\", DATA_DOUBLE, wh * 0.001,\n            \"clock\",         \"Clock\",         DATA_STRING, now,\n            \"flags5\",       \"Flags5\",       DATA_COND, flags5 != 0, DATA_FORMAT, \"%#x\", DATA_INT, flags5,\n            \"flags15\",      \"Flags15\",      DATA_COND, flags15 != 0x40, DATA_FORMAT, \"%#x\", DATA_INT, flags15,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1; // Message successfully decoded\n}\n\n// packet type magic numbers\n#define MLEN_DISPLAY 0x2a\n#define MLEN_CT 0x05\n\nstatic int minim_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble and sync can be aaaa7bb9 or 55557bb9\n    uint8_t const preamble1[] = { 0xaa, 0xaa, 0x7b, 0xb9 };\n    uint8_t const preamble2[] = { 0x55, 0x55, 0x7b, 0xb9 };\n    const unsigned preamble_len = 8 * sizeof(preamble1);\n\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_LENGTH;\n\n    unsigned row = 0; // we expect only one row\n\n    // Search preamble+sync, try alternative\n    unsigned bitpos = bitbuffer_search(bitbuffer, row, 0, preamble1, preamble_len) + preamble_len;\n    if (bitpos >= bitbuffer->bits_per_row[row]) {\n        bitpos = bitbuffer_search(bitbuffer, row, 0, preamble2, preamble_len) + preamble_len;\n    }\n    if (bitpos >= bitbuffer->bits_per_row[row]) {\n        decoder_logf_bitbuffer(decoder, 3, __func__, bitbuffer, \"Sync not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned bits = bitbuffer->bits_per_row[row];\n\n    // Extract frame header\n    unsigned const hdr_len = 4;\n    unsigned const hdr_bits = hdr_len * 8;\n    if (bitpos + hdr_bits >= bits)\n        return DECODE_ABORT_LENGTH;\n\n    bits -= bitpos;\n    uint8_t buf[128];\n    bitbuffer_extract_bytes(bitbuffer, row, bitpos, buf, hdr_bits);\n\n    // Determine frame type based on packet length\n    int data_length = buf[3];\n    if (data_length != MLEN_DISPLAY && data_length != MLEN_CT) {\n        decoder_logf(decoder, 1, __func__,\n                \"Unknown header %02x%02x%02x%02x\",\n                buf[0], buf[1], buf[2], buf[3]);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned bytes = bits / 8;\n    unsigned maxlen = sizeof(buf);\n    if (bytes > maxlen) {\n        decoder_logf(decoder, 1, __func__,\n                \"Too big: %u > %u max bytes\", bits / 8, maxlen);\n        //return DECODE_ABORT_LENGTH;\n        bytes = maxlen;\n    }\n\n    // Check offset to crc16 using data_len @ header[3]\n    unsigned crc_len = hdr_len + buf[3];\n    if (crc_len + 2 > bytes) {\n        decoder_logf(decoder, 1, __func__,\n                \"Truncated - got %u of %u bytes\", bytes, crc_len + 2);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Extract byte-aligned data\n    bitbuffer_extract_bytes(bitbuffer, row, bitpos + hdr_bits, buf + hdr_len, (bytes - hdr_len) * 8);\n\n    // Message Integrity Check\n    unsigned crc = crc16(buf, crc_len, 0x8005, 0);\n    unsigned crc_rcvd = (buf[crc_len] << 8) | buf[crc_len + 1];\n    if (crc != crc_rcvd) {\n        decoder_logf_bitrow(decoder, 1, __func__, buf, (crc_len + 2) * 8,\n                \"Bad CRC. Expected %04X got %04X\", crc, crc_rcvd);\n        return DECODE_FAIL_MIC;\n    }\n\n    if (data_length == MLEN_DISPLAY) {\n        return geo_minim_display_decode(decoder, bitbuffer, buf, bytes);\n    }\n    if (data_length == MLEN_CT) {\n        return geo_minim_ct_sensor_decode(decoder, bitbuffer, buf, bytes);\n    }\n\n    return DECODE_FAIL_SANITY;\n}\n\n// List of fields to appear in the `-F csv` output.\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"power_VA\",\n        \"flags4\",\n        \"uptime_s\",\n        \"power_W\",\n        \"energy_kWh\",\n        \"clock\",\n        \"flags5\",\n        \"flags15\",\n        \"mic\",\n        NULL,\n};\n\nr_device const geo_minim = {\n        .name           = \"GEO minim+ energy monitor\",\n        .modulation     = FSK_PULSE_PCM,\n        .short_width    = 24,\n        .long_width     = 24,\n        .reset_limit    = 3000,\n        .decode_fn      = &minim_decode,\n        .fields         = output_fields,\n};\n"
  },
  {
    "path": "src/devices/govee.c",
    "content": "/** @file\n    Govee Water Leak Detector H5054, Door Contact Sensor B5023.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGovee Water Leak Detector H5054, Door Contact Sensor B5023.\n\nSee https://www.govee.com/\n\nGovee Water Leak Detector H5054:\nhttps://www.govee.com/products/110/water-leak-detector\n\nGovee Door Contact Sensor B5023:\nhttps://www.govee.com/products/27/govee-door-contact-sensor\nhttps://www.govee.com/products/154/door-open-chimes-2-pack\n\nNOTE: The Govee Door Contact sensors only send a message when the contact\n      is opened.\n      Unfortunately, it does NOT send a message when the contact is closed.\n\nData layout:\n\n    II II ?E DD ?? XX\n\n- A data packet is 6 bytes, 48 bits.\n- Bits are likely inverted (short=0, long=1)\n- First 2 bytes are the ID.\n- The upper nibble of byte 3 is unknown.\n  This upper nibble of the Water Leak Sensor is always 0.\n  This upper nibble of the Contact Sensor changes on different\n  Contact sensors, so perhaps it is a continuation of the ID?\n- The lower nibble of byte 3 is the ACTION/EVENT.\n- Byte 4 is the ACTION/EVENT data; battery percentage gauge for event 0xC.\n- Byte 5 is unknown.\n- Last byte contains the parity bits in index 2-6 (101PPPP1).\n  The parity checksum using CRC8 against the first 5 bytes\n\nData decoding:\n\n    ID:8h8h ?4h EVENT:4h EVENTDATA:8h ?8h CHK:3b 4h 1b\n\nBattery levels:\n\n- 100 : 5 Bars\n- 095 : 4 Bars\n- 059 : 4 Bars\n- 026 : 3 Bars\n- 024 : 2 Bars\n- 001 : 1 Bars\n\nRaw data used to select checksum algorithm (after inverting to match used data):\n\n    Binary Data: 01101111 00111010 11111010 11111010 11111000 10101111\n    Parity value from last byte: 0111\n\n    Binary Data: 01101110 00011001 11111010 11111010 11111000 10101111\n    Parity value from last byte: 0111\n\n    Binary Data: 01011100 01100110 11111010 11111010 11111000 10111101\n    Parity value from last byte: 1110\n\n    Binary Data: 01101101 00011110 11111010 11111010 11111000 10100111\n    Parity value from last byte: 0011\n\n    Binary Data: 01100111 11111001 11111010 11111010 11111000 10100001\n    Parity value from last byte: 0000\n\n    Binary Data: 01101110 00101101 11111010 11111010 11111000 10100001\n    Parity value from last byte: 0000\n\n    Binary Data: 01011100 00000111 11111010 11111010 11111000 10110011\n    Parity value from last byte: 1001\n\n    Binary Data: 01101110 01000010 11111010 11111010 11111000 10110011\n    Parity value from last byte: 1001\n\n    Binary Data: 01101110 00111010 11111010 11111010 11111000 10101101\n    Parity value from last byte: 0110\n\n    Binary Data: 00100011 00000011 11111100 01001101 11111100 10110111\n    Parity value from last byte: 1011\n\n    Binary Data: 00100011 00000011 11111100 01000111 11111100 10100011\n    Parity value from last byte: 0001\n\n    Binary Data: 00100011 00000011 11111010 11111010 11111000 10101011\n    Parity value from last byte: 0101\n\n    Binary Data: 00011001 01010111 11111100 01001110 11111100 10100001\n    Parity value from last byte: 0000\n\n    Binary Data: 00110001 00010010 11111100 01000110 11111100 10100111\n    Parity value from last byte: 0011\n\n    Binary Data: 00110001 00010010 11111101 11111101 11111100 10100101\n    Parity value from last byte: 0010\n\n    Binary Data: 00110001 00010010 11111010 11111010 11111000 10101101\n    Parity value from last byte: 0110\n\n    Binary Data: 01010110 00010100 11111010 11111010 11111000 10100011\n    Parity value from last byte: 0001\n\nRevSum input for parity (first 5 bytes, and the parity extracted from the last byte):\n\n    0x6f, 0x3a, 0xfa, 0xfa, 0xf8, 0x07\n    0x6e, 0x19, 0xfa, 0xfa, 0xf8, 0x07\n    0x5c, 0x66, 0xfa, 0xfa, 0xf8, 0x0e\n    0x6d, 0x1e, 0xfa, 0xfa, 0xf8, 0x03\n    0x67, 0xf9, 0xfa, 0xfa, 0xf8, 0x00\n    0x6e, 0x2d, 0xfa, 0xfa, 0xf8, 0x00\n    0x5c, 0x07, 0xfa, 0xfa, 0xf8, 0x09\n    0x6e, 0x42, 0xfa, 0xfa, 0xf8, 0x09\n    0x6e, 0x3a, 0xfa, 0xfa, 0xf8, 0x06\n    0x23, 0x03, 0xfc, 0x4d, 0xfc, 0x0b\n    0x23, 0x03, 0xfc, 0x47, 0xfc, 0x01\n    0x23, 0x03, 0xfa, 0xfa, 0xf8, 0x05\n    0x19, 0x57, 0xfc, 0x4e, 0xfc, 0x00\n    0x31, 0x12, 0xfc, 0x46, 0xfc, 0x03\n    0x31, 0x12, 0xfd, 0xfd, 0xfc, 0x02\n    0x31, 0x12, 0xfa, 0xfa, 0xf8, 0x06\n    0x56, 0x14, 0xfa, 0xfa, 0xf8, 0x01\n\n*/\n\n#include \"decoder.h\"\n\n#define GOVEE_WATER         5054\n#define GOVEE_CONTACT       5023\n\n#define GOVEE_H5054_BYTELEN 6\n#define GOVEE_H5054_BITLEN  48\n\nstatic int govee_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int model_num = GOVEE_WATER;\n\n    if (bitbuffer->num_rows < 3) {\n        return DECODE_ABORT_EARLY; // truncated transmission\n    }\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 6 * 8);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (bitbuffer->bits_per_row[r] > 6 * 8) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // dump raw input code\n    char code_str[13];\n    snprintf(code_str, sizeof(code_str), \"%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5]);\n\n    bitbuffer_invert(bitbuffer);\n\n    int id = (b[0] << 8) | b[1];\n    // reduce false positives\n    if (id == 0xffff) {\n        return DECODE_ABORT_EARLY;\n    }\n    if (b[5] == 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int event_type = b[2] & 0x0f;\n\n    int event = (b[2] << 8) | b[3];\n    if (event == 0xffff) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_logf(decoder, 1, __func__, \"Original Bytes: %02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5]);\n\n    uint8_t parity = (b[5] >> 1 & 0x0F); // Shift 101PPPP1 -> 0101PPPP, then and with 0x0F so we're left with 000PPPP\n\n    decoder_logf(decoder, 1, __func__, \"Parity: %02x\", parity);\n\n    int chk = xor_bytes(b, 5);\n    chk     = (chk >> 4) ^ (chk & 0xf);\n\n    // Parity arguments were discovered using revdgst's RevSum and the data packets included at the top of this file.\n    // https://github.com/triq-org/revdgst\n    if (chk != parity) {\n        decoder_log(decoder, 1, __func__, \"Parity did NOT match.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Only valid for event nibble 0xc\n    // voltage fit value from 8 different sensor units, observed 2 to 3.1 volts\n    int battery         = event_type == 0xc ? b[3] : 0; // percentage gauge\n    float battery_level = battery * 0.01f;\n    int battery_mv      = 1800 + 12 * battery;\n\n    // Strip off the upper nibble\n    event &= 0x0FFF;\n\n    char const *event_str;\n    int wet = -1;\n    // Figure out what event was triggered\n    if (event == 0xafa) {\n        event_str = \"Button Press\";\n        // The H5054 water sensor does not send a message when it transitions from wet to dry nor does it have a\n        // dedicated message to indicate that it is not wet. However, the sensor only sends a \"button press\" message if\n        // the button is pressed while the device is dry (no button press message is sent if the button is pressed while\n        // the sensor is wet). Since we know the sensor is dry when a \"button press\" message is received, \"detect_wet:0\"\n        // is included in the output when the button is pressed as a workaround to allow the user to transition the\n        // device back to the dry state.\n        wet = 0;\n    }\n    else if (event == 0xbfb) {\n        event_str = \"Water Leak\";\n        wet = 1;\n    }\n    else if (event_type == 0xc) {\n        event_str = \"Battery Report\";\n    }\n    else if (event == 0xdfd) {\n        event_str = \"Heartbeat\";\n    }\n    else if (event == 0xe7f) {\n        // Only sent by the Contact sensor\n        model_num = GOVEE_CONTACT;\n        event_str = \"Open\";\n    }\n    else {\n        event_str = \"Unknown\";\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",                 DATA_COND,   model_num == GOVEE_WATER,   DATA_STRING, \"Govee-Water\",\n            \"model\",        \"\",                 DATA_COND,   model_num == GOVEE_CONTACT, DATA_STRING, \"Govee-Contact\",\n            \"id\",           \"\",                 DATA_INT,    id,\n            \"battery_ok\",   \"Battery level\",    DATA_COND,   battery, DATA_DOUBLE, battery_level,\n            \"battery_mV\",   \"Battery\",          DATA_COND,   battery, DATA_FORMAT, \"%d mV\", DATA_INT, battery_mv,\n            \"detect_wet\",   \"\",                 DATA_COND,   wet >= 0, DATA_INT, wet,\n            \"event\",        \"\",                 DATA_STRING, event_str,\n            \"code\",         \"Raw Code\",         DATA_STRING, code_str,\n            \"mic\",          \"Integrity\",        DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_mV\",\n        \"detect_wet\",\n        \"event\",\n        \"code\",\n        \"mic\",\n        NULL,\n};\n\nr_device const govee = {\n        .name        = \"Govee Water Leak Detector H5054, Door Contact Sensor B5023\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 440,  // Threshold between short and long pulse [us]\n        .long_width  = 940,  // Maximum gap size before new row of bits [us]\n        .gap_limit   = 900,  // Maximum gap size before new row of bits [us]\n        .reset_limit = 9000, // Maximum gap size before End Of Message [us]\n        .decode_fn   = &govee_decode,\n        .fields      = output_fields,\n};\n\n/**\nGovee Water Leak Detector H5054\n\nThis is an updated decoder for devices with board versions dated circa 2021 as originally\nreported in issue #2265.\n\nData layout:\n\n    II II XE DD CC CC\n\n- I: 16 bit ID, does not change with battery change\n- X: 4 bit, always 0x3 for the sensors evaluated\n- E: 4 bit event type\n- D: 8 bit event data\n- C: CRC-16/AUG-CCITT, poly=0x1021, init=0x1d0f\n\n\nEvent Information:\n\n- 0x0 : Button Press\n  - The event data (DD) is always 0x54 for the sensors evaluated. Unknown meaning.\n- 0x1 : Battery Report\n  - The event data (DD) reported for new batteries = 0x64 (decimal 100). When inserting\n    older batteries, this value decreased. Looking at prior versions of the device,\n    this appears to be a battery level percentage.\n- 0x2 = Water Leak\n  - The event data (DD) reported appears to be an incrementing counter for the event\n    number. This value is reset to 00 when new batteries are inserted.\n\n    When the first leak occurs, E=2 D=00. This value is transmitted once very 5 seconds\n    until the leak is cleared (sensor dried off). The next leak events will be:\n\n    E=2, D=01\n    E=2, D=02\n    E=2, D=03\n    etc...\n\nCRC Information:\n\nThe CRC was determined by using the tool CRC RevEng: https://reveng.sourceforge.io/:\n\n    ./reveng -w16 -s aaaaaaaaaaaa bbbbbbbbbbbb etc...\n\nwhere aaaaaaaaaaaa, bbbbbbbbbbbb, etc... were the unique codes collected from the\ndevice.\n\n*/\n\nstatic int govee_h5054_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows < 3) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, GOVEE_H5054_BITLEN);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (bitbuffer->bits_per_row[r] > GOVEE_H5054_BITLEN) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    char code_str[13];\n    snprintf(code_str, sizeof(code_str), \"%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5]);\n\n    uint16_t chk = crc16(b, 6, 0x1021, 0x1d0f);\n    if (chk != 0) {\n        return DECODE_FAIL_MIC;\n    }\n\n    const uint16_t id        = b[0] << 8 | b[1];\n    const uint8_t unk16      = (b[2] & 0xf0) >> 4;\n    const uint8_t event      = b[2] & 0xf;\n    const uint8_t event_data = b[3];\n    const uint16_t crc_sum   = b[4] << 8 | b[5];\n\n    decoder_logf(decoder, 1, __func__, \"Original Bytes: %02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5]);\n    decoder_logf(decoder, 1, __func__, \"id=%04x\", id);\n    decoder_logf(decoder, 1, __func__, \"unk16=%x\", unk16);\n    decoder_logf(decoder, 1, __func__, \"event=%x\", event);\n    decoder_logf(decoder, 1, __func__, \"event_data=%02x\", event_data);\n    decoder_logf(decoder, 1, __func__, \"crc_sum=%04x\", crc_sum);\n\n    char const *event_str;\n    int wet = -1;\n    int leak_num = -1;\n    int battery  = -1;\n    switch (event) {\n    case 0x0:\n        event_str = \"Button Press\";\n        // The H5054 water sensor does not send a message when it transitions from wet to dry nor does it have a\n        // dedicated message to indicate that it is not wet. However, the sensor only sends a \"button press\" message if\n        // the button is pressed while the device is dry (no button press message is sent if the button is pressed while\n        // the sensor is wet). Since we know the sensor is dry when a \"button press\" message is received, \"detect_wet:0\"\n        // is included in the output when the button is pressed as a workaround to allow the user to transition the\n        // device back to the dry state.\n        wet = 0;\n        break;\n    case 0x1:\n        event_str = \"Battery Report\";\n        battery   = event_data;\n        break;\n    case 0x2:\n        event_str = \"Water Leak\";\n        wet = 1;\n        leak_num  = event_data;\n        break;\n    default:\n        event_str = \"Unknown\";\n        break;\n    }\n\n    float battery_level = battery * 0.01f;\n    int battery_mv      = 1800 + 12 * battery;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",                 DATA_STRING, \"Govee-Water\",\n            \"id\",           \"\",                 DATA_INT,    id,\n            \"battery_ok\",   \"Battery level\",    DATA_COND,   battery >= 0, DATA_DOUBLE, battery_level,\n            \"battery_mV\",   \"Battery\",          DATA_COND,   battery >= 0, DATA_FORMAT, \"%d mV\", DATA_INT, battery_mv,\n            \"event\",        \"\",                 DATA_STRING, event_str,\n            \"detect_wet\",   \"\",                 DATA_COND,   wet >= 0, DATA_INT, wet,\n            \"leak_num\",     \"Leak Num\",         DATA_COND,   leak_num >= 0, DATA_INT, leak_num,\n            \"code\",         \"Raw Code\",         DATA_STRING, code_str,\n            \"mic\",          \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nr_device const govee_h5054 = {\n        .name        = \"Govee Water Leak Detector H5054\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 440,  // Threshold between short and long pulse [us]\n        .long_width  = 940,  // Maximum gap size before new row of bits [us]\n        .gap_limit   = 900,  // Maximum gap size before new row of bits [us]\n        .reset_limit = 9000, // Maximum gap size before End Of Message [us]\n        .decode_fn   = &govee_h5054_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/gridstream.c",
    "content": "/** @file\n    Decoder for Gridstream RF devices produced by Landis & Gyr.\n\n    Copyright (C) 2023 krvmk\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/** @fn int gridstream_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nLandis & Gyr Gridstream Power Meters.\n\n- Center Frequency: 915 Mhz\n- Frequency Range: 902-928 MHz\n- Channel Spacing: 100kHz, 300kHz\n- Modulation: FSK-PCM (2-FSK, GFSK)\n- Bitrates: 9600, 19200, 38400\n- Preamble: 0xAAAA\n- Syncword v4: 0b0000000001 0b0111111111\n- Syncword v5: 0b0000000001 0b11111111111\n\nThis decoder is based on the information from: https://wiki.recessim.com/view/Landis%2BGyr_GridStream_Protocol\nDatastream is variable length and bitrate depending on type fields\nPreamble\nBytes after preamble are encoded with standard uart settings with start bit, 8 data bits and stop bit.\nData layouts:\n    Subtype 55:\n        AAAAAA SSSS TT YY LLLL KK BBBBBBBBBB WWWWWWWWWW II MMMMMMMM KKKK EEEEEEEE KKKK KKKKKK CCCC KKKK XXXX KK\n    Subtype D2:\n        AAAAAA SSSS TT YY LL K----------K XXXX\n    Subtype D5:\n        AAAAAA SSSS TT YY LLLL KK DDDDDDDD EEEEEEEE II K----------K CCCC KKKK XXXX\n- A - Preamble\n- S - Syncword\n- T - Type\n- Y - Subtype\n- L - Length\n- B - Broadcast\n- D - Dest Address\n- E - Source Address\n- M - Uptime (time since last outage in seconds)\n- I - Counter\n- C - Clock\n- K - Unknown\n- X - CRC (poly 0x1021, init set by provider)\n\n*/\n\n#include \"decoder.h\"\n\nstruct crc_init {\n    uint16_t value;\n    char const *location;\n    char const *provider;\n};\n\n/*\nDecoder will iterate through the known values until checksum is validated.\n\nIn order to identify new values, the reveng application, https://reveng.sourceforge.io/,\ncan determine a missing init value if given several fixed length packet streams.\nSubtype 0x55 with a data length of 0x23 can be used for this.\n\nKnown CRC init values can be added to the code via PR when they have been identified.\n\n*/\nstatic const struct crc_init known_crc_init[] = {\n        {0xe623, \"Kansas City MO\", \"Evergy-Missouri West\"},\n        {0x5fd6, \"Dallas TX\", \"Oncor\"},\n        {0xD553, \"Austin TX\", \"Austin Energy\"},\n        {0x45F8, \"Dallas TX\", \"CoServ\"},\n        {0x62C1, \"Quebec CAN\", \"Hydro-Quebec\"},\n        {0x23D1, \"Seattle WA\", \"Seattle City Light\"},\n        {0x2C22, \"Santa Barbara CA\", \"Southern California Edison\"},\n        {0x142A, \"Washington\", \"Puget Sound Energy\"},\n        {0x47F7, \"Pennsylvania\", \"PPL Electric\"},\n        {0x22c6, \"Long Island NY\", \"PSEG Long Island\"},\n        {0x8819, \"Alameda CA\", \"Alameda Municipal Power\"},\n        {0x4E2D, \"Milwaukee WI\", \"We Energies\"},\n        {0x1D65, \"Phoenix AZ\", \"APS\"},\n        {0xB9A9, \"Mattoon IL\", \"Coles-Moultrie Electric Co-op\"},\n        {0xD1FF, \"Newark NJ\", \"PSEG New Jersey\"},\n        {0xba1f, \"Burleson TX\", \"United Cooperative Services\"},\n};\n\nstatic int gridstream_checksum(int fulllength, uint16_t length, uint8_t *bits, int adjust)\n{\n    uint16_t crc_count = 0;\n    int crc_ok         = 0;\n    uint16_t crc;\n\n    if ((fulllength - 4 + adjust) < length) {\n        return DECODE_ABORT_LENGTH;\n    }\n    crc = (bits[2 + length + adjust] << 8) | bits[3 + length + adjust];\n    do {\n        if (crc16(&bits[4 + adjust], length - 2, 0x1021, known_crc_init[crc_count].value) == crc) {\n            crc_ok = 1;\n        }\n        else {\n            crc_count++;\n        }\n    } while (crc_count < (sizeof(known_crc_init) / sizeof(struct crc_init)) && crc_ok == 0);\n\n    if (!crc_ok) {\n        return DECODE_FAIL_MIC;\n    }\n    else {\n        return crc_count;\n    }\n}\n\nstatic int gridstream_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t const preambleV4[] = {\n            0xAA,\n            0xAA,\n            0x00,\n            0x5F,\n            0xF0,\n    };\n    uint8_t const preambleV5[] = {\n            0xAA,\n            0xAA,\n            0x00,\n            0x7F,\n            0xF8,\n    };\n\n    /* Maximum data length is not yet known, but 256 should be a sufficient buffer size. */\n    uint8_t b[256];\n    int decoded_len;\n    int protocol_version;\n    unsigned offset = bitbuffer_search(bitbuffer, 0, 0, preambleV4, 36) + 36;\n    if (offset >= bitbuffer->bits_per_row[0]) {\n        offset = bitbuffer_search(bitbuffer, 0, 0, preambleV5, 37) + 37;\n        if (offset >= bitbuffer->bits_per_row[0]) {\n            return DECODE_FAIL_SANITY;\n        }\n        unsigned num_bits = MIN(bitbuffer->bits_per_row[0] - offset, sizeof(b) * 10);\n        decoded_len       = extract_bytes_uart(bitbuffer->bb[0], offset, num_bits, b);\n        protocol_version  = 5;\n    }\n    else {\n        unsigned num_bits = MIN(bitbuffer->bits_per_row[0] - offset, sizeof(b) * 10);\n        decoded_len       = extract_bytes_uart(bitbuffer->bb[0], offset, num_bits, b);\n        protocol_version  = 4;\n    }\n\n    if (decoded_len < 5) {\n        return DECODE_FAIL_SANITY;\n    }\n    decoder_log_bitrow(decoder, 1, __func__, b, decoded_len * 8, \"Decoded frame data\");\n\n    if (b[0] == 0x2A) {\n        int subtype = b[1];\n        int subtype_mod = 0;\n        uint16_t stream_len;\n        if (subtype == 0xD2) {\n            stream_len  = b[2];\n            subtype_mod = -1;\n        }\n        else {\n            stream_len = (b[2] << 8) | b[3];\n        }\n\n        int crcidx = gridstream_checksum(decoded_len, stream_len, b, subtype_mod);\n        if (crcidx < 0) {\n            decoder_log(decoder, 1, __func__, \"Bad CRC or unknown init value. \");\n            if ((stream_len == 0x23) && (subtype == 0xAA)) {\n                /* These data types can be used to find new init values. See comment block on line 67. */\n                decoder_log_bitrow(decoder, 1, __func__, &b[4], decoded_len * 8, \"Use RevEng to find init value.\");\n            }\n            return DECODE_FAIL_MIC;\n        }\n        char found_crc[5];\n        sprintf(found_crc, \"%04x\", known_crc_init[crcidx].value);\n\n        char destwanaddress_str[13] = \"\";\n        char srcwanaddress_str[13]  = \"\";\n        char srcaddress_str[9]      = \"\";\n        char destaddress_str[9]     = \"\";\n        int srcwanaddress           = 0;\n        uint32_t uptime             = 0;\n        int clock                   = 0;\n\n        if (subtype == 0x55) {\n            sprintf(destwanaddress_str, \"%02x%02x%02x%02x%02x%02x\", b[5], b[6], b[7], b[8], b[9], b[10]);\n            sprintf(srcwanaddress_str, \"%02x%02x%02x%02x%02x%02x\", b[11], b[12], b[13], b[14], b[15], b[16]);\n            srcwanaddress = 1;\n            sprintf(srcaddress_str, \"%02x%02x%02x%02x\", b[24], b[25], b[26], b[27]);\n            uptime = ((uint32_t)b[18] << 24) | (b[19] << 16) | (b[20] << 8) | b[21];\n        }\n        else if (subtype == 0xD5) {\n            sprintf(destaddress_str, \"%02x%02x%02x%02x\", b[5], b[6], b[7], b[8]);\n            sprintf(srcaddress_str, \"%02x%02x%02x%02x\", b[9], b[10], b[11], b[12]);\n            if (stream_len == 0x47) {\n                clock  = ((uint32_t)b[14] << 24) | (b[15] << 16) | (b[16] << 8) | b[17];\n                uptime = ((uint32_t)b[22] << 24) | (b[23] << 16) | (b[24] << 8) | b[25];\n                sprintf(srcwanaddress_str, \"%02x%02x%02x%02x%02x%02x\", b[30], b[31], b[32], b[33], b[34], b[35]);\n                srcwanaddress = 1;\n            }\n        }\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",                     DATA_STRING,    \"LandisGyr-GS\",\n                \"networkID\",    \"Network ID\",           DATA_STRING,    found_crc,\n                \"location\",     \"Location\",             DATA_STRING,    known_crc_init[crcidx].location,\n                \"provider\",     \"Provider\",             DATA_STRING,    known_crc_init[crcidx].provider,\n                \"subtype\",      \"\",                     DATA_INT,       subtype,\n                \"protoversion\", \"\",                     DATA_INT,       protocol_version,\n                \"mic\",          \"Integrity\",            DATA_STRING,    \"CRC\",\n                \"id\",           \"Source Meter ID\",      DATA_COND,      subtype != 0xD2, DATA_STRING, srcaddress_str,\n                \"wanaddress\",   \"Source Meter WAN ID\",  DATA_COND,      srcwanaddress == 1, DATA_STRING, srcwanaddress_str,\n                \"destaddress\",  \"Target Meter WAN ID\",  DATA_COND,      subtype == 0x55, DATA_STRING, destwanaddress_str,\n                \"destaddress\",  \"Target Meter ID\",      DATA_COND,      subtype == 0xD5, DATA_STRING, destaddress_str,\n                \"timestamp\",    \"Timestamp\",            DATA_COND,      subtype == 0xD5 && stream_len == 0x47, DATA_INT, clock,\n                \"uptime\",       \"Uptime\",               DATA_COND,      uptime > 0, DATA_INT, uptime,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        // Return 1 if message successfully decoded\n        return 1;\n    }\n    else {\n        // Unknown type\n        return 0;\n    }\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"networkID\",\n        \"location\",\n        \"provider\",\n        \"id\",\n        \"subtype\",\n        \"wanaddress\",\n        \"destaddress\",\n        \"uptime\",\n        \"srclocation\",\n        \"destlocation\",\n        \"timestamp\",\n        \"protoversion\",\n        \"framedata\",\n        \"mic\",\n        NULL,\n};\n\nr_device const gridstream96 = {\n        .name        = \"Landis & Gyr Gridstream Power Meters 9.6k\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 104,\n        .long_width  = 104,\n        .reset_limit = 20000,\n        .decode_fn   = &gridstream_decode,\n        .disabled    = 0,\n        .fields      = output_fields,\n};\n\nr_device const gridstream192 = {\n        .name        = \"Landis & Gyr Gridstream Power Meters 19.2k\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 20000,\n        .decode_fn   = &gridstream_decode,\n        .disabled    = 0,\n        .fields      = output_fields,\n};\n\nr_device const gridstream384 = {\n        .name        = \"Landis & Gyr Gridstream Power Meters 38.4k\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 22,\n        .long_width  = 22,\n        .reset_limit = 20000,\n        .decode_fn   = &gridstream_decode,\n        .disabled    = 0,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/gt_tmbbq05.c",
    "content": "/** @file\n    Globaltronics Quigg BBQ GT-TMBBQ-05.\n\n    Copyright (C) 2019 Olaf Glage\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGlobaltronics Quigg BBQ GT-TMBBQ-05.\n\nBBQ thermometer sold at Aldi (germany)\nSimple device, no possibility to select channel. Single temperature measurement.\n\nThe temperature is transmitted in Fahrenheit with an addon of 90. Accuracy is 10 bit. No decimals.\nOne data row contains 33 bits and is repeated 8 times. Each followed by a 0-row. So we have 16 rows in total.\nFirst bit seem to be a static 0. By ignoring this we get nice byte boundaries.\nNext 8 bits are static per device (even after battery change)\nNext 8 bits contain the lower 8 bits of the temperature.\nNext 8 bits are static per device (even after battery change)\nNext 2 bits contain the upper 2 bits of the temperature\nNext 1 bit is unknown\nNext 1 bit is an odd parity bit\nLast 4 bits are the sum of the preceding 5 nibbles (mod 0xf)\n\nHere's the data I used to reverse engineer, more sampes in rtl_test\n\n    y001001001100010000111100110010110  [HI]\n    y001001001010101010111100110010000 [507]\n    y001001001010011010111100110010111  [499]\n    y001001001110101110111100101010110  [381]\n    y001001001110000000111100101011110  [358]\n    y001001001001011010111100101010001  [211]\n    y001001001001000000111100101000011  [198]\n    y001001001111010110111100100000110  [145]\n    y001001001101100010111100100001001  [89]\n    y001001001101011010111100100010101  [83]\n    y001001001101010110111100100010011  [81]\n    y001001001101010010111100100000000  [79]\n    y001001001101010000111100100010000  [78]\n    y001001001101001110111100100011111  [77]\n    y001001001101001100111100100001101  [76]\n    y001001001101001010111100100001100  [75]\n    y001001001101000110111100100001010  [73]\n    y001001001100010100111100100010000  [48]\n    y001001001011011110111100100000010  [21]\n    y001001001011001110111100100011011  [13]\n    y001001001010010010111100100011011  [LO]\n\nPRE:9b TL:8h ID:8h TH:2b 6h\n\nsecond device:\n011100110101001001011001100010001  73\n011100110101010111011001100011000  81\n\nFrame structure:\n    Byte:   H 1        2        3        4\n    Type:   0 SSSSSSSS tttttttt ssssssss TT?Pcccc\n\n- S: static per device (even after battery change)\n- t: temperature+90 F lower 8 bits\n- s: static per device (even after battery change)\n- T: temperature+90 F upper 2 bits\n- P: odd parity bit\n- c: sum of first 5 nibbles\n\n*/\n\n#include \"decoder.h\"\n\nstatic int gt_tmbbq05_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[4], p[4];\n    data_t *data;\n\n    // 33 bit, repeated multiple times (technically it is repeated 8 times, look for 5 identical versions)\n    int r = bitbuffer_find_repeated_row(bitbuffer, 5, 33);\n\n    // we're looking for exactly 33 bits\n    if (r < 0 || bitbuffer->bits_per_row[r] != 33)\n        return DECODE_ABORT_LENGTH;\n\n    // remove the first leading bit and extract the 4 bytes carrying the data\n    bitbuffer_extract_bytes(bitbuffer, r, 1, b, 32);\n\n    // Prevent false positives from 'allzero'\n    // reject if Checksum Id and temperature are all zero\n    // No need to decode/extract values for simple test\n    if (!b[0] && !b[1] && !b[2] && !b[3]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all zero\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* Parity check over 7 nibbles (must be ODD) */\n    memcpy(p, b, 4);\n    p[3] = p[3] & 0xF0;\n\n    if (parity_bytes(p, 4)) {\n        decoder_log(decoder, 2, __func__, \"gt_tmbbq05_decode: parity check failed (should be ODD)\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int sum = add_nibbles(b, 3) + (b[3] >> 4);\n    if ((sum & 0xf) != (b[3] & 0xf)) {\n        decoder_logf_bitrow(decoder, 2, __func__, b, 32, \"Bad checksum (%x)\", sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    // temperature: concat the upper bits to the lower bits and subtract the fixed offset 90\n    int tempf = (((b[3] & 0xc0) << 2) | b[1]) - 90;\n\n    // device id: concat the two bytes\n    int device_id = (b[0] << 8) | b[2];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"GT-TMBBQ05\",\n            \"id\",               \"ID Code\",      DATA_INT,    device_id,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, (float)tempf,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_F\",\n        \"mic\",\n        NULL,\n};\n\nr_device const gt_tmbbq05 = {\n        .name        = \"Globaltronics QUIGG GT-TMBBQ-05\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 4200,\n        .reset_limit = 9100,\n        .decode_fn   = &gt_tmbbq05_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/gt_wt_02.c",
    "content": "/** @file\n    GT-WT-02 sensor on 433.92MHz.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n    original implementation 2015 Paul Ortyl\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nGT-WT-02 sensor on 433.92MHz.\n\nExample and frame description provided by https://github.com/ludwich66\n\n   [01] {37} 34 00 ed 47 60 : 00110100 00000000 11101101 01000111 01100000\n   code, BatOK,not-man-send, Channel1, +23,7°C, 35%\n\n   [01] {37} 34 8f 87 15 90 : 00110100 10001111 10000111 00010101 10010000\n   code, BatOK,not-man-send, Channel1,-12,1°C, 10%\n\nHumidity:\n- the working range is 20-90 %\n- if \"LL\" in display view it sends 10 %\n- if \"HH\" in display view it sends 110%\n\nSENSOR: GT-WT-02 (ALDI Globaltronics..)\n\n   TYP IIIIIIII BMCCTTTT TTTTTTTT HHHHHHHX XXXXX\n\nTYPE Description:\n\n- I = Random Device Code, changes with battery reset\n- B = Battery 0=OK 1=LOW\n- M = Manual Send Button Pressed 0=not pressed 1=pressed\n- C = Channel 00=CH1, 01=CH2, 10=CH3\n- T = Temperature, 12 Bit 2's complement, scaled by 10\n- H = Humidity = 7 Bit bin2dez 00-99, Display LL=10%, Display HH=110% (Range 20-90%)\n- X = Checksum, sum modulo 64\n\nA Lidl AURIO (from 12/2018) with PCB marking YJ-T12 V02 has two extra bits in front.\n*/\n\n#include \"decoder.h\"\n\nstatic int gt_wt_02_process_row(r_device *decoder, bitbuffer_t *bitbuffer, int row)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[row];\n    uint8_t shifted[5];\n\n    if (39 == bitbuffer->bits_per_row[row]) {\n        bitbuffer_extract_bytes(bitbuffer, row, 2, shifted, 37);\n        b = shifted;\n    }\n    else if (37 != bitbuffer->bits_per_row[row])\n        return 0; // DECODE_ABORT_LENGTH\n\n    if (!(b[0] || b[1] || b[2] || b[3] || b[4])) /* exclude all zeros */\n        return 0; // DECODE_ABORT_EARLY\n\n    // sum 8 nibbles (use 31 bits, the last one fill with 0 on 32nd bit)\n    int sum_nibbles =\n          (b[0] >> 4) + (b[0] & 0xF)\n        + (b[1] >> 4) + (b[1] & 0xF)\n        + (b[2] >> 4) + (b[2] & 0xF)\n        + (b[3] >> 4) + (b[3] & 0xe);\n\n    // put last 6 bits into a number\n    int checksum = ((b[3] & 1) << 5) + (b[4] >> 3);\n\n    // accept only correct checksums, (sum of nibbles modulo 64)\n    if ((sum_nibbles & 0x3F) != checksum)\n        return 0; // DECODE_FAIL_MIC\n\n    // humidity: see above the note about working range\n    int humidity = (b[3] >> 1); // extract bits for humidity\n    if (humidity <= 10) // actually the sensors sends 10 below working range of 20%\n        humidity = 0;\n    else if (humidity > 90) // actually the sensors sends 110 above working range of 90%\n        humidity = 100;\n\n    int sensor_id      = (b[0]);          // 8 bits\n    int battery_low    = (b[1] >> 7 & 1); // 1 bits\n    int button_pressed = (b[1] >> 6 & 1); // 1 bits\n    int channel        = (b[1] >> 4 & 3); // 2 bits\n    int temp_raw       = (int16_t)(((b[1] & 0x0f) << 12) | (b[2] << 4)); // uses sign extend\n    float temp_c       = (temp_raw >> 4) * 0.1F;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"GT-WT02\",\n            \"id\",               \"ID Code\",      DATA_INT,    sensor_id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel + 1,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%.0f %%\", DATA_DOUBLE, (double)humidity,\n            \"button\",           \"Button \",      DATA_INT,    button_pressed,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa gt_wt_02_process_row() */\nstatic int gt_wt_02_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int counter = 0;\n    // iterate through all rows, return on first successful\n    for (int row = 0; row < bitbuffer->num_rows && !counter; ++row)\n        counter += gt_wt_02_process_row(decoder, bitbuffer, row);\n    return counter;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        \"mic\",\n        NULL,\n};\n\nr_device const gt_wt_02 = {\n        .name        = \"Globaltronics GT-WT-02 Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2500, // 3ms (old) / 2ms (new)\n        .long_width  = 5000, // 6ms (old) / 4ms (new)\n        .gap_limit   = 8000, // 10ms (old) / 9ms (new) sync gap\n        .reset_limit = 12000,\n        .decode_fn   = &gt_wt_02_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/gt_wt_03.c",
    "content": "/** @file\n    Globaltronics GT-WT-03 sensor on 433.92MHz.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/** @fn int gt_wt_03_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nGlobaltronics GT-WT-03 sensor on 433.92MHz.\n\nThe 01-set sensor has 60 ms packet gap with 10 repeats.\nThe 02-set sensor has no packet gap with 23 repeats.\n\nExample:\n\n    {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes ]\n    {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes Batt-Changed ]\n    {41} 17 cf fe fa ea 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-No  Batt-Changed ]\n    {41} 01 cf 6f 11 b2 80 [ S2 C2 23,8 C 74.8 F 48% Bat-LOW  Manual-No ]\n    {41} 01 c8 d0 2b 76 80 [ S2 C3 -4,4 C 24.1 F 55% Bat-Good Manual-No  Batt-Changed ]\n\nFormat string:\n\n    ID:8h HUM:8d B:b M:b C:2d TEMP:12d CHK:8h 1x\n\nData layout:\n\n   TYP IIIIIIII HHHHHHHH BMCCTTTT TTTTTTTT XXXXXXXX\n\n- I: Random Device Code: changes with battery reset\n- H: Humidity: 8 Bit 00-99, Display LL=10%, Display HH=110% (Range 20-95%)\n- B: Battery: 0=OK 1=LOW\n- M: Manual Send Button Pressed: 0=not pressed, 1=pressed\n- C: Channel: 00=CH1, 01=CH2, 10=CH3\n- T: Temperature: 12 Bit 2's complement, scaled by 10, range-50.0 C (-50.1 shown as Lo) to +70.0 C (+70.1 C is shown as Hi)\n- X: Checksum, xor shifting key per byte\n\nHumidity:\n- the working range is 20-95 %\n- if \"LL\" in display view it sends 10 %\n- if \"HH\" in display view it sends 110%\n\nChecksum:\nPer byte xor the key for each 1-bit, shift per bit. Key list per bit, starting at MSB:\n- 0x00 [07]\n- 0x80 [06]\n- 0x40 [05]\n- 0x20 [04]\n- 0x10 [03]\n- 0x88 [02]\n- 0xc4 [01]\n- 0x62 [00]\nNote: this can also be seen as lower byte of a Galois/Fibonacci LFSR-16, gen 0x00, init 0x3100 (or 0x62 if reversed) resetting at every byte.\n\nBattery voltages:\n- U=<2,65V +- ~5% Battery indicator\n- U=>2.10C +- 5% plausible readings\n- U=2,00V +- ~5% Temperature offset -5°C Humidity offset unknown\n- U=<1,95V +- ~5% does not initialize anymore\n- U=1,90V +- 5% temperature offset -15°C\n- U=1,80V +- 5% Display is showing refresh pattern\n- U=1.75V +- ~5% TX causes cut out\n\n*/\n\n#include \"decoder.h\"\n\nstatic uint8_t chk_rollbyte(uint8_t const message[], unsigned bytes, uint16_t gen)\n{\n    uint8_t sum = 0;\n    for (unsigned k = 0; k < bytes; ++k) {\n        uint8_t data = message[k];\n        uint16_t key = gen;\n        for (int i = 7; i >= 0; --i) {\n            // XOR key into sum if data bit is set\n            if ((data >> i) & 1)\n                sum ^= key & 0xff;\n\n            // roll the key right\n            key = (key >> 1);\n        }\n    }\n    return sum;\n}\n\nstatic int gt_wt_03_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int row = 0;\n    uint8_t *b;\n\n    // nominal 1 row or 23 rows, require more than half to match\n    if (bitbuffer->num_rows > 1)\n        row = bitbuffer_find_repeated_row(bitbuffer, bitbuffer->num_rows / 2 + 1, 41);\n\n    if (row < 0)\n        return DECODE_ABORT_LENGTH;\n\n    if (41 != bitbuffer->bits_per_row[row])\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_invert(bitbuffer);\n    b = bitbuffer->bb[row];\n\n    if (!(b[0] || b[1] || b[2] || b[3] || b[4])) /* exclude all zeros */\n        return DECODE_ABORT_EARLY;\n\n    // accept only correct checksum\n    int chk = chk_rollbyte(b, 4, 0x3100) ^ b[4] ^ 0x2d;\n    if (chk) {\n        decoder_log_bitrow(decoder, 1, __func__, b, 5, \"Invalid checksum \");\n        return DECODE_FAIL_MIC;\n    }\n\n    // humidity: see above the note about working range\n    int humidity = b[1]; // extract 8 bits humidity\n    if (humidity <= 10) // actually the sensors sends 10 below working range of 20%\n        humidity = 0;\n    else if (humidity > 95) // actually the sensors sends 110 above working range of 90%\n        humidity = 100;\n\n    int sensor_id      = (b[0]);          // 8 bits\n    int battery_low    = (b[2] >> 7 & 1); // 1 bits\n    int button_pressed = (b[2] >> 6 & 1); // 1 bits\n    int channel        = (b[2] >> 4 & 3); // 2 bits\n    int temp_raw       = (int16_t)(((b[2] & 0x0f) << 12) | (b[3] << 4)); // uses sign extend\n    float temp_c       = (temp_raw >> 4) * 0.1F;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"GT-WT03\",\n            \"id\",               \"ID Code\",      DATA_INT,    sensor_id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel + 1,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%.0f %%\", DATA_DOUBLE, (double)humidity,\n            \"button\",           \"Button\",       DATA_INT,    button_pressed,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        \"mic\",\n        NULL,\n};\n\nr_device const gt_wt_03 = {\n        .name        = \"Globaltronics GT-WT-03 Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 256,\n        .long_width  = 625,\n        .sync_width  = 855,\n        .gap_limit   = 1000,\n        .reset_limit = 61000,\n        .decode_fn   = &gt_wt_03_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/hcs200.c",
    "content": "/** @file\n    Microchip HCS200/HCS300 KeeLoq Code Hopping Encoder based remotes.\n\n    Copyright (C) 2019, 667bdrm\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nMicrochip HCS200/HCS300 KeeLoq Code Hopping Encoder based remotes.\n\n66 bits transmitted, LSB first.\n\n|  0-31 | Encrypted Portion\n| 32-59 | Serial Number\n| 60-63 | Button Status (S3, S0, S1, S2)\n|  64   | Battery Low\n|  65   | Fixed 1\n\nNote that the button bits are (MSB/first sent to LSB) S3, S0, S1, S2.\nHardware buttons might map to combinations of these bits.\n\n- Datasheet HCS200: http://ww1.microchip.com/downloads/en/devicedoc/40138c.pdf\n- Datasheet HCS300: http://ww1.microchip.com/downloads/en/devicedoc/21137g.pdf\n\nThe warm-up of 12 short pulses is followed by a long 4400 us gap.\nThere are two packets with a 17500 us gap.\n\nrtl_433 -R 0 -X 'n=hcs200,m=OOK_PWM,s=370,l=772,r=9000,g=1500,t=152'\n*/\n\n#include \"decoder.h\"\n\nstatic int hcs200_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Reject codes of wrong length\n    if (bitbuffer->bits_per_row[0] != 12 || bitbuffer->bits_per_row[1] != 66)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = bitbuffer->bb[0];\n    // Reject codes with an incorrect preamble (expected 0xfff)\n    if (b[0] != 0xff || (b[1] & 0xf0) != 0xf0) {\n        decoder_log(decoder, 2, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Second row is data\n    b = bitbuffer->bb[1];\n\n    // No need to decode/extract values for simple test\n    if (b[1] == 0xff && b[2] == 0xff && b[3] == 0xff && b[4] == 0xff\n            && b[5] == 0xff && b[6] == 0xff && b[7] == 0xff) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0xff\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    // The transmission is LSB first, big endian.\n    uint32_t encrypted = ((unsigned)reverse8(b[3]) << 24) | (reverse8(b[2]) << 16) | (reverse8(b[1]) << 8) | (reverse8(b[0]));\n    int serial         = (reverse8(b[7] & 0xf0) << 24) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4]));\n    int btn            = (b[7] & 0x0f);\n    int btn_num        = (btn & 0x08) | ((btn & 0x01) << 2) | (btn & 0x02) | ((btn & 0x04) >> 2); // S3, S0, S1, S2\n    int learn          = (b[7] & 0x0f) == 0x0f;\n    int battery_low    = (b[8] & 0x80) == 0x80;\n    int repeat         = (b[8] & 0x40) == 0x40;\n\n    char encrypted_str[9];\n    snprintf(encrypted_str, sizeof(encrypted_str), \"%08X\", encrypted);\n    char serial_str[9];\n    snprintf(serial_str, sizeof(serial_str), \"%07X\", serial);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING,    \"Microchip-HCS200\",\n            \"id\",               \"\",             DATA_STRING,    serial_str,\n            \"battery_ok\",       \"Battery\",      DATA_INT,       !battery_low,\n            \"button\",           \"Button\",       DATA_INT,       btn_num,\n            \"learn\",            \"Learn mode\",   DATA_INT,       learn,\n            \"repeat\",           \"Repeat\",       DATA_INT,       repeat,\n            \"encrypted\",        \"\",             DATA_STRING,    encrypted_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"button\",\n        \"learn\",\n        \"repeat\",\n        \"encrypted\",\n        NULL,\n};\n\nr_device const hcs200 = {\n        .name        = \"Microchip HCS200/HCS300 KeeLoq Hopping Encoder based remotes\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 370,\n        .long_width  = 772,\n        .gap_limit   = 1500,\n        .reset_limit = 9000,\n        .tolerance   = 152, // us\n        .decode_fn   = &hcs200_callback,\n        .fields      = output_fields,\n};\n\nr_device const hcs200_fsk = {\n        .name        = \"Microchip HCS200/HCS300 KeeLoq Hopping Encoder based remotes (FSK)\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 370,\n        .long_width  = 772,\n        .gap_limit   = 1500,\n        .reset_limit = 9000,\n        .tolerance   = 152, // us\n        .decode_fn   = &hcs200_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/hideki.c",
    "content": "/** @file\n    Hideki Temperature, Humidity, Wind, Rain sensor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nHideki Temperature, Humidity, Wind, Rain sensor.\n\nAlso: Bresser 5CH (Model 7009993)\n\nThe received bits are inverted.\n\nEvery 8 bits are stuffed with a (even) parity bit.\nThe payload (excluding the header) has an byte parity (XOR) check.\nThe payload (excluding the header) has CRC-8, poly 0x07 init 0x00 check.\nThe payload bytes are reflected (LSB first / LSB last) after the CRC check.\n\nTemp:\n\n    11111001 0  11110101 0  01110011 1 01111010 1  11001100 0  01000011 1  01000110 1  00111111 0  00001001 0  00010111 0\n    SYNC+HEAD P   RC cha P     LEN   P     Nr.? P   .1° 1°  P   10°  BV P   1%  10% P     ?     P     XOR   P     CRC   P\n\nTS04:\n\n    00000000  11111111  22222222  33333333  44444444  55555555  66666666  77777777  88888888 99999999\n    SYNC+HEAD cha   RC     LEN        Nr.?    1° .1°  VB   10°   10%  1%     ?         XOR      CRC\n\nWind:\n\n    00000000  11111111  22222222  33333333  44444444  55555555  66666666  77777777  88888888 99999999 AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD\n    SYNC+HEAD cha   RC     LEN        Nr.?    1° .1°  VB   10°    1° .1°  VB   10°   1W .1W  .1G 10W   10G 1G    w°  AA    XOR      CRC\n\nRain:\n\n    00000000  11111111  22222222  33333333  44444444  55555555  66666666  77777777  88888888\n    SYNC+HEAD cha   RC   B LEN        Nr.?   RAIN_L    RAIN_H     0x66       XOR       CRC\n\n*/\n\n#include \"decoder.h\"\n\n#define HIDEKI_MAX_BYTES_PER_ROW 14\n\nenum sensortypes { HIDEKI_UNKNOWN, HIDEKI_TEMP, HIDEKI_TS04, HIDEKI_WIND, HIDEKI_RAIN };\n\nstatic int hideki_ts04_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int ret = 0;\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        int sensortype;\n        // Expect 8, 9, 10, or 14 unstuffed bytes, allow up to 4 missing bits\n        int unstuffed_len = (bitbuffer->bits_per_row[row] + 4) / 9;\n        if (unstuffed_len == 14)\n            sensortype = HIDEKI_WIND;\n        else if (unstuffed_len == 10)\n            sensortype = HIDEKI_TS04;\n        else if (unstuffed_len == 9)\n            sensortype = HIDEKI_RAIN;\n        else if (unstuffed_len == 8)\n            sensortype = HIDEKI_TEMP;\n        else {\n            ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n        unstuffed_len -= 1; // exclude sync\n\n        uint8_t *b = bitbuffer->bb[row];\n        // Expect a start (not inverted) of 00000110 1, but allow missing bits\n        int sync = b[0] << 1 | b[1] >> 7;\n        int startpos = -1;\n        for (int i = 0; i < 4; ++i) {\n            if (sync == 0x0d) {\n                startpos = 9 - i;\n                break;\n            }\n            sync >>= 1;\n        }\n        if (startpos < 0) {\n            ret = DECODE_ABORT_EARLY;\n            continue;\n        }\n\n        // Invert all bits\n        bitbuffer_invert(bitbuffer);\n\n        uint8_t packet[HIDEKI_MAX_BYTES_PER_ROW];\n        // Strip (unstuff) and check parity bit\n        // TODO: refactor to util function\n        int unstuff_error = 0;\n        for (int i = 0; i < unstuffed_len; ++i) {\n            unsigned int offset = startpos + i * 9;\n            packet[i] = (b[offset / 8] << (offset % 8)) | (b[offset / 8 + 1] >> (8 - offset % 8));\n            // check parity\n            uint8_t parity = (b[offset / 8 + 1] >> (7 - offset % 8)) & 1;\n            if (parity != parity8(packet[i])) {\n                decoder_logf(decoder, 1, __func__, \"Parity error at %d\", i);\n                ret = DECODE_FAIL_MIC;\n                unstuff_error = i;\n                break;\n            }\n        }\n        if (unstuff_error) {\n            continue;\n        }\n\n        // XOR check all bytes\n        int chk = xor_bytes(packet, unstuffed_len - 1);\n        if (chk) {\n            decoder_log(decoder, 1, __func__, \"XOR error\");\n            ret = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        // CRC-8 poly=0x07 init=0x00\n        if (crc8(packet, unstuffed_len, 0x07, 0x00)) {\n            decoder_log(decoder, 1, __func__, \"CRC error\");\n            ret = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        // Reflect LSB first to LSB last\n        reflect_bytes(packet, unstuffed_len);\n\n        int pkt_len  = (packet[1] >> 1) & 0x1f;\n        //int pkt_seq  = packet[2] >> 6;\n        //int pkt_type = packet[2] & 0x1f;\n        // 0x0C Anemometer\n        // 0x0D UV sensor\n        // 0x0E Rain level meter\n        // 0x1E Thermo/hygro-sensor\n\n        if (pkt_len + 2 != unstuffed_len) {\n            decoder_log(decoder, 1, __func__, \"LEN error\");\n            ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n\n        int channel = (packet[0] >> 5) & 0x0F;\n        if (channel >= 5) channel -= 1;\n        int rc = packet[0] & 0x0F;\n        int temp = (packet[4] & 0x0F) * 100 + ((packet[3] & 0xF0) >> 4) * 10 + (packet[3] & 0x0F);\n        if (((packet[4]>>7) & 1) == 0) {\n            temp = -temp;\n        }\n        int battery_ok = (packet[4] >> 6) & 1;\n\n        if (sensortype == HIDEKI_TS04) {\n            int humidity = ((packet[5] & 0xF0) >> 4) * 10 + (packet[5] & 0x0F);\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"Hideki-TS04\",\n                    \"id\",               \"Rolling Code\",     DATA_INT,    rc,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    battery_ok,\n                    \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp/10.f,\n                    \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        if (sensortype == HIDEKI_WIND) {\n            int const wd[] = { 0, 15, 13, 14, 9, 10, 12, 11, 1, 2, 4, 3, 8, 7, 5, 6 };\n            int wind_direction = wd[((packet[10] & 0xF0) >> 4)] * 225;\n            int wind_speed = (packet[8] & 0x0F) * 100 + (packet[7] >> 4) * 10 + (packet[7] & 0x0F);\n            int gust_speed = (packet[9] >> 4) * 100 + (packet[9] & 0x0F) * 10 + (packet[8] >> 4);\n            int const ad[] = { 0, 1, -1, 2 }; // i.e. None, CW, CCW, invalid\n            int wind_approach = ad[(packet[10] >> 2) & 0x03];\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"Hideki-Wind\",\n                    \"id\",               \"Rolling Code\",     DATA_INT,    rc,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    battery_ok,\n                    \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp * 0.1f,\n                    \"wind_avg_mi_h\",    \"Wind Speed\",       DATA_FORMAT, \"%.2f mi/h\", DATA_DOUBLE, wind_speed * 0.1f,\n                    \"wind_max_mi_h\",    \"Gust Speed\",       DATA_FORMAT, \"%.2f mi/h\", DATA_DOUBLE, gust_speed * 0.1f,\n                    \"wind_approach\",    \"Wind Approach\",    DATA_INT,    wind_approach,\n                    \"wind_dir_deg\",     \"Wind Direction\",   DATA_FORMAT, \"%.1f\", DATA_DOUBLE, wind_direction * 0.1f,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        if (sensortype == HIDEKI_TEMP) {\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"Hideki-Temperature\",\n                    \"id\",               \"Rolling Code\",     DATA_INT,    rc,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    battery_ok,\n                    \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp * 0.1f,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        if (sensortype == HIDEKI_RAIN) {\n            int rain_units = (packet[4] << 8) | packet[3];\n            battery_ok = (packet[1] >> 6) & 1;\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"Hideki-Rain\",\n                    \"id\",               \"Rolling Code\",     DATA_INT,    rc,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    battery_ok,\n                    \"rain_mm\",          \"Rain\",             DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_units * 0.7f,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        // unknown sensor type\n        return DECODE_FAIL_SANITY;\n    }\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_mi_h\",\n        \"wind_max_mi_h\",\n        \"wind_approach\",\n        \"wind_dir_deg\",\n        \"rain_mm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const hideki_ts04 = {\n        .name        = \"HIDEKI TS04 Temperature, Humidity, Wind and Rain Sensor\",\n        .modulation  = OOK_PULSE_DMC,\n        .short_width = 520,  // half-bit width 520 us\n        .long_width  = 1040, // bit width 1040 us\n        .reset_limit = 4000,\n        .tolerance   = 240,\n        .decode_fn   = &hideki_ts04_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/holman_ws5029.c",
    "content": "/** @file\n    AOK Electronic Limited weather station.\n\n    Copyright (C) 2023 Bruno OCTAU (ProfBoc75) (improve integrity check for all devices here and add support for AOK-5056 weather station PR #2419)\n    Copyright (C) 2023 Christian W. Zuckschwerdt <zany@triq.net> ( reverse galois and xor_shift_bytes check algorithms PR #2419)\n    Copyright (C) 2019 Ryan Mounce <ryan@mounce.com.au> (PCM version)\n    Copyright (C) 2018 Brad Campbell (PWM version)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n/** @fn int holman_ws5029pcm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nAOK Electronic Limited weather station.\n\nKnown Rebrand compatible with:\n- Holman iWeather Station ws5029. https://www.holmanindustries.com.au/products/iweather-station/\n- Conrad Renkforce AOK-5056\n- Optex Electronique 990018 SM-018 5056\n\nAppears to be related to the Fine pos WH1080 and Digitech XC0348.\n\n- Modulation: FSK PCM\n- Frequency: 917.0 MHz +- 40 kHz\n- 10 kb/s bitrate, 100 us symbol/bit time\n\nA transmission burst is sent every 57 seconds. Each burst consists of 3\nrepetitions of the same \"package\" separated by a 1 ms gap.\nThe length of 196 or 218 bits depends on the device type.\n\nPackage format:\n- Preamble            {48}0xAAAAAAAAAAAA\n- Header              {24}0x98F3A5\n- Payload             {96 or 146} see below\n- zeros               {36} 0 with battery ?\n- Checksum/CRC        {8}  xor 12 bytes then reverse Galois algorithm (gen = 0x00, key = 0x31) PR #2419\n- Trailer/postamble   {20} direction (previous ?) and 3 zeros\n\nPayload format: Without UV Lux sensor\n\n    Fixed Values 0x  : AA AA AA AA AA AA 98 F3 A5\n\n    Byte position    : 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15\n    Payload          : II II CC CH HR RR WW Dx xx xx ?x xx ss 0d 00 0\n\n- IIII        station ID (randomised on each battery insertion)\n- CCC         degrees C, signed, in multiples of 0.1 C\n- HH          humidity %\n- RRR         cumulative rain in multiples of 0.79 mm\n- WW          wind speed in km/h\n- D           wind direction (0 = N, 4 = E, 8 = S, 12 = W)\n- xxxxxxxxx   ???, usually zero\n- ss          xor 12 bytes then reverse Galois algorithm (gen = 0x00 , key = 0x31) PR #2419\n\nPayload format: With UV Lux sensor\n\n    Fixed Values 0x  : AA AA AA AA AA AA 98 F3 A5\n\n    Byte position    : 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18\n    Payload          : II II CC CH HR RR WW |         | NN SS 0D 00 00 00 00 0\n                                +-----------+         +-------------+\n                                |                                   |\n                                |   07       08       09       10   |\n                  bits details : DDDDUUUU ULLLLLLL LLLLLLLL LLBBNNNN\n\n- I     station ID (randomised on each battery insertion)\n- C     degrees C, signed, in multiples of 0.1 C\n- H     humidity %\n- R     cumulative rain in mm\n- W     wind speed in km/h\n- D     wind direction (0 = N, 4 = E, 8 = S, 12 = W)\n- U     Index UV\n- L     Lux\n- B     Battery\n- N     Payload number, increase at each message 000->FFF but not always, strange behavior. no clue\n- S     xor 12 bytes then reverse Galois algorithm (gen = 0x00 , key = 0x31) PR #2419\n- D     Previous Wind direction\n- Fixed values to 9 zeros\n\nTo get raw data\n$ rtl_433 -f 917M -X 'name=AOK,modulation=FSK_PCM,short=100,long=100,preamble={48}0xAAAAAA98F3A5,reset=22000'\n\n@sa holman_ws5029pwm_decode()\n\n*/\n\n#include \"decoder.h\"\n\nstatic int holman_ws5029pcm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int const wind_dir_degr[] = {0, 23, 45, 68, 90, 113, 135, 158, 180, 203, 225, 248, 270, 293, 315, 338};\n    uint8_t const preamble[] = {0xAA, 0xAA, 0xAA, 0x98, 0xF3, 0xA5};\n\n    data_t *data;\n    uint8_t b[18];\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 1, __func__, \"Wrong number of rows (%d)\", bitbuffer->num_rows);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned bits = bitbuffer->bits_per_row[0];\n\n    if (bits < 192 ) {                 // too small\n        return DECODE_ABORT_LENGTH;\n    }\n\n    unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble, sizeof (preamble) * 8);\n\n    if (pos >= bits) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_logf(decoder, 2, __func__, \"Found AOK preamble pos: %d\", pos);\n\n    pos += sizeof(preamble) * 8;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, pos, b, sizeof(b) * 8);\n\n    uint8_t chk_digest = b[12];\n    uint8_t chk_calc = xor_bytes(b, 12);\n    // reverse Galois algorithm then (gen = 0x00, key = 0x31) PR #2419\n    int chk_expected = lfsr_digest8_reflect(&chk_calc, 1, 0x00, 0x31);\n\n    if (chk_expected != chk_digest) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int device_id     = (b[0] << 8) | b[1];\n    int temp_raw      = (int16_t)((b[2] << 8) | (b[3] & 0xf0)); // uses sign-extend\n    float temp_c      = (temp_raw >> 4) * 0.1f;\n    int humidity      = ((b[3] & 0x0f) << 4) | ((b[4] & 0xf0) >> 4);\n    int rain_raw      = ((b[4] & 0x0f) << 8) | b[5];\n    float speed_kmh   = (float)b[6];\n    int direction_deg = wind_dir_degr[(b[7] & 0xf0) >> 4];\n    int light_lux    = ((b[8] & 0x7F) << 10) | (b[9] << 2) | ((b[10] & 0xC0) >> 6);\n\n    if (bits < 200 && light_lux == 0) {                 // model without UV LUX\n        float rain_mm     = rain_raw * 0.79f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"Holman-WS5029\",\n                \"id\",               \"Station ID\",        DATA_FORMAT, \"%04X\",       DATA_INT,    device_id,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",     DATA_DOUBLE, temp_c,\n                \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",      DATA_INT,    humidity,\n                \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",    DATA_DOUBLE, rain_mm,\n                \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n                \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT, direction_deg,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (bits < 221) {                         // model with UV LUX\n        float rain_mm    = rain_raw * 1.0f;\n        int uv_index     = ((b[7] & 0x07) << 1) | ((b[8] & 0x80) >> 7);\n        int battery_low  = ((b[10] & 0x30) >> 4);\n        int counter      = ((b[10] & 0x0f) << 8 | b[11]);\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"AOK-5056\",\n                \"id\",               \"Station ID\",        DATA_FORMAT, \"%04X\",      DATA_INT,    device_id,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",    DATA_DOUBLE, temp_c,\n                \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",     DATA_INT,    humidity,\n                \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",   DATA_DOUBLE, rain_mm,\n                \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\", DATA_DOUBLE, speed_kmh,\n                \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT,                              direction_deg,\n                \"uvi\",              \"UV Index\",         DATA_FORMAT, \"%.0f\",      DATA_DOUBLE, (double)uv_index,\n                \"light_lux\",        \"Lux\",              DATA_FORMAT, \"%u\",        DATA_INT,    light_lux,\n                \"counter\",          \"Counter\",          DATA_FORMAT, \"%u\",        DATA_INT,    counter,\n                \"battery_ok\",       \"battery\",          DATA_FORMAT, \"%u\",        DATA_INT,    !battery_low,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"humidity\",\n        \"battery_ok\",\n        \"rain_mm\",\n        \"wind_avg_km_h\",\n        \"wind_dir_deg\",\n        \"uvi\",\n        \"light_lux\",\n        \"counter\",\n        \"mic\",\n        NULL,\n};\n\nr_device const holman_ws5029pcm = {\n        .name        = \"AOK Weather Station rebrand Holman Industries iWeather WS5029, Conrad AOK-5056, Optex 990018\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100,\n        .long_width  = 100,\n        .reset_limit = 19200,\n        .decode_fn   = &holman_ws5029pcm_decode,\n        .fields      = output_fields,\n};\n\n/** @fn int holman_ws5029pwm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nHolman Industries WS5029 weather station using PWM.\n\nPackage format: (invert)\n- Preamble            {24} 0xAAA598\n- Payload             {56} [ see below ]\n- Checksum/CRC         {8} xor_shift_bytes (key = 0x18) PR #2419\n- Trailer/postamble    {8} 0x00 or 0x80\n\nPayload format:\n\n    Byte position    : 00 01 02[03 04 05 06 07 08 09]10 11\n    Payload          : AA A5 98 II BC CC HH RR RW WD SS 00\n\n- I    station ID\n- B    battery low indicator\n- C    degrees C, signed, in multiples of 0.1 C\n- H    Humidity 0-100 %\n- R    Rain is 0.79mm / count , 618 counts / 488.2mm - 190113 - Multiplier is exactly 0.79\n- W    Wind speed in km/h\n- D    Wind direction, clockwise from North, in multiples of 22.5 deg\n- S    xor_shift_bytes , see PR #2419\n\nTo get the raw data :\n$ rtl_433 -f 433.92M -X \"n=Holman-WS5029-PWM,m=FSK_PWM,s=488,l=976,g=2000,r=6000,invert\"\n\n*/\n\nstatic uint8_t xor_shift_bytes(uint8_t const message[], unsigned num_bytes, uint8_t shift_up)   // see #2419 for more details about the xor_shift_bytes , used by PWM device\n{\n    uint8_t result0 = 0;\n    for (unsigned i = 0; i < num_bytes; i += 2) {\n        result0 ^= message[i];\n    }\n    uint8_t result1 = 0;\n    for (unsigned i = 1; i < num_bytes; i += 2) {\n        result1 ^= message[i];\n    }\n    uint8_t resultx = 0;\n    for (unsigned j = 0; j < 7; ++j) {\n        if (shift_up & (1 << j))\n            resultx ^= result0 << (j + 1);\n    }\n    return result0 ^ result1 ^ resultx;\n}\n\nstatic int holman_ws5029pwm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x55, 0x5a, 0x67}; // Preamble/Device inverted\n\n    data_t *data;\n    uint8_t *b;\n    uint16_t temp_raw;\n    int id, humidity, wind_dir, battery_low;\n    float temp_c, rain_mm, speed_kmh;\n\n    // Data is inverted, but all these checks can be performed\n    // and validated prior to inverting the buffer. Invert\n    // only if we have a valid row to process.\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 96);\n    if (r < 0 || bitbuffer->bits_per_row[r] != 96)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[r];\n\n    // Test for preamble / device code\n    if (memcmp(b, preamble, 3))\n        return DECODE_FAIL_SANITY;\n\n    // Invert data for processing\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t chk_digest = b[10];\n    // xor_shift_bytes , see PR #2419\n    int chk_calc = xor_shift_bytes(b, 10, 0x18);\n    //fprintf(stderr, \"%s: 11th byte %02x chk_calc %02x \\n\", __func__, chk_digest, chk_calc );\n\n    if (chk_calc != chk_digest) {\n        return DECODE_FAIL_MIC;\n    }\n\n    id          = b[3];                                                // changes on each power cycle\n    battery_low = (b[4] & 0x80);                                       // High bit is low battery indicator\n    temp_raw    = (int16_t)(((b[4] & 0x0f) << 12) | (b[5] << 4));      // uses sign-extend\n    temp_c      = (temp_raw >> 4) * 0.1f;                              // Convert sign extended int to float\n    humidity    = b[6];                                                // Simple 0-100 RH\n    rain_mm     = ((b[7] << 4) + (b[8] >> 4)) * 0.79f;                 // Multiplier tested empirically over 618 pulses\n    speed_kmh   = (float)(((b[8] & 0xF) << 4) + (b[9] >> 4));          // In discrete kph\n    wind_dir    = b[9] & 0xF;                                          // 4 bit wind direction, clockwise from North\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Holman-WS5029\",\n            \"id\",               \"\",                 DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",     DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",       DATA_INT,    humidity,\n            \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",    DATA_DOUBLE, rain_mm,\n            \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n            \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT,    (int)(wind_dir * 22.5),\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nr_device const holman_ws5029pwm = {\n        .name        = \"Holman Industries iWeather WS5029 weather station (older PWM)\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 488,\n        .long_width  = 976,\n        .reset_limit = 6000,\n        .gap_limit   = 2000,\n        .decode_fn   = &holman_ws5029pwm_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/homelead_hg9901.c",
    "content": "/** @file\n    Homelead HG9901 soil moisture/temp/light level sensor decoder.\n\n    Copyright (C) 2025 Boing <dhs.mobil@gmail.com>, \\@inonoob\n    and Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nHomelead HG9901 soil moisture/temp/light level sensor decoder.\n\n- Shenzhen Homelead Electronics Co., LTD. Wireless Soil Monitor HG9901, e.g. ASIN B0CRKN18C9\n  FCC ID: 2AAXF‐HG9901, Model No: HG01, https://fccid.io/2AAXF-HG9901\n\nKnown rebrands:\n- Geevon T23033 / T230302 Soil Moisture/Temp/Light Level Sensor, ASIN B0D9Z9HLYD\n  see #2977 by emmjaibi for excellent analysis\n- Dr.Meter soil sensor, ASIN B0CQKYTBC6\n- Royal Gardineer ZX8859-944, ASIN B0DQTYYZK8\n- Various other rebrands: Reyke, Vodeson, Midlocater, Kithouse, Vingnut\n- some unbranded sensors on AliEexpress\n\nS.a. #2977 #3189 #3190 #3194 #3299\n\nThis device is a simple garden temperature/moisture transmitter with a small LCD display for local viewing.\n\nExample codes:\n    raw      {65}55aaee8ddae84fcf\n    inverted {65}aa5513fd001630800\n\nThe sensor will send a message every ~30 mins if no changes are measured.\nIf changes are measured the sensor will instantly send messages.\nThis might not happen if the changes have a matching checksum -- apparently that's the check used by the sensor.\nE.g. Moisture 62%, Temperature 23 C, Light Level: 4\nmatches Moisture 59%, Temperature 24 C, Light Level: 6.\n\nThe minimum battery voltage seems to be 1.18V.\n\n## Data transmission\n\n9 repeats of 433.92 MHz (EU region).\nModulation is OOK PWM with 400/1200 us timing, inverted bits.\n\n## Data Layout\n\n        PPPP PPPP PPPP PPPP IIII IIII IIII IIII MMMM MMMM STTT TTTT QQBB LLLL CCCC XXXXXXXX\n\n- P = Preamble of 16 bits with 0xaa55 (inverted)\n- I = ID 16 bits, seems to survive battery changes\n- M = soil moisture 0-100% as an 8 bit integer\n- S = sign for temperature (0 for positive or 1 for negative)\n- T = Temperature as 7 bit integer ~0-100C\n- Q = 2 sequence bits\n  - device sends message on CHS change !\n  - sequence:\n  - S 00  initial phase duration 150 secs\n  - S 01  interval timer 3 mins\n  - S 02  interval timer 15 mins\n  - S 03  interval timer 30 mins\n- B = battery status of 1 (1.22 V) to 3 (above 1.42 V), 0 so far has not been observed?\n- L = light level (9 states from LOW- to HIGH+)\n  - 0 (LOW-)     0\n  - 1 (LOW)    > 120 Lux\n  - 2 (LOW+)   > 250 Lux\n  - 3 (NOR-)   > 480 Lux\n  - 4 (NOR)    > 750 Lux\n  - 5 (NOR+)   >1200 Lux\n  - 6 (HIGH-)  >1700 Lux\n  - 7 (HIGH)   >3800 Lux\n  - 8 (HIGH+)  >5200 Lux, max should be 15000 Lux\n- C = 4 bit checksum\n- X = Trailer of 8 bits equal to 0xf8 , can be ignored\n\nNote: Device drifts in direct sun and shows up to 12C offset.\nNote: Device is NOT waterproof (IP27), don't immerse in water.\nNote: Uses one AA battery AA or rechargeable cell, lasts for up to: 18 months.\n*/\nstatic int homelead_hg9901_decoder(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x55, 0xaa};\n    // Rough estimate of Lux values for light levels 0 - 8.\n    int const lux_estimate[] = {60, 200, 400, 600, 1000, 1500, 2800, 4500, 10000, -1, -1, -1, -1, -1, -1, -1};\n\n    int row = bitbuffer_find_repeated_row(bitbuffer, 1, 65); // expected are 12 repeats but 1 is enough\n    if (row < 0) {\n        return DECODE_ABORT_EARLY; // no good row found\n    }\n\n    // Check that bits_per_row is 65 or a few bits more\n    unsigned row_len = bitbuffer->bits_per_row[row];\n    if (row_len > 65 + 8) {\n        return DECODE_ABORT_EARLY; // wrong Data Length (must be 65)\n    }\n\n    // Search preamble\n    unsigned pos = bitbuffer_search(bitbuffer, row, 0, preamble, 16);\n    if (pos + 65 > row_len) {\n        return DECODE_ABORT_LENGTH; // preamble not found or packet truncated\n    }\n\n    // Invert data\n    bitbuffer_invert(bitbuffer);\n\n    uint8_t *b = bitbuffer->bb[row];\n\n    // Nibble-wide checksum validation\n    int chk = (b[7] & 0xf0) >> 4;\n    int sum = add_nibbles(b, 7) & 0x0f;\n\n    if (sum != chk) {\n        return DECODE_FAIL_MIC; // Checksum mismatch\n    }\n\n    int id          = (b[2] << 8) | b[3];\n    int moisture    = b[4];\n    int t_sign      = (b[5] & 0x80) >> 7;\n    int temperature = b[5] & 0x7f;\n    int sequence    = (b[6] & 0xc0) >> 6;\n    int batt_lvl    = (b[6] & 0x30) >> 4;\n    int light_lvl   = (b[6] & 0x0f);\n    int light_lux   = lux_estimate[light_lvl];\n\n    if (t_sign) {\n        temperature = (0 - temperature);\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n    \t\t\"model\",            \"Model\",            DATA_STRING,    \"Homelead-HG9901\",\n    \t\t\"id\",               \"ID\",               DATA_FORMAT,    \"%04X\",\t \t DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",      \tDATA_INT,    \tbatt_lvl > 1, // Level 1 means \"Low\"\n    \t\t\"battery_pct\",      \"Battery level\",    DATA_INT,       100 * batt_lvl / 3, // Note: this might change with #3103\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT,    \"%.0f C\",    DATA_DOUBLE, (double)temperature,\n            \"moisture\",         \"Moisture\",     \tDATA_FORMAT, \t\"%d %%\", \t DATA_INT, moisture,\n    \t\t\"light_lvl\",        \"Light level\",      DATA_INT,       light_lvl,\n            \"light_lux\",        \"Light\",            DATA_FORMAT,    \"%d lux\",    DATA_INT, light_lux,\n    \t\t\"sequence\",         \"TX Sequence\",      DATA_INT,       sequence,\n    \t\t\"mic\",              \"Integrity\",        DATA_STRING,\t\"CHECKSUM\",\n    \t\tNULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    bitbuffer_invert(bitbuffer); // FIXME: DEBUG, remove this once account_event() is fixed\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_pct\",\n        \"temperature_C\",\n        \"moisture\",\n        \"light_lvl\",\n        \"light_lux\",\n        \"sequence\",\n        \"mic\",\n        NULL,\n};\n\nr_device const homelead_hg9901 = {\n        .name        = \"Homelead HG9901 (Geevon, Dr.Meter, Royal Gardineer) soil moisture/temp/light level sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 432,  // gap is 1000\n        .long_width  = 1228, // gap is 230\n        .gap_limit   = 2000, // packet gap is 3700\n        .reset_limit = 4500,\n        .decode_fn   = &homelead_hg9901_decoder,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/hondaremote.c",
    "content": "/** @file\n    Honda Car Key.\n\n    Copyright (C) 2016 Adrian Stevenson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nHonda Car Key.\n\nIdentifies button event, but does not attempt to decrypt rolling code...\nNote that this is actually Manchester coded and should be changed.\n\n*/\n#include \"decoder.h\"\n\nstatic char const *const command_code[] = {\"boot\", \"unlock\", \"lock\",};\n\nstatic char const *get_command_codes(const uint8_t *bytes)\n{\n    unsigned char command = bytes[46] - 0xAA;\n    if (command < (sizeof(command_code) / sizeof(command_code[0]))) {\n        return command_code[command];\n    } else {\n        return \"unknown\";\n    }\n}\n\nstatic int hondaremote_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    char const *code;\n    uint16_t device_id;\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        b = bitbuffer->bb[row];\n        // Validate package\n        if (((bitbuffer->bits_per_row[row] < 385) || (bitbuffer->bits_per_row[row] > 394)) ||\n                ((b[0] != 0xFF) || (b[38] != 0xFF)))\n            continue; // DECODE_ABORT_LENGTH\n\n        code = get_command_codes(b);\n        device_id = b[44]<<8 | b[45];\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",     DATA_STRING, \"Honda-CarRemote\",\n                \"id\",           \"\",     DATA_INT, device_id,\n                \"code\",         \"\",     DATA_STRING, code,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"code\",\n        NULL,\n};\n\nr_device const hondaremote = {\n        .name        = \"Honda Car Key\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 250,\n        .long_width  = 500,\n        .reset_limit = 2000,\n        .decode_fn   = &hondaremote_callback,\n        .disabled    = 1, // no MIC, weak sanity checks\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/honeywell.c",
    "content": "/** @file\n    Honeywell (Ademco) Door/Window Sensors (345Mhz).\n\n    Copyright (C) 2016 adam1010\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nHoneywell (Ademco) Door/Window Sensors (345.0Mhz).\n\nTested with the Honeywell 5811 Wireless Door/Window transmitters.\n\nAlso: 2Gig DW10 door sensors,\nand Resolution Products RE208 (wire to air repeater).\nAnd DW11 with 96 bit packets.\nAlso 2GIG 345Mhz glass break detectors 2GIG-GB1-345 as channel 0x9\n\nMaybe: 5890PI?\n\n64 bit packets, repeated multiple times per open/close event.\n\nProtocol whitepaper: \"DEFCON 22: Home Insecurity\" by Logan Lamb.\n\nData layout:\n\n    PP PP C IIIII EE SS SS\n\n- P: 16bit Preamble and sync bit (always ff fe)\n- C: 4bit Channel\n- I: 20bit Device serial number / or counter value\n- E: 8bit Event, where 0x80 = Open/Close, 0x04 = Heartbeat / or id\n- S: 16bit CRC\n\n*/\nstatic int honeywell_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 0xFFFE\n    uint8_t const preamble_pattern[2] = {0xff, 0xe0}; // 12 bits\n\n\n    int row = 0; // we expect a single row only. reduce collisions\n    if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[row] < 60) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_invert(bitbuffer);\n\n    int pos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, 12) + 12;\n    int len = bitbuffer->bits_per_row[row] - pos;\n    if (len < 48) {\n        return DECODE_ABORT_LENGTH;\n    }\n    uint8_t b[10] = {0};\n    bitbuffer_extract_bytes(bitbuffer, row, pos, b, 80);\n\n    int channel   = b[0] >> 4;\n    int device_id = ((b[0] & 0xf) << 16) | (b[1] << 8) | b[2];\n    int crc       = (b[4] << 8) | b[5];\n\n    if (device_id == 0 && crc == 0) {\n        return DECODE_ABORT_EARLY; // Reduce collisions\n    }\n\n    if (len > 50) { // DW11\n        decoder_log_bitrow(decoder, 1, __func__, b, (len > 80 ? 80 : len), \"\");\n    }\n\n    int crc_calculated;\n    if (channel == 0x2 || channel == 0x4 || channel == 0x9 || channel == 0xA) {\n        // 2GIG brand\n        crc_calculated = crc16(b, 4, 0x8050, 0);\n    } else { // channel == 0x8\n        crc_calculated = crc16(b, 4, 0x8005, 0);\n    }\n    if (crc != crc_calculated) {\n        return DECODE_FAIL_MIC; // Not a valid packet\n    }\n\n    int event = b[3];\n    // decoded event bits: CTRABHUU\n    // NOTE: not sure if these apply to all device types\n    int contact     = (event & 0x80) >> 7;\n    int tamper      = (event & 0x40) >> 6;\n    int reed        = (event & 0x20) >> 5;\n    int alarm       = (event & 0x10) >> 4;\n    int battery_low = (event & 0x08) >> 3;\n    int heartbeat   = (event & 0x04) >> 2;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",         DATA_STRING, \"Honeywell-Security\",\n            \"id\",           \"\",         DATA_FORMAT, \"%05x\", DATA_INT, device_id,\n            \"channel\",      \"\",         DATA_INT,    channel,\n            \"event\",        \"\",         DATA_FORMAT, \"%02x\", DATA_INT, event,\n            \"state\",        \"\",         DATA_STRING, contact ? \"open\" : \"closed\", // Ignore the reed switch legacy.\n            \"contact_open\", \"\",         DATA_INT,    contact,\n            \"reed_open\",    \"\",         DATA_INT,    reed,\n            \"alarm\",        \"\",         DATA_INT,    alarm,\n            \"tamper\",       \"\",         DATA_INT,    tamper,\n            \"battery_ok\",   \"Battery\",  DATA_INT,    !battery_low,\n            \"heartbeat\",    \"\",         DATA_INT,    heartbeat,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"event\",\n        \"state\",\n        \"contact_open\",\n        \"reed_open\",\n        \"alarm\",\n        \"tamper\",\n        \"battery_ok\",\n        \"heartbeat\",\n        \"mic\",\n        NULL,\n};\n\nr_device const honeywell = {\n        .name        = \"Honeywell Door/Window Sensor, 2Gig DW10/DW11, RE208 repeater\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 156,\n        .long_width  = 0,\n        .reset_limit = 292,\n        .decode_fn   = &honeywell_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/honeywell_cm921.c",
    "content": "/** @file\n    Honeywell CM921 Thermostat.\n\n    Copyright (C) 2020 Christoph M. Wintersteiger <christoph@winterstiger.at>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int honeywell_cm921_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nHoneywell CM921 Thermostat (subset of Evohome).\n\n868Mhz FSK, PCM, Start/Stop bits, reversed, Manchester.\n*/\n\n// #define _DEBUG\n\nstatic int decode_10to8(uint8_t const *b, int pos, int end, uint8_t *out)\n{\n    // we need 10 bits\n    if (pos + 10 > end) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // start bit of 0\n    if (bitrow_get_bit(b, pos) != 0) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // stop bit of 1\n    if (bitrow_get_bit(b, pos + 9) != 1) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    *out = bitrow_get_byte(b, pos + 1);\n\n    return 10;\n}\n\ntypedef struct {\n    uint8_t header;\n    uint8_t num_device_ids;\n    uint8_t device_id[4][3];\n    uint16_t command;\n    uint8_t payload_length;\n    uint8_t payload[256];\n    uint8_t unparsed_length;\n    uint8_t unparsed[256];\n    uint8_t crc;\n} message_t;\n\n/*\ntypedef struct {\n    int t;\n    const char s[4];\n} dev_map_entry_t;\n\nstatic const dev_map_entry_t device_map[] = {\n        {.t = 1, .s = \"CTL\"},  // Controller\n        {.t = 2, .s = \"UFH\"},  // Underfloor heating (HCC80, HCE80)\n        {.t = 3, .s = \" 30\"},  // HCW82??\n        {.t = 4, .s = \"TRV\"},  // Thermostatic radiator valve (HR80, HR91, HR92)\n        {.t = 7, .s = \"DHW\"},  // DHW sensor (CS92)\n        {.t = 10, .s = \"OTB\"}, // OpenTherm bridge (R8810)\n        {.t = 12, .s = \"THm\"}, // Thermostat with setpoint schedule control (DTS92E, CME921)\n        {.t = 13, .s = \"BDR\"}, // Wireless relay box (BDR91) (HC60NG too?)\n        {.t = 17, .s = \" 17\"}, // Unknown - Outside weather sensor?\n        {.t = 18, .s = \"HGI\"}, // Honeywell Gateway Interface (HGI80, HGS80)\n        {.t = 22, .s = \"THM\"}, // Thermostat with setpoint schedule control (DTS92E)\n        {.t = 30, .s = \"GWY\"}, // Gateway (e.g. RFG100?)\n        {.t = 32, .s = \"VNT\"}, // (HCE80) Ventilation (Nuaire VMS-23HB33, VMN-23LMH23)\n        {.t = 34, .s = \"STA\"}, // Thermostat (T87RF)\n        {.t = 63, .s = \"NUL\"}, // No device\n};\n\nstatic void decode_device_id(const uint8_t device_id[3], char *buf, size_t buf_sz)\n{\n    int dev_type = device_id[0] >> 2;\n    int dev_id   = (device_id[0] & 0x03) << 16 | (device_id[1] << 8) | device_id[2];\n\n    char const *dev_name = \" --\";\n    for (size_t i = 0; i < sizeof(device_map) / sizeof(dev_map_entry_t); i++) {\n        if (device_map[i].t == dev_type) {\n            dev_name = device_map[i].s;\n        }\n    }\n\n    snprintf(buf, buf_sz, \"%3s:%06d\", dev_name, dev_id);\n}\n*/\n\nstatic uint8_t next(const uint8_t *bb, unsigned *ipos, unsigned num_bytes)\n{\n    uint8_t r = bitrow_get_byte(bb, *ipos);\n    *ipos += 8;\n    if (*ipos >= num_bytes * 8) {\n        return DECODE_FAIL_SANITY;\n    }\n    return r;\n}\n\nstatic int parse_msg(bitbuffer_t *bmsg, int row, message_t *msg)\n{\n    if (!bmsg || row >= bmsg->num_rows || bmsg->bits_per_row[row] < 8) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    unsigned num_bytes = bmsg->bits_per_row[0]/8;\n    unsigned num_bits = bmsg->bits_per_row[0];\n    unsigned ipos = 0;\n    const uint8_t *bb = bmsg->bb[row];\n    memset(msg, 0, sizeof(message_t));\n\n    // Checksum: All bytes add up to 0.\n    int bsum = add_bytes(bb, num_bytes) & 0xff;\n    int checksum_ok = bsum == 0;\n    msg->crc = bitrow_get_byte(bb, bmsg->bits_per_row[row] - 8);\n\n    if (!checksum_ok) {\n        return DECODE_FAIL_MIC;\n    }\n\n    msg->header = next(bb, &ipos, num_bytes);\n\n    msg->num_device_ids = msg->header == 0x14 ? 1 :\n                          msg->header == 0x18 ? 2 :\n                          msg->header == 0x1c ? 2 :\n                          msg->header == 0x10 ? 2 :\n                          msg->header == 0x3c ? 2 :\n                (msg->header >> 2) & 0x03; // total speculation.\n\n    for (unsigned i = 0; i < msg->num_device_ids; i++) {\n        for (unsigned j = 0; j < 3; j++) {\n            msg->device_id[i][j] = next(bb, &ipos, num_bytes);\n        }\n    }\n\n    msg->command = (next(bb, &ipos, num_bytes) << 8) | next(bb, &ipos, num_bytes);\n    msg->payload_length = next(bb, &ipos, num_bytes);\n\n    for (unsigned i = 0; i < msg->payload_length; i++) {\n        msg->payload[i] = next(bb, &ipos, num_bytes);\n    }\n\n    if (ipos < num_bits - 8) {\n        unsigned num_unparsed_bits = (bmsg->bits_per_row[row] - 8) - ipos;\n        msg->unparsed_length = (num_unparsed_bits + 7) / 8;\n        if (msg->unparsed_length != 0) {\n            bitbuffer_extract_bytes(bmsg, row, ipos, msg->unparsed, num_unparsed_bits);\n        }\n    }\n\n    return ipos;\n}\n\nstatic int honeywell_cm921_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Sources of inspiration:\n    // https://www.domoticaforum.eu/viewtopic.php?f=7&t=5806&start=240\n\n    // preamble=0x55 0xFF 0x00\n    // preamble with start/stop bits=0101010101 0111111111 0000000001\n    //                              =0101 0101 0101 1111 1111 0000 0000 01\n    //                            =0x   5    5    5    F    F    0    0 4\n    // post=10101100\n    // each byte surrounded by start/stop bits (0byte1)\n    // then manchester decode.\n    const uint8_t preamble_pattern[4] = { 0x55, 0x5F, 0xF0, 0x04 };\n    const uint8_t preamble_bit_length = 30;\n    const int row = 0; // we expect a single row only.\n\n    if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[row] < 60) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, bitbuffer->bb[row], bitbuffer->bits_per_row[row], \"\");\n\n    int preamble_start = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, preamble_bit_length);\n    int start = preamble_start + preamble_bit_length;\n    int len = bitbuffer->bits_per_row[row] - start;\n    decoder_logf(decoder, 1, __func__, \"preamble_start=%d start=%d len=%d\", preamble_start, start, len);\n    if (len < 8) {\n        return DECODE_ABORT_LENGTH;\n    }\n    int end = start + len;\n\n    bitbuffer_t bytes = {0};\n    int pos = start;\n    while (pos < end) {\n        uint8_t byte = 0;\n        if (decode_10to8(bitbuffer->bb[row], pos, end, &byte) != 10) {\n            break;\n        }\n        for (unsigned i = 0; i < 8; i++) {\n            bitbuffer_add_bit(&bytes, (byte >> i) & 0x1);\n        }\n        pos += 10;\n    }\n\n    // Skip Manchester breaking header\n    uint8_t header[3] = { 0x33, 0x55, 0x53 };\n    if (bitrow_get_byte(bytes.bb[row], 0) != header[0] ||\n            bitrow_get_byte(bytes.bb[row], 8) != header[1] ||\n            bitrow_get_byte(bytes.bb[row], 16) != header[2]) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Find Footer 0x35 (0x55*)\n    int fi = bytes.bits_per_row[row] - 8;\n    int seen_aa = 0;\n    while (bitrow_get_byte(bytes.bb[row], fi) == 0x55) {\n        seen_aa = 1;\n        fi -= 8;\n    }\n    if (!seen_aa || bitrow_get_byte(bytes.bb[row], fi) != 0x35) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    unsigned first_byte = 24;\n    unsigned end_byte   = fi;\n    unsigned num_bits   = end_byte - first_byte;\n    //unsigned num_bytes = num_bits/8 / 2;\n\n    bitbuffer_t packet = {0};\n    unsigned fpos = bitbuffer_manchester_decode(&bytes, row, first_byte, &packet, num_bits);\n    unsigned man_errors = num_bits - (fpos - first_byte - 2);\n\n#ifndef _DEBUG\n    if (man_errors != 0) {\n        return DECODE_FAIL_SANITY;\n    }\n#endif\n\n    message_t msg;\n\n    int pr = parse_msg(&packet, 0, &msg);\n\n    if (pr <= 0) {\n        return pr;\n    }\n\n    /* clang-format off */\n    data_t *data = data_str(NULL, \"model\",    \"\",             NULL, \"Honeywell-CM921\");\n    /* clang-format on */\n\n    // Sources of inspiration:\n    // https://github.com/Evsdd/The-Evohome-Protocol/wiki\n    // https://www.domoticaforum.eu/viewtopic.php?f=7&t=5806&start=30\n    // (specifically https://www.domoticaforum.eu/download/file.php?id=1396)\n\n    // Decode Device IDs\n\n    char ds[64] = {0}; // up to 4 ids of at most 10+1 chars\n\n    for (unsigned i = 0; i < msg.num_device_ids; i++) {\n        if (i != 0) {\n            strcat(ds, \" \");\n        }\n\n        char buf[16] = {0};\n        // Unused alternative\n        // decode_device_id(msg.device_id[i], buf, sizeof(buf));\n        snprintf(buf, sizeof(buf), \"%02x%02x%02x\",\n                msg.device_id[i][0],\n                msg.device_id[i][1],\n                msg.device_id[i][2]);\n        strcat(ds, buf);\n    }\n\n    data = data_str(data, \"ids\", \"Device IDs\", NULL, ds);\n\n    // Interpret Message\n\n    switch (msg.command) {\n    case 0x1030: {\n        if (msg.payload_length != 16) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        data = data_int(data, \"zone_idx\", \"\", \"%02x\", msg.payload[0]);\n        for (unsigned i = 0; i < 5; i++) { // order fixed?\n            const uint8_t *p = &msg.payload[1 + 3 * i];\n            // *(p+1) == 0x01 always?\n            int value = *(p + 2);\n            switch (*p) {\n            case 0xC8: data = data_int(data, \"max_flow_temp\", \"\", NULL, value); break;\n            case 0xC9: data = data_int(data, \"pump_run_time\", \"\", NULL, value); break;\n            case 0xCA: data = data_int(data, \"actuator_run_time\", \"\", NULL, value); break;\n            case 0xCB: data = data_int(data, \"min_flow_temp\", \"\", NULL, value); break;\n            case 0xCC: /* Unknown, always 0x01? */ break;\n            default:\n                decoder_logf(decoder, 1, __func__, \"Unknown parameter to 0x1030: %x02d=%04d\", *p, value);\n            }\n        }\n        break;\n    }\n    case 0x313F: {\n        if (msg.payload_length != 1 && msg.payload_length != 9) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        switch (msg.payload_length) {\n        case 1:\n            data = data_int(data, \"time_request\", \"\", NULL, msg.payload[0]);\n            break;\n        case 9: {\n            // uint8_t const unknown_0 = msg.payload[0]; /* always == 0? */\n            // uint8_t const unknown_1 = msg.payload[1]; /* direction? */\n            uint8_t const second = msg.payload[2];\n            uint8_t const minute = msg.payload[3];\n            // uint8_t const day_of_week = msg.payload[4] >> 5;\n            uint8_t const hour    = msg.payload[4] & 0x1F;\n            uint8_t const day     = msg.payload[5];\n            uint8_t const month   = msg.payload[6];\n            uint8_t const year[2] = {msg.payload[7], msg.payload[8]};\n            char time_str[256];\n            snprintf(time_str, sizeof(time_str), \"%02d:%02d:%02d %02d-%02d-%04d\", hour, minute, second, day, month, (year[0] << 8) | year[1]);\n            data = data_str(data, \"datetime\", \"\", NULL, time_str);\n            break;\n        }\n        }\n        break;\n    }\n    case 0x0008: {\n        if (msg.payload_length != 2) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        data = data_int(data, \"domain_id\", \"\", NULL, msg.payload[0]);\n        data = data_dbl(data, \"demand\", \"\", NULL, msg.payload[1] * (1 / 200.0F) /* 0xC8 */);\n        break;\n    }\n    case 0x3ef0: {\n        if (msg.payload_length != 3 && msg.payload_length != 6) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        switch (msg.payload_length) {\n        case 3:\n            data = data_dbl(data, \"status\", \"\", NULL, msg.payload[1] * (1 / 200.0F) /* 0xC8 */);\n            break;\n        case 6:\n            data = data_dbl(data, \"boiler_modulation_level\", \"\", NULL, msg.payload[1] * (1 / 200.0F) /* 0xC8 */);\n            data = data_int(data, \"flame_status\", \"\", NULL, msg.payload[3]);\n            break;\n        }\n        break;\n    }\n    case 0x2309: {\n        if (msg.payload_length != 3) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        data = data_int(data, \"zone\", \"\", NULL, msg.payload[0]);\n        // Observation: CM921 reports a very high setpoint during binding (0x7eff); packet: 143255c1230903017efff7\n        data = data_dbl(data, \"setpoint\", \"\", NULL, ((msg.payload[1] << 8) | msg.payload[2]) * (1 / 100.0F));\n        break;\n    }\n    case 0x1100: {\n        if (msg.payload_length != 5 && msg.payload_length != 8) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        data = data_int(data, \"domain_id\", \"\", NULL, msg.payload[0]);\n        data = data_dbl(data, \"cycle_rate\", \"\", NULL, msg.payload[1] * (1 / 4.0F));\n        data = data_dbl(data, \"minimum_on_time\", \"\", NULL, msg.payload[2] * (1 / 4.0F));\n        data = data_dbl(data, \"minimum_off_time\", \"\", NULL, msg.payload[3] * (1 / 4.0F));\n        if (msg.payload_length == 8)\n            data = data_dbl(data, \"proportional_band_width\", \"\", NULL, (msg.payload[5] << 8 | msg.payload[6]) * (1 / 100.0F));\n        break;\n    }\n    case 0x0009: {\n        if (msg.payload_length != 3) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        data = data_int(data, \"device_number\", \"\", NULL, msg.payload[0]);\n        switch (msg.payload[1]) {\n        case 0: data = data_str(data, \"failsafe_mode\", \"\", NULL, \"off\"); break;\n        case 1: data = data_str(data, \"failsafe_mode\", \"\", NULL, \"20-80\"); break;\n        default: data = data_str(data, \"failsafe_mode\", \"\", NULL, \"unknown\");\n        }\n        break;\n    }\n    case 0x3B00: {\n        if (msg.payload_length != 2) {\n            data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n            break;\n        }\n        data = data_int(data, \"domain_id\", \"\", NULL, msg.payload[0]);\n        data = data_dbl(data, \"state\", \"\", NULL, msg.payload[1] * (1 / 200.0F) /* 0xC8 */);\n        break;\n    }\n    case 0x30C9: {\n        size_t num_zones = msg.payload_length / 3;\n        for (size_t i = 0; i < num_zones; i++) {\n            char name[256];\n            snprintf(name, sizeof(name), \"temperature (zone %u)\", msg.payload[3 * i]);\n            int16_t temp = msg.payload[3 * i + 1] << 8 | msg.payload[3 * i + 2];\n            data         = data_dbl(data, name, \"\", NULL, temp * (1 / 100.0F));\n        }\n        break;\n    }\n    case 0x1fd4: {\n        int temp = (msg.payload[1] << 8) | msg.payload[2];\n        data     = data_int(data, \"ticker\", \"\", NULL, temp);\n        break;\n    }\n    case 0x3150: {\n        // example packet Heat Demand: 18 28ad9a 884dd3 3150 0200c6 88\n        data = data_int(data, \"zone\", \"\", NULL, msg.payload[0]);\n        data = data_int(data, \"heat_demand\", \"\", NULL, msg.payload[1]);\n        break;\n    }\n    default: /* Unknown command */\n        data = data_int(data, \"unknown\", \"\", \"%04x\", msg.command);\n        break;\n    }\n\n#ifdef _DEBUG\n    char tstr[256];\n    data = data_hex(data, \"Packet\", NULL, NULL, packet.bb[row], packet.bits_per_row[row] / 8, tstr);\n    data = data_hex(data, \"Header\", NULL, NULL, &msg.header, 1, tstr);\n    uint8_t cmd[2] = {msg.command >> 8, msg.command & 0x00FF};\n    data = data_hex(data, \"Command\", NULL, NULL, cmd, 2, tstr);\n    data = data_hex(data, \"Payload\", NULL, NULL, msg.payload, msg.payload_length, tstr);\n    data = data_hex(data, \"Unparsed\", NULL, NULL, msg.unparsed, msg.unparsed_length, tstr);\n    data = data_hex(data, \"CRC\", NULL, NULL, &msg.crc, 1, tstr);\n    data = data_int(data, \"# man errors\", NULL, NULL, man_errors);\n#endif\n\n    /* clang-format off */\n    data = data_str(data, \"mic\",      \"Integrity\",    NULL, \"CHECKSUM\");\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"ids\",\n#ifdef _DEBUG\n        \"Packet\",\n        \"Header\",\n        \"Command\",\n        \"Payload\",\n        \"Unparsed\",\n        \"CRC\",\n        \"# man errors\",\n#endif\n        \"unknown\",\n        \"time_request\",\n        \"flame_status\",\n        \"zone\",\n        \"setpoint\",\n        \"cycle_rate\",\n        \"minimum_on_time\",\n        \"minimum_off_time\",\n        \"proportional_band_width\",\n        \"device_number\",\n        \"failsafe_mode\",\n        \"ticker\",\n        \"heat_demand\",\n        \"boiler_modulation_level\",\n        \"datetime\",\n        \"domain_id\",\n        \"state\",\n        \"demand\",\n        \"status\",\n        \"zone_idx\",\n        \"max_flow_temp\",\n        \"pump_run_time\",\n        \"actuator_run_time\",\n        \"min_flow_temp\",\n        \"mic\",\n        NULL,\n};\n\nr_device const honeywell_cm921 = {\n        .name        = \"Honeywell CM921 Wireless Programmable Room Thermostat\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 26,\n        .long_width  = 26,\n        .sync_width  = 0,\n        .tolerance   = 5,\n        .reset_limit = 2000,\n        .decode_fn   = &honeywell_cm921_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/honeywell_wdb.c",
    "content": "/** @file\n    Honeywell ActivLink, wireless door bell, PIR Motion sensor.\n\n    Copyright (C) 2018 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nHoneywell ActivLink, wireless door bell, PIR Motion sensor.\n\nFrame documentation courtesy of https://github.com/klohner/honeywell-wireless-doorbell\n\nFrame bits used in Honeywell RCWL300A, RCWL330A, Series 3, 5, 9 and all Decor Series:\n\nWireless Chimes\n\n    0000 0000 1111 1111 2222 2222 3333 3333 4444 4444 5555 5555\n    7654 3210 7654 3210 7654 3210 7654 3210 7654 3210 7654 3210\n    XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX XX.. XXX. .... KEY DATA (any change and receiver doesn't seem to\n                                                                          recognize signal)\n    XXXX XXXX XXXX XXXX XXXX .... .... .... .... .... .... .... KEY ID (different for each transmitter)\n    .... .... .... .... .... 0000 00.. 0000 0000 00.. 000. .... KEY UNKNOWN 0 (always 0 in devices I've tested)\n    .... .... .... .... .... .... ..XX .... .... .... .... .... DEVICE TYPE (10 = doorbell, 01 = PIR Motion sensor)\n    .... .... .... .... .... .... .... .... .... ..XX ...X XXX. FLAG DATA (may be modified for possible effects on\n                                                                           receiver)\n    .... .... .... .... .... .... .... .... .... ..XX .... .... ALERT (00 = normal, 01 or 10 = right-left halo light\n                                                                       pattern, 11 = full volume alarm)\n    .... .... .... .... .... .... .... .... .... .... ...X .... SECRET KNOCK (0 = default, 1 if doorbell is pressed 3x\n                                                                              rapidly)\n    .... .... .... .... .... .... .... .... .... .... .... X... RELAY (1 if signal is a retransmission of a received\n                                                                       transmission, only some models)\n    .... .... .... .... .... .... .... .... .... .... .... .X.. FLAG UNKNOWN (0 = default, but 1 is accepted and I don't\n                                                                              oberserve any effects)\n    .... .... .... .... .... .... .... .... .... .... .... ..X. LOWBAT (1 if battery is low, receiver gives low battery\n                                                                        alert)\n    .... .... .... .... .... .... .... .... .... .... .... ...X PARITY (LSB of count of set bits in previous 47 bits)\n*/\n\n#include \"decoder.h\"\n\nstatic int honeywell_wdb_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row, secret_knock, relay, battery, parity;\n    uint8_t *bytes;\n    data_t *data;\n    unsigned int device, tmp;\n    char const *class, *alert;\n\n    // The device transmits many rows, check for 4 matching rows.\n    row = bitbuffer_find_repeated_row(bitbuffer, 4, 48);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n    bytes = bitbuffer->bb[row];\n\n    if (bitbuffer->bits_per_row[row] != 48)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_invert(bitbuffer);\n\n    /* Parity check (must be EVEN) */\n    parity = parity_bytes(bytes, 6);\n\n    // No need to decode/extract values for simple test\n    if ((!bytes[0] && !bytes[2] && !bytes[4] && !bytes[5])\n       || (bytes[0] == 0xff && bytes[2] == 0xff && bytes[4] == 0xff && bytes[5] == 0xff)) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00 or 0xFF\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (parity) { // ODD parity detected\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"Parity check on row %d failed (%d)\", row, parity);\n        return DECODE_FAIL_MIC;\n    }\n\n    device = bytes[0] << 12 | bytes[1] << 4 | (bytes[2] >> 4);\n    tmp = (bytes[3] & 0x30) >> 4;\n    switch (tmp) {\n    case 0x1: class = \"PIR-Motion\"; break;\n    case 0x2: class = \"Doorbell\"; break;\n    default: class = \"Unknown\"; break;\n    }\n    tmp = bytes[4] & 0x3;\n    switch (tmp) {\n    case 0x0: alert = \"Normal\"; break;\n    case 0x1:\n    case 0x2: alert = \"High\"; break;\n    case 0x3: alert = \"Full\"; break;\n    default: alert = \"Unknown\"; break;\n    }\n    secret_knock = (bytes[5] & 0x10) >> 4;\n    relay = (bytes[5] & 0x8) >> 3;\n    battery = (bytes[5] & 0x2) >> 1;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Honeywell-ActivLink\",\n            \"subtype\",       \"Class\",       DATA_STRING, class,\n            \"id\",            \"Id\",          DATA_FORMAT, \"%x\",   DATA_INT,    device,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !battery,\n            \"alert\",         \"Alert\",       DATA_STRING, alert,\n            \"secret_knock\",  \"Secret Knock\",DATA_FORMAT, \"%d\",   DATA_INT,    secret_knock,\n            \"relay\",         \"Relay\",       DATA_FORMAT, \"%d\",   DATA_INT,    relay,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"subtype\",\n        \"id\",\n        \"battery_ok\",\n        \"alert\",\n        \"secret_knock\",\n        \"relay\",\n        \"mic\",\n        NULL,\n};\n\nr_device const honeywell_wdb = {\n        .name        = \"Honeywell ActivLink, Wireless Doorbell\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 175,\n        .long_width  = 340,\n        .gap_limit   = 0,\n        .reset_limit = 5000,\n        .sync_width  = 500,\n        .decode_fn   = &honeywell_wdb_callback,\n        .fields      = output_fields,\n};\n\nr_device const honeywell_wdb_fsk = {\n        .name        = \"Honeywell ActivLink, Wireless Doorbell (FSK)\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 160,\n        .long_width  = 320,\n        .gap_limit   = 0,\n        .reset_limit = 560,\n        .sync_width  = 500,\n        .decode_fn   = &honeywell_wdb_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ht680.c",
    "content": "/** @file\n    HT680 based Remote control (broadly similar to x1527 protocol).\n\n    Copyright (C) 2016 Igor Polovnikov\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nHT680 based Remote control (broadly similar to x1527 protocol).\n\n- short is 850 us gap 260 us pulse\n- long is 434 us gap 663 us pulse\n\n*/\n\n#include \"decoder.h\"\n\nstatic int ht680_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t b[5]; // 36 bits\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] != 41 || // Length of packet is 41 (36+5)\n                (bitbuffer->bb[row][0] & 0xf8) != 0xa8) // Sync is 10101xxx (5 bits)\n        continue; // DECODE_ABORT_LENGTH\n\n        // remove the 5 sync bits\n        bitbuffer_extract_bytes(bitbuffer, row, 5, b, 36);\n\n        if ((b[1] & 0xf0) != 0xa0 && // A4, A5 always \"open\" on HT680\n            (b[2] & 0x0c) != 0x08 && // AD10 always \"open\" on HT680\n            (b[3] & 0x30) != 0x20 && // AD13 always \"open\" on HT680\n            (b[4] & 0xf0) != 0xa0) // AD16, AD17 always \"open\" on HT680\n        continue; // DECODE_ABORT_EARLY\n\n        // Tristate coding\n        char tristate[21];\n        char *p = tristate;\n        for (int byte = 0; byte < 5; byte++) {\n            for (int bit = 7; bit > 0; bit -= 2) {\n                switch ((b[byte] >> (bit-1)) & 0x03) {\n                    case 0x00: *p++ = '0'; break;\n                    case 0x01: *p++ = 'X'; break; // Invalid code 01\n                    case 0x02: *p++ = 'Z'; break; // Floating state Z is 10\n                    case 0x03: *p++ = '1'; break;\n                    default: *p++ = '?'; break; // Unknown error\n                }\n            }\n        }\n        // remove last two unused bits\n        p -= 2;\n        *p = '\\0';\n\n        int address = (b[0]<<12) | (b[1]<<4) | b[2] >> 4;\n        int button1 = (b[3]>>0) & 0x03;\n        int button2 = (b[3]>>2) & 0x03;\n        int button3 = (b[3]>>6) & 0x03;\n        int button4 = (b[2]>>0) & 0x03;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",    \"\",                 DATA_STRING, \"HT680-Remote\",\n                \"id\",       \"Address\",          DATA_FORMAT, \"0x%06X\", DATA_INT, address,\n                \"button1\",  \"Button 1\",         DATA_STRING, button1 == 3 ? \"PRESSED\" : \"\",\n                \"button2\",  \"Button 2\",         DATA_STRING, button2 == 3 ? \"PRESSED\" : \"\",\n                \"button3\",  \"Button 3\",         DATA_STRING, button3 == 3 ? \"PRESSED\" : \"\",\n                \"button4\",  \"Button 4\",         DATA_STRING, button4 == 3 ? \"PRESSED\" : \"\",\n                \"tristate\", \"Tristate code\",    DATA_STRING, tristate,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"button1\",\n        \"button2\",\n        \"button3\",\n        \"button4\",\n        \"tristate\",\n        NULL,\n};\n\nr_device const ht680 = {\n        .name        = \"HT680 Remote control\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 200,\n        .long_width  = 600,\n        .gap_limit   = 1200,\n        .reset_limit = 14000,\n        .decode_fn   = &ht680_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ibis_beacon.c",
    "content": "/** @file\n    IBIS vehicle information beacon.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nIBIS vehicle information beacon.\n(used in public transportation)\n\nThe packet is 28 manchester encoded bytes with a Preamble of 0xAAB and\n16-bit CRC, containing a company ID, vehicle ID, (door opening) counter,\nand various flags.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int ibis_beacon_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t search = 0xAB; // preamble is 0xAAB\n    uint8_t msg[32];\n    unsigned len;\n    unsigned pos;\n    unsigned i;\n    int id;\n    unsigned counter;\n    int crc;\n    int crc_calculated;\n    char code_str[63];\n\n    // 224 bits data + 12 bits preamble\n    if (bitbuffer->num_rows != 1 || bitbuffer->bits_per_row[0] < 232 || bitbuffer->bits_per_row[0] > 250) {\n        return DECODE_ABORT_LENGTH; // Unrecognized data\n    }\n\n    pos = bitbuffer_search(bitbuffer, 0, 0, &search, 8);\n    if (pos > 26) {\n        return DECODE_ABORT_EARLY; // short buffer or preamble not found\n    }\n    pos += 8; // skip preamble\n    len = bitbuffer->bits_per_row[0] - pos;\n    // we want 28 bytes (224 bits)\n    if (len < 224) {\n        return DECODE_ABORT_LENGTH; // short buffer\n    }\n    len = 224; // cut the last pulse\n\n    bitbuffer_extract_bytes(bitbuffer, 0, pos, (uint8_t *)&msg, len);\n\n    crc_calculated = crc16(msg, 26, 0x8005, 0x0000);\n    crc = (msg[26] << 8) | msg[27];\n    if (crc != crc_calculated) {\n        return DECODE_FAIL_MIC; // bad crc\n    }\n\n    id = ((msg[5]&0x0f) << 12) | (msg[6] << 4) | ((msg[7]&0xf0) >> 4);\n    counter = ((unsigned)msg[20] << 24) | (msg[21] << 16) | (msg[22] << 8) | msg[23];\n\n    for (i=0; i<(len+7)/8 ; ++i) {\n        sprintf(&code_str[i*2], \"%02x\", msg[i]);\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",    \"\",             DATA_STRING,    \"IBIS-Beacon\",\n            \"id\",       \"Vehicle No.\",  DATA_INT,       id,\n            \"counter\",  \"Counter\",      DATA_INT,       counter,\n            \"code\",     \"Code data\",    DATA_STRING,    code_str,\n            \"mic\",      \"Integrity\",    DATA_STRING,    \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"counter\",\n        \"code\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ibis_beacon = {\n        .name        = \"IBIS beacon\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 30,  // Nominal width of clock half period [us]\n        .long_width  = 0,   // Not used\n        .reset_limit = 100, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &ibis_beacon_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ikea_sparsnas.c",
    "content": "/** @file\n    IKEA Sparsnäs Energy Meter Monitor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nIKEA Sparsnäs Energy Meter Monitor.\n\n@warning This decoder is not stateless.\n\nThe IKEA Sparsnäs consists of a display unit, and a sender unit. The display unit\ndisplays and stores the values sent by the sender unit. It is not needed for this\ndecoder. The sender unit is placed by the energy meter. The sender unit has an\nIR photo sensor which is placed over the energy meter impulse diode. The sender\nalso has an external antenna, which should be placed where it can provide non-\ninterfered transmissions.\n\nThe energy meter sends a fixed number of pulses per kWh. This is different per\nunit, but usual values are 500, 1000 and 2000. This is usually indicated like\n\n1000 imp/kWh\n\non the front of the meter. This value goes into ikea_sparsnas_pulses_per_kwh\nin this file. The sender also has a unique ID which is used in the encryption\nkey, hence it is needed here to decrypt the data. The sender ID is on a sticker\nin the battery compartment. There are three groups of three digits there. The\nlast six digits are your sender ID. Eg \"400 617 633\" gives you the sender id\n617633. This number goes into IKEA_SPARSNAS_SENSOR_ID in this file.\n\n\nThe data is sent using CPFSK modulation. It requires PD_MIN_PULSE_SAMPLES in\npulse_detect.h to be lowered to 5 to be able to demodulate at 250kS/s. The\npreamble is optimally 4 bytes of 0XAA. Then the sync word 0xD201. Here only\nthe last 2 bytes of the 0xAA preamble is checked, as the first ones seems\nto be corrupted quite often. There are plenty of integrity checks made on\nthe demodulated package which makes this compromise OK.\n\nPacket structure according to: https://github.com/strigeus/sparsnas_decoder\n(with some changes by myself)\n\n0:  uint8_t length;        // Always 0x11\n1:  uint8_t sender_id_lo;  // Lowest byte of sender ID\n2:  uint8_t unknown;       // Not sure\n3:  uint8_t major_version; // Always 0x07 - the major version number of the sender.\n4:  uint8_t minor_version; // Always 0x0E - the minor version number of the sender.\n5:  uint32_t sender_id;    // ID of sender\n9:  uint16_t sequence;     // Sequence number of current packet\n11: uint16_t effect;       // Current effect usage\n13: uint32_t pulses;       // Total number of pulses\n17: uint8_t battery;       // Battery level, 0-100%\n18: uint16_t CRC;          // 16 bit CRC of bytes 0-17\n\nExample packet: 0x11a15f070ea2dfefe6d5fdd20547e6340ae7be61\n\n\nThe packet's integrity can be checked with the 16b CRC at the end of the packet.\nThere are also several other ways to check the integrity of the package.\n - (preamble)\n - CRC\n - The decrypted sensor ID\n - the constant bytes at 0, 3 and 4\n\nThe decryption, CRC is calculation, value extraction and interpretation is\ntaken from https://github.com/strigeus/sparsnas_decoder and adapted to\nthis application. Many thanks to strigeus!\n\nMost other things are from https://github.com/kodarn/Sparsnas which is an\namazing repository of the IKEA Sparsnäs. Everything is studied with greay\ndetail. Many thanks to kodarn!\n\n*/\n\n\n#include \"decoder.h\"\n#define IKEA_SPARSNAS_MESSAGE_BITLEN 160    // 20 bytes incl 8 bit length, 8 bit address, 128 bits data, and 16 bits of CRC. Excluding preamble and sync word\n#define IKEA_SPARSNAS_MESSAGE_BYTELEN    ((IKEA_SPARSNAS_MESSAGE_BITLEN + 7) / 8)\n#define IKEA_SPARSNAS_MESSAGE_BITLEN_MAX 260 // Just for early sanity checks\n\n#define IKEA_SPARSNAS_PREAMBLE_BITLEN 32\n\n#define IKEA_SPARSNAS_CRC_INIT 0xffff\n#define IKEA_SPARSNAS_CRC_POLY 0x8005\n\n#define IKEA_SPARSNAS_ID_KEY_SUB 0x5D38E8CB\n\nstatic uint16_t const ikea_sparsnas_pulses_per_kwh = 1000;\nstatic uint32_t ikea_sparsnas_sensor_id = 0;\n\nstatic uint32_t ikea_sparsnas_brute_force_encryption(uint8_t buffer[18])\n{\n    uint8_t const b5 = buffer[5 + 0];\n    uint8_t const b6 = buffer[5 + 1];\n    uint8_t const b7 = buffer[5 + 2];\n    uint8_t const b8 = buffer[5 + 3];\n    uint8_t const battery_enc = buffer[17];\n\n    uint8_t d0, d1, d2;\n    uint8_t const d3 = b8 ^ 0x47;\n\n    uint32_t dec_sensor_id, key_sensor_id;\n    uint8_t battery_dec, k0, k1, k2, k4;\n\n    for (k0=0;k0<0xFF;++k0) {\n        d0 = b5 ^ k0;\n        if (d0 > 0x0F) { //will result in sensor_id > 999999\n            continue; // DECODE_FAIL_SANITY\n        }\n        for (k1=0;k1<0xFF;++k1) {\n            d1 = b6 ^ k1;\n\n            for (k2=0;k2<0xFF;++k2) {\n\n                d2 = b7 ^ k2;\n                battery_dec = battery_enc ^ k2;\n                dec_sensor_id = (unsigned)d0 << 24 | d1 << 16 | d2 << 8 | d3;\n\n                if (dec_sensor_id > 999999) { //sensor id is at most 6 digits\n                    continue; // DECODE_FAIL_SANITY\n                }\n\n                for (k4=0;k4<0xFF;++k4) {\n                    key_sensor_id  = ((unsigned)k0 << 24 | k4 << 16 | k2 << 8 | k1) + IKEA_SPARSNAS_ID_KEY_SUB;\n\n                    if ((dec_sensor_id == key_sensor_id) && (battery_dec <= 100)) {\n                        return dec_sensor_id;\n                    }\n                }\n            }\n        }\n    }\n    return 0;\n}\n\nstatic int ikea_sparsnas_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[4] = {0xAA, 0xAA, 0xD2, 0x01};\n\n    if ((bitbuffer->bits_per_row[0] < IKEA_SPARSNAS_MESSAGE_BITLEN) || (bitbuffer->bits_per_row[0] > IKEA_SPARSNAS_MESSAGE_BITLEN_MAX)) {\n        decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer,\n                \"Too short or too long packet received. Expected %d, received %d\", IKEA_SPARSNAS_MESSAGE_BITLEN, bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Look for preamble\n    uint16_t bitpos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, IKEA_SPARSNAS_PREAMBLE_BITLEN);\n\n    if ((bitbuffer->bits_per_row[0] == bitpos) || (bitpos + IKEA_SPARSNAS_MESSAGE_BITLEN > bitbuffer->bits_per_row[0])) {\n        decoder_log_bitbuffer(decoder, 2, __func__, bitbuffer, \"malformed package, preamble not found. (Expected 0xAAAAD201)\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // extract message, discarding preamble\n    uint8_t buffer[IKEA_SPARSNAS_MESSAGE_BYTELEN];\n    bitbuffer_extract_bytes(bitbuffer, 0, bitpos + IKEA_SPARSNAS_PREAMBLE_BITLEN, buffer, IKEA_SPARSNAS_MESSAGE_BITLEN);\n\n    decoder_log_bitbuffer(decoder, 2, __func__, bitbuffer, \"\");\n    decoder_log_bitrow(decoder, 2, __func__, buffer, IKEA_SPARSNAS_MESSAGE_BITLEN, \"Encrypted message\");\n    // CRC check\n    uint16_t crc_calculated = crc16(buffer, IKEA_SPARSNAS_MESSAGE_BYTELEN - 2, IKEA_SPARSNAS_CRC_POLY, IKEA_SPARSNAS_CRC_INIT);\n    uint16_t crc_received = buffer[18] << 8 | buffer[19];\n\n    if (crc_received != crc_calculated) {\n        decoder_logf(decoder, 2, __func__, \"CRC check failed (0x%X != 0x%X)\", crc_calculated, crc_received);\n        return DECODE_FAIL_MIC;\n    }\n\n    //Decryption\n    if (!ikea_sparsnas_sensor_id) {\n        decoder_log(decoder, 2, __func__, \"No sensor ID configured. Brute forcing encryption.\");\n        ikea_sparsnas_sensor_id = ikea_sparsnas_brute_force_encryption(buffer);\n        if (ikea_sparsnas_sensor_id) {\n            decoder_logf(decoder, 2, __func__, \"Found valid sensor ID %06u. If reported values does not make sense, this might be incorrect.\", ikea_sparsnas_sensor_id);\n        } else {\n            decoder_log(decoder, 2, __func__, \"No valid sensor ID found.\");\n        }\n\n    }\n\n    uint8_t decrypted[18];\n\n    uint8_t key[5];\n    uint32_t const sensor_id_sub = ikea_sparsnas_sensor_id - IKEA_SPARSNAS_ID_KEY_SUB;\n\n    key[0] = (uint8_t)(sensor_id_sub >> 24);\n    key[1] = (uint8_t)(sensor_id_sub);\n    key[2] = (uint8_t)(sensor_id_sub >> 8);\n    key[3] = 0x47;\n    key[4] = (uint8_t)(sensor_id_sub >> 16);\n\n    for (size_t i = 0; i < 5; i++)\n        decrypted[i] = buffer[i];\n\n    for (size_t i = 0; i < 13; i++)\n        decrypted[5 + i] = buffer[5 + i] ^ key[i % 5];\n\n    // Additional integrity checks\n    uint32_t rcv_sensor_id = (unsigned)decrypted[5] << 24 | decrypted[6] << 16 | decrypted[7] << 8 | decrypted[8];\n\n    decoder_logf(decoder, 2, __func__, \"CRC OK (%X == %X)\", crc_calculated, crc_received);\n    decoder_logf(decoder, 2, __func__, \"Encryption key: 0x%X%X%X%X%X\", key[0], key[1], key[2], key[3], key[4]);\n    decoder_log_bitrow(decoder, 2, __func__, decrypted, 18 * 8, \"Decrypted\");\n    decoder_logf(decoder, 2, __func__, \"Received sensor id: %06u\", rcv_sensor_id);\n\n    if (rcv_sensor_id != ikea_sparsnas_sensor_id) {\n        decoder_logf(decoder, 2, __func__, \"Malformed package, or wrong sensor id. Received sensor id (%06u) not the same as sender (%d)\", rcv_sensor_id, ikea_sparsnas_sensor_id);\n    }\n\n    if ((!ikea_sparsnas_sensor_id) || (rcv_sensor_id != ikea_sparsnas_sensor_id)) {\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",         \"Model\",               DATA_STRING, \"Ikea-Sparsnas\",\n                \"id\",            \"Sensor ID\",           DATA_INT, ikea_sparsnas_sensor_id,\n                \"mic\",           \"Integrity\",           DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    if (decrypted[0] != 0x11) {\n        decoder_log_bitrow(decoder, 2, __func__, decrypted + 5, 13 * 8, \"Message malformed\");\n        decoder_logf(decoder, 2, __func__, \"Message malformed (byte0=%X expected %X)\", decrypted[0], 0x11);\n        return DECODE_FAIL_SANITY;\n    }\n    if (decrypted[3] != 0x07) {\n        decoder_log_bitrow(decoder, 2, __func__, decrypted + 5, 13 * 8, \"Message malformed\");\n        decoder_logf(decoder, 2, __func__, \"Message malformed (byte3=%X expected %X)\", decrypted[0], 0x07);\n        return DECODE_FAIL_SANITY;\n    }\n\n    //Value extraction and interpretation\n    uint16_t sequence_number = (decrypted[9] << 8 | decrypted[10]);\n    uint16_t effect = (decrypted[11] <<  8 | decrypted[12]);\n    uint32_t pulses = ((unsigned)decrypted[13] << 24 | decrypted[14] << 16 | decrypted[15] << 8 | decrypted[16]);\n    uint8_t battery =  decrypted[17];\n    //float watt = effect * 24.;\n    uint8_t mode = decrypted[4]^0x0f;\n\n    //if (mode == 1) {     //Note that mode cycles between 0-3 when you first put in the batteries in\n    //  watt = ((3600000.0 / ikea_sparsnas_pulses_per_kwh) * 1024.0) / effect;\n    //} else if (mode == 0) { // special mode for low power usage\n    //  watt = effect * 0.24 / ikea_sparsnas_pulses_per_kwh;\n    //}\n    float cumulative_kWh = ((float)pulses) / ((float)ikea_sparsnas_pulses_per_kwh);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"Model\",               DATA_STRING, \"Ikea-Sparsnas\",\n            \"id\",            \"Sensor ID\",           DATA_INT,    rcv_sensor_id,\n            \"sequence\",      \"Sequence Number\",     DATA_INT,    sequence_number,\n            \"battery_ok\",    \"Battery level\",       DATA_DOUBLE, battery * 0.01f, // 0-100\n            \"pulses_per_kWh\", \"Pulses per kWh\",     DATA_INT,    ikea_sparsnas_pulses_per_kwh,\n            \"cumulative_kWh\", \"Cumulative kWh\",     DATA_FORMAT, \"%7.3fkWh\", DATA_DOUBLE,  cumulative_kWh,\n            \"effect\",        \"Effect\",              DATA_FORMAT, \"%dW\", DATA_INT,  effect,\n            \"pulses\",        \"Pulses\",              DATA_INT,    pulses,\n            \"mode\",          \"Mode\",                DATA_INT,    mode,\n            \"mic\",           \"Integrity\",           DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"sequence\",\n        \"battery_ok\",\n        \"pulses_per_kwh\",\n        \"cumulative_kWh\",\n        \"effect\",\n        \"pulses\",\n        \"mode\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ikea_sparsnas = {\n        .name        = \"IKEA Sparsnas Energy Meter Monitor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 27,\n        .long_width  = 27,\n        .gap_limit   = 1000,\n        .reset_limit = 3000,\n        .decode_fn   = &ikea_sparsnas_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/infactory.c",
    "content": "/** @file\n    inFactory outdoor temperature and humidity sensor.\n\n    Copyright (C) 2017 Sirius Weiß <siriuz@gmx.net>\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n    Copyright (C) 2020 Hagen Patzke <hpatzke@gmx.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int infactory_decode(r_device *decoder, bitbuffer_t *bitbuffer)\ninFactory outdoor temperature and humidity sensor.\n\nOutdoor sensor, transmits temperature and humidity data\n- inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station)\n- nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark\n- DAY 73365 (weather station + sensor), Schou Company AS, Denmark\n- Tchibo NC-3982-675\n\nKnown brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China.\n\n\nTransmissions includes an id. Every 60 seconds the sensor transmits 6 packets:\n\n    0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001\n    iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn\n\n- i: identification // changes on battery switch\n- c: CRC-4 // CCITT checksum, see below for computation specifics\n- u: TX-button, also set for 3 sec at power-up\n- b: battery low // flag to indicate low battery voltage\n- h: Humidity // BCD-encoded, each nibble is one digit, 'A0' means 100%rH\n- t: Temperature // in °F as binary number with one decimal place + 90 °F offset\n- n: Channel // Channel number 1 - 3\n\nUsage:\n\n    # rtl_433 -f 434052000 -R 91 -F json:log.json\n    # rtl_433 -R 91 -F json:log.json\n    # rtl_433 -C si\n\nPayload looks like this:\n\n    [00] {40} 0f 30 5c e7 61 : 00001111 00110000 01011100 11100111 01100001\n\n(See below for more information about the signal timing.)\n*/\n\nstatic int infactory_crc_check(uint8_t *b)\n{\n    uint8_t msg_crc, crc, msg[5];\n    memcpy(msg, b, 5);\n    msg_crc = msg[1] >> 4;\n    // for CRC computation, channel bits are at the CRC position(!)\n    msg[1] = (msg[1] & 0x0F) | (msg[4] & 0x0F) << 4;\n    // crc4() only works with full bytes\n    crc = crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704\n    crc ^= msg[4] >> 4; // last nibble is only XORed\n    return (crc == msg_crc);\n}\n\nstatic int infactory_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->bits_per_row[0] != 40)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = bitbuffer->bb[0];\n\n    // Check that the last 4 bits of message are not 0 (channel number 1 - 3)\n    if (!(b[4] & 0x0F))\n        return DECODE_ABORT_EARLY;\n\n    if (!infactory_crc_check(b))\n        return DECODE_FAIL_MIC;\n\n    int id          = b[0];\n    int button      = (b[1] >> 3) & 1;\n    int battery_low = (b[1] >> 2) & 1;\n    int temp_raw    = (b[2] << 4) | (b[3] >> 4);\n    int humidity    = (b[3] & 0x0F) * 10 + (b[4] >> 4); // BCD, 'A0'=100%rH\n    int channel     = b[4] & 0x03;\n\n    float temp_f    = (temp_raw - 900) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"inFactory-TH\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"button\",           \"Button\",       DATA_INT,    button,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_F\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\n/*\nAnalysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433):\n\nObserved On-Off-Key (OOK) data pattern:\n\n    preamble            syncPrefix        data...(40 bit)                        syncPostfix\n    HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL\n\nBreakdown:\n\n- four preamble pairs '1'/'0' each with a length of ca. 1000us\n- syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us\n- syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us\n- data0 (0-bits) have then a '0' pulse length of ca. 2000us\n- data1 (1-bits) have then a '0' pulse length of ca. 4000us\n- syncPost after dataPtr has a '0' pulse length of ca. 16000us\n\nThis analysis is the reason for the new r_device definitions below.\nNB: pulse_slicer_ppm does not use .gap_limit if .tolerance is set.\n*/\n\nr_device const infactory = {\n        .name        = \"inFactory, nor-tec, FreeTec NC-3982-913 temperature humidity sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .sync_width  = 500,  // Sync pulse width (recognized, but not used)\n        .short_width = 2000, // Width of a '0' gap\n        .long_width  = 4000, // Width of a '1' gap\n        .reset_limit = 5000, // Maximum gap size before End Of Message [us]\n        .tolerance   = 750,  // Width interval 0=[1250..2750] 1=[3250..4750], should be quite robust\n        .decode_fn   = &infactory_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/inkbird_ith20r.c",
    "content": "/** @file\n    Decoder for Inkbird ITH-20R.\n\n    Copyright (C) 2020 Dmitriy Kozyrev\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nDecoder for Inkbird ITH-20R.\n\nhttps://www.ink-bird.com/products-data-logger-ith20r.html\n\nAlso: Inkbird IBS-P01R Pool Thermometer.\n\nThe compact 3-in-1 multifunction outdoor sensor transmits the data on 433.92 MHz.\nThe device uses FSK-PCM encoding,\nThe device sends a transmission every ~80 sec.\n\nDecoding borrowed from https://groups.google.com/forum/#!topic/rtl_433/oeExmwoBI0w\n\n- Total packet length 14563 bits:\n- Preamble: aa aa aa ... aa aa (14400 on-off sync bits)\n- Sync Word (16 bits): 2DD4\n- Data (147 bits):\n- Byte    Sample      Comment\n- 0-2     D3910F      Always the same across devices, a device type?\n- 3       00          00 - normal work , 40 - unlink sensor (button pressed 5s), 80 - battery replaced\n- 4       01          Changes from 1 to 2 if external sensor present\n- 5-6     0301        Unknown (also seen 0201), sw version? Seen 0x0001 on IBS-P01R.\n- 7       58          Battery % 0-100\n- 8-9     A221        Device id, always the same for a sensor but each sensor is different\n- 10-11   D600        Temperature in C * 10, little endian, so 0xD200 is 210, 21.0C or 69.8F\n- 12-13   F400        Temperature C * 10 for the external sensor,  0x1405 if not connected\n- 14-15   D301        Relative humidity %  * 10, little endian, so 0xC501 is 453 or 45.3%\n- 16-17   38FB        CRC16\n- 18      0           Unknown 3 bits (seen 0 and 2)\n\nCRC16 (bytes 0-15), without sync word):\npoly=0x8005  init=0x2f61  refin=true  refout=true  xorout=0x0000  check=0x3583  residue=0x0000\n\nTo look at unknown data fields run with -vv key.\n\nDecoder written by Dmitriy Kozyrev, 2020\n*/\n\n#include \"decoder.h\"\n\n#define INKBIRD_ITH20R_CRC_POLY 0xA001  // reflected 0x8005\n#define INKBIRD_ITH20R_CRC_INIT 0x86F4  // reflected 0x2f61\n\n\nstatic int inkbird_ith20r_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xaa, 0x2d, 0xd4};\n\n    data_t *data;\n    uint8_t msg[19];\n\n    if ((bitbuffer->num_rows != 1)\n            || (bitbuffer->bits_per_row[0] < 187)\n            /*|| (bitbuffer->bits_per_row[0] > 14563)*/) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH; // Unrecognized data\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof (preamble_pattern) * 8);\n\n    if (start_pos == bitbuffer->bits_per_row[0]) {\n        return DECODE_FAIL_SANITY;  // Not found preamble\n    }\n\n    start_pos += sizeof (preamble_pattern) * 8;\n    unsigned len = bitbuffer->bits_per_row[0] - start_pos;\n\n    decoder_logf(decoder, 2, __func__, \"start_pos=%u len=%u\", start_pos, len);\n\n    if (((len + 7) / 8) < sizeof (msg)) {\n        decoder_logf(decoder, 1, __func__, \"%u too short\", len);\n        return DECODE_ABORT_LENGTH; // Message too short\n    }\n    // truncate any excessive bits\n    len = MIN(len, sizeof (msg) * 8);\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, len);\n\n    // CRC check\n    uint16_t crc_calculated = crc16lsb(msg, 16, INKBIRD_ITH20R_CRC_POLY, INKBIRD_ITH20R_CRC_INIT);\n    uint16_t crc_received = msg[17] << 8 | msg[16];\n\n    decoder_logf(decoder, 2, __func__, \"CRC 0x%04X = 0x%04X\", crc_calculated, crc_received);\n\n    if (crc_received != crc_calculated) {\n        decoder_logf(decoder, 1, __func__, \"CRC check failed (0x%04X != 0x%04X)\", crc_calculated, crc_received);\n        return DECODE_FAIL_MIC;\n    }\n\n    uint32_t subtype = (msg[3] << 24 | msg[2] << 16 | msg[1] << 8 | msg[0]);\n    int sensor_num = msg[4];\n    uint16_t word56 = (msg[6] << 8 | msg[5]);\n    float battery_ok = msg[7] * 0.01f;\n    uint16_t sensor_id = (msg[9] << 8 | msg[8]);\n    float temperature = ((int16_t)(msg[11] << 8 | msg[10])) * 0.1f;\n    float temperature_ext = ((int16_t)(msg[13] << 8 | msg[12])) * 0.1f;\n    float humidity = (msg[15] << 8 | msg[14]) * 0.1f;\n    uint8_t word18 = msg[18];\n\n    decoder_logf(decoder, 1, __func__, \"dword0-3= 0x%08X word5-6= 0x%04X byte18= 0x%02X\", subtype, word56, word18);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Inkbird-ITH20R\",\n            \"id\",               \"\",             DATA_INT,    sensor_id,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery_ok,\n            \"sensor_num\",       \"\",             DATA_INT,    sensor_num,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            \"temperature_2_C\",  \"Temperature2\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature_ext,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%.1f %%\", DATA_DOUBLE, humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"sensor_num\",\n        \"temperature_C\",\n        \"temperature_2_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const inkbird_ith20r = {\n        .name        = \"Inkbird ITH-20R temperature humidity sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100,  // Width of a '0' gap\n        .long_width  = 100,  // Width of a '1' gap\n        .reset_limit = 4000, // Maximum gap size before End Of Message [us]\n        .decode_fn   = &inkbird_ith20r_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/inovalley-kw9015b.c",
    "content": "/** @file\n    Inovalley kw9015b rain and Temperature weather station.\n\n    Copyright (C) 2015 Alexandre Coffignal\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nInovalley kw9015b rain and Temperature weather station.\n\nAlso TFA-Dostmann rain-sensor 30.3161 (see #1531) with a 0.45mm rain per tip.\n\nData layout:\n\n    IIII??RR BRRPtttt TTTTTTTT rrrrrrrr CCCC\n\n- I : 4-bit ID\n- ? : 2-bit unknown always 00\n- T : 12-bit Temp in C, signed, scaled by 10\n- R : 12-bit Rain\n- B : 1-bit battery (0 means battery ok, 1 means low battery)\n- P : 1-bit power up (when batteries are inserted is 1, then always 0)\n- C : 4-bit Checksum (nibble sum)\n*/\n\n#include \"decoder.h\"\n\nstatic int kw9015b_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int row;\n    uint8_t *b;\n    int temp_raw, rain, device;\n    unsigned char chksum;\n    float temp_c;\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 36)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[row];\n\n    device   = (reverse8(b[0]) & 0x0f);\n    temp_raw = (int16_t)((reverse8(b[2]) << 8) | (reverse8(b[1]) & 0xf0)); // sign-extend\n    temp_c   = (temp_raw >> 4) * 0.1f;\n    rain     = ((reverse8(b[0]) & 0xc0) << 4) | ((reverse8(b[1]) & 0x06) << 7) | reverse8(b[3]);\n    chksum   = ((reverse8(b[0]) >> 4) + (reverse8(b[0]) & 0x0f) +\n              (reverse8(b[1]) >> 4) + (reverse8(b[1]) & 0x0f) +\n              (reverse8(b[2]) >> 4) + (reverse8(b[2]) & 0x0f) +\n              (reverse8(b[3]) >> 4) + (reverse8(b[3]) & 0x0f));\n    int battery_low = b[1] >> 7;\n\n    if ((chksum & 0x0f) != (reverse8(b[4]) & 0x0f))\n        return DECODE_FAIL_MIC;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Inovalley-kw9015b\",\n            \"id\",               \"\",             DATA_INT,    device,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"rain\",             \"Rain Count\",   DATA_INT,    rain, // TODO: remove this\n            \"rain_mm\",          \"Rain total\",   DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain * 0.45f,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const kw9015b_csv_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"rain\", // TODO: remove this\n        \"rain_mm\",\n        NULL,\n};\n\nr_device const kw9015b = {\n        .name        = \"Inovalley kw9015b, TFA Dostmann 30.3161 (Rain and temperature sensor)\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 4800,\n        .reset_limit = 10000,\n        .decode_fn   = &kw9015b_callback,\n        .disabled    = 1,\n        .fields      = kw9015b_csv_output_fields,\n};\n"
  },
  {
    "path": "src/devices/insteon.c",
    "content": "/** @file\n    Insteon RF decoder.\n\n    Copyright (C) 2020 Peter Shipley\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/** @fn int parse_insteon_pkt(r_device *decoder, bitbuffer_t *bits, unsigned int row, unsigned int start_pos)\nInsteon RF decoder.\n\n    \"Insteon is a home automation (domotics) technology that enables\n    light switches, lights, thermostats, leak sensors, remote controls,\n    motion sensors, and other electrically powered devices to interoperate\n    through power lines, radio frequency (RF) communications, or both\n    [ from wikipedia ]\n\nthe Insteon RF protocol is a series of 28 bit packets containing one byte of data\n\n\nEach byte (X) is encoded as 28 bits:\n>     '11' followed by\n>     5 bit index number (manchester encoded)\n>     8 bit byte (manchester encoded)\n\nAll values are written in LSB format (Least Significant Bit first)\n\nThe first byte is always transmitted with a index of 32 (11111)\nall following bytes are transmitted with a decrementing index count with the final byte with index 0\n\n    Dat   index dat         LSB index dat     manchester                     '11' + manchester\n    03 -> 11111 00000011 -> 11111 11000000 -> 0101010101 0101101010101010 -> 1101010101010101101010101010\n    E5 -> 01011 11100101 -> 11010 10100111 -> 0101100110 0110011010010101 -> 1101011001100110011010010101\n    3F -> 01010 00111111 -> 01010 11111100 -> 1001100110 0101010101011010 -> 1110011001100101010101011010\n    16 -> 01001 00010110 -> 01010 11111100 -> 0110100110 1001011001101010 -> 1101101001101001011001101010\n\n[Insteon RF Toolkit](https://github.com/evilpete/insteonrf/Doc)\n\n## Printed packet format notation\n\n   *flag* **:** *to_address* **:** *from_address* : command_data crc\n\n`43 : 226B3F : 2B7811 : 13 01  35`\n\n## Settings\n\n- Frequency: 915MHz\n- SampleRate: 1024K\n- Modulation: FSK\n\n*/\n\n#include \"decoder.h\"\n\n// 1100111010101010\nstatic uint8_t const insteon_preamble[] = {0xCE, 0xAA};\n\n#define INSTEON_PACKET_MIN 10\n#define INSTEON_PACKET_MAX 13\n#define INSTEON_PACKET_MIN_EXT 23\n#define INSTEON_PACKET_MAX_EXT 32\n#define INSTEON_BITLEN_MIN (INSTEON_PACKET_MIN * 28) + sizeof(insteon_preamble)\n#define INSTEON_PREAMBLE_LEN 16\n\n\n/*\n    calc checksum of extended packet data\n    (differs from normal packet)\n\n    takes an instion packet in form of a list of uint8_t\n    and returns CRC in the form of a uint8_t\n\n    using :\n        ((Not(sum of cmd1..d13)) + 1) and 255\n*/\n\nstatic uint8_t gen_ext_crc(uint8_t *dat)\n{\n    uint8_t r = 0;\n\n    for (int i = 7; i < 22; i++) {\n        r += dat[i];\n    }\n\n    r = ~r;\n    r = r + 1;\n    r = (r & 0xFF);\n\n    return ((uint8_t)r);\n}\n\n/*\n    calc checksum of normal packet data\n    (differs from extended packet)\n\n    takes an instion packet in form of a list of uint8_t\n    and returns uint8_t the CRC for RF packet\n*/\nstatic uint8_t gen_crc(uint8_t *dat)\n{\n    uint8_t r = 0;\n\n    for (int i = 0; i < 9; i++) {\n        r ^= dat[i];\n        r ^= ((r ^ (r << 1)) & 0x0F) << 4;\n    }\n\n    return (r);\n}\n\nstatic int parse_insteon_pkt(r_device *decoder, bitbuffer_t *bits, unsigned int row, unsigned int start_pos)\n{\n    uint8_t results[35]   = {0};\n    uint8_t results_len   = 0;\n    bitbuffer_t i_bits    = {0};\n    bitbuffer_t d_bits    = {0};\n    unsigned int next_pos = 0;\n    uint8_t i             = 0;\n    uint8_t pkt_i, pkt_d;\n\n    // move past preamble\n    start_pos += 7;\n\n    /*\n    We are looking as something line this\n        110101010101010110101010101011....\n\n    which we an break down as\n\n        11 0101010101 0101101010101010 11....\n\n        \"11\" + 10 manchester bits LSB + 16 manchester bits LSB + \"11\"\n\n        we decode this into a\n            5 bits LSB (always 32 in the first block)\n            8 bits LSB (flag bits for the upcoming packet\n\n\n        Flag fields (MSB format):\n            \"maxhops\"  = (flag & 0b00000011)\n            \"hopsleft\" = (flag & 0b00001100)\n            \"extended\" = (flag & 0b00010000)\n            \"ack\"      = (flag & 0b00100000)\n            \"group\"    = (flag & 0b01000000)\n            \"bcast\"    = (flag & 0b10000000)\n            \"mtype\"    = (flag & 0b11100000)\n\n        (we can discard the 5 bit digit)\n\n        after this we can index forward 28 bits (2 + 10 + 16)\n\n    */\n\n    next_pos = bitbuffer_manchester_decode(bits, row, start_pos, &i_bits, 5);\n    pkt_i    = reverse8(i_bits.bb[0][0]);\n\n    next_pos               = bitbuffer_manchester_decode(bits, row, next_pos, &d_bits, 8);\n    pkt_d                  = reverse8(d_bits.bb[0][0]);\n    results[results_len++] = pkt_d;\n\n    if (pkt_i != 31) { // should always be 31 (0b11111) in first block of packet\n        return DECODE_ABORT_EARLY;\n    }\n\n    bitbuffer_extract_bytes(bits, row, start_pos + 26, &i, 2);\n    // Check for packet delimiter  marker bits (at least once)\n    if (i != 0xc0) {                 // 0b11000000\n        return DECODE_FAIL_SANITY; // There should be two high bits '11' between packets\n    }\n\n    // printBits(sizeof(d), &d);\n    int extended        = 0;\n\n    uint8_t max_pkt_len = INSTEON_PACKET_MAX;\n    uint8_t min_pkt_len = INSTEON_PACKET_MIN;\n    if (results[0] & 0x10) {\n        extended    = 1;\n        max_pkt_len = INSTEON_PACKET_MAX_EXT;\n        min_pkt_len = INSTEON_PACKET_MIN_EXT;\n    }\n\n    decoder_logf(decoder, 1, __func__, \"start_pos %u row_length %hu =  %u\",\n            start_pos, bits->bits_per_row[row], (bits->bits_per_row[row] - start_pos));\n\n    {\n    decoder_log(decoder, 1, __func__, \"pkt_i pkt_d next length count\");\n    uint8_t buffy[4];\n    bitbuffer_extract_bytes(bits, row, start_pos - 2, buffy, 30);\n    decoder_logf_bitrow(decoder, 1, __func__, buffy, 30, \"%2d %02X %03u %u %2d\",\n            pkt_i, pkt_d, next_pos, (next_pos - start_pos), 0);\n    }\n\n    /*   Is this overkill ??\n    unsigned int l;\n    if (extended) {\n         l = 642;\n     } else {\n         l = 278;\n     }\n     if ((bits->bits_per_row[row] - start_pos)  < l) {\n        decoder_logf(decoder, 1, __func__, \"row to short for %s packet type\",\n                (extended ? \"extended\" : \"regular\"));\n        return DECODE_ABORT_LENGTH;     // row to short for packet type\n     }\n     */\n\n    /*\n        The data is contained in 26bit blocks containing 26bit manchester\n        the resulting 13bits contains 5bit of packet index\n        and 8bits of data\n    */\n    uint8_t prev_i=33;\n    for (int j = 1; j < max_pkt_len; j++) {\n        unsigned y;\n        start_pos += 28;\n        bitbuffer_clear(&i_bits);\n        bitbuffer_clear(&d_bits);\n        next_pos = bitbuffer_manchester_decode(bits, row, start_pos, &i_bits, 5);\n        next_pos = bitbuffer_manchester_decode(bits, row, next_pos, &d_bits, 8);\n\n        y = (next_pos - start_pos);\n        if (y != 26) {\n            decoder_logf(decoder, 1, __func__, \"stop %u != 26\", y);\n            break;\n        }\n\n        // bitbuffer_extract_bytes(bits, row, start_pos -2, buff, 8);\n        // printBits(sizeof(buff), buff);\n\n        pkt_i = reverse8(i_bits.bb[0][0]);\n        pkt_d = reverse8(d_bits.bb[0][0]);\n\n        results[results_len++] = pkt_d;\n\n        {\n        uint8_t buffy[4];\n        bitbuffer_extract_bytes(bits, row, start_pos - 2, buffy, 30);\n        decoder_logf_bitrow(decoder, 1, __func__, buffy, 30, \"%2d %02X %03u %u %2d\",\n                pkt_i, pkt_d, next_pos, (next_pos - start_pos), j);\n        // parse_insteon_pkt: curr packet (3f) { 1} d6 : 1\n        }\n\n        // packet index should decrement\n        if (pkt_i < prev_i) {\n            prev_i = pkt_i;\n        } else {\n            return DECODE_ABORT_EARLY;\n        }\n    }\n\n    // decoder_log_bitrow(decoder, 2, __func__, results, results_len * 8, \"results\");\n\n    if (results_len < min_pkt_len) {\n        decoder_logf(decoder, 2, __func__, \"fail: short packet %d < 9\", results_len);\n        return 0;\n    }\n\n    uint8_t crc_val;\n    if (extended) {\n        crc_val = gen_ext_crc(results);\n    }\n    else {\n        crc_val = gen_crc(results);\n    }\n\n    if (results[min_pkt_len - 1] != crc_val) {\n        decoder_logf(decoder, 2, __func__, \"fail: bad CRC %02X != %02X %s\", results[min_pkt_len], crc_val,\n                    (extended ? \"extended\" : \"\"));\n        return DECODE_FAIL_MIC;\n    }\n\n    char pkt_from_addr[8]   = {0};\n    char pkt_to_addr[32]    = {0};\n    char pkt_formatted[256] = {0};\n    char cmd_str[92]        = {0};\n\n    snprintf(pkt_to_addr, sizeof(pkt_to_addr), \"%02X%02X%02X\",\n            results[3], results[2], results[1]);\n    snprintf(pkt_from_addr, sizeof(pkt_from_addr), \"%02X%02X%02X\",\n            results[6], results[5], results[4]);\n\n    char *p = cmd_str;\n    int cmd_array[32];\n    int cmd_array_len = 0;\n    for (int j = 7; j < min_pkt_len - 1; j++) {\n        p += sprintf(p, \"%02X \", results[j]);\n        cmd_array[cmd_array_len++] = (int)results[j];\n    }\n\n    char payload[INSTEON_PACKET_MAX_EXT * 2 + 2] = {0};\n    p                = payload;\n    for (int j = 0; j < results_len; j++) {\n        p += sprintf(p, \"%02X\", results[j]);\n    }\n\n    snprintf(pkt_formatted, sizeof(pkt_formatted), \"%02X : %s : %s : %s %02X\",\n            results[0], pkt_to_addr, pkt_from_addr, cmd_str, results[min_pkt_len - 1]);\n\n    /*\n    flag = b[0]\n    \"maxhops\"  = (flag & 0b00000011)\n    \"hopsleft\" = (flag & 0b00001100)\n    \"extended\" = (flag & 0b00010000)\n    \"ack\"      = (flag & 0b00100000)\n    \"group\"    = (flag & 0b01000000)\n    \"bcast\"    = (flag & 0b10000000)\n    \"mtype\"    = (flag & 0b11100000)\n    */\n\n    int hopsmax = (results[0] & 0x03);\n    int hopsleft = (results[0] >> 2) & 0x03;\n\n    // char hops_str[8] = {0};\n    // snprintf(hops_str, sizeof(hops_str), \"%d / %d\",\n    //         (results[0] & 0x03),\n    //         (results[0] >> 2) & 0x03);\n\n    int pkt_type = (results[0] >> 5) & 0x07;\n    char const *const messsage_text[8] = {\n            \"Direct Message\",                         // 000\n            \"ACK of Direct Message\",                  // 001\n            \"Group Cleanup Direct Message\",           // 010\n            \"ACK of Group Cleanup Direct Message\",    // 011\n            \"Broadcast Message\",                      // 100\n            \"NAK of Direct Message\",                  // 101\n            \"Group Broadcast Message\",                // 110\n            \"NAK of Group Cleanup Direct Message\"};   // 111\n\n    char const *pkt_type_str = messsage_text[pkt_type];\n    // decoder_log_bitrow(decoder, 0, __func__, results, 8, \"Flag\");\n    //decoder_logf(decoder, 0, __func__, \"pkt_type: %02X\", pkt_type);\n\n    decoder_logf_bitrow(decoder, 2, __func__, results, min_pkt_len * 8, \"type %s\", pkt_type_str);\n\n    // Format data\n    /*\n    int data_payload[35];\n    for (int j = 0; j < min_pkt_len; j++) {\n        data_payload[j] = (int)results[j];\n    }\n    */\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",     \"\",                DATA_STRING, \"Insteon\",\n         // \"id\",        \"\",                DATA_INT,    sensor_id,\n         // \"data\",     \"Data\",             DATA_INT,    value,\n            \"from_id\",   \"From_Addr\",       DATA_STRING, pkt_from_addr,\n            \"to_id\",     \"To_Addr\",         DATA_STRING, pkt_to_addr,\n            \"msg_type\",  \"Message_Type\",    DATA_INT,    pkt_type,\n            \"msg_str\",   \"Message_Str\",     DATA_STRING, pkt_type_str,\n         //   \"command\",   \"Command\",         DATA_STRING, cmd_str,\n            \"extended\",  \"Extended\",        DATA_INT,    extended,\n         // \"hops\",      \"Hops\",            DATA_STRING, hops_str,\n            \"hopsmax\",   \"Hops_Max\",        DATA_INT,    hopsmax,\n            \"hopsleft\",  \"Hops_Left\",       DATA_INT,    hopsleft,\n            \"formatted\", \"Packet\",          DATA_STRING, pkt_formatted,\n            \"mic\",       \"Integrity\",       DATA_STRING, \"CRC\",\n            \"payload\",   \"Payload\",         DATA_STRING, payload,\n            \"cmd_dat\",   \"CMD_Data\",        DATA_ARRAY,  data_array(cmd_array_len, DATA_INT, cmd_array),\n        //  \"payload\",   \"Payload\",         DATA_ARRAY,  data_array(min_pkt_len, DATA_INT, data_payload),\n            NULL);\n\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    // Return 1 if message successfully decoded\n    return 1;\n}\n\n/**\nInsteon RF decoder.\n@sa parse_insteon_pkt()\n*/\nstatic int insteon_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // unsigned int pkt_start_pos;\n    uint16_t row;\n    unsigned int ret_value = 0;\n    int fail_value         = 0;\n    // unsigned int pkt_cnt   = 0;\n\n    // decoder_logf(decoder, 2, __func__, \"row complete row / bit_index : %d, %d\", row, bit_index);\n\n    decoder_logf(decoder, 2, __func__, \"new buffer %hu rows\", bitbuffer->num_rows);\n\n    bitbuffer_invert(bitbuffer);\n\n    /*\n     * loop over all rows and look for preamble\n    */\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        unsigned bit_index = 0;\n        // Validate message and reject it as fast as possible : check for preamble\n\n        if (bitbuffer->bits_per_row[row] < INSTEON_BITLEN_MIN) {\n            // decoder_logf(decoder, 1, __func__, \"short row row=%hu len=%hu\", row, bitbuffer->bits_per_row[row]);\n            fail_value = DECODE_ABORT_LENGTH;\n            continue;\n        }\n        // decoder_logf(decoder, 1, __func__, \"New row=%d len=%d\",  row, bitbuffer->bits_per_row[row]);\n\n        while (1) {\n            unsigned search_index = bit_index;\n            int ret;\n\n            if ((bitbuffer->bits_per_row[row] - bit_index) < INSTEON_BITLEN_MIN) {\n                 // decoder_log(decoder, 2, __func__, \"short remainder\");\n                 break;\n             }\n\n            decoder_logf(decoder, 2, __func__, \"bitbuffer_search at row / search_index : %d, %u %u (%d)\",\n                        row, search_index, bit_index, bitbuffer->bits_per_row[row]);\n\n            search_index = bitbuffer_search(bitbuffer, row, search_index, insteon_preamble, INSTEON_PREAMBLE_LEN);\n\n            if (search_index >= bitbuffer->bits_per_row[row]) {\n                if (bit_index == 0)\n                    decoder_logf(decoder, 2, __func__, \"insteon_preamble not found %u %u %d\",\n                        search_index, bit_index, bitbuffer->bits_per_row[row]);\n                break;\n            }\n\n            decoder_logf(decoder, 1, __func__, \"parse_insteon_pkt at: row / search_index : %hu, %u (%hu)\",\n                        row, search_index, bitbuffer->bits_per_row[row]);\n\n            ret = parse_insteon_pkt(decoder, bitbuffer, row, search_index);\n\n            // decoder_logf(decoder, 1, __func__, \"parse_insteon_pkt ret value %d\", ret_value);\n            if (ret > 0) { // preamble good, decode good\n                ret_value += ret;\n                bit_index = search_index + INSTEON_BITLEN_MIN; // move a full packet length\n            }\n            else { // preamble good, decode fail\n                if (ret < 0)\n                    fail_value = ret;\n                bit_index = search_index + INSTEON_PREAMBLE_LEN; // move to next preamble\n            }\n        }\n    }\n\n    if (ret_value > 0)\n        return 1;\n    else\n        return fail_value;\n}\n\n/*\n * List of fields that may appear in the output\n *\n * Used to determine what fields will be output in what\n * order for this device when using -F csv.\n *\n */\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        // \"id\",\n        // \"data\",\n        \"from_id\",\n        \"to_id\",\n        \"msg_type\",     // packet type at int\n        \"msg_type_str\",  // packet type as formatted string\n        // \"command\",\n        \"extended\",     // 0= short pkt, 1=extended pkt\n        \"hops_max\",     // almost always 3\n        \"hops_left\",    // remaining hops\n        \"formatted\",   // entire packet as a formatted string with hex\n        \"mic\",\n        \"payload\",      // packet as a hex string\n        \"cmd_dat\",      // array of int containing command + data\n        \"msg_str\",\n        \"hopsmax\",\n        \"hopsleft\",\n        // \"raw\",\n        // \"raw_message\",\n        NULL,\n};\n\n//     -X 'n=Insteon_F16,m=FSK_PCM,s=110,l=110,t=15,g=20000,r=20000,invert,match={16}0x6666'\n\nr_device const insteon = {\n        .name        = \"Insteon\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 110, // short gap is 132 us\n        .long_width  = 110, // long gap is 224 us\n        .gap_limit   = 500, // some distance above long\n        .tolerance   = 15,\n        .reset_limit = 1000, // a bit longer than packet gap\n        .decode_fn   = &insteon_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/interlogix.c",
    "content": "/** @file\n    Interlogix/GE/UTC Wireless Device Decoder.\n\n    Copyright (C) 2017 Brent Bailey <bailey.brent@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nInterlogix/GE/UTC Wireless Device Decoder.\n\nAlso tested with ELK-319DWM module as well as a Alula RE101 319.5MHz sensor (both short preamble).\n\n- Frequency: 319.5 MHz\n\nDecoding done per us patent #5761206\nhttps://www.google.com/patents/US5761206\n\n## Protocol Bits\n\n- 00-02 976 uS RF front porch pulse\n- 03-14 12 sync pulses, logical zeros\n- 15 start pulse, logical one\n- 16-35 20 bit sensor identification code (ID bits 0-19)\n- 36-39 4 bit device type code (DT bits 0-3)\n- 40-42 3 bit trigger count (TC bit 0-2)\n- 43 low battery bit\n- 44 F1 latch bit NOTE that F1 latch bit and debounce are reversed.  Typo or endianness issue?\n- 45 F1 debounced level\n- 46 F2 latch bit\n- 47 F2 debounced level\n- 48 F3 latch bit (cover latch for contact sensors)\n- 49 F3 debounced level\n- 50 F4 latch bit\n- 51 F4 debounced level\n- 52 F5 positive latch bit\n- 53 F5 debounced level\n- 54 F5 negative latch bit\n- 55 even parity over odd bits 15-55\n- 56 odd parity over even bits 16-56\n- 57 zero/one, programmable\n- 58 RF on for 366 uS (old stop bit)\n- 59 one\n- 60-62 modulus 8 count of number of ones in bits 15-54\n- 63 zero (new stop bit)\n\n## Protocol Description\n\n- Bits 00 to 02 are a 976 ms RF front porch pulse, providing a wake up period that allows the\n  system controller receiver to synchronize with the incoming packet.\n- Bits 3 to 14 include 12 sync pulses, e.g., logical 0's, to synchronize the receiver.\n- Bit 15 is a start pulse, e.g., a logical 1, that tells the receiver that data is to follow.\n- Bits 16-58 provide information regarding the transmitter and associated sensor. In other\n  embodiments, bits 16-58 may be replaced by an analog signal.\n- Bits 16 to 35 provide a 20-bit sensor identification code that uniquely identifies the particular\n  sensor sending the message. Bits 36 to 39 provide a 4 bit device-type code that identifies the\n  specific-type of sensor, e.g., smoke, PIR, door, window, etc. The combination of the sensor\n  bits and device bits provide a set of data bits.\n- Bits 40 through 42 provide a 3-bit trigger count that is incremented for each group of message\n  packets. The trigger count is a simple but effective way for preventing a third party from\n  recording a message packet transmission and then re-transmitting that message packet\n  transmission to make the system controller think that a valid message packet is being transmitted.\n- Bit 43 provides the low battery bit.\n- Bits 44 through 53 provide the latch bit value and the debounced value for each of the five inputs\n  associated with the transmitter. For the F5 input, both a positive and negative latch bit are provided.\n- Bit 55 provides even parity over odd bits 15 to 55.\n- Bit 56 provides odd parity over even bits 16 to 56.\n- Bit 57 is a programmable bit that can be used for a variety of applications, including providing an\n  additional bit that could be used for the sensor identification code or device type code.\n- Bit 58 is a 366 ms RF on signal that functions as the \"old\" stop bit. This bit provides compatibility with\n  prior system controllers that may be programmed to receive a 58-bit message.\n- Bit 59 is a logical 1.\n- Bits 60 to 62 are a modulus eight count of the number of 1 bits in bits 15 through 54, providing enhanced\n  error detection information to be used by the system controller. Finally, bit 63 is the \"new\" stop bit,\n  e.g., a logical 0, that tells the system controller that it is the end of the message packet.\n\n## Addendum\n\nGE/Interlogix keyfobs do not follow the documented iti protocol and it\nappears the protocol was misread by the team that created the keyfobs.\nThe button states are sent in the three trigger count bits (bit 40-42)\nand no battery status appears to be provided. 4 buttons and a single\nmulti-button press (buttons 1 - lock and buttons 2 - unlock) for a total\nof 5 buttons available on the keyfob.\n\nFor contact sensors, latch 3 (typically the tamper/case open latch) will\nfloat (giving misreads) if the external contacts are used (ie; closed)\nand there is no 4.7 Kohm end of line resistor in place on the external\ncircuit\n*/\n\n#include \"decoder.h\"\n\n#define INTERLOGIX_MSG_BIT_LEN 46\n\nstatic int interlogix_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble message\n    // only searching for 0000 0001 (bottom 8 bits of the 13 bits preamble)\n    uint8_t const preamble_pattern[1] = {0x01}; // 8 bits\n\n    data_t *data;\n    unsigned int row = 0;\n    char const *device_type;\n    int low_battery;\n    char const *f1_latch_state;\n    char const *f2_latch_state;\n    char const *f3_latch_state;\n    char const *f4_latch_state;\n    char const *f5_latch_state;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Check if the message length is between the length seen in test files (57)\n    // and the 64 bits discussed above.\n    if (bitbuffer->bits_per_row[0] < 57\n            || bitbuffer->bits_per_row[0] > 64) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // search for preamble and exit if not found\n    unsigned int bit_offset = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, (sizeof preamble_pattern) * 8);\n    if (bit_offset == bitbuffer->bits_per_row[row]) {\n        decoder_logf(decoder, 2, __func__, \"Preamble not found, bit_offset: %u\", bit_offset);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // set message starting position (just past preamble and sync bit)\n    bit_offset += (sizeof preamble_pattern) * 8;\n\n    uint8_t message[(INTERLOGIX_MSG_BIT_LEN + 7) / 8];\n\n    bitbuffer_extract_bytes(bitbuffer, row, bit_offset, message, INTERLOGIX_MSG_BIT_LEN);\n\n    // reduce false positives, abort if id or code looks wrong\n    if (message[0] == 0x00 && message[1] == 0x00 && message[2] == 0x00)\n        return DECODE_FAIL_SANITY;\n    if (message[0] == 0xff && message[1] == 0xff && message[2] == 0xff)\n        return DECODE_FAIL_SANITY;\n    if (message[3] == 0x00 && message[4] == 0x00 && message[5] == 0x00)\n        return DECODE_FAIL_SANITY;\n    if (message[3] == 0xff && message[4] == 0xff && message[5] == 0xff)\n        return DECODE_FAIL_SANITY;\n\n    // parity check: even data bits from message[0 .. 40] and odd data bits from message[1 .. 41]\n    // i.e. 5 bytes and two (top-most) bits.\n    int parity = message[0] ^ message[1] ^ message[2] ^ message[3] ^ message[4]; // parity as byte\n    parity = (parity >> 4) ^ (parity & 0xF); // fold to nibble\n    parity = (parity >> 2) ^ (parity & 0x3); // fold to 2 bits\n    parity ^= message[5] >> 6; // add check bits\n    int parity_error = parity ^ 0x3; // both parities are odd, i.e. 1 on success\n\n    if (parity_error) {\n        decoder_logf(decoder, 1, __func__, \"Parity check failed (%d %d)\", parity >> 1, parity & 1);\n        return DECODE_FAIL_MIC;\n    }\n\n    char device_type_id[2];\n    snprintf(device_type_id, sizeof(device_type_id), \"%01x\", (reverse8(message[2]) >> 4));\n\n    switch ((reverse8(message[2]) >> 4)) {\n    case 0xa: device_type = \"contact\"; break;\n    case 0xf: device_type = \"keyfob\"; break;\n    case 0x4: device_type = \"motion\"; break;\n    case 0x6: device_type = \"heat\"; break;\n    case 0x9: device_type = \"glass\"; break; // switch1 changes from open to closed on trigger\n    case 0xd: device_type = \"glass\"; break; // newer Shatterpro\n    case 0xe: device_type = \"freeze\"; break;\n    case 0x2: device_type = \"smoke\"; break;\n    case 0x3: device_type = \"panic\"; break;\n\n    default: device_type = \"unknown\"; break;\n    }\n\n    char device_serial[7];\n    snprintf(device_serial, sizeof(device_serial), \"%02x%02x%02x\", reverse8(message[2]), reverse8(message[1]), reverse8(message[0]));\n\n    char raw_message[7];\n    snprintf(raw_message, sizeof(raw_message), \"%02x%02x%02x\", message[3], message[4], message[5]);\n\n    // keyfob logic. see protocol description addendum for protocol exceptions\n    if ((reverse8(message[2]) >> 4) == 0xf) {\n        low_battery    = 0;\n        f1_latch_state = ((message[3] & 0xe) == 0x4) ? \"CLOSED\" : \"OPEN\";\n        f2_latch_state = ((message[3] & 0xe) == 0x8) ? \"CLOSED\" : \"OPEN\";\n        f3_latch_state = ((message[3] & 0xe) == 0xc) ? \"CLOSED\" : \"OPEN\";\n        f4_latch_state = ((message[3] & 0xe) == 0x2) ? \"CLOSED\" : \"OPEN\";\n        f5_latch_state = ((message[3] & 0xe) == 0xa) ? \"CLOSED\" : \"OPEN\";\n    } else {\n        low_battery    = (message[3] & 0x10) ? 1 : 0;\n        f1_latch_state = (message[3] & 0x04) ? \"OPEN\" : \"CLOSED\";\n        f2_latch_state = (message[3] & 0x01) ? \"OPEN\" : \"CLOSED\";\n        f3_latch_state = (message[4] & 0x40) ? \"OPEN\" : \"CLOSED\";\n        f4_latch_state = (message[4] & 0x10) ? \"OPEN\" : \"CLOSED\";\n        f5_latch_state = (message[4] & 0x04) ? \"OPEN\" : \"CLOSED\";\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",       \"Model\",         DATA_STRING, \"Interlogix-Security\",\n            \"subtype\",     \"Device Type\",   DATA_STRING, device_type,\n            \"id\",          \"ID\",            DATA_STRING, device_serial,\n            \"battery_ok\",  \"Battery\",       DATA_INT,    !low_battery,\n            \"switch1\",     \"Switch1 State\", DATA_STRING, f1_latch_state,\n            \"switch2\",     \"Switch2 State\", DATA_STRING, f2_latch_state,\n            \"switch3\",     \"Switch3 State\", DATA_STRING, f3_latch_state,\n            \"switch4\",     \"Switch4 State\", DATA_STRING, f4_latch_state,\n            \"switch5\",     \"Switch5 State\", DATA_STRING, f5_latch_state,\n            \"raw_message\", \"Raw Message\",   DATA_STRING, raw_message,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"subtype\",\n        \"id\",\n        \"raw_message\",\n        \"battery_ok\",\n        \"switch1\",\n        \"switch2\",\n        \"switch3\",\n        \"switch4\",\n        \"switch5\",\n        NULL,\n};\n\nr_device const interlogix = {\n        .name        = \"Interlogix GE UTC Security Devices\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 122,\n        .long_width  = 244,\n        .reset_limit = 500, // Maximum gap size before End Of Message\n        .decode_fn   = &interlogix_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/intertechno.c",
    "content": "/** @file\n    Intertechno remotes.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nIntertechno remotes.\n\nIntertechno remote labeled ITT-1500 that came with 3x ITR-1500 remote outlets. The set is labeled IT-1500.\nThe PPM consists of a 220µs high followed by 340µs or 1400µs of gap.\n\nThere is another type of remotes that have an ID prefix of 0x56 and slightly shorter timing.\n\n */\n\n#include \"decoder.h\"\n\nstatic int intertechno_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitrow_t *bb = bitbuffer->bb;\n    uint8_t *b = bitbuffer->bb[1];\n\n    if (bb[0][0] != 0 || (bb[1][0] != 0x56 && bb[1][0] != 0x69))\n        return DECODE_ABORT_EARLY;\n\n    char id_str[11];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4]);\n    int slave   = b[7] & 0x0f;\n    int master  = (b[7] & 0xf0) >> 4;\n    int command = b[6] & 0x07;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",     DATA_STRING,    \"Intertechno-Remote\",\n            \"id\",               \"\",     DATA_STRING,    id_str,\n            \"slave\",            \"\",     DATA_INT,       slave,\n            \"master\",           \"\",     DATA_INT,       master,\n            \"command\",          \"\",     DATA_INT,       command,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"slave\",\n        \"master\",\n        \"command\",\n        NULL,\n};\n\nr_device const intertechno = {\n        .name        = \"Intertechno 433\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 330,\n        .long_width  = 1400,\n        .gap_limit   = 1700,\n        .reset_limit = 10000,\n        .decode_fn   = &intertechno_callback,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/jasco.c",
    "content": "/** @file\n    Jasco/GE Choice Alert Wireless Device Decoder.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nJasco/GE Choice Alert Wireless Device Decoder.\n\n- Frequency: 318.01 MHz\n\nManchester PCM with a de-sync preamble of 0xFC0C (11111100000011000).\n\nPackets are 32 bit, 24 bit data and 8 bit XOR checksum.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int jasco_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xfc, 0x0c}; // length 16\n\n    if (bitbuffer->bits_per_row[0] < 80\n            || bitbuffer->bits_per_row[0] > 87) {\n        if (bitbuffer->bits_per_row[0] > 0) {\n            decoder_logf(decoder, 2, __func__, \"invalid bit count %d\", bitbuffer->bits_per_row[0]);\n        }\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble, 16) + 16;\n\n    if (start_pos + 64 > bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_manchester_decode(bitbuffer, 0, start_pos, &packet_bits, 32);\n\n    if (packet_bits.bits_per_row[0] < 32) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t *b = packet_bits.bb[0];\n\n    int chk = b[0] ^ b[1] ^ b[2] ^ b[3];\n    if (chk) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int sensor_id = (b[0] << 8) | b[1];\n\n    int s_closed = ((b[2] & 0xef) == 0xef);\n    // int battery = 0;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Jasco-Security\",\n            \"id\",               \"Id\",           DATA_INT,    sensor_id,\n            \"status\",           \"Closed\",       DATA_INT,    s_closed,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"status\",\n        \"mic\",\n        NULL,\n};\n\nr_device const jasco = {\n        .name        = \"Jasco/GE Choice Alert Security Devices\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 250,\n        .long_width  = 250,\n        .reset_limit = 1800, // Maximum gap size before End Of Message\n        .decode_fn   = &jasco_decode,\n        .fields      = output_fields,\n\n};\n"
  },
  {
    "path": "src/devices/kedsum.c",
    "content": "/** @file\n    Kedsum temperature and humidity sensor (http://amzn.to/25IXeng).\n\n    Copyright (C) 2016 John Lifsey\n    Enhanced (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLargely the same as esperanza_ews, s3318p.\n@sa esperanza_ews.c s3318p.c\n\nMy models transmit at a bit lower freq. of around 433.71 Mhz.\nAlso NC-7415 from Pearl.\n\nFrame structure:\n\n    Byte:      0        1        2        3        4\n    Nibble:    1   2    3   4    5   6    7   8    9   10\n    Type:   00 IIIIIIII BBCC++++ ttttTTTT hhhhHHHH FFFFXXXX\n\n- I: unique id. changes on powercycle\n- B: Battery state 10 = Ok, 01 = weak, 00 = bad\n- C: channel, 00 = ch1, 10=ch3\n- + low temp nibble\n- t: med temp nibble\n- T: high temp nibble\n- h: humidity low nibble\n- H: humidity high nibble\n- F: flags\n- X: CRC-4 poly 0x3 init 0x0 xor last 4 bits\n*/\n\n#include \"decoder.h\"\n\nstatic int kedsum_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[5];\n    data_t *data;\n\n    // the signal should start with 15 sync pulses (empty rows)\n    // require at least 5 received syncs\n    if (bitbuffer->num_rows < 5\n            || bitbuffer->bits_per_row[0] != 0\n            || bitbuffer->bits_per_row[1] != 0\n            || bitbuffer->bits_per_row[2] != 0\n            || bitbuffer->bits_per_row[3] != 0\n            || bitbuffer->bits_per_row[4] != 0)\n        return DECODE_ABORT_EARLY;\n\n    // the signal should have 6 repeats with a sync pulse between\n    // require at least 4 received repeats\n    int r = bitbuffer_find_repeated_row(bitbuffer, 4, 42);\n    if (r < 0 || bitbuffer->bits_per_row[r] != 42)\n        return DECODE_ABORT_LENGTH;\n\n    // remove the two leading 0-bits and align the data\n    bitbuffer_extract_bytes(bitbuffer, r, 2, b, 40);\n\n    // CRC-4 poly 0x3, init 0x0 over 32 bits then XOR the next 4 bits\n    int crc = crc4(b, 4, 0x3, 0x0) ^ (b[4] >> 4);\n    if (crc != (b[4] & 0xf))\n        return DECODE_FAIL_MIC;\n\n    int id       = (b[0]);\n    int battery  = (b[1] >> 6); // level 0-2\n    int channel  = ((b[1] & 0x30) >> 4) + 1;\n    int temp_raw = ((b[2] & 0x0f) << 8) | (b[2] & 0xf0) | (b[1] & 0x0f);\n    int humidity = ((b[3] & 0x0f) << 4) | ((b[3] & 0xf0) >> 4);\n    float temp_f = (temp_raw - 900) * 0.1f;\n\n    int flags = (b[1] & 0xc0) | (b[4] >> 4);\n\n    battery = battery == 2 ? 100 : battery * 10; // level 0,1,2 -> 0,10,100\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Kedsum-TH\",\n            \"id\",               \"ID\",               DATA_INT,    id,\n            \"channel\",          \"Channel\",          DATA_INT,    channel,\n            \"battery_ok\",       \"Battery level\",    DATA_DOUBLE, battery * 0.01f,\n            \"flags\",            \"Flags2\",           DATA_INT,    flags,\n            \"temperature_F\",    \"Temperature\",      DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"flags\",\n        \"temperature_F\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const kedsum = {\n        .name        = \"Kedsum Temperature & Humidity Sensor, Pearl NC-7415\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 4400,\n        .reset_limit = 9400,\n        .decode_fn   = &kedsum_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/kerui.c",
    "content": "/** @file\n    Kerui PIR / Contact Sensor.\n\n    Copyright (C) 2016 Karl Lattimer\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nKerui PIR / Contact Sensor.\n\nSuch as\nhttp://www.ebay.co.uk/sch/i.html?_from=R40&_trksid=p2050601.m570.l1313.TR0.TRC0.H0.Xkerui+pir.TRS0&_nkw=kerui+pir&_sacat=0\n\nalso tested with:\n- KERUI D026 Window Door Magnet Sensor Detector (433MHz) https://fccid.io/2AGNGKR-D026\n  events: open / close / tamper / battery low (below 5V of 12V battery)\n- Water leak sensor WD51\n- Mini Pir P831\n\nNote: simple 24 bit fixed ID protocol (x1527 style) and should be handled by the flex decoder.\nThere is a leading sync bit with a wide gap which runs into the preceding packet, it's ignored as 25th data bit.\n\nThere are slight timing differences between the older sensors and new ones like Water leak sensor WD51 and Mini Pir P831.\nLong: 860-1016 us, short: 304-560 us, older sync: 480 us, newer sync: 340 us,\n*/\n\n#include \"decoder.h\"\n\nstatic int kerui_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int id;\n    int cmd;\n    char const *cmd_str;\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 9, 25); // expected are 25 packets, require 9\n    if (r < 0)\n        return DECODE_ABORT_LENGTH;\n\n    if (bitbuffer->bits_per_row[r] != 25)\n        return DECODE_ABORT_LENGTH;\n    b = bitbuffer->bb[r];\n\n    // No need to decode/extract values for simple test\n    if (!b[0] && !b[1] && !b[2]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    //invert bits, short pulse is 0, long pulse is 1\n    b[0] = ~b[0];\n    b[1] = ~b[1];\n    b[2] = ~b[2];\n\n    id  = (b[0] << 12) | (b[1] << 4) | (b[2] >> 4);\n    cmd = b[2] & 0x0F;\n    switch (cmd) {\n    case 0xa: cmd_str = \"motion\"; break;\n    case 0xe: cmd_str = \"open\"; break;\n    case 0x7: cmd_str = \"close\"; break;\n    case 0xb: cmd_str = \"tamper\"; break;\n    case 0x5: cmd_str = \"water\"; break;\n    case 0xf: cmd_str = \"battery\"; break;\n    default: cmd_str = NULL; break;\n    }\n\n    if (!cmd_str)\n        return DECODE_ABORT_EARLY;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",                 DATA_STRING, \"Kerui-Security\",\n            \"id\",           \"ID (20bit)\",       DATA_FORMAT, \"0x%x\", DATA_INT, id,\n            \"cmd\",          \"Command (4bit)\",   DATA_FORMAT, \"0x%x\", DATA_INT, cmd,\n            \"motion\",       \"\",                 DATA_COND, cmd == 0xa, DATA_INT, 1,\n            \"opened\",       \"\",                 DATA_COND, cmd == 0xe, DATA_INT, 1,\n            \"opened\",       \"\",                 DATA_COND, cmd == 0x7, DATA_INT, 0,\n            \"tamper\",       \"\",                 DATA_COND, cmd == 0xb, DATA_INT, 1,\n            \"water\",        \"\",                 DATA_COND, cmd == 0x5, DATA_INT, 1,\n            \"battery_ok\",   \"Battery\",          DATA_COND, cmd == 0xf, DATA_INT, 0,\n            \"state\",        \"State\",            DATA_STRING, cmd_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"cmd\",\n        \"motion\",\n        \"opened\",\n        \"tamper\",\n        \"water\",\n        \"battery_ok\",\n        \"state\",\n        NULL,\n};\n\nr_device const kerui = {\n        .name        = \"Kerui PIR / Contact Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 420,\n        .long_width  = 960,\n        .gap_limit   = 1100,\n        .reset_limit = 9900,\n        .tolerance   = 160,\n        .decode_fn   = &kerui_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/klimalogg.c",
    "content": "/** @file\n    Klimalogg/30.3180.IT sensor decoder.\n\n    Copyright (C) 2020 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nKlimalogg/30.3180.IT sensor decoder.\n\nAlso Klimalogg/30.3181.IT (no humidity) sensor decoder.\n\nWorking decoder and information from https://github.com/baycom/tfrec\n\nThe message is 2 bytes of sync word plus 9 bytes of data.\nThe whole message (including sync word) is bit reflected.\n\nData layout:\n\n    0x2d 0xd4 II II sT TT HH BB SS 0x56 CC\n\n-  2d d4: Sync word\n-  II(14:0): 15 bit ID of sensor (printed on the back and displayed after powerup)\n-  II(15) is either 1 or 0 (fixed, depends on the sensor)\n-  s(3:0): Learning sequence 0...f, after learning fixed 8\n-  TTT: Temperature in BCD in .1degC steps, offset +40degC (-> -40...+60)\n-  HH(6:0): rel. Humidity in % (binary coded, no BCD!), 0x6a when saturated or n/a\n-  BB(7): Low battery if =1\n-  BB(6:4): 110 or 111 (for 3199)\n-  SS(7:4): sequence number (0...f)\n-  SS(3:0): 0000 (fixed)\n-  56: Type?\n-  CC: CRC8 from ID to 0x56 (polynomial x^8 + x^5 + x^4  + 1)\n\nNote: The rtl_433 generic dsp code does not work well with these signals\nplay with the -l option (5000-15000 range) or a high sample rate.\n\n*/\n\nstatic int klimalogg_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xB4, 0x2B}; // 0x2d, 0xd4 bit reflected\n\n    if (bitbuffer->bits_per_row[0] < 11 * 8) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 16) + 16;\n    if (bit_offset + 9 * 8 > bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t b[9];\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, b, 9 * 8);\n\n    if (b[7] != 0x6a) { // 0x56 bit reflected\n        return DECODE_FAIL_SANITY;\n    }\n\n    reflect_bytes(b, 9);\n\n    int crc = crc8(b, 9, 0x31, 0);\n    if (crc) {\n        return DECODE_FAIL_MIC;\n    }\n\n    /* Extract parameters */\n    int id            = (b[0] & 0x7f) << 8 | b[1];\n    int temp_raw      = (b[2] & 0x0f) * 100 + (b[3] >> 4) * 10 + (b[3] & 0x0f);\n    float temperature = (temp_raw - 400) * 0.1f;\n    int humidity      = (b[4] & 0x7f); // fixed 0x6a when saturated or n/a\n    int battery_low   = (b[5] & 0x80) >> 7;\n    int sequence_nr   = (b[6] & 0xf0) >> 4;\n\n    // Set humidity error code to 100%\n    if (humidity == 0x6a) {\n        humidity = 100;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Klimalogg-Pro\",\n            \"id\",               \"Id\",               DATA_FORMAT, \"%04x\", DATA_INT, id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",      DATA_DOUBLE, temperature,\n            \"humidity\",         \"Humidity\",         DATA_INT,    humidity,\n            \"sequence_nr\",      \"Sequence Number\",  DATA_INT,    sequence_nr,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"sequence_nr\",\n        \"mic\",\n        NULL,\n};\n\nr_device const klimalogg = {\n        .name        = \"Klimalogg\",\n        .modulation  = OOK_PULSE_NRZS,\n        .short_width = 26,\n        .long_width  = 0,\n        .gap_limit   = 0,\n        .reset_limit = 1000,\n        .decode_fn   = &klimalogg_decode,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse.c",
    "content": "/** @file\n    LaCrosse TX 433 Mhz Temperature and Humidity Sensors.\n\n    Copyright (C) 2015 Robert C. Terzi\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLaCrosse TX 433 Mhz Temperature and Humidity Sensors.\n- Tested: TX-7U and TX-6U (Temperature only)\n- Not Tested but should work: TX-3, TX-4\n- also TFA Dostmann 30.3120.90 sensor (for e.g. 35.1018.06 (WS-9015) station)\n- also TFA Dostmann 30.3121 sensor\n\nProtocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php\n\nMessage is 44 bits, 11 x 4 bit nybbles:\n\n    [00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check]\n\nNotes:\n- Zero Pulses are longer (1400 uS High, 1000 uS Low) = 2400 uS\n- One Pulses are shorter (550 uS High, 1000 uS Low) = 1600 uS\n- Sensor id changes when the battery is changed\n- Primary Value are BCD with one decimal place: vvv = 12.3\n- Secondary value is integer only intval = 12, seems to be a repeat of primary\n  This may actually be an additional data check because the 4 bit checksum\n  and parity bit is  pretty week at detecting errors.\n- Temperature is in Celsius with 50.0 added (to handle negative values)\n- Humidity values appear to be integer precision, decimal always 0.\n- There is a 4 bit checksum and a parity bit covering the three digit value\n- Parity check for TX-3 and TX-4 might be different.\n- Msg sent with one repeat after 30 mS\n- Temperature and humidity are sent as separate messages\n- Frequency for each sensor may be could be off by as much as 50-75 khz\n- LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK\n  so they can't be decoded by rtl_433 currently.\n- Temperature and Humidity are sent in different messages bursts.\n\n*/\n\n#include \"decoder.h\"\n\n#define LACROSSE_TX_BITLEN        44\n#define LACROSSE_NYBBLE_CNT        11\n\nstatic int lacrossetx_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int events = 0;\n    int result = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        // break out the message nybbles into separate bytes\n        // The LaCrosse protocol is based on 4 bit nybbles.\n        uint8_t *pRow = bitbuffer->bb[row];\n        uint8_t msg_nybbles[LACROSSE_NYBBLE_CNT];\n        int16_t rowlen = bitbuffer->bits_per_row[row];\n\n        // Actual Packet should start with 0x0A and be 6 bytes\n        // actual message is 44 bit, 11 x 4 bit nybbles.\n        if (rowlen != LACROSSE_TX_BITLEN) {\n            result = DECODE_ABORT_LENGTH;\n            continue; // DECODE_ABORT_LENGTH\n        }\n        if (pRow[0] != 0x0a) {\n            result = DECODE_ABORT_EARLY;\n            continue; // DECODE_ABORT_EARLY\n        }\n\n        for (int i = 0; i < LACROSSE_NYBBLE_CNT; i++) {\n            msg_nybbles[i] = 0;\n        }\n\n        // Move nybbles into a byte array\n        // Compute parity and checksum at the same time.\n        uint8_t parity = 0;\n        for (int i = 0; i < 44; i++) {\n            uint8_t rbyte_no = i / 8;\n            uint8_t rbit_no = 7 - (i % 8);\n            uint8_t mnybble_no = i / 4;\n            uint8_t mbit_no = 3 - (i % 4);\n            uint8_t bit = (pRow[rbyte_no] & (1 << rbit_no)) ? 1 : 0;\n            msg_nybbles[mnybble_no] |= (bit << mbit_no);\n\n            // Check parity on three bytes of data value\n            // TX3U might calculate parity on all data including\n            // sensor id and redundant integer data\n            if (mnybble_no > 4 && mnybble_no < 8) {\n                parity += bit;\n            }\n\n            //decoder_logf(decoder, 0, __func__, \"recv: [%d/%d] %d -> msg [%d/%d] %02x, Parity: %d\",\n            //        rbyte_no, rbit_no, bit, mnybble_no, mbit_no, msg_nybbles[mnybble_no], parity);\n        }\n\n        uint8_t parity_bit = msg_nybbles[4] & 0x01;\n        parity += parity_bit;\n\n        // Validate Checksum (4 bits in last nybble)\n        uint8_t checksum = 0;\n        for (int i = 0; i < 10; i++) {\n            checksum = (checksum + msg_nybbles[i]) & 0x0F;\n        }\n\n        // decoder_logf(decoder, 0, __func__,\"Parity: %d, parity bit %d, Good %d\", parity, parity_bit, parity % 2);\n\n        if (checksum != msg_nybbles[10] || (parity % 2 != 0)) {\n            decoder_logf(decoder, 2, __func__,\n                    \"LaCrosse TX Checksum/Parity error: Comp. %d != Recv. %d, Parity %d\",\n                    checksum, msg_nybbles[10], parity);\n            result = DECODE_FAIL_MIC;\n            continue; // DECODE_FAIL_MIC\n        }\n\n        // TODO: check if message length is a valid value\n        //uint8_t msg_len      = msg_nybbles[1];\n        uint8_t msg_type       = msg_nybbles[2];\n        uint8_t sensor_id      = (msg_nybbles[3] << 3) + (msg_nybbles[4] >> 1);\n        uint16_t msg_value_raw = (msg_nybbles[5] << 8) | (msg_nybbles[6] << 4) | msg_nybbles[7];\n        float msg_value        = msg_nybbles[5] * 10 + msg_nybbles[6] + msg_nybbles[7] * 0.1f;\n        int msg_value_int      = msg_nybbles[8] * 10 + msg_nybbles[9];\n\n        // Check Repeated data values as another way of verifying\n        // message integrity.\n        if (msg_nybbles[5] != msg_nybbles[8]\n                || msg_nybbles[6] != msg_nybbles[9]) {\n            decoder_logf(decoder, 1, __func__,\n                    \"Sensor %02x, type: %d: message value mismatch int(%.1f) != %d?\\n\",\n                    sensor_id, msg_type, msg_value, msg_value_int);\n            result = DECODE_FAIL_SANITY;\n            continue; // DECODE_FAIL_SANITY\n        }\n\n        if (msg_type == 0x00) {\n            float temp_c = msg_value - 50.0f;\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",             DATA_STRING, \"LaCrosse-TX\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"PARITY\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            events++;\n        }\n        else if (msg_type == 0x0E) {\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",             DATA_STRING, \"LaCrosse-TX\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"humidity\",         \"Humidity\",     DATA_COND,   msg_value_raw != 0xff, DATA_FORMAT, \"%.1f %%\", DATA_DOUBLE, msg_value,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"PARITY\",\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            events++;\n        }\n        else  {\n            // TODO: this should be reported/counted as exception, not considered debug\n            decoder_logf(decoder, 1, __func__,\n                    \"Sensor %02x: Unknown Reading type %d, % 3.1f (%d)\\n\",\n                    sensor_id, msg_type, msg_value, msg_value_int);\n        }\n    }\n\n    if (events) {\n        return events;\n    }\n\n    return result;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const lacrossetx = {\n        .name        = \"LaCrosse TX Temperature / Humidity Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 550,  // 550 us pulse + 1000 us gap is 1\n        .long_width  = 1400, // 1400 us pulse + 1000 us gap is 0\n        .gap_limit   = 3000, // max gap is 1000 us\n        .reset_limit = 8000, // actually: packet gap is 29000 us\n        .sync_width  = 0,    // not used\n        .decode_fn   = &lacrossetx_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_breezepro.c",
    "content": "/** @file\n    LaCrosse Technology View LTV-WSDTH01 Breeze Pro Wind Sensor.\n\n    Copyright (C) 2020 Mike Bruski (AJ9X) <michael.bruski@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLaCrosse Technology View LTV-WSDTH01 Breeze Pro Wind Sensor.\n\nLaCrosse Color Forecast Station (model 79400) utilizes the remote temp/\nhumidity/wind speed/wind direction sensor LTV-WSDTH01.\n\nProduct pages:\nhttps://www.lacrossetechnology.com/products/79400\nhttps://www.lacrossetechnology.com/products/ltv-wsdth01\n\nSpecifications:\n- Wind Speed Range: 0 to 178 kmh\n- Degrees of Direction: 360 deg with 16 Cardinal Directions\n- Outdoor Temperature Range: -29 C to 60 C\n- Outdoor Humidity Range: 10 to 99 %RH\n- Update Interval: Every 31 Seconds\n\nInternal inspection of the remote sensor reveals that the device\nutilizes a HopeRF CMT2119A ISM transmitter chip which is capable of\ntransmitting up to 32 bytes of data on any ISM frequency using OOK\nor (G)FSK modulation.  In this application, the sensor sends\nFSK_PCM on a center frequency of 914.938 MHz.  FWIW, FCC filings\nand photos would seem to indicate that the LTV-WSDTH01 and TX145wsdth\nare physically identical devices with different antenna.  The MCU\nprogramming of the latter is most likely different given it transmits\nan OOK data stream on 432.92 MHz.\n\nAn inspection of the 79400 console reveals that it employs a HopeRF\nCMT2219A ISM receiver chip.  An application note is available that\nprovides further info into the capabilities of the CMT2119A and\nCMT2219A.\n\n(http://www.cmostek.com/download/CMT2119A_v0.95.pdf)\n(http://www.cmostek.com/download/CMT2219A.pdf)\n(http://www.cmostek.com/download/AN138%20CMT2219A%20Configuration%20Guideline.pdf)\n\nProtocol Specification:\n\nData bits are NRZ encoded with logical 1 and 0 bits 106.842us in length.\n\n    SYNC:32h ID:24h ?:4b SEQ:3d ?:1b TEMP:12d HUM:12d WSPD:12d WDIR:12d CHK:8h END:32h\n\nPacket length is 264 bits according to inspectrum broken down as follows:\n\n- warm-up:         7 bytes (0x55, aligned with sync word these are 0xaa)\n- preamble/sync    4 bytes 0xd2aa2dd4 (see as 0x695516ea05)\n- device id:       3 bytes (matches bar code underside of unit covering pgm port)\n- x1:              4 bit   (unknown, bit 0?00 might be 'battery low')\n- sequence:        3 bits  (0-7, one up per packet, then repeats)\n- x2:              1 bit   (unknown)\n- celsius:        12 bits  (offset 400, scale 10, range: -29 C to 60 C)\n- humidity:       12 bits  (10 to 99% relative humidity)\n- wind speed:     12 bits  (0.0 to 178.0 kMh)\n- wind direction: 12 bits  (0 to 359 deg)\n- checksum:        8 bits  (CRC-8 poly 0x31 init 0x00 over 10 bytes after sync)\n- trailer:        32 bytes (0xd2d2d200)\n\nThe sensor generates a packet every 'n' seconds but only transmits if one or\nmore of the following conditions are satisfied:\n\n- temp changes +/- 0.8 degrees C\n- humidity changes +/- 1%\n- wind speed changes +/- 0.5 kM/h\n\nThus, if there is a gap in sequencing, it is due to bad packet[s] (too short,\nfailed CRC) or packet[s] that didn't satisfy at least one of these three\nconditions. 'n' above varies with temperature.  At 0C and above, 'n' is 31.\nBetween -17C and 0C, 'n' is 60.  Below -17C, 'n' is 360.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int lacrosse_breezepro_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xd2, 0xaa, 0x2d, 0xd4};\n\n    uint8_t b[11];\n    uint32_t id;\n    int flags, seq, offset, chk;\n    int raw_temp, humidity, raw_speed, direction;\n    float temp_c, speed_kmh;\n\n    if (bitbuffer->bits_per_row[0] < 264) {\n        decoder_logf(decoder, 1, __func__, \"Wrong packet length: %d\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    offset = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 11 * 8);\n\n    chk = crc8(b, 11, 0x31, 0x00);\n    if (chk) {\n        decoder_log(decoder, 1, __func__, \"CRC failed!\");\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"\");\n\n    id        = (b[0] << 16) | (b[1] << 8) | b[2];\n    flags     = (b[3] & 0xf1); // masks off seq bits\n    seq       = (b[3] & 0x0e) >> 1;\n    raw_temp  = b[4] << 4 | ((b[5] & 0xf0) >> 4);\n    humidity  = ((b[5] & 0x0f) << 8) | b[6];\n    raw_speed = b[7] << 4 | ((b[8] & 0xf0) >> 4);\n    direction = ((b[8] & 0x0f) << 8) | b[9];\n\n    // base and/or scale adjustments\n    temp_c = (raw_temp - 400) * 0.1f;\n    speed_kmh = raw_speed * 0.1f;\n\n    if (humidity < 0 || humidity > 100\n            || temp_c < -40 || temp_c > 70\n            || direction < 0 || direction > 360\n            || speed_kmh < 0 || speed_kmh > 200) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"LaCrosse-BreezePro\",\n            \"id\",               \"Sensor ID\",        DATA_FORMAT, \"%06x\", DATA_INT, id,\n            \"seq\",              \"Sequence\",         DATA_FORMAT, \"%01x\", DATA_INT, seq,\n            \"flags\",            \"unknown\",          DATA_INT,     flags,\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"wind_avg_km_h\",    \"Wind speed\",       DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_INT,    direction,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"seq\",\n        \"flags\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_km_h\",\n        \"wind_dir_deg\",\n        \"mic\",\n        NULL,\n};\n\n// flex decoder m=FSK_PCM, s=107, l=107, r=5900\nr_device const lacrosse_breezepro = {\n        .name        = \"LaCrosse Technology View LTV-WSDTH01 Breeze Pro Wind Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 107,\n        .long_width  = 107,\n        .reset_limit = 5900,\n        .decode_fn   = &lacrosse_breezepro_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_r1.c",
    "content": "/** @file\n    LaCrosse Technology View LTV-R1, LTV-R3 Rainfall Gauge, LTV-W1/W2 Wind Sensor.\n\n    Copyright (C) 2020 Mike Bruski (AJ9X) <michael.bruski@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nLaCrosse Technology View LTV-R1, LTV-R3 Rainfall Gauge, LTV-W1/W2 Wind Sensor and TFA View Rainfall Gauge 30.3802.02\n\nProduct pages:\nhttps://www.lacrossetechnology.com/products/ltv-r1\nhttps://www.lacrossetechnology.com/products/724-2310\n\nSpecifications:\n- Rainfall 0 to 9999.9 mm\n\nNo internal inspection of the sensors was performed so can only\nspeculate that the remote sensors utilize a HopeRF CMT2119A ISM\ntransmitter chip which is tuned to 915Mhz.\n\nNo internal inspection of the console was performed but if the above\nassumption is true, then the console most likely uses the HopeRF\nCMT2219A ISM receiver chip.\n\n(http://www.cmostek.com/download/CMT2119A_v0.95.pdf)\n(http://www.cmostek.com/download/CMT2219A.pdf)\n(http://www.cmostek.com/download/AN138%20CMT2219A%20Configuration%20Guideline.pdf)\n\nProtocol Specification:\n\nData bits are NRZ encoded with logical 1 and 0 bits 104us in length.\n\nChecksum is CRC-8 poly 0x31 init 0x00 over 7 (10 for R3) bytes following SYNC.\n\nNote that the rain zero value seems to be `00aa00` with a known byte order of `HH??LL`.\nIt's unknown if the 16-bit value would reset or roll over into the middle byte (with whitening)?\n\n## LTV-R1:\n\nFull preamble is `fff00000 aaaaaaaa d2aa2dd4`.\n\n    PRE:32h SYNC:32h ID:24h ?:4b SEQ:3d ?:1b RAIN:24h CRC:8h CHK?:8h TRAILER:96h\n\n    {164} 380322  0e  00aa14  6a  93  00...\n    {164} 380322  00  00aa1a  60  81  00...\n    {162} 380322  06  00aa26  d1  04  00...\n\n## LTV-R3 and TFA 30.3802.02\n\nDoes not have the CRC at byte 8 but a second 24 bit value and the check at byte 11. This can cause incorrect matching to `lacrosse_breezepro_decode`.\nFull preamble is `aaaaaaaaaaaaaa d2aa2dd4`.\n\n    PRE:58h SYNC:32h ID:24h ?:4b SEQ:3d ?:1b RAIN:24h RAIN:24h CRC:8h TRAILER:56h\n\n    {144} 71061d 42 00aa00 00aa00  c6  0000000000000000 [zero]\n    {144} 71061d 08 00aac3 00aab7  01  0000000000000000 [before 8-bit rollover]\n    {144} 71061d 02 01aa03 01aa03  46  0000000000000000 [after 8-bit rollover]\n    {145} 70f6a2 00 015402 015401  ae  00...\n    {142} 70f6a0 88 015400 015400  24  00...\n    {143} 70f6a2 46 00a800 015401  e2  00...\n    {144} 70f6a2 48 00aa02 00aa00  3d  00...\n    {144} 70f6a2 02 005408 015406  0a  00...\n    {141} 70f6a2 04 01540e 01540b  90  00...\n    {142} 70f6a2 0a 00aa04 015410  48  00...\n    {143} 70f6a2 04 00aa0a 01541b  12  00...\n    {142} 70f6a2 0c 00aa0a 01541a  ac  00...\n    {144} 70f6a2 04 00aa0d 00aa0d  89  00...\n    {143} 70f6a2 0c 00aa0d 00aa0d  56  00...\n\nEuropean TFA 30.3802.02 looks and behaves identically to LTV-R3, but works on 868MHz and seems to consistently shorter padding.\n\n    {105} 72b9f2 0c 01aa1a 01aa14  08  00000\n    {105} 72b9f2 06 01aa25 01aa25  06  00000 //  17 ticks =  4.25mm (Cloud API rounds to 4.3mm)\n    {105} 72b9f2 02 01aadc 01aadc  25  00000 // 183 ticks = 42.75mm\n\n## LTV-W1 (also LTV-W2):\n\nFull preamble is `aaaaaaaaaaaaaa d2aa2dd4`.\n\n    ID:24h BATTLOW:1b STARTUP:1b ?:2b SEQ:3h ?:1b 8h8h8h WIND:12d 12h CRC:8h TRAILER 8h8h8h8h8h8h8h8h\n\n    d2aa2dd4 0fb220 0e aaaaaa 07f aaa fe 00000000000000 [13 km Good battery]\n    d2aa2dd4 0fb220 02 aaaaaa 0bf aaa ad 00000000000000 [19 km Good battery]\n    d2aa2dd4 0fb220 08 aaaaaa 011 aaa 39 00000000000000 [4 km Good battery]\n    d2aa2dd4 0fb220 0a aaaaaa 000 aaa f2 00000000000000 [2 km Good battery]\n    d2aa2dd4 0fb220 06 aaaaaa 000 aaa da 00000000000000 [0 km Good battery]\n    d2aa2dd4 0fb220 0e aaaaaa 000 aaa 05 00000000000000 [0 km]\n    d2aa2dd4 0fb220 06 aaaaaa 000 aaa da 00000000000000 [0 km]\n    d2aa2dd4 0fb220 0e aaaaaa 000 aaa 05 00000000000000 [0 km]\n    d2aa2dd4 0fb220 0a aaaaaa 000 aaa f2 00000000000000 [0 km]\n    d2aa2dd4 0fb220 42 aaaaaa 000 aaa 73 00000000000000 [startup good]\n    d2aa2dd4 0fb220 44 aaaaaa 000 aaa 67 00000000000000 [startup good]\n    d2aa2dd4 0fb220 0a aaaaaa 000 aaa f2 00000000000000 [good]\n    d2aa2dd4 0fb220 c2 aaaaaa 000 aaa cf 00000000000000 [startup weak]\n    d2aa2dd4 0fb220 c4 aaaaaa 000 aaa db 00000000000000 [startup weak]\n    d2aa2dd4 0fb220 c6 aaaaaa 000 aaa 38 00000000000000 [startup weak]\n    d2aa2dd4 0fb220 c8 aaaaaa 000 aaa f3 00000000000000 [weak]\n    d2aa2dd4 0fb220 8a aaaaaa 000 aaa 4e 00000000000000 [weak]\n*/\n\nstatic int lacrosse_r1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble (LTV-R1) is `fff00000 aaaaaaaa d2aa2dd4`\n    // full preamble (LTV-R3, LTV-W1, TFA 30.3802.02) is `aaaaaaaaaaaaaa d2aa2dd4`\n    uint8_t const preamble_pattern[] = {0xd2, 0xaa, 0x2d, 0xd4};\n\n    uint8_t b[20];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n    if (msg_len < 170) { // allows shorter preamble for LTV-R3 (>200) and TFA 30.3802.02 (on 868MHz around 177)\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    } else if (msg_len > 272) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    } else {\n        decoder_logf(decoder, 1, __func__, \"packet length: %d\", msg_len);\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 20 * 8);\n\n    int rev = 1;\n    int chk = crc8(b, 11, 0x31, 0x00);\n    if (chk == 0\n            && b[4] == 0xaa && b[5] == 0xaa && b[6] == 0xaa\n            && (b[8] & 0x0f) == 0x0a && b[9] == 0xaa) {\n        rev = 9; // LTV-W1/W2\n    }\n    else if (chk == 0 && b[10] != 0) {\n        rev = 3; // LTV-R3 and TFA 30.3802.02\n    }\n    else {\n        chk = crc8(b, 8, 0x31, 0x00);\n        if (b[10] != 0 || chk != 0) { // make sure this really is a LTV-R1 and not just a CRC collision\n            decoder_log(decoder, 1, __func__, \"CRC failed!\");\n            return DECODE_FAIL_MIC;\n        }\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0] - offset, \"\");\n\n    // Note that the rain zero value is 00aa00 with a known byte order of HH??LL.\n    // We just prepend the middle byte and assume whitening. Let's hope we get feedback someday.\n    int id        = (b[0] << 16) | (b[1] << 8) | b[2];\n    int flags     = (b[3] & 0x31); // masks off knonw bits\n    int batt_low  = (b[3] & 0x80) >> 7;\n    int startup   = (b[3] & 0x40) >> 6;\n    int seq       = (b[3] & 0x0e) >> 1;\n    int raw_rain1 = ((b[5] ^ 0xaa) << 16) | (b[4] << 8) | (b[6]);\n    int raw_rain2 = ((b[8] ^ 0xaa) << 16) | (b[7] << 8) | (b[9]); // only LTV-R3\n    int raw_wind  = (b[7] << 4) | (b[8] >> 4); // only LTV-W1/W2\n\n    // Seems rain is 0.25mm per tip, not sure what rain2 is\n    float rain_mm = raw_rain1 * 0.25f;\n    float rain2_mm = raw_rain2 * 0.25f;\n    // wind speed on LTV-W1/W2\n    float wspeed_kmh = raw_wind * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_COND,   rev == 1,  DATA_STRING, \"LaCrosse-R1\",\n            \"model\",            \"\",                 DATA_COND,   rev == 3,  DATA_STRING, \"LaCrosse-R3\",\n            \"model\",            \"\",                 DATA_COND,   rev == 9,  DATA_STRING, \"LaCrosse-W1\",\n            \"id\",               \"Sensor ID\",        DATA_FORMAT, \"%06x\",    DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !batt_low,\n            \"startup\",          \"Startup\",          DATA_COND,   startup,   DATA_INT,    startup,\n            \"seq\",              \"Sequence\",         DATA_INT,    seq,\n            \"flags\",            \"Unknown\",          DATA_COND,   flags,     DATA_INT,    flags,\n            \"rain_mm\",          \"Total Rain\",       DATA_COND,   rev != 9,  DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, rain_mm,\n            \"rain2_mm\",         \"Total Rain2\",      DATA_COND,   rev == 3,  DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, rain2_mm,\n            \"wind_avg_km_h\",    \"Wind Speed\",       DATA_COND,   rev == 9,  DATA_FORMAT, \"%.1f km/h\", DATA_DOUBLE, wspeed_kmh,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"startup\",\n        \"seq\",\n        \"flags\",\n        \"rain_mm\",\n        \"rain2_mm\",\n        \"wind_avg_km_h\",\n        \"mic\",\n        NULL,\n};\n\n// flex decoder m=FSK_PCM, s=104, l=104, r=9600\nr_device const lacrosse_r1 = {\n        .name        = \"LaCrosse Technology View LTV-R1, LTV-R3 Rainfall Gauge, LTV-W1/W2 Wind Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 104,\n        .long_width  = 104,\n        .reset_limit = 9600,\n        .decode_fn   = &lacrosse_r1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_th3.c",
    "content": "/** @file\n    LaCrosse Technology View LTV-TH3 & LTV-TH2 Thermo/Hygro Sensor.\n\n    Copyright (C) 2020 Mike Bruski (AJ9X) <michael.bruski@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLaCrosse Technology View LTV-TH3 & LTV-TH2 Thermo/Hygro Sensor.\n\nLaCrosse Color Forecast Station (model S84060) utilizes the remote\nThermo/Hygro LTV-TH3 and LTV-WR1 multi sensor (wind spd/dir and rain).\nLaCrosse Color Forecast Station (model C84343) utilizes the remote\nThermo/Hygro LTV-TH2.\n\nProduct pages:\nhttps://www.lacrossetechnology.com/products/S84060\nhttps://www.lacrossetechnology.com/products/ltv-th3\nhttps://www.lacrossetechnology.com/products/C84343\nhttps://www.lacrossetechnology.com/products/ltv-th2\n\nSpecifications:\n- Outdoor Temperature Range: -40 C to 60 C\n- Outdoor Humidity Range: 10 to 99 %RH\n- Update Interval: Every 30 Seconds\n\nNo internal inspection of the sensors was performed so can only\nspeculate that the remote sensors utilize a HopeRF CMT2119A ISM\ntransmitter chip which is tuned to 915Mhz.\n\nAgain, no inspection of the S84060 or C84343 console was performed but\nit probably utilizes a HopeRF CMT2219A ISM receiver chip.  An application\nnote is available that provides further info into the capabilities of the\nCMT2119A and CMT2219A.\n\n(http://www.cmostek.com/download/CMT2119A_v0.95.pdf)\n(http://www.cmostek.com/download/CMT2219A.pdf)\n(http://www.cmostek.com/download/AN138%20CMT2219A%20Configuration%20Guideline.pdf)\n\nProtocol Specification:\n\nData bits are NRZ encoded.  Logical 1 and 0 bits are 104us in\nlength for the LTV-TH3 and 107us for the LTV-TH2.\n\nLTV-TH3\n    SYNC:32h ID:24h ?:4b SEQ:3b ?:1b TEMP:12d HUM:12d CHK:8h END:\n\n    CHK is CRC-8 poly 0x31 init 0x00 over 7 bytes following SYN\n\nLTV-TH2\n    SYNC:32h ID:24h ?:4b SEQ:3b ?:1b TEMP:12d HUM:12d CHK:8h END:\n\nSequence# 2 & 6\n    CHK is CRC-8 poly 0x31 init 0x00 over 7 bytes following SYN\nSequence# 0,1,3,4,5 & 7\n    CHK is CRC-8 poly 0x31 init 0xac over 7 bytes following SYN\n\n\n*/\n\n#include \"decoder.h\"\n\nstatic int lacrosse_th_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xd2, 0xaa, 0x2d, 0xd4};\n\n    data_t *data;\n    uint8_t b[11];\n    uint32_t id;\n    int flags, seq, offset, chk3, chk2, model_num;\n    int raw_temp, humidity;\n    float temp_c;\n\n    // bit length is specified as 104us for the TH3 (~256 bits per packet)\n    // but the TH2 bit length is actually 107us leading the bitbuffer to\n    // report the packet length as ~286 bits long.  We'll use this fact\n    // to identify which of the two models actually sent the data.\n    if (bitbuffer->bits_per_row[0] < 156) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    } else if (bitbuffer->bits_per_row[0] > 290) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    } else {\n        decoder_logf(decoder, 1, __func__, \"packet length: %d\", bitbuffer->bits_per_row[0]);\n        model_num = (bitbuffer->bits_per_row[0] < 280) ? 3 : 2;\n    }\n\n    offset = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 8 * 8);\n\n    // failing the CRC checks indicates the packet is corrupt <OR>\n    // this is not a LTV-TH3 or LTV-TH2 sensor\n    chk3 = crc8(b, 8, 0x31, 0x00);\n    chk2 = crc8(b, 8, 0x31, 0xac);\n    if (chk3 != 0 && chk2 != 0) {\n        decoder_log(decoder, 1, __func__, \"CRC failed!\");\n        return DECODE_FAIL_MIC;\n    }\n\n    id       = (b[0] << 16) | (b[1] << 8) | b[2];\n    flags    = (b[3] & 0xf1); // masks off seq bits\n    seq      = (b[3] & 0x0e) >> 1;\n    raw_temp = b[4] << 4 | ((b[5] & 0xf0) >> 4);\n    humidity = ((b[5] & 0x0f) << 8) | b[6];\n\n    // base and/or scale adjustments\n    temp_c = (raw_temp - 400) * 0.1f;\n\n    if (humidity < 0 || humidity > 100 || temp_c < -50 || temp_c > 70)\n        return DECODE_FAIL_SANITY;\n\n    /* clang-format off */\n    data = data_make(\n         \"model\",            \"\",                 DATA_COND, model_num == 3, DATA_STRING, \"LaCrosse-TH3\",\n         \"model\",            \"\",                 DATA_COND, model_num != 3, DATA_STRING, \"LaCrosse-TH2\",\n         \"id\",               \"Sensor ID\",        DATA_FORMAT, \"%06x\", DATA_INT, id,\n         \"seq\",              \"Sequence\",         DATA_INT,     seq,\n         \"flags\",            \"unknown\",          DATA_INT,     flags,\n         \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temp_c,\n         \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n         \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n         NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"seq\",\n        \"flags\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\n// flex decoder n=TH3, m=FSK_PCM, s=104, l=104, r=9600\n// flex decoder n=TH2, m=FSK_PCM, s=107, l=107, r=5900\n// TH3 parameters should be good enough for both sensors\nr_device const lacrosse_th3 = {\n        .name        = \"LaCrosse Technology View LTV-TH Thermo/Hygro Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 104,\n        .long_width  = 104,\n        .reset_limit = 9600,\n        .decode_fn   = &lacrosse_th_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_tx141x.c",
    "content": "/** @file\n    LaCrosse TX141-Bv2, TX141TH-Bv2, TX141-Bv3, TX145wsdth sensor.\n\n    Changes done by Andrew Rivett <veggiefrog@gmail.com>. Copyright is\n    retained by Robert Fraczkiewicz.\n\n    Copyright (C) 2017 Robert Fraczkiewicz <aromring@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLaCrosse TX141-Bv2, TX141TH-Bv2, TX141-Bv3, TX145wsdth sensor.\n\nAlso TFA 30.3221.02 (a TX141TH-Bv2),\nalso TFA 30.3222.02 (a LaCrosse-TX141W).\nAlso TFA 30.3249.02 (a TX141TH-Bv2),\nalso TFA 30.3251.10 (a LaCrosse-TX141W).\nalso some rebrand (ORIA WA50B) with a slightly longer timing, s.a. #2088\nalso TFA 30.3243.02 (a LaCrosse-TX141Bv3)\nalso LaCrosse TX141-Bv4 (seems identical to LaCrosse-TX141Bv3)\n\nLaCrosse Color Forecast Station (model C85845), or other LaCrosse product\nutilizing the remote temperature/humidity sensor TX141TH-Bv2 transmitting\nin the 433.92 MHz band. Product pages:\nhttp://www.lacrossetechnology.com/c85845-color-weather-station/\nhttp://www.lacrossetechnology.com/tx141th-bv2-temperature-humidity-sensor\n\nThe TX141TH-Bv2 protocol is OOK modulated PWM with fixed period of 625 us\nfor data bits, preambled by four long startbit pulses of fixed period equal\nto ~1666 us. Hence, it is similar to Bresser Thermo-/Hygro-Sensor 3CH.\n\nA single data packet looks as follows:\n1) preamble - 833 us high followed by 833 us low, repeated 4 times:\n\n     ----      ----      ----      ----\n    |    |    |    |    |    |    |    |\n          ----      ----      ----      ----\n\n2) a train of 40 data pulses with fixed 625 us period follows immediately:\n\n     ---    --     --     ---    ---    --     ---\n    |   |  |  |   |  |   |   |  |   |  |  |   |   |\n         --    ---    ---     --     --    ---     -- ....\n\nA logical 1 is 417 us of high followed by 208 us of low.\nA logical 0 is 208 us of high followed by 417 us of low.\nThus, in the example pictured above the bits are 1 0 0 1 1 0 1 ....\n\nThe TX141TH-Bv2 sensor sends 12 of identical packets, one immediately following\nthe other, in a single burst. These 12-packet bursts repeat every 50 seconds. At\nthe end of the last packet there are two 833 us pulses (\"post-amble\"?).\n\nThe TX141-Bv3 has a revision which only sends 4 packets per transmission.\n\nThe data is grouped in 5 bytes / 10 nybbles\n\n    [id] [id] [flags] [temp] [temp] [temp] [humi] [humi] [chk] [chk]\n\n- id:    8 bit random integer generated at each powers up\n- flags: 4 bit for battery low indicator, test button press, and channel\n- temp: 12 bit unsigned temperature in degrees Celsius, scaled by 10, offset 500, range -40 C to 60 C\n- humi:  8 bit integer indicating relative humidity in %.\n- chk:   8 bit checksum is a digest, 0x31, 0xf4, reflected\n\nA count enables us to determine the quality of radio transmission.\n\n*** Addition of TX141 temperature only device, Jan 2018 by Andrew Rivett <veggiefrog@gmail.com>**\n\nThe TX141-BV2 is the temperature only version of the TX141TH-BV2 sensor.\n\nChanges:\n- Changed minimum bit length to 32 (tx141b is temperature only)\n- LACROSSE_TX141_BITLEN is 37 instead of 40.\n- The humidity variable has been removed for TX141.\n- Battery check bit is inverse of TX141TH.\n- temp_f removed, temp_c (celsius) is what's provided by the device.\n\n- TX141TH-BV3 bitlen is 41\n\nAddition of TX141W and TX145wsdth:\n\n    PRE5b ID19h BAT1b TEST?1b CH?2h TYPE4h TEMP_WIND12d HUM_DIR12d CHK8h 1x\n\n- type 1 has temp+hum (temp is offset 500 and scale 10)\n- type 2 has wind speed (km/h scale 10) and direction (degrees)\n- checksum is CRC-8 poly 0x31 init 0x00 over preceding 7 bytes\n\n*/\n\n#include \"decoder.h\"\n\n// Define the types of devices this file supports (uses expected bitlen)\n#define LACROSSE_TX141B 32\n#define LACROSSE_TX141 37\n#define LACROSSE_TX141TH 40\n#define LACROSSE_TX141BV3 33\n#define LACROSSE_TX141W 65\n\nstatic int lacrosse_tx141x_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int r;\n    int device;\n    uint8_t *b;\n\n    // Find the most frequent data packet\n    // reduce false positives, require at least 5 out of 12 or 3 of 4 repeats.\n    // allows 4-repeats transmission to contain a bogus extra row.\n    r = bitbuffer_find_repeated_row(bitbuffer, bitbuffer->num_rows > 5 ? 5 : 3, 32); // 32\n    if (r < 0) {\n        // try again for TX141W/TX145wsdth, require at least 2 out of 3-7 repeats.\n        r = bitbuffer_find_repeated_row(bitbuffer, 2, 64); // 65\n    }\n    if (r < 0) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if (bitbuffer->bits_per_row[r] >= 64) {\n        device = LACROSSE_TX141W;\n    }\n    else if (bitbuffer->bits_per_row[r] > 41) {\n        return DECODE_ABORT_LENGTH;\n    }\n    else if (bitbuffer->bits_per_row[r] >= 41) {\n        if (bitbuffer->num_rows > 12) {\n            return DECODE_ABORT_LENGTH; // false-positive with GT-WT03\n        }\n        device = LACROSSE_TX141TH; // actually TX141TH-BV3\n    }\n    else if (bitbuffer->bits_per_row[r] >= 40) {\n        device = LACROSSE_TX141TH;\n    }\n    else if (bitbuffer->bits_per_row[r] >= 37) {\n        device = LACROSSE_TX141;\n    }\n    else if (bitbuffer->bits_per_row[r] == 32) {\n        device = LACROSSE_TX141B;\n    } else {\n        device = LACROSSE_TX141BV3;\n    }\n\n    bitbuffer_invert(bitbuffer);\n    b = bitbuffer->bb[r];\n\n    if (device == LACROSSE_TX141W) {\n        int pre = (b[0] >> 3);\n        if (pre != 0x01) {\n            return DECODE_ABORT_EARLY;\n        }\n\n        int chk = crc8(b, 8, 0x31, 0x00);\n        if (chk) {\n            return DECODE_FAIL_MIC;\n        }\n\n        int id          = ((b[0] & 0x07) << 16) | (b[1] << 8) | b[2];\n        int battery_low = (b[3] >> 7);\n        int test        = (b[3] & 0x40) >> 6;\n        int channel     = (b[3] & 0x30) >> 4;\n        int type        = (b[3] & 0x0f);\n        int temp_raw    = (b[4] << 4) | (b[5] >> 4);\n        int humidity    = ((b[5] & 0x0f) << 8) | b[6];\n\n        if (type == 1) {\n            // Temp/Hum\n            float temp_c = (temp_raw - 500) * 0.1f;\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"LaCrosse-TX141W\",\n                    \"id\",               \"Sensor ID\",        DATA_FORMAT, \"%05x\", DATA_INT, id,\n                    \"channel\",          \"Channel\",          DATA_FORMAT, \"%01x\", DATA_INT, channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n                    \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                    \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                    \"test\",             \"Test?\",            DATA_INT,    test,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n        }\n        else if (type == 2) {\n            // Wind\n            float speed_kmh = temp_raw * 0.1f;\n            // wind direction is in humidity field\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"LaCrosse-TX141W\",\n                    \"id\",               \"Sensor ID\",        DATA_FORMAT, \"%05x\", DATA_INT, id,\n                    \"channel\",          \"Channel\",          DATA_FORMAT, \"%01x\", DATA_INT, channel,\n                    \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n                    \"wind_avg_km_h\",    \"Wind speed\",       DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n                    \"wind_dir_deg\",     \"Wind direction\",   DATA_INT,    humidity,\n                    \"test\",             \"Test?\",            DATA_INT,    test,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n        }\n        else {\n            decoder_logf(decoder, 1, __func__, \"unknown subtype: %d\", type);\n            return DECODE_FAIL_OTHER;\n        }\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    int id = b[0];\n    int battery_low;\n    if (device == LACROSSE_TX141TH) {\n        battery_low = (b[1] >> 7);\n    }\n    else { // LACROSSE_TX141 || LACROSSE_TX141BV3\n        battery_low = !(b[1] >> 7);\n    }\n    int test     = (b[1] & 0x40) >> 6;\n    int channel  = (b[1] & 0x30) >> 4;\n    int temp_raw = ((b[1] & 0x0F) << 8) | b[2];\n    float temp_c = (temp_raw - 500) * 0.1f; // Temperature in C\n\n    int humidity = 0;\n    if (device == LACROSSE_TX141TH) {\n        humidity = b[3];\n    }\n\n    if (0 == id || (device == LACROSSE_TX141TH && (0 == humidity || humidity > 100)) || temp_c < -40.0 || temp_c > 140.0) {\n        decoder_logf(decoder, 1, __func__, \"data error, id: %d, humidity:%d, temp:%f\", id, humidity, temp_c);\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (device == LACROSSE_TX141B) {\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",              DATA_STRING, \"LaCrosse-TX141B\",\n                \"id\",            \"Sensor ID\",     DATA_FORMAT, \"%02x\", DATA_INT, id,\n                \"temperature_C\", \"Temperature\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"battery_ok\",    \"Battery\",       DATA_INT,    !battery_low,\n                \"test\",          \"Test?\",         DATA_STRING, test ? \"Yes\" : \"No\",\n                NULL);\n        /* clang-format on */\n    } else if (device == LACROSSE_TX141) {\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",              DATA_STRING, \"LaCrosse-TX141Bv2\",\n                \"id\",            \"Sensor ID\",     DATA_FORMAT, \"%02x\", DATA_INT, id,\n                \"channel\",       \"Channel\",       DATA_INT, channel,\n                \"temperature_C\", \"Temperature\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"battery_ok\",    \"Battery\",       DATA_INT,    !battery_low,\n                \"test\",          \"Test?\",         DATA_STRING, test ? \"Yes\" : \"No\",\n                NULL);\n        /* clang-format on */\n    }\n    else if (device == LACROSSE_TX141BV3) {\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",              DATA_STRING, \"LaCrosse-TX141Bv3\",\n                \"id\",            \"Sensor ID\",     DATA_FORMAT, \"%02x\", DATA_INT, id,\n                \"channel\",       \"Channel\",       DATA_INT, channel,\n                \"battery_ok\",    \"Battery\",       DATA_INT,    !battery_low,\n                \"temperature_C\", \"Temperature\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"test\",          \"Test?\",         DATA_STRING, test ? \"Yes\" : \"No\",\n                NULL);\n        /* clang-format on */\n    }\n    else {\n        // Digest check for TX141TH-Bv2\n        if (lfsr_digest8_reflect(b, 4, 0x31, 0xf4) != b[4]) {\n            decoder_logf(decoder, 1, __func__, \"Checksum digest TX141TH failed\");\n            return DECODE_FAIL_MIC;\n        }\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",              DATA_STRING, \"LaCrosse-TX141THBv2\",\n                \"id\",            \"Sensor ID\",     DATA_FORMAT, \"%02x\", DATA_INT, id,\n                \"channel\",       \"Channel\",       DATA_INT, channel,\n                \"battery_ok\",    \"Battery\",       DATA_INT,    !battery_low,\n                \"temperature_C\", \"Temperature\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",      \"Humidity\",      DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                \"test\",          \"Test?\",         DATA_STRING, test ? \"Yes\" : \"No\",\n                \"mic\",           \"Integrity\",     DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_km_h\",\n        \"wind_dir_deg\",\n        \"test\",\n        \"mic\",\n        NULL,\n};\n\n// note TX141W, TX145wsdth: m=OOK_PWM, s=256, l=500, r=1888, y=748\nr_device const lacrosse_tx141x = {\n        .name        = \"LaCrosse TX141-Bv2, TX141TH-Bv2, TX141-Bv3, TX141W, TX145wsdth, (TFA, ORIA) sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 208,  // short pulse is 208 us + 417 us gap\n        .long_width  = 417,  // long pulse is 417 us + 208 us gap\n        .sync_width  = 833,  // sync pulse is 833 us + 833 us gap\n        .gap_limit   = 625,  // long gap (with short pulse) is ~417 us, sync gap is ~833 us\n        .reset_limit = 1700, // maximum gap is 1250 us (long gap + longer sync gap on last repeat)\n        .decode_fn   = &lacrosse_tx141x_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_tx31u.c",
    "content": "/** @file\n    LaCrosse TX31U-IT protocol.\n\n    Copyright (C) 2023 Craig Johnston\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nDecoder for LaCrosse transmitter provided with the WS-1910TWC-IT product.\nBranded with \"The Weather Channel\" logo.\nhttps://www.lacrossetechnology.com/products/ws-1910twc-it\n\nFCC ID: OMO-TX22U\nFSK_PCM @915 MHz, 116usec/bit\n\n## Protocol\n\nData format:\n\nThis transmitter uses a variable length protocol that includes 1-5 measurements\nof 2 bytes each.  The first nibble of each measurement identifies the sensor.\n\n    Sensor      Code    Encoding\n    TEMP          0       BCD tenths of a degree C plus 400 offset.\n                              EX: 0x0653 is 25.3 degrees C\n    HUMID         1       BCD % relative humidity.\n                              EX: 0x1068 is 68%\n    UNKNOWN       2       This is probably reserved for a rain gauge (TX32U-IT) - NOT TESTED\n    WIND_AVG_DIR  3       Wind direction and decimal time averaged wind speed in m/sec.\n                              First nibble is direction in units of 22.5 degrees.\n    WIND_MAX      4       Decimal maximum wind speed in m/sec during last reporting interval.\n                              First nibble is 0x1 if wind sensor input is lost.\n\n\n       a    a    a    a    2    d    d    4    a    2    e    5    0    6    5    3    c    0\n    Bits :\n    1010 1010 1010 1010 0010 1101 1101 0100 1010 0010 1110 0101 0000 0110 0101 0011 1100 0000\n    Bytes num :\n    ----1---- ----2---- ----3---- ----4---- ----5---- ----6---- ----7---- ----8---- ----N----\n    ~~~~~~~~~~~~~~~~~~~ 2 bytes preamble (0xaaaa)\n                        ~~~~~~~~~~~~~~~~~~~ bytes 3 and 4 sync word of 0x2dd4\n    sensor model (always 0xa)               ~~~~ 1st nibble of byte 5\n    Random device id (6 bits)                    ~~~~ ~~ 2nd nibble of byte 5 and bits 7-6 of byte 6\n    Initial training mode (all sensors report)          ~ bit 5 of byte 6\n    no external sensor detected                          ~ bit 4 of byte 6\n    low battery indication                                 ~ bit 3 of byte 6\n    count of sensors reporting (1 to 5)                     ~~~ bits 2,1,0 of byte 6\n    sensor code                                                 ~~~~ 1st nibble of byte 7\n    sensor reading (meaning varies, see above)                       ~~~~ ~~~~ ~~~~ 2nd nibble of byte 7 and byte 8\n    ---\n    --- repeat sensor code:reading as specified in count value above\n    ---\n    crc8 (poly 0x31 init 0x00) of bytes 5 thru (N-1)                                ~~~~ ~~~~ last byte\n\n## Developer's comments\n\nThe WS-1910TWC-IT does not have a rain gauge or wind direction vane.  The readings output here\nare inferred from the output data, and correlating it with other similar Lacrosse devices.\nThese readings have not been tested.\n\n*/\n\n#include \"decoder.h\"\n\n#define BIT(pos)               (1 << (pos))\n#define CHECK_BIT(y, pos)      ((0u == ((y) & (BIT(pos)))) ? 0u : 1u)\n#define SET_LSBITS(len)        (BIT(len) - 1)                       // the first len bits are '1' and the rest are '0'\n#define BF_PREP(y, start, len) (((y) & SET_LSBITS(len)) << (start)) // Prepare a bitmask\n#define BF_GET(y, start, len)  (((y) >> (start)) & SET_LSBITS(len))\n\n#define TX31U_MIN_LEN_BYTES    9  // assume at least one measurement\n#define TX31U_MAX_LEN_BYTES    20 // actually shouldn't be more than 18, but we'll be generous\n\nstatic int lacrosse_tx31u_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n\n    // There will only be one row\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // search for expected start sequence\n    uint8_t const start_match[] = {0xaa, 0xaa, 0x2d, 0xd4}; // preamble + sync word (32 bits)\n    unsigned int start_pos      = bitbuffer_search(bitbuffer, 0, 0, start_match, sizeof(start_match) * 8);\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n    uint8_t msg_bytes = (bitbuffer->bits_per_row[0] - start_pos) / 8;\n\n    if (msg_bytes < TX31U_MIN_LEN_BYTES) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bytes\", msg_bytes);\n        return DECODE_ABORT_LENGTH;\n    }\n    else if (msg_bytes > TX31U_MAX_LEN_BYTES) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bytes\", msg_bytes);\n        return DECODE_ABORT_LENGTH;\n    }\n    else {\n        decoder_logf(decoder, 2, __func__, \"packet length: %d\", msg_bytes);\n    }\n\n    decoder_log(decoder, 1, __func__, \"LaCrosse TX31U-IT detected\");\n\n    uint8_t msg[TX31U_MAX_LEN_BYTES];\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, msg_bytes * 8);\n\n    // int model = BF_GET(msg[4], 4, 4);\n    int sensor_id = (BF_GET(msg[4], 0, 4) << 2) | BF_GET(msg[5], 6, 2);\n    // int training = CHECK_BIT(msg[5], 5);\n    int no_ext_sensor = CHECK_BIT(msg[5], 4);\n    int battery_low   = CHECK_BIT(msg[5], 3);\n    int measurements  = BF_GET(msg[5], 0, 3);\n\n    // Check message integrity\n    int expected_bytes = 6 + measurements * 2 + 1;\n    if (msg_bytes >= expected_bytes) { // did we get shorted?\n        int r_crc = msg[expected_bytes - 1];\n        int c_crc = crc8(&msg[4], 2 + measurements * 2, 0x31, 0x00);\n        if (r_crc != c_crc) {\n            decoder_logf(decoder, 1, __func__, \"LaCrosse TX31U-IT bad CRC: calculated %02x, received %02x\", c_crc, r_crc);\n            return DECODE_FAIL_MIC;\n        }\n    }\n    else {\n        decoder_logf(decoder, 1, __func__, \"Packet truncated: received %d bytes, expected %d bytes\", msg_bytes, expected_bytes);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    /* clang-format off */\n    // what we know from the header\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"LaCrosse-TX31UIT\",\n            \"id\",               \"\",             DATA_INT,    sensor_id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            NULL);\n\n    // decode each measurement we get and append them.\n    enum sensor_type { TEMP=0, HUMIDITY, RAIN, WIND_AVG, WIND_MAX };\n    for (int m=0; m<measurements; ++m ) {\n        uint8_t type = BF_GET(msg[6+m*2], 4, 4 );\n        uint8_t nib1 = BF_GET(msg[6+m*2], 0, 4 );\n        uint8_t nib2 = BF_GET(msg[7+m*2], 4, 4 );\n        uint8_t nib3 = BF_GET(msg[7+m*2], 0, 4 );\n        switch (type) {\n            case TEMP: {\n                float temp_c = 10*nib1 + nib2 + 0.1f*nib3 - 40.0f; // BCD offset 40 deg C\n                data = data_dbl(data, \"temperature_C\",    \"Temperature\",  \"%.1f C\", temp_c);\n            } break;\n            case HUMIDITY: {\n                int humidity = 100*nib1 + 10*nib2 + nib3; // BCD %\n                data = data_int(data, \"humidity\",         \"Humidity\",     \"%u %%\",  humidity);\n            } break;\n            case RAIN: {\n                int raw_rain = (nib1<<8) + (nib2<<4) + nib3; // count of contact closures\n                if ( !no_ext_sensor && raw_rain > 0) { // most of these do not have rain gauges.  Suppress output if zero.\n                    data = data_int(data, \"rain\",         \"raw_rain\",     \"%03x\",   raw_rain);\n                }\n            } break;\n            case WIND_AVG: {\n                if ( !no_ext_sensor ) {\n                    float wind_dir = nib1 * 22.5f ; // compass direction in degrees\n                    float wind_avg = ((nib2<<4) + nib3) * 0.1f * 3.6f; // wind values are decimal m/sec, convert to km/h\n                    data = data_dbl(data, \"wind_dir_deg\",   \"Wind direction\",   \"%.1f\",       wind_dir);\n                    data = data_dbl(data, \"wind_avg_km_h\",  \"Wind speed\",       \"%.1f km/h\",  wind_avg);\n                }\n            } break;\n            case WIND_MAX: {\n                int wind_input_lost = CHECK_BIT(nib1, 0); // a sensor was attached, but now not detected\n                if ( !no_ext_sensor && !wind_input_lost ) {\n                    float wind_max = ((nib2<<4) + nib3) * 0.1f * 3.6f; // wind values are decimal m/sec, convert to km/h\n                    data = data_dbl(data, \"wind_max_km_h\",  \"Wind gust\",     \"%.1f km/h\",  wind_max);\n                }\n            } break;\n            default:\n                decoder_logf(decoder, 1, __func__, \"LaCrosse TX31U-IT unknown sensor type %d\", type);\n            break;\n        }\n    }\n\n    data = data_str(data, \"mic\",              \"Integrity\",  NULL,   \"CRC\");\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_km_h\",\n        \"wind_max_km_h\",\n        \"wind_dir_deg\",\n        \"mic\",\n        NULL,\n};\n\n// Receiver for the Lacrosse TX31U-IT\nr_device const lacrosse_tx31u = {\n        .name        = \"LaCrosse TX31U-IT, The Weather Channel WS-1910TWC-IT\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 116,\n        .long_width  = 116,\n        .reset_limit = 20000,\n        .decode_fn   = &lacrosse_tx31u_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_tx34.c",
    "content": "/** @file\n    LaCrosse TX34-IT rain gauge decoder.\n\n    Copyright (C) 2021 Reynald Poittevin <reynald@poittevin.name>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nLaCrosse TX34-IT rain gauge.\n\nCan be bought here: https://en.lacrossetechnology.fr/P-20-A1-WSTX34IT.html\n\nThis sensor sends a frame every 6.5 s.\n\nThe LaCrosse \"IT+\" family share some specifications:\n- Frequency: 868.3 MHz\n- Modulation: FSK/PCM\n- Bit duration: 58 µs\n- Frame size: 64 bits (including preamble)\n\nFrame format:\n\n-------------\n| 1010 1010 | preamble (some bits may be lost)\n-------------\n| 0010 1101 | 0x2dd4: sync word\n| 1101 0100 |\n-------------\n| MMMM DDDD | MMMM: sensor model (5 for rain gauge, 9 for thermo/hydro...)\n| DDNW 0000 | DDDDDD: device ID (0 to 63, random at startup)\n| GGGG GGGG | N: new battery (on for about 420 minutes after startup)\n| GGGG GGGG | W: weak battery (on when battery voltage < 2 volts)\n------------- GGGGGGGGGGGGGGGG: bucket tipping counter\n| CCCC CCCC | CCCCCCCC: CRC8 (poly 0x31 init 0x00) on previous 4 bytes\n-------------\n\nThis decoder decodes generic LaCrosse IT+ frames and filters TX34 ones.\nCould be merged with existing TX29 decoder... or not.\n*/\n\n#include \"decoder.h\"\n\n#define LACROSSE_TX34_ITMODEL 5\n#define LACROSSE_TX34_PAYLOAD_BITS 40\n#define LACROSSE_TX34_RAIN_FACTOR 0.222f\n\nstatic int lacrosse_tx34_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // 20 bits preamble (shifted left): 1010b 0x2DD4\n    uint8_t const preamble[] = {0xa2, 0xdd, 0x40};\n\n    // process all rows\n    int events = 0;\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n\n        // search for preamble\n        unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, 20) + 20;\n        if (start_pos + LACROSSE_TX34_PAYLOAD_BITS > bitbuffer->bits_per_row[row])\n            continue; // preamble not found\n        decoder_log(decoder, 2, __func__, \"LaCrosse IT frame detected\");\n        // get payload\n        uint8_t b[5];\n        bitbuffer_extract_bytes(bitbuffer, row, start_pos, b, LACROSSE_TX34_PAYLOAD_BITS);\n        // verify CRC\n        int r_crc = b[4];\n        int c_crc = crc8(b, 4, 0x31, 0x00);\n        if (r_crc != c_crc) {\n            // bad CRC: reject IT frame\n            decoder_logf(decoder, 1, __func__, \"LaCrosse IT frame bad CRC: calculated %02x, received %02x\", c_crc, r_crc);\n            continue;\n        }\n\n        // check model\n        if (((b[0] & 0xF0) >> 4) != LACROSSE_TX34_ITMODEL)\n            continue; // not a rain gauge...\n\n        // decode payload\n        int sensor_id = ((b[0] & 0x0F) << 2) | (b[1] >> 6);\n        int new_batt  = (b[1] & 0x20) >> 5;\n        int low_batt  = (b[1] & 0x10) >> 4;\n        int rain_tick = (b[2] << 8) | b[3];\n        float rain_mm = rain_tick * LACROSSE_TX34_RAIN_FACTOR;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"LaCrosse-TX34IT\",\n                \"id\",           \"\",             DATA_INT,    sensor_id,\n                \"battery_ok\",   \"Battery\",      DATA_INT,    !low_batt,\n                \"newbattery\",   \"New battery\",  DATA_INT,    new_batt,\n                \"rain_mm\",      \"Total rain\",   DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n                \"rain_raw\",     \"Raw rain\",     DATA_INT,    rain_tick,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        events++;\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"newbattery\",\n        \"rain_mm\",\n        \"rain_raw\",\n        \"mic\",\n        NULL,\n};\n\nr_device const lacrosse_tx34 = {\n        .name        = \"LaCrosse TX34-IT rain gauge\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 58,\n        .long_width  = 58,\n        .reset_limit = 4000,\n        .decode_fn   = &lacrosse_tx34_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_tx35.c",
    "content": "/** @file\n    LaCrosse/StarMeteo/Conrad TX35 protocol.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nGeneric decoder for LaCrosse \"IT+\" (instant transmission) protocol.\nParam device29or35 must be \"29\" or \"35\" depending of the device.\n\nLaCrosse/StarMeteo/Conrad TX35DTH-IT, TFA Dostmann 30.3155     Temperature/Humidity Sensors.\nLaCrosse/StarMeteo/Conrad TX29-IT, TFA Dostmann 30.3159.IT     Temperature Sensors.\nFound at 868240000Hz.\n\nLaCrosse TX25U Temperature/Temperature Probe at 915 MHz\n\n## Protocol\n\nExample data : https://github.com/merbanan/rtl_433_tests/tree/master/tests/lacrosse/06/gfile-tx29.cu8\n\n       a    a    2    d    d    4    9    2    8    4    4    8    6    a    e    c\n    Bits :\n    1010 1010 0010 1101 1101 0100 1001 0010 1000 0100 0100 1000 0110 1010 1110 1100\n    Bytes num :\n    ----1---- ----2---- ----3---- ----4---- ----5---- ----6---- ----7---- ----8----\n    ~~~~~~~~~ 1st byte\n    preamble, sequence 10B repeated 4 times (see below)\n              ~~~~~~~~~~~~~~~~~~~ bytes 2 and 3\n    sync word of 0x2dd4\n                                  ~~~~ 1st nibble of bytes 4\n    sensor model (always 9)\n                                       ~~~~ ~~ 2nd nibble of bytes 4 and 1st and 2nd bits of byte 5\n    Random device id (6 bits)\n                                              ~ 3rd bits of byte 5\n    new battery indicator\n                                               ~ 4th bits of byte 5\n    unknown, unused\n                                                 ~~~~ ~~~~ ~~~~ 2nd nibble of byte 5 and byte 6\n    temperature, in bcd *10 +40\n                                                                ~ 1st bit of byte 7\n    weak battery\n                                                                 ~~~ ~~~~ 2-8 bits of byte 7\n    humidity, in%. If == 0x6a : no humidity sensor\n                   If == 0x7d : temperature is actually second probe temperature channel\n                                                                          ~~~~ ~~~~ byte 8\n    crc8 (poly 0x31 init 0x00) of bytes\n\n## Developer's comments\n\nI have noticed that depending of the device, the message received has different length.\nIt seems some sensor send a long preamble (33 bits, 0 / 1 alternated), and some send only\nsix bits as the preamble. I own 3 sensors TX29, and two of them send a long preamble.\nSo this decoder synchronize on the following sequence:\n\n    1010 1000 1011 0111 0101 0010 01--\n       A    8    B    7    5    2    4\n\n-  0 -  5 : short preamble [101010B]\n-  6 - 14 : sync word [2DD4h]\n- 15 - 19 : sensor model [9]\n\nShort preamble example (sampling rate - 1Mhz):\nhttps://github.com/merbanan/rtl_433_tests/tree/master/tests/lacrosse/06/gfile-tx29-short-preamble.cu8.\n\nTX29 and TX35 share the same protocol, but pulse are different length, thus this decoder\nhandle the two signal and we use two r_device struct (only differing by the pulse width).\n\nTX25U alternates between a temperature only packet and a packet with temperature and humidity\nwhere a special humidity flag value of 125 indicates the second channel instead of humidity.\n0x40 is added to the id to distinguish between channels.\n\nThere's no way to distinguish between the TX35 and TX25U models\n*/\n\n#include \"decoder.h\"\n\n#define LACROSSE_TX29_NOHUMIDSENSOR  0x6a // Sensor do not support humidity\n#define LACROSSE_TX25_PROBE_FLAG     0x7d // Humidity flag to indicate probe temperature channel\n#define LACROSSE_TX29_MODEL          29 // Model number\n#define LACROSSE_TX35_MODEL          35\n\nstatic int lacrosse_it(r_device *decoder, bitbuffer_t *bitbuffer, int device29or35)\n{\n    // 4 bits of preamble, sync word 2dd4, sensor model 9: 24 bit\n    uint8_t const preamble[] = {0xa2, 0xdd, 0x49};\n\n    int events = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        // Validate message and reject it as fast as possible : check for preamble\n        unsigned int start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, 24);\n        // no preamble detected, move to the next row\n        if (start_pos >= bitbuffer->bits_per_row[row])\n            continue; // DECODE_ABORT_EARLY\n        decoder_logf(decoder, 1, __func__, \"LaCrosse TX29/35 detected, buffer is %d bits length, device is TX%d\", bitbuffer->bits_per_row[row], device29or35);\n        // remove preamble and keep only five octets\n        uint8_t b[5];\n        bitbuffer_extract_bytes(bitbuffer, row, start_pos + 20, b, 40);\n\n        // Check message integrity\n        int r_crc = b[4];\n        int c_crc = crc8(b, 4, 0x31, 0x00);\n        if (r_crc != c_crc) {\n            decoder_logf(decoder, 1, __func__, \"LaCrosse TX29/35 bad CRC: calculated %02x, received %02x\", c_crc, r_crc);\n            // reject row\n            continue; // DECODE_FAIL_MIC\n        }\n\n        // message \"envelope\" has been validated, start parsing data\n        int sensor_id   = ((b[0] & 0x0f) << 2) | (b[1] >> 6);\n        float temp_c    = 10 * (b[1] & 0x0f) + 1 * ((b[2] >> 4) & 0x0f) + 0.1f * (b[2] & 0x0f) - 40.0f;\n        int new_batt    = (b[1] >> 5) & 1;\n        int battery_low = b[3] >> 7;\n        int humidity    = b[3] & 0x7f;\n\n        data_t *data;\n        if ((humidity == LACROSSE_TX29_NOHUMIDSENSOR) || (humidity == LACROSSE_TX25_PROBE_FLAG)) {\n            if (humidity == LACROSSE_TX25_PROBE_FLAG)\n                sensor_id += 0x40;      // Change ID to distinguish between the main and probe channels\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_COND,   device29or35 == 29, DATA_STRING, \"LaCrosse-TX29IT\",\n                    \"model\",            \"\",             DATA_COND,   device29or35 != 29, DATA_STRING, \"LaCrosse-TX35DTHIT\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                    \"newbattery\",       \"NewBattery\",   DATA_INT,    new_batt,\n                    \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n        }\n        else {\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_COND,   device29or35 == 29, DATA_STRING, \"LaCrosse-TX29IT\",\n                    \"model\",            \"\",             DATA_COND,   device29or35 != 29, DATA_STRING, \"LaCrosse-TX35DTHIT\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                    \"newbattery\",       \"NewBattery\",   DATA_INT,    new_batt,\n                    \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                    \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n                    NULL);\n            /* clang-format on */\n        }\n\n        decoder_output_data(decoder, data);\n        events++;\n    }\n    return events;\n}\n\n/**\nWrapper for the TX29 and TX25U device.\n@sa lacrosse_it()\n*/\nstatic int lacrossetx29_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    return lacrosse_it(decoder, bitbuffer, LACROSSE_TX29_MODEL);\n}\n\n/**\nWrapper for the TX35 device.\n@sa lacrosse_it()\n*/\nstatic int lacrossetx35_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    return lacrosse_it(decoder, bitbuffer, LACROSSE_TX35_MODEL);\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"newbattery\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\n// Receiver for the TX29 and TX25U device\nr_device const lacrosse_tx29 = {\n        .name        = \"LaCrosse TX29IT, TFA Dostmann 30.3159.IT Temperature sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 55, // 58 us for TX34-IT\n        .long_width  = 55, // 58 us for TX34-IT\n        .reset_limit = 4000,\n        .decode_fn   = &lacrossetx29_callback,\n        .fields      = output_fields,\n};\n\n// Receiver for the TX35 device\nr_device const lacrosse_tx35 = {\n        .name        = \"LaCrosse TX35DTH-IT, TFA Dostmann 30.3155 Temperature/Humidity sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 105,\n        .long_width  = 105,\n        .reset_limit = 4000,\n        .decode_fn   = &lacrossetx35_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_wr1.c",
    "content": "/** @file\n    LaCrosse Technology View LTV-WR1 Multi Sensor.\n\n    Copyright (C) 2020 Mike Bruski (AJ9X) <michael.bruski@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLaCrosse Technology View LTV-WR1 Multi Sensor.\n\nLaCrosse Color Forecast Station (model S84060?) utilizes the remote\nThermo/Hygro LTV-TH3 and LTV-WR1 multi sensor (wind spd/dir and rain).\n\nProduct pages:\nhttps://www.lacrossetechnology.com/products/S84060\nhttps://www.lacrossetechnology.com/products/ltv-wr1\n\nSpecifications:\n- Wind Speed Range: 0 to 188 kmh\n- Degrees of Direction: 0 to 359 degrees\n- Rainfall 0 to 9999.9 mm\n- Update Interval: Every 30 Seconds\n\nNo internal inspection of the sensors was performed so can only\nspeculate that the remote sensors utilize a HopeRF CMT2119A ISM\ntransmitter chip which is tuned to 915Mhz.\n\nAgain, no inspection of the S84060 console was performed but it\nprobably employs a HopeRF CMT2219A ISM receiver chip.  An\napplication note is available that provides further info into the\ncapabilities of the CMT2119A and CMT2219A.\n\n(http://www.cmostek.com/download/CMT2119A_v0.95.pdf)\n(http://www.cmostek.com/download/CMT2219A.pdf)\n(http://www.cmostek.com/download/AN138%20CMT2219A%20Configuration%20Guideline.pdf)\n\nProtocol Specification:\n\nData bits are NRZ encoded with logical 1 and 0 bits 104us in length.\n\nLTV-WR1\n    SYN:32h ID:24h ?:4b SEQ:3d ?:1b WSPD:12d WDIR:12d RAIN1:12d RAIN2:12d CHK:8h\n\n    CHK is CRC-8 poly 0x31 init 0x00 over 10 bytes following SYN\n\n*/\n\n#include \"decoder.h\"\n\nstatic int lacrosse_wr1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xd2, 0xaa, 0x2d, 0xd4};\n\n    data_t *data;\n    uint8_t b[11];\n    uint32_t id;\n    int flags, seq, offset, chk;\n    int raw_wind, direction, raw_rain1, raw_rain2;\n    float speed_kmh;\n    // float rain_mm;\n\n    if (bitbuffer->bits_per_row[0] < 120) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    } else if (bitbuffer->bits_per_row[0] > 156) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    } else {\n        decoder_logf(decoder, 1, __func__, \"packet length: %d\", bitbuffer->bits_per_row[0]);\n    }\n\n    offset = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 11 * 8);\n\n    chk = crc8(b, 11, 0x31, 0x00);\n    if (chk) {\n        decoder_log(decoder, 1, __func__, \"CRC failed!\");\n        return DECODE_FAIL_MIC;\n    }\n\n    id        = (b[0] << 16) | (b[1] << 8) | b[2];\n    flags     = (b[3] & 0xf1); // masks off seq bits\n    seq       = (b[3] & 0x0e) >> 1;\n    raw_wind  = b[4] << 4 | ((b[5] & 0xf0) >> 4);\n    direction = ((b[5] & 0x0f) << 8) | b[6];\n    raw_rain1 = b[7] << 4 | ((b[8] & 0xf0) >> 4);\n    raw_rain2 = ((b[8] & 0x0f) << 8) | b[9];\n\n    // base and/or scale adjustments\n    speed_kmh = raw_wind * 0.1f;\n    if (speed_kmh < 0 || speed_kmh > 200 || direction < 0 || direction > 360)\n        return DECODE_FAIL_SANITY;\n\n    //rain_mm   = 0.0;  // dummy until we know what raw_rain1 and raw_rain2 mean\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WR1\",\n            \"id\",               \"Sensor ID\",        DATA_FORMAT, \"%06x\", DATA_INT, id,\n            \"seq\",              \"Sequence\",         DATA_INT,     seq,\n            \"flags\",            \"unknown\",          DATA_INT,     flags,\n            \"wind_avg_km_h\",        \"Wind speed\",       DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_INT,    direction,\n            \"rain1\",            \"raw_rain1\",        DATA_FORMAT, \"%03x\", DATA_INT, raw_rain1,\n            \"rain2\",            \"raw_rain2\",        DATA_FORMAT, \"%03x\", DATA_INT, raw_rain2,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"seq\",\n        \"flags\",\n        \"wind_avg_km_h\",\n        \"wind_dir_deg\",\n        \"rain1\",\n        \"rain2\",\n        \"mic\",\n        NULL,\n};\n\n// flex decoder m=FSK_PCM, s=104, l=104, r=9600\nr_device const lacrosse_wr1 = {\n        .name        = \"LaCrosse Technology View LTV-WR1 Multi Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 104,\n        .long_width  = 104,\n        .reset_limit = 9600,\n        .decode_fn   = &lacrosse_wr1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrosse_ws7000.c",
    "content": "/** @file\n    LaCrosse WS7000/WS2500 weather sensors.\n\n    Copyright (C) 2019 ReMiOS and Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLaCrosse WS7000/WS2500 weather sensors.\nAlso sold by ELV and Conrad. Related to ELV WS 2000.\n\n- WS2500-19 brightness sensor\n- WS7000-20 meteo sensor (temperature/humidity/pressure)\n- WS7000-16 Rain Sensor\n- WS7000-15 wind sensor\n\nPWM 400 us / 800 us with fixed bit width of 1200 us.\nMessages are sent as nibbles (4 bits) with LSB sent first.\nA frame is composed of a preamble followed by nibbles (4 bits) separated by a 1-bit.\n\nMessage Layout:\n\n    P P S A D..D X C\n\n- Preamble: 10x bit \"0\", bit \"1\"\n- Sensor Type:  Value 0..9 determining the sensor type\n  - 0 = WS7000-27/28 Thermo sensor (interval 177s - Addr * 0.5s)\n  - 1 = WS7000-22/25 Thermo/Humidity sensor (interval 177s - Addr * 0.5s)\n  - 2 = WS7000-16 Rain sensor (interval 173s - Addr * 0.5s)\n  - 3 = WS7000-15 Wind sensor (interval 169s - Addr * 0.5s)\n  - 4 = WS7000-20 Thermo/Humidity/Barometer sensor (interval 165s - Addr * 0.5s)\n  - 5 = WS2500-19 Brightness sensor (interval 161s - Addr * 0.5s)\n- Address:  Value 0..7 for the sensor address\n  - In case of a negative temperature the MSB of the Address becomes \"1\"\n- Data:     3-10 nibbles with BCD encoded sensor data values.\n- XOR:      Nibble holding XOR of the S ^ A ^ Data nibbles\n- Checksum: Sum of all nibbles + 5 (i.e. S + A + nibble(0) + .. + nibble(n) + XOR + 5) & 0xF\n\n*/\n\n#include \"decoder.h\"\n\nstatic int lacrosse_ws7000_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x01}; // 8 bits\n    uint8_t const data_size[] = {3, 6, 3, 6, 10, 7}; // data nibbles by sensor type\n\n    data_t *data;\n    uint8_t b[14] = {0}; // LaCrosse WS7000-20 meteo sensor: 14 nibbles\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 8) + 8;\n    if (start_pos >= bitbuffer->bits_per_row[0])\n        return DECODE_ABORT_EARLY;\n\n    unsigned max_bits = MIN(14 * 5, bitbuffer->bits_per_row[0] - start_pos);\n    unsigned len      = extract_nibbles_4b1s(bitbuffer->bb[0], start_pos, max_bits, b);\n    if (len < 7) // at least type, addr, 3 data, xor, add nibbles needed\n        return DECODE_ABORT_LENGTH;\n\n    reflect_nibbles(b, len);\n\n    int type = b[0];\n    int addr = b[1] & 0x7;\n    int id   = (type << 4) | addr;\n\n    if (type > 5) {\n        decoder_logf(decoder, 2, __func__, \"LaCrosse-WS7000: unhandled sensor type (%d)\", type);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned data_len = data_size[type];\n    if (len < data_len) {\n        decoder_logf(decoder, 2, __func__, \"LaCrosse-WS7000: short data (%u of %u)\", len, data_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // check xor sum\n    if (xor_bytes(b, len - 1)) {\n        decoder_log(decoder, 2, __func__, \"LaCrosse-WS7000: checksum error (xor)\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // check add sum (all nibbles + 5)\n    if (((add_bytes(b, len - 1) + 5) & 0xf) != b[len - 1]) {\n        decoder_log(decoder, 2, __func__, \"LaCrosse-WS7000: checksum error (add)\");\n        return DECODE_FAIL_MIC;\n    }\n\n    if (type == 0) {\n        // 0 = WS7000-27/28 Thermo sensor\n        int sign          = (b[1] & 0x8) ? -1 : 1;\n        float temperature = ((b[4] * 10) + (b[3] * 1) + (b[2] * 0.1f)) * sign;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WS700027\",\n                \"id\",               \"\",                 DATA_INT,    id,\n                \"channel\",          \"\",                 DATA_INT,    addr,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 1) {\n        // 1 = WS7000-22/25 Thermo/Humidity sensor\n        int sign          = (b[1] & 0x8) ? -1 : 1;\n        float temperature = ((b[4] * 10) + (b[3] * 1) + (b[2] * 0.1f)) * sign;\n        int humidity      = (b[7] * 10) + (b[6] * 1) + (b[5] * 0.1f);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WS700022\",\n                \"id\",               \"\",                 DATA_INT,    id,\n                \"channel\",          \"\",                 DATA_INT,    addr,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"humidity\",         \"Humidity\",         DATA_INT,    humidity,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 2) {\n        // 2 = WS7000-16 Rain sensor\n        int rain = (b[4] << 8) | (b[3] << 4) | (b[2]);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WS700016\",\n                \"id\",               \"\",                 DATA_INT,    id,\n                \"channel\",          \"\",                 DATA_INT,    addr,\n                \"rain_mm\",          \"Rain counter\",     DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain * 0.3,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 3) {\n        // 3 = WS7000-15 Wind sensor\n        float speed     = (b[4] * 10) + (b[3] * 1) + (b[2] * 0.1f);\n        float direction = ((b[7] >> 2) * 100) + (b[6] * 10) + (b[5] * 1);\n        float deviation = (b[7] & 0x3) * 22.5f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WS700015\",\n                \"id\",               \"\",                 DATA_INT,    id,\n                \"channel\",          \"\",                 DATA_INT,    addr,\n                \"wind_avg_km_h\",    \"Wind speed\",       DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed,\n                \"wind_dir_deg\",     \"Wind direction\",   DATA_DOUBLE, direction,\n                \"wind_dev_deg\",     \"Wind deviation\",   DATA_DOUBLE, deviation,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 4) {\n        // 4 = WS7000-20 Thermo/Humidity/Barometer sensor\n        int sign          = (b[1] & 0x8) ? -1 : 1;\n        float temperature = ((b[4] * 10) + (b[3] * 1) + (b[2] * 0.1f)) * sign;\n        int humidity      = (b[7] * 10) + (b[6] * 1) + (b[5] * 0.1f);\n        int pressure      = (b[10] * 100) + (b[9] * 10) + (b[8] * 1) + 200;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WS700020\",\n                \"id\",               \"\",                 DATA_INT,    id,\n                \"channel\",          \"\",                 DATA_INT,    addr,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"humidity\",         \"Humidity\",         DATA_INT,    humidity,\n                \"pressure_hPa\",     \"Pressure\",         DATA_INT,    pressure,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (type == 5) {\n        // 5 = WS2500-19 Brightness sensor\n        unsigned brightness = (b[4] * 100) + (b[3] * 10) + (b[2] * 1);\n        int b_exponent = b[5]; // 10^exp\n        int exposition = (b[8] * 100) + (b[7] * 10) + (b[6] * 1);\n        for (int i = b_exponent; i > 0; --i)\n            brightness *= 10;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"LaCrosse-WS250019\",\n                \"id\",               \"\",                 DATA_INT,    id,\n                \"channel\",          \"\",                 DATA_INT,    addr,\n                \"light_lux\",        \"Brightness\",       DATA_INT,    brightness,\n                \"exposure_mins\",    \"Exposition\",       DATA_INT,    exposition,\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return DECODE_FAIL_SANITY; // should not be reached\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"rain_mm\",\n        \"wind_avg_km_h\",\n        \"wind_dir_deg\",\n        \"wind_dev_deg\",\n        \"temperature_C\",\n        \"humidity\",\n        \"pressure_hPa\",\n        \"light_lux\",\n        \"exposure_mins\",\n        \"mic\",\n        NULL,\n};\n\nr_device const lacrosse_ws7000 = {\n        .name        = \"LaCrosse/ELV/Conrad WS7000/WS2500 weather sensors\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 400,\n        .long_width  = 800,\n        .reset_limit = 1100,\n        .decode_fn   = &lacrosse_ws7000_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lacrossews.c",
    "content": "/** @file\n    LaCrosse WS-2310 / WS-3600 433 Mhz Weather Station.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/** @fn int lacrossews_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nLaCrosse WS-2310 / WS-3600 433 Mhz Weather Station.\n\n- long pulse 1464 us\n- short pulse 368 us\n- fixed gap 1336 us\n\nPacket Format is 53 bits/ 13 nibbles:\n\n|  bits | nibble\n| ----- | ------\n|  0- 3 | 0 - 0000\n|  4- 7 | 1 - 1001 for WS-2310, 0110 for WS-3600\n|  8-11 | 2 - Type  GPTT  G=0, P=Parity, Gust=Gust, TT=Type  GTT 000=Temp, 001=Humidity, 010=Rain, 011=Wind, 111-Gust\n| 12-15 | 3 - ID High\n| 16-19 | 4 - ID Low\n| 20-23 | 5 - Data Types  GWRH  G=Gust Sent, W=Wind Sent, R=Rain Sent, H=Humidity Sent\n| 24-27 | 6 - Parity TUU? T=Temp Sent, UU=Next Update, 00=8 seconds, 01=32 seconds, 10=?, 11=128 seconds, ?=?\n| 28-31 | 7 - Value1\n| 32-35 | 8 - Value2\n| 36-39 | 9 - Value3\n| 40-43 | 10 - ~Value1\n| 44-47 | 11 - ~Value2\n| 48-51 | 12 - Check Sum = Nibble sum of nibbles 0-11\n*/\n\n#include \"decoder.h\"\n\n#define LACROSSE_WS_BITLEN 52\n\nstatic int lacrossews_detect(r_device *decoder, uint8_t *pRow, uint8_t *msg_nybbles, int16_t rowlen)\n{\n    int i;\n    uint8_t rbyte_no, rbit_no, mnybble_no, mbit_no;\n    uint8_t bit, checksum = 0, parity = 0;\n\n    // Weather Station 2310 Packets\n    if (rowlen != LACROSSE_WS_BITLEN)\n        return DECODE_ABORT_LENGTH;\n    if (pRow[0] != 0x09 && pRow[0] != 0x06)\n        return DECODE_ABORT_EARLY;\n\n    for (i = 0; i < (LACROSSE_WS_BITLEN / 4); i++) {\n        msg_nybbles[i] = 0;\n    }\n\n    // Move nybbles into a byte array\n    // Compute parity and checksum at the same time.\n    for (i = 0; i < LACROSSE_WS_BITLEN; i++) {\n        rbyte_no = i / 8;\n        rbit_no = 7 - (i % 8);\n        mnybble_no = i / 4;\n        mbit_no = 3 - (i % 4);\n        bit = (pRow[rbyte_no] & (1 << rbit_no)) ? 1 : 0;\n        msg_nybbles[mnybble_no] |= (bit << mbit_no);\n        if (i == 9 || (i >= 27 && i <= 39))\n            parity += bit;\n    }\n\n    for (i = 0; i < 12; i++) {\n        checksum += msg_nybbles[i];\n    }\n    checksum = checksum & 0x0F;\n\n    int checksum_ok = msg_nybbles[7] == (msg_nybbles[10] ^ 0xF)\n            && msg_nybbles[8] == (msg_nybbles[11] ^ 0xF)\n            && (parity & 0x1) == 0x1\n            && checksum == msg_nybbles[12];\n\n    if (!checksum_ok) {\n        decoder_logf_bitrow(decoder, 2, __func__, msg_nybbles, LACROSSE_WS_BITLEN,\n                \"LaCrosse Packet Validation Failed error: Checksum Comp. %d != Recv. %d, Parity %d\",\n                checksum, msg_nybbles[12], parity);\n        return DECODE_FAIL_MIC;\n    }\n\n    return 1;\n}\n\nstatic int lacrossews_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row;\n    int events = 0;\n    uint8_t msg_nybbles[(LACROSSE_WS_BITLEN / 4)];\n    uint8_t ws_id, msg_type, sensor_id;\n    // uint8_t msg_data, msg_unknown, msg_checksum;\n    int msg_value_bcd, msg_value_bcd2, msg_value_bin;\n    float temp_c, wind_dir, wind_spd, rain_mm;\n    data_t *data;\n\n    for (row = 0; row < bitbuffer->num_rows; row++) {\n        // break out the message nybbles into separate bytes\n        if (lacrossews_detect(decoder, bitbuffer->bb[row], msg_nybbles, bitbuffer->bits_per_row[row]) <= 0)\n            continue; // DECODE_ABORT_EARLY\n\n        ws_id          = (msg_nybbles[0] << 4) + msg_nybbles[1];\n        msg_type       = ((msg_nybbles[2] >> 1) & 0x4) + (msg_nybbles[2] & 0x3);\n        sensor_id      = (msg_nybbles[3] << 4) + msg_nybbles[4];\n        //msg_data       = (msg_nybbles[5] << 1) + (msg_nybbles[6] >> 3);\n        //msg_unknown    = msg_nybbles[6] & 0x01;\n        msg_value_bcd  = msg_nybbles[7] * 100 + msg_nybbles[8] * 10 + msg_nybbles[9];\n        msg_value_bcd2 = msg_nybbles[7] * 10 + msg_nybbles[8];\n        msg_value_bin  = (msg_nybbles[7] * 256 + msg_nybbles[8] * 16 + msg_nybbles[9]);\n        //msg_checksum   = msg_nybbles[12];\n\n        switch (msg_type) {\n\n        case 0: // Temperature\n            if (ws_id == 0x6)\n                temp_c = (msg_value_bcd - 400) * 0.1f;\n            else\n                temp_c = (msg_value_bcd - 300) * 0.1f;\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_COND, ws_id == 0x6, DATA_STRING, \"LaCrosse-WS3600\",\n                    \"model\",            \"\",             DATA_COND, ws_id != 0x6, DATA_STRING, \"LaCrosse-WS2310\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            events++;\n            break;\n\n        case 1: // Humidity\n            if (msg_nybbles[7] == 0xA && msg_nybbles[8] == 0xA) {\n                decoder_logf(decoder, 1, __func__, \"LaCrosse WS %02X-%02X: Humidity Error\",\n                            ws_id, sensor_id);\n                break;\n            }\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_COND, ws_id == 0x6, DATA_STRING, \"LaCrosse-WS3600\",\n                    \"model\",            \"\",             DATA_COND, ws_id != 0x6, DATA_STRING, \"LaCrosse-WS2310\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"humidity\",         \"Humidity\",     DATA_INT,    msg_value_bcd2,\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            events++;\n            break;\n\n        case 2: // Rain\n            rain_mm = 0.5180f * msg_value_bin;\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_COND, ws_id == 0x6, DATA_STRING, \"LaCrosse-WS3600\",\n                    \"model\",            \"\",             DATA_COND, ws_id != 0x6, DATA_STRING, \"LaCrosse-WS2310\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"rain_mm\",          \"Rainfall\",     DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, rain_mm,\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            events++;\n            break;\n\n        case 3: // Wind\n\n        case 7: // Gust\n            wind_dir = msg_nybbles[9] * 22.5f;\n            wind_spd = (msg_nybbles[7] * 16 + msg_nybbles[8]) * 0.1f;\n            if (msg_nybbles[7] == 0xF && msg_nybbles[8] == 0xE) {\n                decoder_logf(decoder, 1, __func__, \"WS %02X-%02X: %s Not Connected\",\n                        ws_id, sensor_id, msg_type == 3 ? \"Wind\" : \"Gust\");\n                break;\n            }\n\n            /* clang-format off */\n            data = data_make(\n                    \"model\",            \"\",             DATA_COND, ws_id == 0x6, DATA_STRING, \"LaCrosse-WS3600\",\n                    \"model\",            \"\",             DATA_COND, ws_id != 0x6, DATA_STRING, \"LaCrosse-WS2310\",\n                    \"id\",               \"\",             DATA_INT,    sensor_id,\n                    \"wind_avg_m_s\",     \"Wind speed\",   DATA_COND,   msg_type == 3, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_spd,\n                    \"wind_max_m_s\",     \"Gust speed\",   DATA_COND,   msg_type != 3, DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind_spd,\n                    \"wind_dir_deg\",     \"Direction\",    DATA_DOUBLE, wind_dir,\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            events++;\n            break;\n\n        default:\n            decoder_logf(decoder, 1, __func__,\n                    \"WS %02X-%02X: Unknown data type %d, bcd %d bin %d\\n\",\n                    ws_id, sensor_id, msg_type, msg_value_bcd, msg_value_bin);\n            events++;\n        }\n    }\n\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"humidity\",\n        \"rain_mm\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"wind_dir_deg\",\n        NULL,\n};\n\nr_device const lacrossews = {\n        .name        = \"LaCrosse WS-2310 / WS-3600 Weather Station\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 368,\n        .long_width  = 1464,\n        .reset_limit = 8000,\n        .decode_fn   = &lacrossews_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/lightwave_rf.c",
    "content": "/** @file\n    LightwaveRF protocol.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLightwaveRF protocol.\n\nStub for decoding test data only\n\nReference: https://wiki.somakeit.org.uk/wiki/LightwaveRF_RF_Protocol\n*/\n\n#include \"decoder.h\"\n\n/// Decode a nibble from byte value\n/// Will return -1 if invalid byte is input\nstatic int lightwave_rf_nibble_from_byte(uint8_t in)\n{\n    int nibble = -1; // Default error\n    switch (in) {\n    case 0xF6: nibble = 0x0; break;\n    case 0xEE: nibble = 0x1; break;\n    case 0xED: nibble = 0x2; break;\n    case 0xEB: nibble = 0x3; break;\n    case 0xDE: nibble = 0x4; break;\n    case 0xDD: nibble = 0x5; break;\n    case 0xDB: nibble = 0x6; break;\n    case 0xBE: nibble = 0x7; break;\n    case 0xBD: nibble = 0x8; break;\n    case 0xBB: nibble = 0x9; break;\n    case 0xB7: nibble = 0xA; break;\n    case 0x7E: nibble = 0xB; break;\n    case 0x7D: nibble = 0xC; break;\n    case 0x7B: nibble = 0xD; break;\n    case 0x77: nibble = 0xE; break;\n    case 0x6F:\n        nibble = 0xF;\n        break;\n        // default: // Just return error\n    }\n    return nibble;\n}\n\nstatic int lightwave_rf_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitrow_t *bb = bitbuffer->bb;\n\n    // Validate package\n    // Transmitted pulses are always 72\n    // Pulse 72 (delimiting \"1\" is not demodulated, as gap becomes End-Of-Message - thus expected length is 71\n    if (bitbuffer->bits_per_row[0] != 71\n            || bitbuffer->num_rows != 1) // There should be only one message (and we use the rest...)\n        return DECODE_ABORT_LENGTH;\n\n    // Polarity is inverted\n    bitbuffer_invert(bitbuffer);\n\n    // Expand all \"0\" to \"10\" (bit stuffing)\n    // row_in = 0, row_out = 1\n    bitbuffer_add_row(bitbuffer);\n    for (unsigned n = 0; n < bitbuffer->bits_per_row[0]; ++n) {\n        if (bitrow_get_bit(bb[0], n)) {\n            bitbuffer_add_bit(bitbuffer, 1);\n        } else {\n            bitbuffer_add_bit(bitbuffer, 1);\n            bitbuffer_add_bit(bitbuffer, 0);\n        }\n    }\n\n    // Check length is correct\n    // Due to encoding there will be two \"0\"s per byte, thus message grows to 91 bits\n    if (bitbuffer->bits_per_row[1] != 91)\n        return DECODE_ABORT_LENGTH;\n\n    // Check initial delimiter bit is \"1\"\n    unsigned bit_idx = 0;\n    uint8_t delimiter_bit = bitrow_get_bit(bb[1], bit_idx++);\n    if (delimiter_bit == 0)\n        return DECODE_ABORT_EARLY; // Decode error\n\n    // Strip delimiter bits\n    // row_in = 1, row_out = 2\n    bitbuffer_add_row(bitbuffer);\n    for (unsigned n = 0; n < 10; ++n) { // We have 10 bytes\n        delimiter_bit = bitrow_get_bit(bb[1], bit_idx++);\n        if (delimiter_bit == 0)\n            return DECODE_ABORT_EARLY; // Decode error\n\n        for (unsigned m = 0; m < 8; ++m) {\n            bitbuffer_add_bit(bitbuffer, bitrow_get_bit(bb[1], bit_idx++));\n        }\n    }\n    // Final delimiter bit will be missing - so do not check...\n\n    // Decode bytes to nibbles\n    // row_in = 2, row_out = 3\n    bitbuffer_add_row(bitbuffer);\n    for (unsigned n = 0; n < 10; ++n) { // We have 10 bytes/nibbles\n        int nibble = lightwave_rf_nibble_from_byte(bb[2][n]);\n        if (nibble < 0) {\n            decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"Nibble decode error %X, idx: %u\", bb[2][n], n);\n            return DECODE_FAIL_SANITY; // Decode error\n        }\n        for (unsigned m = 0; m < 4; ++m) { // Add nibble one bit at a time...\n            bitbuffer_add_bit(bitbuffer, (nibble & (8 >> m)) >> (3 - m));\n        }\n    }\n\n    // Decoded nibbles are in row 3\n    int id = bb[3][2] << 16 | bb[3][3] << 8 | bb[3][4];\n    int subunit = (bb[3][1] & 0xF0) >> 4;\n    int command = bb[3][1] & 0x0F;\n    int parameter = bb[3][0];\n\n    decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"Row 0 = Input, Row 1 = Zero bit stuffing, Row 2 = Stripped delimiters, Row 3 = Decoded nibbles\");\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\", DATA_STRING, \"Lightwave-RF\",\n            \"id\",           \"\", DATA_FORMAT, \"%06x\", DATA_INT, id,\n            \"subunit\",      \"\", DATA_INT,    subunit,\n            \"command\",      \"\", DATA_INT,    command,\n            \"parameter\",    \"\", DATA_INT,    parameter,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"subunit\",\n        \"command\",\n        \"parameter\",\n        NULL,\n};\n\nr_device const lightwave_rf = {\n        .name        = \"LightwaveRF\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 250,  // Short gap 250µs, long gap 1250µs, (Pulse width is 250µs)\n        .long_width  = 1250, //\n        .reset_limit = 1500, // Gap between messages is unknown so let us get them individually\n        .decode_fn   = &lightwave_rf_callback,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/m_bus.c",
    "content": "/** @file\n    Wireless M-Bus (EN 13757-4).\n\n   Copyright (C) 2018 Tommy Vestermark\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 2 of the License, or\n   (at your option) any later version.\n*/\n/**\nWireless M-Bus (EN 13757-4).\n\nImplements the Physical layer (RF receiver) and Data Link layer of the\nWireless M-Bus protocol. Will return a data string (including the CI byte)\nfor further processing by an Application layer (outside this program).\n*/\n#include \"decoder.h\"\n\n#define BLOCK1A_SIZE 12     // Size of Block 1, format A\n#define BLOCK1B_SIZE 10     // Size of Block 1, format B\n#define BLOCK2B_SIZE 118    // Maximum size of Block 2, format B\n#define BLOCK1_2B_SIZE 128\n\n// Convert two BCD encoded nibbles to an integer\nstatic unsigned bcd2int(uint8_t bcd)\n{\n    return 10*(bcd>>4) + (bcd & 0xF);\n}\n\n// Mapping from 6 bits to 4 bits. \"3of6\" coding used for Mode T\nstatic uint8_t m_bus_decode_3of6(uint8_t byte)\n{\n    uint8_t out = 0xF0; // Error\n    //fprintf(stderr,\"Decode %0d\\n\", byte);\n    switch(byte) {\n        case 22:    out = 0x00;  break;  // 0x16\n        case 13:    out = 0x01;  break;  // 0x0D\n        case 14:    out = 0x02;  break;  // 0x0E\n        case 11:    out = 0x03;  break;  // 0x0B\n        case 28:    out = 0x04;  break;  // 0x1C\n        case 25:    out = 0x05;  break;  // 0x19\n        case 26:    out = 0x06;  break;  // 0x1A\n        case 19:    out = 0x07;  break;  // 0x13\n        case 44:    out = 0x08;  break;  // 0x2C\n        case 37:    out = 0x09;  break;  // 0x25\n        case 38:    out = 0x0A;  break;  // 0x26\n        case 35:    out = 0x0B;  break;  // 0x23\n        case 52:    out = 0x0C;  break;  // 0x34\n        case 49:    out = 0x0D;  break;  // 0x31\n        case 50:    out = 0x0E;  break;  // 0x32\n        case 41:    out = 0x0F;  break;  // 0x29\n        default:    break;  // Error\n    }\n    return out;\n}\n\n// Decode input 6 bit nibbles to output 4 bit nibbles (packed in bytes). \"3of6\" coding used for Mode T\n// Bad data must be handled with second layer CRC\nstatic int m_bus_decode_3of6_buffer(uint8_t const *bits, unsigned bit_offset, uint8_t* output, unsigned num_bytes)\n{\n    int successful_contiguous_bytes = -1;\n    for (unsigned n=0; n<num_bytes; ++n) {\n        uint8_t nibble_h = m_bus_decode_3of6(bitrow_get_byte(bits, n*12+bit_offset) >> 2);\n        uint8_t nibble_l = m_bus_decode_3of6(bitrow_get_byte(bits, n*12+bit_offset+6) >> 2);\n        if (nibble_h > 0xf || nibble_l > 0xf) {\n            // return -1;  // fail at first 3of6 decoding error\n            nibble_l &= 0x0F;  // assume logical 0 nibble if 3of6 decoding error, let CRC fail decoding if necessary\n            if (successful_contiguous_bytes < 0) successful_contiguous_bytes = n;  // return count found until the first error\n        }\n        output[n] = (nibble_h << 4) | nibble_l;\n    }\n    if (successful_contiguous_bytes < 0) successful_contiguous_bytes = num_bytes;  // if all data decoded successfully\n    return successful_contiguous_bytes;\n}\n\n// Validate CRC\nstatic int m_bus_crc_valid(r_device *decoder, const uint8_t *bytes, unsigned crc_offset)\n{\n    static const uint16_t CRC_POLY = 0x3D65;\n    uint16_t crc_calc = ~crc16(bytes, crc_offset, CRC_POLY, 0);\n    uint16_t crc_read = (((uint16_t)bytes[crc_offset] << 8) | bytes[crc_offset+1]);\n    if (crc_calc != crc_read) {\n        decoder_logf(decoder, 1, __func__, \"M-Bus: CRC error: Calculated 0x%X, Read: 0x%X\", (unsigned)crc_calc, (unsigned)crc_read);\n        return 0;\n    }\n    return 1;\n}\n\n// Decode two bytes into three letters of five bits\nstatic void m_bus_manuf_decode(uint16_t m_field, char *three_letter_code)\n{\n    three_letter_code[0] = (m_field >> 10 & 0x1F) + 0x40;\n    three_letter_code[1] = (m_field >> 5 & 0x1F) + 0x40;\n    three_letter_code[2] = (m_field & 0x1F) + 0x40;\n    three_letter_code[3] = 0;\n}\n\n// Decode device type string\nstatic char const *m_bus_device_type_str(uint8_t devType)\n{\n    char const *str = \"\";\n    switch(devType) {\n        case 0x00:  str = \"Other\";  break;\n        case 0x01:  str = \"Oil\";  break;\n        case 0x02:  str = \"Electricity\";  break;\n        case 0x03:  str = \"Gas\";  break;\n        case 0x04:  str = \"Heat\";  break;\n        case 0x05:  str = \"Steam\";  break;\n        case 0x06:  str = \"Warm Water\";  break;\n        case 0x07:  str = \"Water\";  break;\n        case 0x08:  str = \"Heat Cost Allocator\";  break;\n        case 0x09:  str = \"Compressed Air\";  break;\n        case 0x0A:\n        case 0x0B:  str = \"Cooling load meter\";  break;\n        case 0x0C:  str = \"Heat\";  break;\n        case 0x0D:  str = \"Heat/Cooling load meter\";  break;\n        case 0x0E:  str = \"Bus/System component\";  break;\n        case 0x0F:  str = \"Unknown\";  break;\n        case 0x15:  str = \"Hot Water\";  break;\n        case 0x16:  str = \"Cold Water\";  break;\n        case 0x17:  str = \"Hot/Cold Water meter\";  break;\n        case 0x18:  str = \"Pressure\";  break;\n        case 0x19:  str = \"A/D Converter\";  break;\n        case 0x1A:  str = \"Smoke detector\"; break;\n        case 0x1B:  str = \"Room sensor\"; break;\n        case 0x1C:  str = \"Gas detector\"; break;\n        case 0x20:  str = \"Breaker (electricity)\"; break;\n        case 0x21:  str = \"Valve (gas or water)\"; break;\n        case 0x28:  str = \"Waste water meter\"; break;\n        case 0x29:  str = \"Garbage\"; break;\n        case 0x2A:  str = \"Carbon dioxide\"; break;\n        case 0x25:  str = \"Customer unit (display device)\";break;\n        case 0x31:  str = \"Communication controller\";break;\n        case 0x32:  str = \"Unidirectional repeater\";break;\n        case 0x33:  str = \"Bidirectional repeater\";break;\n        case 0x36:  str = \"Radio converter (system side)\";break;\n        case 0x37:  str = \"Radio converter (meter side)\";break;\n        default:    break;  // Unknown\n    }\n    return str;\n}\n\n// Data structure for application layer\ntypedef struct {\n    uint8_t     CI;         // Control info\n    uint8_t     AC;         // Access number\n    uint8_t     ST;\n    uint16_t    CW;         // Configuration word\n    uint8_t     pl_offset;  // Payload offset\n    /* KNX */\n    uint8_t     knx_ctrl;\n    uint16_t    src;\n    uint16_t    dst;\n    uint8_t     l_npci;\n    uint8_t     tpci;\n    uint8_t     apci;\n} m_bus_block2_t;\n\n// Data structure for block 1\ntypedef struct {\n    uint8_t     L;        // Length\n    uint8_t     C;        // Control\n    char        M_str[4]; // Manufacturer (encoded as 2 bytes)\n    uint32_t    A_ID;     // Address, ID\n    uint8_t     A_Version;    // Address, Version\n    uint8_t     A_DevType;    // Address, Device Type\n    uint16_t    CRC;      // Optional (Only for Format A)\n    m_bus_block2_t block2;\n    int         knx_mode;\n    uint8_t     knx_sn[6];\n} m_bus_block1_t;\n\ntypedef struct {\n    unsigned    length;\n    uint8_t     data[512];\n} m_bus_data_t;\n\nstatic float const humidity_factor[2] = { 0.1f, 1.0f };\n\nstatic char const *oms_hum[4][4] = {\n{\"humidity\",\"average_humidity_1h\",\"average_humidity_24h\",\"error_04\", },\n{\"maximum_humidity_1h\",\"maximum_humidity_24h\",\"error_13\",\"error_14\",},\n{\"minimum_humidity_1h\",\"minimum_humidity_24h\",\"error_23\",\"error_24\",},\n{\"error_31\",\"error_32\",\"error_33\",\"error_34\",}\n};\n\nstatic char const *oms_hum_el[4][4] = {\n{\"Humidity\",\"Average Humidity 1h\",\"Average Humidity 24h\",\"Error [0][4]\", },\n{\"Maximum Humidity 1h\",\"Maximum Humidity 24h\",\"Error [1][3]\",\"Error [1][4]\",},\n{\"Minimum Humidity 1h\",\"Minimum Humidity 24h\",\"Error [2][3]\",\"Error [2][4]\",},\n{\"Error 31\",\"Error 32\",\"Error 33\",\"Error 34\",}\n};\n\nstatic char const *history_hours[4] = {\n        \"1h\", \"24h\", \"err[2]\", \"err[3]\",\n};\n\nstatic char const *history_months[12][2] = {\n        {\"m1\", \"of month -1\"},\n        {\"m2\", \"of month -2\"},\n        {\"m3\", \"of month -3\"},\n        {\"m4\", \"of month -4\"},\n        {\"m5\", \"of month -5\"},\n        {\"m6\", \"of month -6\"},\n        {\"m7\", \"of month -7\"},\n        {\"m8\", \"of month -8\"},\n        {\"m9\", \"of month -9\"},\n        {\"m10\", \"of month -10\"},\n        {\"m11\", \"of month -11\"},\n        {\"m12\", \"of month -12\"},\n};\n\nstatic char const *value_types_tab[4][2] = {\n        {\"inst\", \"\"},\n        {\"max\", \"Max\"},\n        {\"min\", \"Min\"},\n        {\"err\", \"Err\"},\n};\n\nenum UnitType {\n    kEnergy_Wh = 0,\n    kEnergy_J,\n    kVolume,\n    kMass,\n    kPower_W,\n    kPower_Jh,\n    kVolumeFlow_h,\n    kVolumeFlow_min,\n    kVolumeFlow_s,\n    kMassFlow,\n    kTemperatureFlow,\n    kTemperatureReturn,\n    kTemperatureDiff,\n    kTemperatureExtern,\n    kPressure,\n    kTimeDate,\n    kDate,\n    kHca,\n    kOnTimeSec,\n    kOnTimeMin,\n    kOnTimeHours,\n    kOnTimeDays,\n    kOperTimeSec,\n    kOperTimeMin,\n    kOperTimeHours,\n    kOperTimeDays,\n};\n\nstatic char const *unit_names[][3] = {\n        /* 0 */ {\"energy_wh\", \"Energy\", \"Wh\"},\n        /* 1 */ {\"energy_j\", \"Energy\", \"J\"},\n        /* 2 */ {\"volume\", \"Volume\", \"m3\"},\n        /* 3 */ {\"mass\", \"Mass\", \"kg\"},\n        /* 4 */ {\"power_w\", \"Power\", \"W\"},\n        /* 5 */ {\"power_jh\", \"Power\", \"J/h\"},\n        /* 6 */ {\"volume_flow_h\", \"Volume flow\", \"m3/h\"},\n        /* 7 */ {\"volume_flow_min\", \"Volume flow\", \"m3/min\"},\n        /* 8 */ {\"volume_flow_s\", \"Volume flow\", \"l/s\"},\n        /* 9 */ {\"mass_flow\", \"Mass flow\", \"kg/h\"},\n        /*10 */ {\"temperature_flow\", \"Flow temperature\", \"C\"},\n        /*11 */ {\"temperature_return\", \"Return temperature\", \"C\"},\n        /*12 */ {\"temperature_diff\", \"Temperature diff\", \"K\"},\n        /*13 */ {\"temperature_ext\", \"Temperature extern\", \"C\"},\n        /*14 */ {\"pressure\", \"Pressure\", \"bar\"},\n        /*15 */ {\"timedate\", \"TimeDate\", \"\"},\n        /*16 */ {\"date\", \"Date\", \"\"},\n        /*17 */ {\"hca\", \"HCA\", \"\"},\n        /*18 */ {\"ontime_s\", \"OnTime\", \"s\"},\n        /*19 */ {\"ontime_m\", \"OnTime\", \"min\"},\n        /*20 */ {\"ontime_h\", \"OnTime\", \"hours\"},\n        /*21 */ {\"ontime_d\", \"OnTime\", \"days\"},\n        /*22 */ {\"opertime_s\", \"OperTime\", \"s\"},\n        /*23 */ {\"opertime_m\", \"OperTime\", \"min\"},\n        /*24 */ {\"opertime_h\", \"OperTime\", \"hours\"},\n        /*25 */ {\"opertime_d\", \"OperTime\", \"days\"},\n};\n\n// exponent                    -3     -2    -1    0  1   2    3     4\n// index                        0      1     2    3  4   5    6     7\nstatic double const pow10_table[8] = { 0.001, 0.01, 0.1, 1, 10, 100, 1000, 10000 };\n\nstatic data_t *append_str(data_t *data, enum UnitType unit_type, uint8_t value_type, uint8_t sn,\n    char const *key_extra, char const *pretty_extra, char const *value)\n{\n    char key[100] = {0};\n    char pretty[100] = {0};\n\n    value_type &= 0x3;\n\n    if (!key_extra || !*key_extra) {\n        snprintf(key, sizeof(key), \"%s_%s_%d\", value_types_tab[value_type][0], unit_names[unit_type][0], sn);\n    } else {\n        snprintf(key, sizeof(key), \"%s_%s_%s_%d\", value_types_tab[value_type][0], unit_names[unit_type][0], key_extra, sn);\n    }\n\n    if (!pretty_extra || !*pretty_extra) {\n        snprintf(pretty, sizeof(pretty), \"%s %s[%d]\", value_types_tab[value_type][1], unit_names[unit_type][1], sn);\n    } else {\n        snprintf(pretty, sizeof(pretty), \"%s %s %s\", value_types_tab[value_type][1], unit_names[unit_type][1], pretty_extra);\n    }\n\n    return data_str(data, key, pretty, NULL, value);\n\n}\n\nstatic data_t *append_val(data_t *data, enum UnitType unit_type, uint8_t value_type, uint8_t sn,\n    char const *key_extra, char const *pretty_extra, int64_t val, int exp)\n{\n    char const *prefix = \"\";\n    char buffer_val[256] = {0};\n\n    if (exp < -6) {\n        exp += 6;\n        prefix = \"u\";\n    } else if (exp < -3) {\n        exp += 3;\n        prefix = \"m\";\n    } else if (exp <= 0) {\n        prefix = \"\";\n    } else if (exp <= 3) {\n        exp -= 3;\n        prefix = \"k\";\n    } else if (exp <= 6) {\n        exp -= 6;\n        prefix = \"M\";\n    } else if (exp <= 9) {\n        exp -= 9;\n        prefix = \"G\";\n    }\n    // adapt for table index\n    exp += 3;\n    if (exp < 0 || exp > 7) {\n        fprintf(stderr, \"M-Bus: Program error, exp (%d) is out of bounds\", exp);\n        return data;\n    }\n    double fvalue = val * pow10_table[exp];\n\n    snprintf(buffer_val, sizeof(buffer_val), \"%.3f %s%s\", fvalue, prefix, unit_names[unit_type][2]);\n\n    return append_str(data, unit_type, value_type, sn, key_extra, pretty_extra, buffer_val);\n}\n\nstatic size_t m_bus_tm_decode(const uint8_t *data, size_t data_size, char *output, size_t output_size)\n{\n    size_t out_len = 0;\n\n    if (output == NULL) {\n        return 0;\n    }\n\n    switch(data_size) {\n        case 6:                // Type I = Compound CP48: Date and Time\n            if ((data[1] & 0x80) != 0) { // Time valid ?\n                out_len = snprintf(output, output_size, \"invalid\");\n                break;\n            }\n\n            out_len = snprintf(output, output_size, \"%02d-%02d-%02dT%02d:%02d:%02d\",\n                ((data[3] & 0xE0) >> 5) | ((data[4] & 0xF0) >> 1),\n                data[4] & 0x0F,\n                data[3] & 0x1F,\n                data[2] & 0x1F,\n                data[1] & 0x3F,\n                data[0] & 0x3F\n            );\n            // (data[0] & 0x40) ? 1 : 0;  // day saving time\n            break;\n        case 4:           // Type F = Compound CP32: Date and Time\n            if ((data[0] & 0x80) != 0) {    // Time valid ?\n                out_len = snprintf(output, output_size, \"invalid\");\n                break;\n            }\n            out_len = snprintf(output, output_size, \"%02d-%02d-%02dT%02d:%02d:00\",\n                ((data[2] & 0xE0) >> 5) | ((data[3] & 0xF0) >> 1), // year\n                data[3] & 0x0F, // mon\n                data[2] & 0x1F, // mday\n                data[1] & 0x1F, // hour\n                data[0] & 0x3F // sec\n            );\n            // (data[1] & 0x80) ? 1 : 0;  // day saving time\n            break;\n        case 2:           // Type G: Compound CP16: Date\n            if ((data[1] & 0x0F) > 12) { // Date valid ?\n                out_len = snprintf(output, output_size, \"invalid\");\n                break;\n            }\n            out_len = snprintf(output, output_size, \"%02d-%02d-%02d\",\n                ((data[0] & 0xE0) >> 5) | ((data[1] & 0xF0) >> 1), // year\n                data[1] & 0x0F, // mon\n                data[0] & 0x1F  // mday\n            );\n            break;\n        default:\n            out_len = snprintf(output, output_size, \"unknown\");\n            break;\n    }\n    return out_len;\n}\n\n/**\n * @brief decode value from the data stream\n *\n * @param b             data stream\n * @param dif_coding    type/size of value\n * @param out_value     pointer where value will be stored\n * @return size_t       number of consumed bytes. -1 if error or unknown coding\n */\nstatic int m_bus_decode_val(const uint8_t *b, uint8_t dif_coding, int64_t *out_value)\n{\n    uint64_t val = 0;\n    *out_value = 0;\n\n    switch (dif_coding) {\n        case 15: // special function\n            return -1;\n        case 14: // 12 digit BCD\n            for (int i=5; i >= 0;--i) {\n                *out_value = (*out_value * 10) + (b[i] >> 4);\n                *out_value = (*out_value * 10) + (b[i] & 0xF);\n            }\n            return 6;\n        case 13: // variable len\n            return -1;\n        case 12: // 8 digit BCD\n            for (int i=3; i >= 0;--i) {\n                *out_value = (*out_value * 10) + (b[i] >> 4);\n                *out_value = (*out_value * 10) + (b[i] & 0xF);\n            }\n            return 4;\n        case 11: // 6 digit BCD\n            for (int i=2; i >= 0;--i) {\n                *out_value = (*out_value * 10) + (b[i] >> 4);\n                *out_value = (*out_value * 10) + (b[i] & 0xF);\n            }\n            return 3;\n        case 10: // 4 digit BCD\n            for (int i=1; i >= 0;--i) {\n                *out_value = (*out_value * 10) + (b[i] >> 4);\n                *out_value = (*out_value * 10) + (b[i] & 0xF);\n            }\n            return 2;\n        case 9: // 2 digit BCD\n            *out_value = (b[0] >> 4) * 10;\n            *out_value += b[0] & 0xF;\n            return 1;\n        case 8: // Selection for Readout\n            return -1;\n        case 7: // 64bit\n            for (int i=7; i >= 0;--i) {\n                *out_value = (*out_value << 8) | b[i];\n            }\n            return 8;\n        case 6: // 48bit\n            if (b[5] & 0x80) {\n                val = 0xFFFFFF;\n            }\n            for (int i=5; i >= 0;--i) {\n                val = (val << 8) | b[i];\n            }\n            *out_value = (int64_t)val;\n            return 6;\n        case 5: // 32bit float\n            *out_value = 0; // TODO\n            return -1;\n        case 4: // 32bit\n            *out_value = (int32_t)(b[3] << 24 | b[2] << 16 | b[1] << 8 | b[0]);\n            return 4;\n        case 3: // 24bit\n            if (b[2] & 0x80) {\n                val = 0xFFFFFFFFFF;\n            }\n            val = (val << 8) | b[2];\n            val = (val << 8) | b[1];\n            val = (val << 8) | b[0];\n            *out_value = (int64_t)val;\n            return 3;\n        case 2: // 16bit\n            *out_value = (int16_t)(b[1] << 8 |  b[0]);\n            return 2;\n        case 1: // 8bit\n            *out_value = (int8_t)b[0];\n            return 1;\n        case 0: // no data\n            return 0;\n        default:\n            break;\n    }\n    return -1;\n}\n\n/**\n * @brief decode wireless mbus records\n *\n * @param[in,out] inout_data    pointer to output data for decoded records\n * @param b             input buffer with records\n * @param dif_coding    Data Information - Length and coding of data (2=16bit,4=32bit, etc)\n * @param vif_linear    Value Information Field\n * @param vif_uam       Value Information Field - unit type + multiplier\n * @param dif_sn        Data Information Field - storage number\n * @param dif_ff        Data Information Field - function field (00b    Instantaneous value\n *                                                               01b    Maximum value\n *                                                               10b    Minimum value\n *                                                               11b    Value during error state)\n * @param dif_su        Data Information Field -\n * @return int\n */\nstatic int m_bus_decode_records(data_t **inout_data, const uint8_t *b, uint8_t dif_coding, uint8_t vif_linear, uint8_t vif_uam, uint8_t dif_sn, uint8_t dif_ff, uint8_t dif_su)\n{\n    data_t *data = *inout_data;\n    int ret = 0;\n    int state;\n    int64_t val = 0;\n\n    ret = m_bus_decode_val(b, dif_coding, &val);\n\n    // for reverse engineering\n    // fprintf(stderr, \"**decoding dif_coding=%d, vif=0x%02x, vif_uam=0x%02x, dif_ff=%d, dif_sn=%d, dif_su=%d, b[3]=0x%02X, b[2]=0x%02X,b[1]=0x%02X, b[0]=0x%02X, val=%ld**\\n\",\n    //                  dif_coding, vif_linear, vif_uam, dif_ff, dif_sn, dif_su, b[3], b[2], b[1], b[0], val);\n\n    switch (vif_linear) {\n        case 0:\n            if ((vif_uam&0xF8) == 0) {\n                // E000 0nnn Energy 10nnn-3 Wh  0.001Wh to 10000Wh\n                data = append_val(data, kEnergy_Wh, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x7));\n            } else if ((vif_uam&0xF8) == 0x08) {\n                // E000 1nnn    Energy  10nnn J 0.001kJ to 10000kJ\n                data = append_val(data, kEnergy_J, dif_ff, dif_sn, \"\", \"\", val, vif_uam&0x7);\n            } else if ((vif_uam&0xF8) == 0x10) {\n                // E001 0nnn    Volume  10nnn-6 m3  0.001l to 10000l\n\n                if (dif_sn == 0) {\n                    data = append_val(data, kVolume, dif_ff, dif_sn, \"\", \"\", val, -6 + (vif_uam&0x7));\n                } else\n                if (dif_sn >= 8 && dif_sn <= 19) {\n                    dif_sn -= 8;\n                    data = append_val(data, kVolume, dif_ff, dif_sn,\n                        history_months[dif_sn][0], history_months[dif_sn][1], val, -6 + (vif_uam&0x7));\n                }\n\n            } else if ((vif_uam&0xF8) == 0x18) {\n                // E001 1nnn    Mass    10nnn-3 kg  0.001kg to 10000kg\n                data = append_val(data, kEnergy_J, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x7));\n            } else if ((vif_uam&0xFC) == 0x20) {\n                /* E010 00nn    On Time nn = 00 seconds\n                                        nn = 01 minutes\n                                        nn = 10 hours\n                                        nn = 11 days */\n                switch (vif_uam&3) {\n                    case 0: data = append_val(data, kOnTimeSec, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    case 1: data = append_val(data, kOnTimeMin, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    case 2: data = append_val(data, kOnTimeHours, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    case 3: data = append_val(data, kOnTimeDays, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    default: break;\n                }\n            } else if ((vif_uam&0xFC) == 0x24) {\n                // E010 01nn    Operating Time  coded like OnTime\n                switch (vif_uam&3) {\n                    case 0: data = append_val(data, kOperTimeSec, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    case 1: data = append_val(data, kOperTimeMin, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    case 2: data = append_val(data, kOperTimeHours, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    case 3: data = append_val(data, kOperTimeDays, dif_ff, dif_sn, \"\", \"\", val, 0); break;\n                    default: break;\n                }\n            } else if ((vif_uam&0xF8) == 0x28) {\n                // E010 1nnn    Power  10nnn-3 W    0.001W to 10000W\n                data = append_val(data, kPower_W, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x7));\n            } else if ((vif_uam&0xF8) == 0x30) {\n                // E011 0nnn    Power   10nnn J/h   0.001kJ/h to 10000kJ/h\n                data = append_val(data, kPower_Jh, dif_ff, dif_sn, \"\", \"\", val, vif_uam&0x7);\n            } else if ((vif_uam&0xF8) == 0x38) {\n                // E011 1nnn    Volume Flow 10nnn-6 m3/h   0.001l/h to 10000l/h\n                data = append_val(data, kVolumeFlow_h, dif_ff, dif_sn, \"\", \"\", val, -6 + (vif_uam&0x7));\n            } else if ((vif_uam&0xF8) == 0x40) {\n                // E100 0nnn    Volume Flow ext.    10nnn-7 m3/min  0.0001l/min to 1000l/min\n                data = append_val(data, kVolumeFlow_min, dif_ff, dif_sn, \"\", \"\", val, -7 + (vif_uam&0x7));\n            } else if ((vif_uam&0xF8) == 0x48) {\n                // E100 1nnn    Volume Flow ext.   10nnn-9 m³/s    0.001ml/s to 10000ml/s\n                // in litres so exp -3\n                data = append_val(data, kVolumeFlow_s, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x7));\n            } else if ((vif_uam&0xF8) == 0x50) {\n                // E101 0nnn    Mass flow   10nnn-3 kg/h    0.001kg/h to 10000kg/h\n                data = append_val(data, kMassFlow, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x7));\n            } else if ((vif_uam&0xFC) == 0x58) {\n                // E101 10nn    Flow Temperature 10nn-3 °C 0.001°C to 1°C\n                data = append_val(data, kTemperatureFlow, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x3));\n            } else if ((vif_uam&0xFC) == 0x5C) {\n                // E101 11nn    Return Temperature 10nn-3 °C    0.001°C to 1°C\n                data = append_val(data, kTemperatureReturn, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x3));\n            } else if ((vif_uam&0xFC) == 0x60) {\n                // E110 00nn    Temperature Difference  10nn-3 K    1mK to 1000mK\n                data = append_val(data, kTemperatureDiff, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x3));\n            } else if ((vif_uam&0xFC) == 0x64) {\n                // E110 01nn    External temperature    10 nn-3 ° C 0.001 ° C to 1 ° C\n                data = append_val(data, kTemperatureExtern, dif_ff, dif_sn, \"\", history_hours[dif_sn&0x3], val, -3 + (vif_uam&0x3));\n            } else if ((vif_uam&0xFC) == 0x68) {\n                // E110 10nn    Pressure    10nn-3 bar 1mbar to 1000mbar\n                data = append_val(data, kPressure, dif_ff, dif_sn, \"\", \"\", val, -3 + (vif_uam&0x3));\n            } else if ((vif_uam&0xFE) == 0x6C) {\n                // E110 110n    Time Point  n = 0 date, n = 1 time & date\n                char buff_time[256] = {0};\n\n                if (vif_uam&1) {\n                    if (m_bus_tm_decode(b, dif_coding, buff_time, sizeof(buff_time))) {\n                        data = append_str(data, kTimeDate, dif_ff, dif_sn, \"\", \"\", buff_time);\n                    }\n                } else {\n                    if (m_bus_tm_decode(b, dif_coding, buff_time, sizeof(buff_time))) {\n                        data = append_str(data, kDate, dif_ff, dif_sn, \"\", \"\", buff_time);\n                    }\n                }\n\n            } else if (vif_uam == 0x6E) {\n                // E110 1110    Units for H.C.A.        dimensionless\n                data = append_val(data, kHca, dif_ff, dif_sn, \"\", \"\", val, 0);\n            } else if ((vif_uam&0xFC) == 0x70) {\n                // E111 00nn    Averaging Duration coded like OnTime\n            } else if ((vif_uam&0xFC) == 0x74) {\n                // E111 01nn    Actuality Duration coded like OnTime\n            } else if (vif_uam == 0x78) {\n                // E111 1000    Fabrication No\n            } else if (vif_uam == 0x79) {\n                // E111 1001    Enhanced Identification\n            } else if (vif_uam == 0x7A) {\n                // E111 1010    Bus Address     data type C (x=8)\n            } else {\n                // reserved\n                data = data_str(data, \"unknown\", \"Unknown\", NULL, \"none\");\n            }\n\n            break;\n        case 0x7B:\n            switch(vif_uam>>1) {\n                case 0xD:\n                    data = data_dbl(data,\n                            oms_hum[dif_ff&0x3][dif_sn&0x3], oms_hum_el[dif_ff&0x3][dif_sn&0x3], \"%.1f %%\", val*humidity_factor[vif_uam&0x1]);\n                    break;\n                default:\n                    break;\n            }\n            break;\n        case 0x7D:\n            switch(vif_uam) {\n                case 0x1b:\n                    // If tamper is triggered the bit 0 and 4 is set\n                    // Open  sets bits 2 and 6 to 1\n                    // Close sets bits 2 and 6 to 0\n                    state = b[0]&0x44;\n                    data  = data_str(data, \"switch\", \"Switch\", NULL, (state == 0x44) ? \"open\" : \"closed\");\n                    break;\n                case 0x3a:\n                    /* Only use 32 bits of 48 available */\n                    data = data_int(data,\n                            ((dif_su==0)?\"counter_0\":\"counter_1\"), ((dif_su==0)?\"Counter 0\":\"Counter 1\"), \"%d\", (b[3]<<24|b[2]<<16|b[1]<<8|b[0]));\n                    break;\n                default:\n                    break;\n            }\n            break;\n        default:\n            break;\n    }\n    *inout_data = data;\n    return ret;\n}\n\nstatic void parse_payload(data_t *data, const m_bus_block1_t *block1, const m_bus_data_t *out)\n{\n    uint8_t off = block1->block2.pl_offset;\n    const uint8_t *b = out->data;\n\n    /* Align offset pointer, there might be 2 0x2F bytes */\n    if (b[off] == 0x2F) off++;\n    if (b[off] == 0x2F) off++;\n\n// [02 65] 9f08 [42 65] 9e08 [8201 65] 8f08 [02 fb1a] 3601 [42 fb1a] 3701 [8201 fb1a] 3001\n\n//[02 65] b408 [42 65] a008 [8201 65] 6408 [22 65] 9608 [12 65] ac08 [62 65] 2808 [52 65] 920802fb1a470142fb1a4a018201fb1a550122fb1a4a0112fb1a4a0162fb1a3c0152fb1a6c01066dbb3197902100\n\n    /* Payload must start with a DIF */\n    while (off < block1->L) {\n        uint8_t dif;\n        uint8_t dife_array[10] = {0};\n        uint8_t dife_cnt;\n        uint8_t dif_coding;\n        uint8_t dif_sn;\n        uint8_t dif_ff;\n        uint8_t dif_su;\n        uint8_t vif;\n        uint8_t vife_array[10] = {0};\n        uint8_t vife_cnt;\n        uint8_t vif_uam;\n        uint8_t vif_linear;\n\n        dife_cnt = 0;\n        vife_cnt = 0;\n\n        /* Parse DIF */\n        dif = b[off];\n        dif_sn = (dif&0x40) >> 6;\n        while (b[off]&0x80) {\n            off++;\n            dife_array[dife_cnt++] = b[off];\n            if (dife_cnt >= 10) return;\n        }\n        // Only use first dife in dife_array\n        dif_sn = ((dife_array[0]&0x0F) << 1) | dif_sn;\n        dif_su = ((dife_array[0]&0x40) >> 6);\n        off++;\n        dif_coding = dif&0x0F;\n        dif_ff = (dif&0x30) >> 4;\n\n        /* Parse VIF */\n        vif = b[off];\n\n        while (b[off]&0x80) {\n            off++;\n            vife_array[vife_cnt++] = b[off]&0x7F;\n            if (vife_cnt >= 10) return;\n        }\n        off++;\n        /* Linear VIF-extension */\n        if (vif == 0xFB) {\n            vif_linear = 0x7B;\n            vif_uam = vife_array[0];\n        } else if (vif  == 0xFD) {\n            vif_linear = 0x7D;\n            vif_uam = vife_array[0];\n        } else {\n            vif_linear = 0;\n            vif_uam = vif&0x7F;\n        }\n\n        int consumed = m_bus_decode_records(&data, &b[off], dif_coding, vif_linear, vif_uam, dif_sn, dif_ff, dif_su);\n        if (consumed == -1) return;\n\n        off +=consumed;\n    }\n}\n\nstatic int parse_block2(const m_bus_data_t *in, m_bus_block1_t *block1)\n{\n    m_bus_block2_t *b2 = &block1->block2;\n    const uint8_t *b = in->data+BLOCK1A_SIZE;\n\n    if (block1->knx_mode) {\n        b2->knx_ctrl = b[0];\n        b2->src = b[1]<< 8 | b[2];\n        b2->dst = b[3]<< 8 | b[4];\n        b2->l_npci = b[5];\n        b2->tpci = b[6];\n        b2->apci = b[7];\n        /* data */\n    } else {\n        b2->CI = b[0];\n        /* Short transport layer */\n        if (b2->CI == 0x7A) {\n            b2->AC = b[1];\n            b2->ST = b[2];\n            b2->CW = b[4]<<8 | b[3];\n            b2->pl_offset = BLOCK1A_SIZE-2 + 5;\n        }\n    //    fprintf(stderr, \"Instantaneous Value: %02x%02x : %f\\n\",b[9],b[10],((b[10]<<8)|b[9])*0.01);\n    }\n    return 0;\n}\n\nstatic int m_bus_decode_format_a(r_device *decoder, const m_bus_data_t *in, m_bus_data_t *out, m_bus_block1_t *block1)\n{\n\n    // Get Block 1\n    block1->L         = in->data[0];\n    block1->C         = in->data[1];\n\n    /* Check for KNX RF default values */\n    if ((in->data[2]==0xFF) && (in->data[3]==0x03)) {\n        block1->knx_mode = 1;\n        memcpy(block1->knx_sn, &in->data[4], 6);\n    } else {\n        m_bus_manuf_decode((uint32_t)(in->data[3] << 8 | in->data[2]), block1->M_str);    // Decode Manufacturer\n        block1->A_ID      = bcd2int(in->data[7])*1000000 + bcd2int(in->data[6])*10000 + bcd2int(in->data[5])*100 + bcd2int(in->data[4]);\n        block1->A_Version = in->data[8];\n        block1->A_DevType = in->data[9];\n    }\n\n    // Store length of data\n    out->length      = block1->L-9 + BLOCK1A_SIZE-2;\n\n    // Validate CRC\n    if (!m_bus_crc_valid(decoder, in->data, 10)) return 0;\n\n    // Check length of package is sufficient\n    unsigned num_data_blocks = (block1->L-9+15)/16;      // Data blocks are 16 bytes long + 2 CRC bytes (not counted in L)\n    if ((block1->L < 9) || ((block1->L-9)+num_data_blocks*2 > in->length-BLOCK1A_SIZE)) {   // add CRC bytes for each data block\n        decoder_logf(decoder, 1, __func__, \"M-Bus: Package (%u) too short for packet Length: %u\", in->length, block1->L);\n        decoder_logf(decoder, 1, __func__, \"M-Bus: %u > %u\", (block1->L-9)+num_data_blocks*2, in->length-BLOCK1A_SIZE);\n        return 0;\n    }\n\n    memcpy(out->data, in->data, BLOCK1A_SIZE-2);\n    // Get all remaining data blocks and concatenate into data array (removing CRC bytes)\n    for (unsigned n=0; n < num_data_blocks; ++n) {\n        const uint8_t *in_ptr   = in->data+BLOCK1A_SIZE+n*18;       // Pointer to where data starts. Each block is 18 bytes\n        uint8_t *out_ptr        = out->data+n*16 + BLOCK1A_SIZE-2;                   // Pointer into block where data starts.\n        uint8_t block_size      = MIN(block1->L-9-n*16, 16)+2;      // Maximum block size is 16 Data + 2 CRC\n\n        // Validate CRC\n        if (!m_bus_crc_valid(decoder, in_ptr, block_size-2)) return 0;\n\n        // Get block data\n        memcpy(out_ptr, in_ptr, block_size);\n    }\n\n    parse_block2(in, block1);\n\n    return 1;\n}\n\nstatic int m_bus_decode_format_b(r_device *decoder, const m_bus_data_t *in, m_bus_data_t *out, m_bus_block1_t *block1)\n{\n    // Get Block 1\n    block1->L         = in->data[0];\n    block1->C         = in->data[1];\n    m_bus_manuf_decode((uint32_t)(in->data[3] << 8 | in->data[2]), block1->M_str);    // Decode Manufacturer\n    block1->A_ID      = bcd2int(in->data[7])*1000000 + bcd2int(in->data[6])*10000 + bcd2int(in->data[5])*100 + bcd2int(in->data[4]);\n    block1->A_Version = in->data[8];\n    block1->A_DevType = in->data[9];\n\n    // Store length of data\n    out->length      = block1->L-(9+2) + BLOCK1B_SIZE-2;\n\n    // Check length of package is sufficient\n    if ((block1->L < 12) || (block1->L+1 > (int)in->length)) {   // L includes all bytes except itself\n        decoder_logf(decoder, 1, __func__, \"M-Bus: Package too short for Length: %u\", block1->L);\n        return 0;\n    }\n\n    // Validate CRC\n    if (!m_bus_crc_valid(decoder, in->data, MIN(block1->L-1, (BLOCK1B_SIZE+BLOCK2B_SIZE)-2))) return 0;\n\n    // Get data from Block 2\n    memcpy(out->data, in->data, (MIN(block1->L-11, BLOCK2B_SIZE-2))+BLOCK1B_SIZE);\n\n    // Extract extra block for long telegrams (not tested!)\n    uint8_t L_OFFSET = BLOCK1B_SIZE+BLOCK2B_SIZE-1;     // How much to subtract from L (127)\n    if (block1->L > (L_OFFSET+2)) {        // Any more data? (besided 2 extra CRC)\n        // Validate CRC\n        if (!m_bus_crc_valid(decoder, in->data+BLOCK1B_SIZE+BLOCK2B_SIZE, block1->L-L_OFFSET-2)) return 0;\n\n        // Get Block 3\n        memcpy(out->data+(BLOCK2B_SIZE-2), in->data+BLOCK2B_SIZE, block1->L-L_OFFSET-2);\n\n        out->length -= 2;   // Subtract the two extra CRC bytes\n    }\n    // Include the final CRC, for wmbusmeters to verify decryption\n    out->length += 2;\n    return 1;\n}\n\nstatic int m_bus_output_data(r_device *decoder, bitbuffer_t *bitbuffer, const m_bus_data_t *out, const m_bus_block1_t *block1, char const *mode)\n{\n    (void)bitbuffer; // note: to match the common decoder function signature\n\n    data_t  *data;\n\n    // Make data string\n    char str_buf[1024];\n    sprintf(str_buf, \"%02x\", out->data[0]);  // Adjust telegram length\n    for (unsigned n=1; n<out->length; n++) { sprintf(str_buf+n*2, \"%02x\", out->data[n]); }\n\n    // Output data\n    if (block1->knx_mode) {\n        char sn_str[7*2] = {0};\n        for (unsigned n=0; n<6; n++) { sprintf(sn_str+n*2, \"%02x\", block1->knx_sn[n]); }\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",    \"\",             DATA_STRING,    \"KNX-RF\",\n                \"sn\",       \"SN\",           DATA_STRING,    sn_str,\n                \"knx_ctrl\", \"KNX-Ctrl\",     DATA_FORMAT,    \"0x%02X\", DATA_INT, block1->block2.knx_ctrl,\n                \"src\",      \"Src\",          DATA_FORMAT,    \"0x%04X\", DATA_INT, block1->block2.src,\n                \"dst\",      \"Dst\",          DATA_FORMAT,    \"0x%04X\", DATA_INT, block1->block2.dst,\n                \"l_npci\",   \"L/NPCI\",       DATA_FORMAT,    \"0x%02X\", DATA_INT, block1->block2.l_npci,\n                \"tpci\",     \"TPCI\",         DATA_FORMAT,    \"0x%02X\", DATA_INT, block1->block2.tpci,\n                \"apci\",     \"APCI\",         DATA_FORMAT,    \"0x%02X\", DATA_INT, block1->block2.apci,\n                \"data_length\",\"Data Length\",DATA_INT,       out->length,\n                \"data\",     \"Data\",         DATA_STRING,    str_buf,\n                \"mic\",      \"Integrity\",    DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n    } else {\n        /* clang-format off */\n        data = data_make(\n                \"model\",    \"\",             DATA_STRING,    \"Wireless-MBus\",\n                \"mode\",     \"Mode\",         DATA_STRING,    mode,\n                \"M\",        \"Manufacturer\", DATA_STRING,    block1->M_str,\n                \"id\",       \"ID\",           DATA_INT,       block1->A_ID,\n                \"version\",  \"Version\",      DATA_INT,       block1->A_Version,\n                \"type\",     \"Device Type\",  DATA_FORMAT,    \"0x%02X\",   DATA_INT, block1->A_DevType,\n                \"type_string\",  \"Device Type String\",   DATA_STRING,        m_bus_device_type_str(block1->A_DevType),\n                \"C\",        \"Control\",      DATA_FORMAT,    \"0x%02X\",   DATA_INT, block1->C,\n//                \"L\",        \"Length\",       DATA_INT,       block1->L,\n                \"data_length\",  \"Data Length\",          DATA_INT,           out->length,\n                \"data\",     \"Data\",         DATA_STRING,    str_buf,\n                \"mic\",      \"Integrity\",    DATA_STRING,    \"CRC\",\n                NULL);\n        /* clang-format on */\n    }\n    if (block1->block2.CI) {\n        /* clang-format off */\n        data = data_int(data, \"CI\",     \"Control Info\",         \"0x%02X\",   block1->block2.CI);\n        data = data_int(data, \"AC\",     \"Access number\",        \"0x%02X\",   block1->block2.AC);\n        data = data_int(data, \"ST\",     \"Device Type\",          \"0x%02X\",   block1->block2.ST);\n        data = data_int(data, \"CW\",     \"Configuration Word\",   \"0x%04X\",   block1->block2.CW);\n        /* clang-format on */\n    }\n    /* Encryption not supported */\n    if (!(block1->block2.CW&0x0500)) {\n        parse_payload(data, block1, out);\n    } else {\n        /* clang-format off */\n        data = data_int(data, \"payload_encrypted\", \"Payload Encrypted\", NULL, 1);\n        /* clang-format on */\n    }\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nWireless M-Bus, Mode C&T.\n@sa m_bus_output_data()\n*/\nstatic int m_bus_mode_c_t_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static const uint8_t PREAMBLE_T[]  = {0x54, 0x3D};      // Mode T Preamble (always format A - 3of6 encoded)\n//  static const uint8_t PREAMBLE_CA[] = {0x55, 0x54, 0x3D, 0x54, 0xCD};  // Mode C, format A Preamble\n//  static const uint8_t PREAMBLE_CB[] = {0x55, 0x54, 0x3D, 0x54, 0x3D};  // Mode C, format B Preamble\n\n    m_bus_data_t    data_in     = {0};  // Data from Physical layer decoded to bytes\n    m_bus_data_t    data_out    = {0};  // Data from Data Link layer\n    m_bus_block1_t  block1      = {0};  // Block1 fields from Data Link layer\n    char const *mode;\n\n    // Validate package length\n    if (bitbuffer->bits_per_row[0] < (32+13*8) || bitbuffer->bits_per_row[0] > (64+256*12)) {  // Min/Max (Preamble + payload)\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a Mode T or C data package\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, PREAMBLE_T, sizeof(PREAMBLE_T)*8);\n    if (bit_offset + 13*8 >= bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"PREAMBLE_T: found at: %u\", bit_offset);\n    bit_offset += sizeof(PREAMBLE_T)*8;     // skip preamble\n\n    uint8_t next_byte = bitrow_get_byte(bitbuffer->bb[0], bit_offset);\n    bit_offset += 8;\n    // Mode C\n    if (next_byte == 0x54) {\n        mode = \"C\";\n        next_byte = bitrow_get_byte(bitbuffer->bb[0], bit_offset);\n        bit_offset += 8;\n        // Format A\n        if (next_byte == 0xCD) {\n            decoder_log(decoder, 1, __func__, \"M-Bus: Mode C, Format A\");\n            // Extract data\n            data_in.length = (bitbuffer->bits_per_row[0]-bit_offset)/8;\n            bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, data_in.data, data_in.length*8);\n            // Decode\n            if (!m_bus_decode_format_a(decoder, &data_in, &data_out, &block1))\n                return DECODE_FAIL_SANITY;\n        }\n        // Format B\n        else if (next_byte == 0x3D) {\n            decoder_log(decoder, 1, __func__, \"M-Bus: Mode C, Format B\");\n            // Extract data\n            data_in.length = (bitbuffer->bits_per_row[0]-bit_offset)/8;\n            bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, data_in.data, data_in.length*8);\n            // Decode\n            if (!m_bus_decode_format_b(decoder, &data_in, &data_out, &block1))\n                return DECODE_FAIL_SANITY;\n        }\n        // Unknown Format\n        else {\n            decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"M-Bus: Mode C, Unknown format: 0x%X\", next_byte);\n            return 0;\n        }\n    }   // Mode C\n    // Mode T\n    else {\n        mode = \"T\";\n        bit_offset -= 8; // Rewind offset to start of telegram\n        decoder_log(decoder, 1, __func__, \"M-Bus: Mode T\");\n        decoder_log(decoder, 1, __func__, \"Experimental - Not tested\");\n        // Extract data\n\n        data_in.length = (bitbuffer->bits_per_row[0]-bit_offset)/12;    // Each byte is encoded into 12 bits\n\n        decoder_logf(decoder, 1, __func__, \"MBus telegram length: %u\", data_in.length);\n        if (m_bus_decode_3of6_buffer(bitbuffer->bb[0], bit_offset, data_in.data, data_in.length) < 0) {\n            decoder_log(decoder, 1, __func__, \"M-Bus: Decoding error\");\n            return DECODE_FAIL_SANITY;\n        }\n        // Decode\n        if (!m_bus_decode_format_a(decoder, &data_in, &data_out, &block1)) {\n            decoder_log_bitrow(decoder, 1, __func__, data_in.data, data_in.length, \"MBus telegram unknown format\");\n            return DECODE_FAIL_SANITY;\n        }\n    }   // Mode T\n\n    m_bus_output_data(decoder, bitbuffer, &data_out, &block1, mode);\n    return 1;\n}\n\n/**\nWireless M-Bus, Mode R.\n@sa m_bus_output_data()\n*/\nstatic int m_bus_mode_r_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static const uint8_t PREAMBLE_RA[]  = {0x55, 0x54, 0x76, 0x96};      // Mode R, format A (B not supported)\n\n    m_bus_data_t    data_in     = {0};  // Data from Physical layer decoded to bytes\n    m_bus_data_t    data_out    = {0};  // Data from Data Link layer\n    m_bus_block1_t  block1      = {0};  // Block1 fields from Data Link layer\n\n    // Validate package length\n    if (bitbuffer->bits_per_row[0] < (32+13*8) || bitbuffer->bits_per_row[0] > (64+256*8)) {  // Min/Max (Preamble + payload)\n        return 0;\n    }\n\n    // Find a data package\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, PREAMBLE_RA, sizeof(PREAMBLE_RA)*8);\n    if (bit_offset + 13*8 >= bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        return 0;\n    }\n    bit_offset += sizeof(PREAMBLE_RA)*8;     // skip preamble\n\n    decoder_log(decoder, 1, __func__, \"M-Bus: Mode R, Format A\");\n    decoder_log(decoder, 1, __func__, \"Experimental - Not tested\");\n    // Extract data\n    data_in.length = (bitbuffer->bits_per_row[0]-bit_offset)/8;\n    bitbuffer_extract_bytes(bitbuffer, 0, bit_offset, data_in.data, data_in.length*8);\n    // Decode\n    if (!m_bus_decode_format_a(decoder, &data_in, &data_out, &block1))    return 0;\n\n    m_bus_output_data(decoder, bitbuffer, &data_out, &block1, \"R\");\n    return 1;\n}\n\n/**\nWireless M-Bus, Mode F.\n@sa m_bus_output_data()\n\nUntested code, signal samples missing.\n*/\nstatic int m_bus_mode_f_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static const uint8_t PREAMBLE_F[]  = {0x55, 0xF6};      // Mode F Preamble\n//  static const uint8_t PREAMBLE_FA[] = {0x55, 0xF6, 0x8D};  // Mode F, format A Preamble\n//  static const uint8_t PREAMBLE_FB[] = {0x55, 0xF6, 0x72};  // Mode F, format B Preamble\n\n    //m_bus_data_t    data_in     = {0};  // Data from Physical layer decoded to bytes\n    //m_bus_data_t    data_out    = {0};  // Data from Data Link layer\n    //m_bus_block1_t  block1      = {0};  // Block1 fields from Data Link layer\n\n    // Validate package length\n    if (bitbuffer->bits_per_row[0] < (32+13*8) || bitbuffer->bits_per_row[0] > (64+256*8)) {  // Min/Max (Preamble + payload)\n        return 0;\n    }\n\n    // Find a Mode F data package\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, PREAMBLE_F, sizeof(PREAMBLE_F)*8);\n    if (bit_offset + 13*8 >= bitbuffer->bits_per_row[0]) {  // Did not find a big enough package\n        return 0;\n    }\n    bit_offset += sizeof(PREAMBLE_F)*8;     // skip preamble\n\n    uint8_t next_byte = bitrow_get_byte(bitbuffer->bb[0], bit_offset);\n    // bit_offset += 8;\n    // Format A\n    if (next_byte == 0x8D) {\n        decoder_log(decoder, 1, __func__, \"M-Bus: Mode F, Format A\");\n        decoder_log(decoder, 1, __func__, \"Not implemented\");\n        return 1;\n    }\n    // Format B\n    else if (next_byte == 0x72) {\n        decoder_log(decoder, 1, __func__, \"M-Bus: Mode F, Format B\");\n        decoder_log(decoder, 1, __func__, \"Not implemented\");\n        return 1;\n    }\n    // Unknown Format\n    else {\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"M-Bus: Mode F, Unknown format: 0x%X\", next_byte);\n        return 0;\n    }\n\n    //m_bus_output_data(decoder, bitbuffer, &data_out, &block1, \"F\");\n    return 1;\n}\n\n/**\nWireless M-Bus, Mode S.\n@sa m_bus_output_data()\n*/\nstatic int m_bus_mode_s_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static const uint8_t PREAMBLE_S[]  = {0x54, 0x76, 0x96};  // Mode S Preamble\n    static const uint8_t PREAMBLE_T_DN[] = {0xaa, 0xab, 0x32};  // Mode T Downlink Preamble\n    bitbuffer_t packet_bits = {0};\n    m_bus_data_t    data_in     = {0};  // Data from Physical layer decoded to bytes\n    m_bus_data_t    data_out    = {0};  // Data from Data Link layer\n    m_bus_block1_t  block1      = {0};  // Block1 fields from Data Link layer\n\n    // Validate package length\n    if (bitbuffer->bits_per_row[0] < (32+13*8) || bitbuffer->bits_per_row[0] > (64+256*8)) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find a Mode T-downlink data package\n    unsigned offset = bitbuffer_search(bitbuffer, 0, 0, PREAMBLE_T_DN, sizeof(PREAMBLE_T_DN) * 8);\n    offset += sizeof(PREAMBLE_T_DN) * 8;\n    if (offset < bitbuffer->bits_per_row[0]) { // Did find a big enough package\n        bitbuffer_invert(bitbuffer);\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"M-Bus: Mode T Downlink\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Find a Mode S data package\n    unsigned bit_offset = bitbuffer_search(bitbuffer, 0, 0, PREAMBLE_S, sizeof(PREAMBLE_S)*8);\n    bit_offset += sizeof(PREAMBLE_S) * 8;\n    if (bit_offset >= bitbuffer->bits_per_row[0]) { // Did not find a big enough package\n        return DECODE_ABORT_EARLY;\n    }\n    bitbuffer_manchester_decode(bitbuffer, 0, bit_offset, &packet_bits, 800);\n    data_in.length = (bitbuffer->bits_per_row[0]);\n    bitbuffer_extract_bytes(&packet_bits, 0, 0, data_in.data, data_in.length);\n\n    if (!m_bus_decode_format_a(decoder, &data_in, &data_out, &block1))    return 0;\n\n    m_bus_output_data(decoder, bitbuffer, &data_out, &block1, \"S\");\n\n    return 1;\n}\n\n// NOTE: we'd need to add \"value_types_tab X unit_names X n\" fields\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"mode\",\n        \"id\",\n        \"version\",\n        \"type\",\n        \"type_string\",\n        \"CI\",\n        \"AC\",\n        \"ST\",\n        \"CW\",\n        \"sn\",\n        \"knx_ctrl\",\n        \"src\",\n        \"dst\",\n        \"l_npci\",\n        \"tpci\",\n        \"apci\",\n        \"crc\",\n        \"M\",\n        \"C\",\n        \"data_length\",\n        \"data\",\n        \"mic\",\n        \"temperature_C\",\n        \"average_temperature_1h_C\",\n        \"average_temperature_24h_C\",\n        \"humidity\",\n        \"average_humidity_1h\",\n        \"average_humidity_24h\",\n        \"minimum_temperature_1h_C\",\n        \"maximum_temperature_1h_C\",\n        \"minimum_temperature_24h_C\",\n        \"maximum_temperature_24h_C\",\n        \"minimum_humidity_1h\",\n        \"maximum_humidity_1h\",\n        \"minimum_humidity_24h\",\n        \"maximum_humidity_24h\",\n        \"switch\",\n        \"counter_0\",\n        \"counter_1\",\n        NULL,\n};\n\n// Mode C1, C2 (Meter TX), T1, T2 (Meter TX),\n// Frequency 868.95 MHz, Bitrate 100 kbps (uplink), Modulation NRZ FSK\nr_device const m_bus_mode_c_t = {\n        .name        = \"Wireless M-Bus, Mode C&T, 100kbps (-f 868.95M -s 1200k)\", // Minimum samplerate = 1.2 MHz (12 samples of 100kb/s)\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 10,  // Bit rate: 100 kb/s\n        .long_width  = 10,  // NRZ encoding (bit width = pulse width)\n        .reset_limit = 500, //\n        .decode_fn   = &m_bus_mode_c_t_callback,\n        .fields      = output_fields,\n};\n\n// Mode T communication in downlink direction at 32.768 kbps\nr_device const m_bus_mode_c_t_downlink = {\n        .name        = \"Wireless M-Bus, Mode T, 32.768kbps (-f 868.3M -s 1000k)\", // Minimum samplerate = 1 MHz (15 samples of 32kb/s manchester coded)\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = (1000.0 / 32.768), // ~31 us per bit\n        .long_width  = (1000.0 / 32.768),\n        .reset_limit = ((1000.0 / 32.768) * 9), // 9 bit periods\n        .decode_fn   = &m_bus_mode_c_t_callback,\n        .fields      = output_fields,\n};\n\n// Mode S1, S1-m, S2, T2 (Meter RX),    (Meter RX not so interesting)\n// Frequency 868.3 MHz, Bitrate 32.768 kbps, Modulation Manchester FSK\nr_device const m_bus_mode_s = {\n        .name        = \"Wireless M-Bus, Mode S, 32.768kbps (-f 868.3M -s 1000k)\", // Minimum samplerate = 1 MHz (15 samples of 32kb/s manchester coded)\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = (1000.0 / 32.768), // ~31 us per bit\n        .long_width  = (1000.0 / 32.768),\n        .reset_limit = ((1000.0 / 32.768) * 9), // 9 bit periods\n        .decode_fn   = &m_bus_mode_s_callback,\n        .fields      = output_fields,\n};\n\n// Mode C2 (Meter RX)\n// Frequency 869.525 MHz, Bitrate 50 kbps, Modulation Manchester\n//      Note: Not so interesting, as it is only Meter RX\n\n// Mode R2\n// Frequency 868.33 MHz, Bitrate 4.8 kbps, Modulation Manchester FSK\n//      Preamble {0x55, 0x54, 0x76, 0x96} (Format A) (B not supported)\n// Untested stub!!! (Need samples)\nr_device const m_bus_mode_r = {\n        .name        = \"Wireless M-Bus, Mode R, 4.8kbps (-f 868.33M)\",\n        .modulation  = FSK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = (1000.0f / 4.8f / 2),    // ~208 us per bit -> clock half period ~104 us\n        .long_width  = 0,                       // Unused\n        .reset_limit = (1000.0f / 4.8f * 1.5f), // 3 clock half periods\n        .decode_fn   = &m_bus_mode_r_callback,\n        .disabled    = 1, // Disable per default, as it runs on non-standard frequency\n};\n\n// Mode N\n// Frequency 169.400 MHz to 169.475 MHz in 12.5/25/50 kHz bands\n// Bitrate 2.4/4.8 kbps, Modulation GFSK,\n//      Preamble {0x55, 0xF6, 0x8D} (Format A)\n//      Preamble {0x55, 0xF6, 0x72} (Format B)\n//      Note: FDMA currently not supported, but Mode F2 may be usable for 2.4\n// Bitrate 19.2 kbps, Modulation 4 GFSK (9600 BAUD)\n//      Note: Not currently possible with rtl_433\n\n// Mode F2\n// Frequency 433.82 MHz, Bitrate 2.4 kbps, Modulation NRZ FSK\n//      Preamble {0x55, 0xF6, 0x8D} (Format A)\n//      Preamble {0x55, 0xF6, 0x72} (Format B)\n// Untested stub!!! (Need samples)\nr_device const m_bus_mode_f = {\n        .name        = \"Wireless M-Bus, Mode F, 2.4kbps\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 1000.0f / 2.4f, // ~417 us\n        .long_width  = 1000.0f / 2.4f, // NRZ encoding (bit width = pulse width)\n        .reset_limit = 5000,           // ??\n        .decode_fn   = &m_bus_mode_f_callback,\n        .disabled    = 1, // Disable per default, as it runs on non-standard frequency\n};\n"
  },
  {
    "path": "src/devices/markisol.c",
    "content": "/** @file\n    Markisol (a.k.a E-Motion, BOFU, Rollerhouse, BF-30x, BF-415) curtains remote.\n\n    Copyright (C) 2021 Dan Stahlke <dan@stahlke.org>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nMarkisol curtains remote.\n\nProtocol description:\nEach frame starts with:\n    hi 4886us\n    lo 2470us\n    hi 1647us\n    lo 315us\nThen follow 40 bits:\n    zero: hi 670us, lo 320us\n    one : hi 348us, lo 642us\n\nThis is OOK_PULSE_PWM encoding.  The frame is erroneously interpred as a bit (so bitbuffer_t reports\n41 bits rather than 40).  We discard this bit during recording.  The last frame erroneosly picks up\nan extra bit at the end; we ignore this as well.\n\nPacket interpretation:\n    16 bits - unique ID of remote\n    16 bits - channel, zone, and control\n    8  bits - checksum (all bytes, including this one, sum to 1)\n\nThe second pack of 16 bits is interwoven:\n    buf[2] & 0x0f - channel, in the range 1-15\n    buf[2] & 0x20 - bit 0 of zone\n    buf[2] & 0xd0 - bits 0,2,3 of control\n    buf[3] & 0x10 - bit 1 of control\n    buf[3] & 0x80 - bit 1 of zone\n    buf[3] & 0x6f - unknown; for my remotes (buf[3] & 0x6f) == 0x01 always\n*/\n\n#include \"decoder.h\"\n\nstatic int markisol_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t buf[5];\n    uint8_t cksum = 0;\n    int got_proper_row_length = 0;\n    for (int i = 0; i < bitbuffer->num_rows; i++) {\n        decoder_logf(decoder, 1, __func__, \"bits_per_row[%d] = %d\", i, bitbuffer->bits_per_row[i]);\n        if (bitbuffer->bits_per_row[i] == 41 || bitbuffer->bits_per_row[i] == 42) {\n            uint8_t *b = bitbuffer->bb[i];\n            for (int j = 0; j < 5; ++j) {\n                buf[j] = (b[j] << 1) + (b[j + 1] >> 7); // shift stream to discard spurious first bit\n                buf[j] = ~reverse8(buf[j]);\n                cksum += buf[j];\n            }\n            got_proper_row_length = 1;\n            break;\n        }\n    }\n\n    if (!got_proper_row_length)\n        return DECODE_ABORT_EARLY;\n\n    decoder_logf(decoder, 1, __func__, \"%02x %02x %02x %02x %02x cksum=%02x\", buf[0], buf[1], buf[2], buf[3], buf[4], cksum);\n\n    if (cksum != 1)\n        return DECODE_FAIL_MIC;\n\n    int address = (buf[0] << 8) | buf[1];\n    int channel = buf[2] & 0xf;\n    int control = ((buf[2] >> 4) & ~2) | ((buf[3] & 0x10) >> 3);\n    int zone    = ((buf[2] & 0x20) >> 5) + ((buf[3] & 0x80) >> 6) + 1;\n    // buf[3] seems to be always 0x01, 0x11, 0x81, 0x91\n    // ... so there are 6 bits that seem constant (for my remotes)\n\n    char const *const control_strs[] = {\n            \"Limit (0)\", // seems like Limit=0 for channel=1, otherwise Limit=13\n            \"Down (1)\",\n            \"? (2)\",\n            \"H-Down (3)\",\n            \"Confirm (4)\",\n            \"Stop (5)\",\n            \"? (6)\",\n            \"? (7)\",\n            \"? (8)\",\n            \"? (9)\",\n            \"? (10)\",\n            \"? (11)\",\n            \"Up (12)\",\n            \"Limit (13)\",\n            \"H-Up (14)\",\n            \"? (15)\",\n    };\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",          \"Model\",          DATA_STRING, \"Markisol\",\n            \"id\",             \"\",               DATA_FORMAT, \"%04X\", DATA_INT, address,\n            \"control\",        \"Control\",        DATA_STRING, control_strs[control],\n            \"channel\",        \"Channel\",        DATA_INT,    channel,\n            \"zone\",           \"Zone\",           DATA_INT,    zone,\n            \"mic\",            \"Integrity\",      DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"control\",\n        \"channel\",\n        \"zone\",\n        \"mic\",\n        NULL,\n};\n\n// rtl_433 -f 433900000 -X 'n=name,m=OOK_PWM,s=368,l=704,r=10000,g=10000,t=0,y=5628'\n\nr_device const markisol = {\n        .name        = \"Markisol, E-Motion, BOFU, Rollerhouse, BF-30x, BF-415 curtain remote\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 368,\n        .long_width  = 704,\n        .sync_width  = 5628,\n        .gap_limit   = 2000,\n        .reset_limit = 2000,\n        .decode_fn   = &markisol_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/marlec_solar.c",
    "content": "/** @file\n    Decoder for Marlec Solar iBoost+ devices.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n/**\nDecoder for Marlec Solar iBoost+ devices.\n\nNote: work in progress, very similar to Archos-TBH.\n\n- Modulation: FSK PCM\n- Frequency: 868.3MHz\n- 20 us bit time\n- based on TI CC1100\n\nPayload format:\n- Preamble          {32} 0xaaaaaaaa\n- Syncword          {32} 0xd391d391\n- Length            {8}\n- Payload           {n}\n- Checksum          {16} CRC16 poly=0x8005 init=0xffff\n\nUsual payload lengths seem to be 37 (0x25), 105 (0x69), 66 (0x42).\n\nTo get raw data:\n\n    ./rtl_433 -f 868.3M -X 'n=Marlec,m=FSK_PCM,s=20,l=20,g=350,r=600,preamble=aad391d391'\n*/\n\n#include \"decoder.h\"\n\nstatic int marlec_solar_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {\n            /*0xaa, 0xaa, */ 0xaa, 0xaa, // preamble\n            0xd3, 0x91, 0xd3, 0x91       // sync word\n    };\n\n    data_t *data;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int row = 0;\n    // Validate message and reject it as fast as possible : check for preamble\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, sizeof (preamble) * 8);\n\n    if (start_pos == bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_EARLY; // no preamble detected\n    }\n\n    // check min length\n    if (bitbuffer->bits_per_row[row] < 12 * 8) { //sync(4) + preamble(4) + len(1) + data(1) + crc(2)\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t len;\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos + sizeof (preamble) * 8, &len, 8);\n\n    // usual lengths seem to be 37 (0x25), 105 (0x69), 66 (0x42)\n    if (len > 105) {\n        decoder_logf(decoder, 1, __func__, \"packet to large (%d bytes), drop it\", len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t frame[108] = {0}; // arbitrary limit of 1 len byte + 105 data bytes + 2 bytes crc\n    frame[0] = len;\n    // Get frame (len doesn't include the length byte or the crc16 bytes)\n    bitbuffer_extract_bytes(bitbuffer, row,\n            start_pos + (sizeof (preamble) + 1) * 8,\n            &frame[1], (len + 2) * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, frame, (len + 1) * 8, \"frame data\");\n\n    uint16_t crc = crc16(frame, len + 1, 0x8005, 0xffff);\n\n    if ((frame[len + 1] << 8 | frame[len + 2]) != crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC invalid %04x != %04x\",\n                frame[len + 1] << 8 | frame[len + 2], crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    int const SAVED_TODAY     = 0xCA;\n    int const SAVED_YESTERDAY = 0xCB;\n    int const SAVED_LAST_7    = 0xCC;\n    int const SAVED_LAST_28   = 0xCD;\n    int const SAVED_TOTAL     = 0xCE;\n\n    int frame_type  = frame[3];\n    // if (frame[3] == 0x22) {\n    int boost_time  = frame[6]; // boost time remaining (minutes)\n    int solar_off   = frame[7];\n    int tank_hot    = frame[8];\n    int battery_low = frame[13];\n    int heating     = (int16_t)((frame[17]) | (frame[18] << 8));\n    int import_val  = (frame[19]) | (frame[20] << 8) | (frame[21] << 16) | (frame[22] << 24);\n    int saved_type  = frame[25];\n    int saved_val   = (frame[26]) | (frame[27] << 8) | (frame[28] << 16) | (frame[29] << 24);\n    //}\n\n    char frame_str[sizeof(frame) * 2 + 1]   = {0};\n    for (int i = 0; i < len; ++i)\n        sprintf(&frame_str[i * 2], \"%02x\", frame[i + 1]);\n\n    int is_data = frame_type == 0x22;\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Marlec-Solar\",\n            \"boost_time\",       \"\",             DATA_COND, is_data, DATA_INT, boost_time,\n            \"solar_off\",        \"\",             DATA_COND, is_data, DATA_INT, solar_off,\n            \"tank_hot\",         \"\",             DATA_COND, is_data, DATA_INT, tank_hot,\n            \"battery_low\",      \"\",             DATA_COND, is_data, DATA_INT, battery_low,\n            \"heating\",          \"\",             DATA_COND, is_data, DATA_INT, heating,\n            \"import_val\",       \"\",             DATA_COND, is_data, DATA_INT, import_val,\n            \"saved_today\",      \"\",             DATA_COND, is_data && saved_type == SAVED_TODAY, DATA_INT, saved_val,\n            \"saved_yesterday\",  \"\",             DATA_COND, is_data && saved_type == SAVED_YESTERDAY, DATA_INT, saved_val,\n            \"saved_last_7\",     \"\",             DATA_COND, is_data && saved_type == SAVED_LAST_7, DATA_INT, saved_val,\n            \"saved_last_28\",    \"\",             DATA_COND, is_data && saved_type == SAVED_LAST_28, DATA_INT, saved_val,\n            \"saved_total\",      \"\",             DATA_COND, is_data && saved_type == SAVED_TOTAL, DATA_INT, saved_val,\n            \"raw\",              \"Raw data\",     DATA_STRING, frame_str,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"boost_time\",\n        \"solar_off\",\n        \"tank_hot\",\n        \"battery_low\",\n        \"heating\",\n        \"import_val\",\n        \"saved_today\",\n        \"saved_yesterday\",\n        \"saved_last_7\",\n        \"saved_last_28\",\n        \"saved_total\",\n        \"raw\",\n        \"mic\",\n        NULL,\n};\n\nr_device const marlec_solar = {\n        .name        = \"Marlec Solar iBoost+ sensors\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 20,\n        .long_width  = 20,\n        .reset_limit = 300,\n        .decode_fn   = &marlec_solar_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/maverick_et73.c",
    "content": "/** @file\n    Maverick ET-73.\n\n    Copyright (C) 2018 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nMaverick ET-73.\n\nBased on TP12 code\n\n    [00] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [01] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [02] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [03] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [04] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [05] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [06] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [07] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [08] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [09] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [10] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [11] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [12] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n    [13] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n\n\nLayout appears to be:\n\n              II 11 12 22 XX XX\n    [01] {48} 68 00 01 0b 90 fc : 01101000 00000000 00000001 00001011 10010000 11111100\n\n- I = random id\n- 1 = temperature sensor 1 12 bits\n- 2 = temperature sensor 2 12 bits\n- X = unknown, checksum maybe ?\n\n*/\n\n#include \"decoder.h\"\n\nstatic int maverick_et73_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n  int temp1_raw, temp2_raw, row;\n    float temp1_c, temp2_c;\n    uint8_t *bytes;\n    unsigned int device;\n    data_t *data;\n\n    // The device transmits many rows, let's check for 3 matching.\n    row = bitbuffer_find_repeated_row(bitbuffer, 3, 48);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    bytes = bitbuffer->bb[row];\n    if ((!bytes[0] && !bytes[1] && !bytes[2] && !bytes[3])\n            || (bytes[0] == 0xFF && bytes[1] == 0xFF && bytes[2] == 0xFF && bytes[3] == 0xFF))  {\n        return DECODE_ABORT_EARLY; // reduce false positives\n    }\n\n    if (bitbuffer->bits_per_row[row] != 48)\n        return DECODE_ABORT_LENGTH;\n\n    device = bytes[0];\n\n    decoder_log_bitrow(decoder, 1, __func__, bytes, 48, \"\");\n\n    // Repack the nibbles to form a 12-bit field representing the 2's-complement temperatures,\n    //   then right shift by 4 to sign-extend the 12-bit field to a 16-bit integer for float conversion\n    temp1_raw = (int16_t)(bytes[1] << 8 | (bytes[2] & 0xf0)); // uses sign-extend\n    temp1_c   = (temp1_raw >> 4) * 0.1f;\n    temp2_raw = (int16_t)(((bytes[2] & 0x0f) << 12) | bytes[3] << 4); // uses sign-extend\n    temp2_c   = (temp2_raw >> 4) * 0.1f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Maverick-ET73\",\n            \"id\",               \"Random Id\",        DATA_INT, device,\n            \"temperature_1_C\",  \"Temperature 1\",    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp1_c,\n            \"temperature_2_C\",  \"Temperature 2\",    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp2_c,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_1_C\",\n        \"temperature_2_C\",\n        NULL,\n};\n\nr_device const maverick_et73 = {\n        .name        = \"Maverick ET73\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1050,\n        .long_width  = 2050,\n        .gap_limit   = 2200,\n        .reset_limit = 4400, // 4050 us nominal packet gap\n        .decode_fn   = &maverick_et73_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/maverick_et73x.c",
    "content": "/** @file\n    Maverick ET-73x BBQ Sensor.\n\n    Copyright (C) 2016 gismo2004\n    Credits to all users of mentioned forum below!\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nMaverick ET-73x BBQ Sensor.\n\nFCC-Id: TKCET-733\n\nThe thermometer transmits 4 identical messages every 12 seconds at 433.92 MHz,\nusing on-off keying and 2000bps Manchester encoding,\nwith each message preceded by 8 carrier pulses 230 us wide and 5 ms apart.\n\nEach message consists of 26 nibbles (104 bits total) which are again manchester (IEEE) encoded (52 bits)\nFor nibble 24 some devices are sending 0x1 or 0x2 ?\n\nPayload:\n\n- P = 12 bit Preamble (raw 0x55666a, decoded 0xfa8)\n- F =  4 bit device state (2=default; 7=init)\n- T = 10 bit temp1 (degree C, offset by 532)\n- t = 10 bit temp2 (degree C, offset by 532)\n- D = 16 bit digest (over FTt, includes non-transmitted device id renewed on a device reset) gen 0x8810 init 0xdd38\n\n    nibble: 0 1 2 3 4 5 6  7 8 9 10 11 12\n    msg:    P P P F T T Tt t t D D  D  D\n    PRE:12h FLAG:4h TA:10d TB:10d | DIGEST:16h\n\nfurther information can be found here: https://forums.adafruit.com/viewtopic.php?f=8&t=25414\nnote that the mentioned quaternary conversion is actually manchester code.\n*/\n\n#include \"decoder.h\"\n\nstatic int maverick_et73x_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    bitbuffer_t mc = {0};\n\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_EARLY;\n\n    //check correct data length\n    if (bitbuffer->bits_per_row[0] != 104) // 104 raw half-bits, 52 bits payload\n        return DECODE_ABORT_LENGTH;\n\n    //check for correct preamble (0x55666a)\n    if ((bitbuffer->bb[0][0] != 0x55) || bitbuffer->bb[0][1] != 0x66 || bitbuffer->bb[0][2] != 0x6a)\n        return DECODE_ABORT_EARLY; // preamble missing\n\n    // decode the inner manchester encoding\n    bitbuffer_manchester_decode(bitbuffer, 0, 0, &mc, 104);\n\n    // we require 7 bytes 13 nibble rounded up (b[6] highest reference below)\n    if (mc.bits_per_row[0] < 52) {\n        return DECODE_FAIL_SANITY; // manchester_decode fail\n    }\n\n    uint8_t *b = mc.bb[0];\n    int pre    = (b[0] << 4) | (b[1] & 0xf0) >> 4;\n    int flags  = b[1] & 0x0f;\n    int temp1  = (b[2] << 2) | (b[3] & 0xc0) >> 6;\n    int temp2  = (b[3] & 0x3f) << 4 | (b[4] & 0xf0) >> 4;\n    int digest = (b[4] & 0x0f) << 12 | b[5] << 4 | b[6] >> 4;\n\n    float temp1_c = temp1 - 532.0f;\n    float temp2_c = temp2 - 532.0f;\n\n    char const *status = \"unknown\";\n    if (flags == 2)\n        status = \"default\";\n    else if (flags == 7)\n        status = \"init\";\n\n    uint8_t chk[3];\n    bitbuffer_extract_bytes(&mc, 0, 12, chk, 24);\n\n    //digest is used to represent a session. This means, we get a new id if a reset or battery exchange is done.\n    int id = lfsr_digest16(chk, 3, 0x8810, 0xdd38) ^ digest;\n\n    decoder_logf(decoder, 1, __func__, \"pre %03x, flags %x, t1 %d, t2 %d, digest %04x, chk_data %02x%02x%02x, digest xor'ed: %04x\",\n                pre, flags, temp1, temp2, digest, chk[0], chk[1], chk[2], id);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                     DATA_STRING, \"Maverick-ET73x\",\n            \"id\",               \"Session_ID\",           DATA_INT,    id,\n            \"status\",           \"Status\",               DATA_STRING, status,\n            \"temperature_1_C\",  \"TemperatureSensor1\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp1_c,\n            \"temperature_2_C\",  \"TemperatureSensor2\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp2_c,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"status\",\n        \"temperature_1_C\",\n        \"temperature_2_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const maverick_et73x = {\n        .name        = \"Maverick ET-732/733 BBQ Sensor\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 230,\n        .long_width  = 0, //not used\n        .reset_limit = 4000,\n        //.reset_limit = 6000, // if pulse_slicer_manchester_zerobit implements gap_limit\n        //.gap_limit   = 1000, // if pulse_slicer_manchester_zerobit implements gap_limit\n        .decode_fn   = &maverick_et73x_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/maverick_xr30.c",
    "content": "/** @file\n    Maverick XR-30 BBQ Sensor.\n\n    Copyright (C) 2022 jbfunk\n    Heavily derived from Maverick ET-73x BBQ Sensor (maverick_et73x.c), Copyright (C) 2016 gismo2004\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nMaverick XR-30 BBQ Sensor.\n\nThe thermometer transmits 4 identical messages every 12 seconds at 433.92 MHz,\n\nEach message consists of 26 nibbles (104 bits total) but the first (non-data) bit (1) is getting dropped sometimes in reception, so for analysis the payload is shifted 7 bits left to align the bytes (or 8 bits if 0xaa is observed rather than 0x55 as the first byte received)\n\nPayload:\n\n- P = 32 bit preamble (0xaaaaaaaa; 7 or 8 bits shifted left for analysis)\n- S = 32 bit sync-word (0xd391d391)\n- F =  4 bit device state (0=default; 5=init)\n- T = 10 bit temp1 (degree C, offset by 532)\n- t = 10 bit temp2 (degree C, offset by 532)\n- D = 16 bit digest (over FTt, includes non-transmitted device id renewed on a device reset) gen 0x8810 init 0x0d42\n\n    byte (after shift):       0   1   2   3   4   5     6     7     8     9     10    11\n    nibble (after shift):     0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23\n    msg:                  P P P P P P P P S S S S S  S  S  S  F  T  T  Tt t  t  D  D  D  D\n    PRE:32h SYNC: 32h FLAG:4h T:10d t:10d | DIGEST:16h\n\n*/\n\n#include \"decoder.h\"\n\nstatic int maverick_xr30_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_EARLY;\n\n    //check correct data length\n    if (bitbuffer->bits_per_row[0] != 104) // 104 bits\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t b[12];\n    //check for correct preamble/sync (0xaaaaaad391d391)\n    if (bitbuffer->bb[0][0] == 0x55) {\n        bitbuffer_extract_bytes(bitbuffer, 0, 7, b, 12 * 8); // shift in case first bit was not received properly\n    } else if (bitbuffer->bb[0][0] == 0xaa) {\n        bitbuffer_extract_bytes(bitbuffer, 0, 8, b, 12 * 8); // shift in case first bit was received properly\n    } else {\n        return DECODE_ABORT_EARLY; // preamble/sync missing\n    }\n    if (b[0] != 0xaa || b[1] != 0xaa || b[2] != 0xaa || b[3] != 0xd3 || b[4] != 0x91 || b[5] != 0xd3 || b[6] != 0x91)\n        return DECODE_ABORT_EARLY; // preamble/sync missing\n\n    int sync   = b[3] << 24 | b[4] << 16 | b[5] << 8 | b[6];\n    int flags  = (b[7] & 0xf0) >> 4;\n    int temp1  = (b[7] & 0x0f) << 6 | (b[8] & 0xfc) >> 2;\n    int temp2  = (b[8] & 0x03) << 8 | b[9];\n    int digest = b[10] << 8 | b[11];\n\n    float temp1_c = temp1 - 532.0f;\n    float temp2_c = temp2 - 532.0f;\n\n    char const *status = \"unknown\";\n    if (flags == 0)\n        status = \"default\";\n    else if (flags == 5)\n        status = \"init\";\n\n    //digest is used to represent a session. This means, we get a new id if a reset or battery exchange is done.\n    int id = lfsr_digest16(&b[7], 3, 0x8810, 0x0d42) ^ digest;\n\n    decoder_logf(decoder, 1, __func__, \"sync %08x, flags %x, t1 %d, t2 %d, digest %04x, chk_data %02x%02x%02x, digest xor'ed: %04x\",\n                sync, flags, temp1, temp2, digest, b[7], b[8], b[9], id);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                     DATA_STRING, \"Maverick-XR30\",\n            \"id\",               \"Session_ID\",           DATA_INT,    id,\n            \"status\",           \"Status\",               DATA_STRING, status,\n            \"temperature_1_C\",  \"TemperatureSensor1\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp1_c,\n            \"temperature_2_C\",  \"TemperatureSensor2\",   DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp2_c,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"status\",\n        \"temperature_1_C\",\n        \"temperature_2_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const maverick_xr30 = {\n        .name        = \"Maverick XR-30 BBQ Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 360,\n        .long_width  = 360,\n        .reset_limit = 4096,\n        .decode_fn   = &maverick_xr30_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/maverick_xr50.c",
    "content": "/** @file\n    Maverick XR-50 BBQ Sensor Europe Version.\n\n    Copyright (C) 2025 Luca Pinasco\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nMaverick XR-50 BBQ Sensor.\n\nExamples:\n\n    555555555555555a5545ba8100de0008343e9e001234821e000b543e9e0014a0ce4d401555400000\n    555555555555555a5545ba8100de0008347d1e0008347d1e0008347d1e0008347d02c01555400000\n    555555555555555a5545ba86811a5c2d5cc13a5e2d5cc1d85c89743e985b89883e80801555400\n\nalign preamble sync word:\n\n    PP PP PP PP PP SS SS SS SS\n    .. aa aa aa aa d2 aa 2d d4\n\nData layout after sync word:\n\n    Byte Position  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 TT TT TT TT TT TT\n    Sample        34 08 d2 e1 6a e6 09 d2 f1 6a e6 0e c2 e4 4b a1 f4 c2 dc 4c 41 f4 04 00 aa aa 00 0\n    Sample        08 06 f0 00 41 a1 f4 f0 00 91 a4 10 f0 00 5a a1 f4 f0 00 a5 06 72 6a 00 aa aa 00 00 00\n    Sample        08 06 f0 00 41 a3 e8 f0 00 41 a3 e8 f0 00 41 a3 e8 f0 00 41 a3 e8 16 00 aa aa 00 00 00\n    Sample        08 06 f0 00 41 a1 f4 f0 00 91 a4 10 f0 00 5a a1 f4 f0 00 a5 06 72 6a 00 aa aa 00 00 00 [no probe]\n    Layout        II II FT TT HH HL LL FT TT HH HL LL FT TT HH HL LL FT TT HH HL LL CC TT TT TT TT TT TT\n                       [Probe 1      ][Probe 2      ][Probe 3      ][Probe 4      ]\n\n- II:{16} Probably ID, 0x0806 or 0x3408 from above samples\n- F:{4} Some flags, depends on the presence of probe, if temp below, within or above range\n    0xF = No probe, in that case, the TTT = 0x000\n    0xD = Temp below low temp value\n    0xC = Temp within the range, between low and high temp values set.\n    0x? all flags not yet guessed\n\n- TTT:{12} Actual probe Temperature in Celsius, offset 500, scale 10\n- HHH:{12} High Temperature set, Celsius, offset 500, scale 10\n- LLL:{12} Low Temperature set, Celsius, offset 500, scale 10\n- CC :{8}  CRC 8, poly 0x31, init 0x00, final XOR 0x00, from all previous 22 bytes.\n*/\nstatic int maverick_xr50_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = { 0xd2, 0xaa, 0x2d, 0xd4 };\n\n    uint8_t b[23];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n\n    int msg_len = bitbuffer->bits_per_row[0];\n    int start_pos = bitbuffer_search(bitbuffer,0, 0, preamble, sizeof(preamble) * 8);\n\n    if (start_pos  >= msg_len ) {\n        decoder_log(decoder, 3, __func__, \"Sync word not found\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if ((msg_len - start_pos ) < 184 ) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    start_pos += sizeof(preamble) * 8;\n\n    // Need 23 bytes, last bit are useless trailing bits\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, b, 23 * 8);\n\n    if (crc8(b, 23, 0x31, 0x00)) {\n        decoder_logf(decoder, 1, __func__, \"CRC Error, found: %02x, expected: %02x\", b[22], crc8(b, 22, 0x31, 0x00));\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, b, 23 * 8, \"MSG\");\n\n    int id = b[0] << 8 | b[1];\n\n    int p_1_flags    = (b[2] & 0xf0) >> 4;\n    int p_1_temp_raw = ((b[2] & 0x0f) << 8) | b[3];\n    int p_1_high_raw = (b[4] << 4) | ((b[5] & 0xf0) >> 4);\n    int p_1_low_raw  = ((b[5] & 0x0f) << 8) | b[6];\n\n    int p_2_flags    = (b[7] & 0xf0) >> 4;\n    int p_2_temp_raw = ((b[7] & 0x0f) << 8) | b[8];\n    int p_2_high_raw = (b[9] << 4) | ((b[10] & 0xf0) >> 4);\n    int p_2_low_raw  = ((b[10] & 0x0f) << 8) | b[11];\n\n    int p_3_flags    = (b[12] & 0xf0) >> 4;\n    int p_3_temp_raw = ((b[12] & 0x0f) << 8) | b[13];\n    int p_3_high_raw = (b[14] << 4) | ((b[15] & 0xf0) >> 4);\n    int p_3_low_raw  = ((b[15] & 0x0f) << 8) | b[16];\n\n    int p_4_flags    = (b[17] & 0xf0) >> 4;\n    int p_4_temp_raw = ((b[17] & 0x0f) << 8) | b[18];\n    int p_4_high_raw = (b[19] << 4) | ((b[20] & 0xf0) >> 4);\n    int p_4_low_raw  = ((b[20] & 0x0f) << 8) | b[21];\n\n    float p1_temp     = (p_1_temp_raw - 500) * 0.1f;\n    float p1_set_high = (p_1_high_raw - 500) * 0.1f;\n    float p1_set_low  = (p_1_low_raw - 500) * 0.1f;\n\n    float p2_temp     = (p_2_temp_raw - 500) * 0.1f;\n    float p2_set_high = (p_2_high_raw - 500) * 0.1f;\n    float p2_set_low  = (p_2_low_raw - 500) * 0.1f;\n\n    float p3_temp     = (p_3_temp_raw - 500) * 0.1f;\n    float p3_set_high = (p_3_high_raw - 500) * 0.1f;\n    float p3_set_low  = (p_3_low_raw - 500) * 0.1f;\n\n    float p4_temp     = (p_4_temp_raw - 500) * 0.1f;\n    float p4_set_high = (p_4_high_raw - 500) * 0.1f;\n    float p4_set_low  = (p_4_low_raw - 500) * 0.1f;\n\n    /* clang-format off */\n   data_t *data = data_make(\n            \"model\",                \"\",                 DATA_STRING, \"Maverick-XR50\",\n            \"id\",                   \"\",                 DATA_FORMAT, \"%04x\",    DATA_INT, id,\n            \"probe_1_flags\",        \"Flags Probe 1\",    DATA_FORMAT, \"%1x\",     DATA_INT, p_1_flags,\n            \"temperature_1_C\",      \"Temperature 1\",    DATA_COND, p_1_temp_raw != 0, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p1_temp,\n            \"setpoint_high_1_C\",    \"Setpoint 1 high\",  DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p1_set_high,\n            \"setpoint_low_1_C\",     \"Setpoint 1 low\",   DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p1_set_low,\n            \"probe_2_flags\",        \"Flags Probe 2\",    DATA_FORMAT, \"%1x\",     DATA_INT, p_2_flags,\n            \"temperature_2_C\",      \"Temperature 2\",    DATA_COND, p_2_temp_raw != 0, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p2_temp,\n            \"setpoint_high_2_C\",    \"Setpoint 2 high\",  DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p2_set_high,\n            \"setpoint_low_2_C\",     \"Setpoint 2 low\",   DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p2_set_low,\n            \"probe_3_flags\",        \"Flags Probe 3\",    DATA_FORMAT, \"%1x\",     DATA_INT, p_3_flags,\n            \"temperature_3_C\",      \"Temperature 3\",    DATA_COND, p_3_temp_raw != 0, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p3_temp,\n            \"setpoint_high_3_C\",    \"Setpoint 3 high\",  DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p3_set_high,\n            \"setpoint_low_3_C\",     \"Setpoint 3 low\",   DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p3_set_low,\n            \"probe_4_flags\",        \"Flags Probe 4\",    DATA_FORMAT, \"%1x\",     DATA_INT, p_4_flags,\n            \"temperature_4_C\",      \"Temperature 4\",    DATA_COND, p_4_temp_raw != 0, DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p4_temp,\n            \"setpoint_high_4_C\",    \"Setpoint 4 high\",  DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p4_set_high,\n            \"setpoint_low_4_C\",     \"Setpoint 4 low\",   DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, p4_set_low,\n            \"mic\",                  \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL\n    );\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"probe_1_flags\",\n        \"temperature_1_C\",\n        \"setpoint_high_1_C\",\n        \"setpoint_low_1_C\",\n        \"probe_2_flags\",\n        \"temperature_2_C\",\n        \"setpoint_high_2_C\",\n        \"setpoint_low_2_C\",\n        \"probe_3_flags\",\n        \"temperature_3_C\",\n        \"setpoint_high_3_C\",\n        \"setpoint_low_3_C\",\n        \"probe_4_flags\",\n        \"temperature_4_C\",\n        \"setpoint_high_4_C\",\n        \"setpoint_low_4_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const maverick_xr50 = {\n        .name        = \"Maverick XR-50 BBQ Sensor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 107,\n        .long_width  = 107,\n        .reset_limit = 2200,\n        .decode_fn   = &maverick_xr50_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/mebus.c",
    "content": "/** @file\n    Mebus 433.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nMebus 433.\n\n@todo Documentation needed.\n*/\nstatic int mebus433_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitrow_t *bb = bitbuffer->bb;\n    int16_t temp;\n    int8_t hum;\n    uint8_t address;\n    uint8_t channel;\n    uint8_t battery;\n    uint8_t unknown1;\n    uint8_t unknown2;\n    data_t *data;\n\n    // TODO: missing packet length validation\n\n    if (bb[0][0] == 0 && bb[1][4] !=0 && (bb[1][0] & 0x60) && bb[1][3]==bb[5][3] && bb[1][4] == bb[12][4]) {\n\n        address = bb[1][0] & 0x1f;\n\n        channel = ((bb[1][1] & 0x30) >> 4) + 1;\n        // Always 0?\n        unknown1 = (bb[1][1] & 0x40) >> 6;\n        battery  = bb[1][1] & 0x80;\n\n        // Upper 4 bits are stored in nibble 1, lower 8 bits are stored in nibble 2\n        // upper 4 bits of nibble 1 are reserved for other usages.\n        temp = (int16_t)((uint16_t)(bb[1][1] << 12) | bb[1][2] << 4);\n        temp = temp >> 4;\n        // lower 4 bits of nibble 3 and upper 4 bits of nibble 4 contains\n        // humidity as decimal value\n        hum = (bb[1][3] << 4 | bb[1][4] >> 4);\n\n        // Always 0b1111?\n        unknown2 = (bb[1][3] & 0xf0) >> 4;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Mebus-433\",\n                \"id\",               \"Address\",      DATA_INT,    address,\n                \"channel\",          \"Channel\",      DATA_INT,    channel,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !!battery,\n                \"unknown1\",         \"Unknown 1\",    DATA_INT,    unknown1,\n                \"unknown2\",         \"Unknown 2\",    DATA_INT,    unknown2,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp * 0.1f,\n                \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, hum,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return DECODE_ABORT_EARLY;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"unknown1\",\n        \"unknown2\",\n        \"temperature_C\",\n        \"humidity\",\n        NULL,\n};\n\nr_device const mebus433 = {\n        .name        = \"Mebus 433\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 800,  // guessed, no samples available\n        .long_width  = 1600, // guessed, no samples available\n        .gap_limit   = 2400,\n        .reset_limit = 6000,\n        .decode_fn   = &mebus433_decode,\n        .disabled    = 1, // add docs, tests, false positive checks and then re-enable\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/megacode.c",
    "content": "/** @file\n    Decoder for Linear Megacode Garage & Gate Remotes.\n\n    Copyright (C) 2021 Aaron Spangler <aaron777@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nDecoder for Linear Megacode Garage & Gate Remotes. (Fixed/non-rolling code).\n\nA Linear Megacode transmission consists of 24 bit frames starting with the\nmost significant bit and ending with the least. Each of the 24 bit frames is\n6 milliseconds wide and always contains a single 1 millisecond pulse. A frame\nwith more than 1 pulse or a frame with no pulse is invalid and a receiver\nshould reset and begin watching for another start bit.\n\nThe position of the pulse within the bit frame determines if it represents a\nbinary 0 or binary 1. If the pulse is within the first half of the frame, it\nrepresents binary 0. The second half of the frame represents a binary 1.\n\nReferences:\n- https://github.com/aaronsp777/megadecoder/blob/main/Protocol.md\n- https://wiki.cuvoodoo.info/doku.php?id=megacode\n- https://fccid.io/EF4ACP00872/Test-Report/Megacode-2-112615.pdf\n\nExample:\n\n    raw: 8DF78A\n    facility: 1 id: 48881 button: 2\n    bits: 10010000010000010000000010000010010000000010000010000010000010000010010000000010000010000010000010010000010000010000000010010000000010010... (up to 148 bits)\n\n    $ rtl_433 -g 100 -f 318M -X \"n=Megacode,m=OOK_PCM,s=1000,l=1000,g=8000,r=10000\"\n\n*/\n\n#include \"decoder.h\"\n\nstatic int megacode_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row = bitbuffer_find_repeated_row(bitbuffer, 1, 144);\n    if (row < 0)\n        return DECODE_ABORT_LENGTH;\n    int l = bitbuffer->bits_per_row[row];\n    if (l < 136 || l > 148)\n        return DECODE_ABORT_LENGTH;\n\n    uint32_t raw      = 0;\n    int frame_counter = 0;\n    uint8_t *b        = bitbuffer->bb[row];\n\n    for (int i = 0; i < l; i++) {\n        if ((b[i / 8] << (i % 8)) & 0x80) {\n            if ((i + 4) % 6 > 2)\n                raw |= 0x800000 >> ((i + 4) / 6);\n            frame_counter++;\n        }\n    }\n\n    if (frame_counter != 24)\n        return DECODE_FAIL_SANITY;\n\n    int facility = (raw >> 19) & 0xf;\n    int id       = (raw >> 3) & 0xffff;\n    int button   = raw & 0x7;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",    \"\",               DATA_STRING, \"Megacode-Remote\",\n            \"id\",       \"Transmitter ID\", DATA_INT,    id,\n            \"raw\",      \"Raw\",            DATA_FORMAT, \"%06X\", DATA_INT, raw,\n            \"facility\", \"Facility Code\",  DATA_INT,    facility,\n            \"button\",   \"Button\",         DATA_INT,    button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"raw\",\n        \"facility\",\n        \"button\",\n        NULL,\n};\n\nr_device const megacode = {\n        .name        = \"Linear Megacode Garage/Gate Remotes\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 1000,\n        .long_width  = 1000,\n        .gap_limit   = 9000,\n        .reset_limit = 20000,\n        .decode_fn   = &megacode_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/missil_ml0757.c",
    "content": "/** @file\n    Missil ML0757 weather station with temperature, wind and rain sensor.\n\n    Copyright (C) 2020 Marius Lindvall\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nMissil ML0757 weather station with temperature, wind and rain sensor.\n\nThe unit sends two different alternating packets, one for temperature and one\nfor rainfall and wind. All packets are 40 bits and are transferred 9 times.\nPacket structure appears to be as follows:\n\n                         BIT\n          0   1   2   3   4   5   6   7   8\n      0x0 +---+---+---+---+---+---+---+---+\n          | Device ID                     |\n      0x1 +---+---+---+---+---+---+---+---+\n    B     |BAT| ? | ? | ? | ? |RWP| ? | ? | <-- FLAGS BYTE\n    Y 0x2 +---+---+---+---+---+---+---+---+\n    T     | Data field 1                 >|\n    E 0x3 +---+---+---+---+---+---+---+---+\n          |<Data field 1  | Data field 2 >|\n      0x4 +---+---+---+---+---+---+---+---+\n          |<Data field 2  | 1 | 1 | 1 | 1 |\n      0x5 +---+---+---+---+---+---+---+---+\n\nWhen flag bit RWP is not set, data field 1 is (temp in °C * 10) as a signed\n12-bit integer, and data field 2 (8 bits) is unknown.\n\nWhere when bit RWP is set, data field 1 is accumulated rainfall in number of\nsteps as a signed 12-bit integer, where each step is 0.45 mm of rain, but when\nthe sign bit flips, the counter appears to reset to 0. Data field 2 is wind\nspeed as an 8 bit integer where 0x00 = 0 km/h, 0x80 = 1.4 km/h, 0xC0 = 2.8 km/h,\nand any other value is ((value + 2) * 1.4) km/h. I know this doesn't intuitively\nmake sense, but it's what my testing has come up with and it matches what is\nshown on the display.\n\nThe BAT flag is set if the transmitter has low battery. The other flags could\nnot be determined.\n\nPackets are sent in sequences of type temp, rain+wind, temp, rain+wind, etc.\nwith ~36-37 seconds between each packet.\n\nAll packets begin with an empty row in addition to the 9 rows of repeated data.\n*/\n\n#include \"decoder.h\"\n\n#define MISSIL_ML0757_FLAG_RWP  0x04 // Rain+Wind packet flag\n#define MISSIL_ML0757_FLAG_BAT  0x80 // Battery low flag\n\nstatic int missil_ml0757_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;\n    int id, flags, f12bit, f8bit;\n    float temp_c, rainfall, wind_kph;\n    int flag_bat, flag_rwp;\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 5, 40);\n    if (r < 0)\n        return DECODE_ABORT_EARLY;\n\n    b = bitbuffer->bb[r];\n\n    if (bitbuffer->bits_per_row[0] > 0)\n        return DECODE_ABORT_EARLY; // First row must be 0-length\n\n    if (bitbuffer->bits_per_row[r] > 40)\n        return DECODE_ABORT_LENGTH; // Message too long\n\n    if ((b[4] & 0x0F) != 0x0F)\n        return DECODE_ABORT_EARLY; // Tail bits not 1111\n\n    // Read fields from sensor data\n    id     = b[0];\n    flags  = b[1];\n    f12bit = (int16_t)((b[2] << 4) | (b[3] >> 4)) & 0xFFF;\n    f8bit  = (((b[3] & 0x0F) << 4) | (b[4] >> 4)) & 0xFF;\n\n    // Parse flags\n    flag_bat = flags & MISSIL_ML0757_FLAG_BAT;\n    flag_rwp = flags & MISSIL_ML0757_FLAG_RWP;\n\n    // Parse temperature\n    if (f12bit & 0x800) // Sign bit set; negative temperature\n        temp_c = (0x1000 - f12bit) * -0.1f;\n    else\n        temp_c = f12bit * 0.1f;\n\n    // Parse rainfall\n    rainfall = f12bit * 0.45f;\n\n    // Parse wind speed\n    switch (f8bit) {\n    case 0x00: wind_kph = 0.0f; break;\n    case 0x80: wind_kph = 1.4f; break;\n    case 0xC0: wind_kph = 2.8f; break;\n    default: wind_kph = (f8bit + 2) * 1.4f; break;\n    }\n\n    if (flag_rwp) { // Rainwall and wind\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Missil-ML0757\",\n                \"id\",               \"ID\",           DATA_INT,    id,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !flag_bat,\n                \"rain_mm\",          \"Total rain\",   DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, rainfall,\n                \"wind_avg_km_h\",    \"Wind speed\",   DATA_FORMAT, \"%.2f km/h\", DATA_DOUBLE, wind_kph,\n                NULL);\n        /* clang-format on */\n    }\n    else { // Temperature\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Missil-ML0757\",\n                \"id\",               \"ID\",           DATA_INT,    id,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !flag_bat,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                NULL);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"wind_avg_km_h\",\n        \"rain_mm\",\n        NULL,\n};\n\nr_device const missil_ml0757 = {\n        .name        = \"Missil ML0757 weather station\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 975,\n        .long_width  = 1950,\n        .gap_limit   = 2500,\n        .reset_limit = 4500,\n        .tolerance   = 100,\n        .decode_fn   = &missil_ml0757_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/mueller_hotrod.c",
    "content": "/** @file\n    Mueller Hot Rod water meter.\n\n    Copyright (C) 2024 Christian W. Zuckschwerdt <zany@triq.net>\n    Copyright (C) 2024 Bruno OCTAU (ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nMueller Hot Rod water meter.\n\nS.a. #2719 Decoder desired for Mueller Systems Hot Rod transmitter (water meter), open by \"howardtopher\", related to Hod Rod v2 transmitter\nS.a. #2752 Decoder for Mueller Hot Rod V1 Water Meter Transmitter, open by \"dolai1\", related to Hod Rod v1 transmitter\n\nBoth version v1 and v2 protocols look same format.\n\nFlex decoder:\n\n    rtl_433 -X 'n=hotrod,m=FSK_PCM,s=26,l=26,r=2500,preamble=feb100'\n\nRaw RF Signal:\n\n    {136}ffffffffffd62002884cc2c092f1201f80\n    {135}fff555555fd62002884cc2c092f1201f80\n    {135}ffeaaaaabfac40051099858125e2403f00\n    {134}000002aabfac40051099858125e54015c0\n    {134}00000000000040051099858125e54015c0\n\nThe preamble is not stable because of the GFSK encoding not well handle by rtl_433.\n\nData layout:\n    PP PP PP YY YY YY  0  1  2  3  4  5  6  7  8  9 10 11 ...\n    aa aa aa fe b1 00 II II II II GG GG GG GF CC ?? ?? ?? ...\n\n- PP: {xx} Preamble 0xaaaaa but not stable, see RF samples above.\n- YY: {24} Sync word 0xfeb100\n- II: {32} Device ID\n- GG: {28} 7 niblles BCD water cumulative volume, US liquid gallon\n- FF: {4} Flag, protocol version, battery_low ??? to be confirmed later.\n- CC: {8} CRC-8/UTI, poly 0x07, init 0x00, xorout 0x55\n- ??: extra trailing bit not used, related to GFSK/FSK encoding.\n\n*/\n\nstatic int mueller_hotrod_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xfe, 0xb1, 0x00};\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_log(decoder, 2, __func__, \"Row check failed\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // 3 byte for the sync word + 9 byte for data = 96 bits in total, too short if less\n    if (bitbuffer->bits_per_row[0] < 96) {\n        decoder_log(decoder, 2, __func__, \"Len before preamble check failed\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Find the preamble\n    unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n    if ((pos + 9 * 8) >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"Len after preamble check failed\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t b[9];\n    bitbuffer_extract_bytes(bitbuffer, 0, pos + 24, b, 72); // 9 x 8 bit\n    decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, \"MSG\");\n\n    // poly 0x07, init 0x00, xorout 0x55\n    int crc_calc = crc8(b, 8, 0x07, 0x00) ^ 0x55;\n    if (crc_calc != b[8]) {\n        decoder_logf(decoder, 2, __func__, \"CRC check failed : %0x %0x\", b[8], crc_calc);\n        return 0;\n    }\n\n    char id_str[16];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x%02x\", b[0], b[1], b[2], b[3]);\n\n    // 7 nibbles BCD (28 bit) = volume_gal\n    int volume = ((b[4] & 0xf0) >> 4)*1000000+(b[4] & 0x0f)*100000+((b[5] & 0xf0) >> 4)*10000+(b[5] & 0x0f)*1000+((b[6] & 0xf0) >> 4)*100+(b[6] & 0x0f)*10+((b[7] & 0xf0) >> 4);\n    int flag   = b[7] & 0x0f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",       \"\",          DATA_STRING, \"Mueller-HotRod\",\n            \"id\",          \"\",          DATA_STRING, id_str,\n            \"volume_gal\",  \"Volume\",    DATA_FORMAT, \"%u gal\", DATA_INT, volume,\n            \"flag\",        \"Flag\",      DATA_FORMAT, \"%x\", DATA_INT, flag,\n            \"mic\",         \"Integrity\", DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"volume_gal\",\n        \"flag\",\n        \"mic\",\n        NULL,\n};\n\nr_device const mueller_hotrod = {\n        .name        = \"Mueller Hot Rod water meter\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 26,\n        .long_width  = 26,\n        .reset_limit = 2500,\n        .decode_fn   = &mueller_hotrod_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/neptune_r900.c",
    "content": "/** @file\n    Neptune R900 flow meter decoder.\n\n    Copyright (C) 2022 Jeffrey S. Ruby\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n#include \"decoder.h\"\n\n/** @fn int neptune_r900_decode(r_device *decoder, bitbuffer_t * bitbuffer)\nNeptune R900 flow meter decoder.\n\nThe product site lists E-CODER R900 amd MACH10 R900. Not sure if this decodes both.\n\nTested on E-CODER R900 capture files.\n\nThe device uses PPM encoding,\n- 1 is encoded as 30 us pulse.\n- 0 is encoded as 30 us gap.\n\nA gap longer than 320 us is considered the end of the transmission.\n\nThe device sends a transmission every xx seconds.\n\nA transmission starts with a preamble of 0xAA,0xAA,0xAA,0xAB,0x52,0xCC,0xD2\nBut, it is \"zero\" based, so if you insert a zero bit to the beginning of the bitstream,\nthe preamble is:\n- 0x55,0x55,0x55,0x55,0xA9,0x66,0x69,0x65\n\nIt should be sufficient to find the start of the data after 0x55,0x55,0x55,0xA9,0x66,0x69,0x65.\n\nOnce the payload is decoded, the message is as follows:\n(from https://github.com/bemasher/rtlamr/wiki/Protocol#r900-consumption-message)\n- ID - 32 bits\n- Unkn1 - 8 bits\n- NoUse - 6 bits\n- BackFlow - 6 bits    // found this to be 2 bits in my case ???\n- Consumption - 24 bits\n- Unkn3 - 2 bits\n- Leak - 4 bits\n- LeakNow - 2 bits\n\nSome addidtional information here: https://github.com/bemasher/rtlamr/issues/29\n\nAfter decoding the bitstream into 104 bits of payload, the layout appears to be:\n\nData layout:\n\n    IIIIIIII IIIIIIII IIIIIIII IIIIIIII UUUUUUUU ???NNNBB CCCCCCCC CCCCCCCC CCCCCCCC UU?TTTLL EEEEEEEE EEEEEEEE EEEEEEEE\n\n- I: 32-bit little-endian id\n- U:  8-bit Unknown1\n- N:  6-bit NoUse (3 bits)\n- B:  2-bit backflow flag\n- C: 24-bit Consumption Data, might be 1/10 gallon units\n- U:  2-bit Unknown3\n- T:  4-bit days of leak mapping (3 bits)\n- L:  2-bit leak flag type\n- E: 24-bit extra data????\n*/\n\nint const map16to6[16] = { -1, -1, -1, 0, -1, 1, 2, -1, -1, 5, 4, -1, 3, -1, -1, -1 };\n\nstatic void decode_5to8(bitbuffer_t *bytes, uint8_t *base6_dec)\n{\n    // is there a better way to convert groups of 5 bits to groups of 8 bits?\n    for (int i=0; i < 21; i++) {\n        uint8_t data = base6_dec[i];\n        bitbuffer_add_bit(bytes, data >> 4 & 0x01);\n        bitbuffer_add_bit(bytes, data >> 3 & 0x01);\n        bitbuffer_add_bit(bytes, data >> 2 & 0x01);\n        bitbuffer_add_bit(bytes, data >> 1 & 0x01);\n        bitbuffer_add_bit(bytes, data >> 0 & 0x01);\n    }\n}\n\nstatic int neptune_r900_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // partial preamble and sync word shifted by 1 bit\n    uint8_t const preamble[] = {0x55, 0x55, 0x55, 0xa9, 0x66, 0x69, 0x65};\n    int const preamble_length = sizeof(preamble) * 8;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Search for preamble and sync-word\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble, preamble_length);\n\n    // check that (bitbuffer->bits_per_row[0]) greater than (start_pos+sizeof(preamble)*8+168)\n    if (start_pos + preamble_length + 168 > bitbuffer->bits_per_row[0])\n        return DECODE_ABORT_LENGTH;\n\n    // No preamble detected\n    if (start_pos == bitbuffer->bits_per_row[0])\n        return DECODE_ABORT_EARLY;\n\n    decoder_logf(decoder, 1, __func__, \"Neptune R900 detected, buffer is %d bits length\", bitbuffer->bits_per_row[0]);\n\n    // Remove preamble and sync word, keep whole payload\n    uint8_t bits[21]; // 168 bits\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos + preamble_length, bits, 21 * 8);\n\n    uint8_t *bb = bitbuffer->bb[0];\n    bitbuffer_t bytes = {0};\n    uint8_t base6_dec[21] = {0};\n    int count = 0;\n\n    /*\n     * Each group of four of these chips must be interpreted as a digit in base 6\n     *             according to the following mapping:\n     * 0011 -> 0\n     * 0101 -> 1\n     * 0110 -> 2\n     * 1100 -> 3\n     * 1010 -> 4\n     * 1001 -> 5\n    */\n    // create a pair of char bit array of '0' and '1' for each base6 byte\n    for (uint8_t k = start_pos+preamble_length; k < start_pos + preamble_length + 168; k=k+8) {\n        uint8_t byte = bitrow_get_byte(bb, k);\n        int highNibble = map16to6[(byte >> 4 & 0xF)];\n        int lowNibble = map16to6[(byte & 0xF)];\n\n        if (highNibble < 0 || lowNibble < 0)\n            return DECODE_ABORT_EARLY;\n\n        base6_dec[count] = (6 * highNibble) + lowNibble;\n        count++;\n    }\n\n    // convert the base6 integers above into binary bits for decoding data\n    // this reduces the 168 bits to 105 bits (104 bits??)\n    // the first 80 bits are used in this decoder, the last 24 bits are decoded as extra\n    decode_5to8(&bytes, base6_dec);\n    uint8_t b[13]; // 104 bits\n    bitbuffer_extract_bytes(&bytes, 0, 0, b, sizeof(b)*8);\n\n    // decode the data\n\n    // meter_id 32 bits\n    uint32_t meter_id = ((uint32_t)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | (b[3]);\n    //Unkn1 8 bits\n    int unkn1 = b[4];\n    //Unkn2 3 bits\n    int unkn2 = b[5] >> 5;\n    //NoUse 3 bits\n    // 0 = 0 days\n    // 1 = 1-2 days\n    // 2 = 3-7 days\n    // 3 = 8-14 days\n    // 4 = 15-21 days\n    // 5 = 22-34 days\n    // 6 = 35+ days\n    int nouse = ((b[5] >> 1)&0x0F) >> 1;\n    //BackFlow 2 bits\n    // During the last 35 days\n    // 0 = none\n    // 1 = low\n    // 2 = high\n    int backflow = b[5]&0x03;\n    //Consumption 24 bits\n    int consumption = (b[6] << 16) | (b[7] << 8) | (b[8]);\n    //Unkn3 2 bits + 1 bit ???\n    int unkn3 = b[9] >> 5;\n    //Leak 3 bits\n    // 0 = 0 days\n    // 1 = 1-2 days\n    // 2 = 3-7 days\n    // 3 = 8-14 days\n    // 4 = 15-21 days\n    // 5 = 22-34 days\n    // 6 = 35+ days\n    int leak = ((b[9] >> 1)&0x0F) >> 1;\n    //LeakNow 2 bits\n    // During the last 24 hours\n    // 0 = none\n    // 1 = low (intermittent leak) water used for at least 50 of the 96 15-minute intervals\n    // 2 = high (continuous leak) water use in every 15-min interval for the last 24 hours\n    int leaknow = b[9]&0x03;\n    // extra 24 bits ???\n    char extra[7];\n    snprintf(extra, sizeof(extra),\"%02x%02x%02x\", b[10], b[11], b[12]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",       \"\",    DATA_STRING, \"Neptune-R900\",\n            \"id\",          \"\",    DATA_INT,    meter_id,\n            \"unkn1\",       \"\",    DATA_INT,    unkn1,\n            \"unkn2\",       \"\",    DATA_INT,    unkn2,\n            \"nouse\",       \"\",    DATA_INT,    nouse,\n            \"backflow\",    \"\",    DATA_INT,    backflow,\n            \"consumption\", \"\",    DATA_INT,    consumption,\n            \"unkn3\",       \"\",    DATA_INT,    unkn3,\n            \"leak\",        \"\",    DATA_INT,    leak,\n            \"leaknow\",     \"\",    DATA_INT,    leaknow,\n            \"extra\",       \"\",    DATA_STRING, extra,\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    // Return 1 if message successfully decoded\n    return 1;\n}\n\n/*\n * List of fields that may appear in the output\n *\n * Used to determine what fields will be output in what\n * order for this device when using -F csv.\n *\n */\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"unkn1\",\n        \"unkn2\",\n        \"nouse\",\n        \"backflow\",\n        \"consumption\",\n        \"unkn3\",\n        \"leak\",\n        \"leaknow\",\n        \"extra\",\n        NULL,\n};\n\n\n/*\n * r_device - registers device/callback. see rtl_433_devices.h\n */\nr_device const neptune_r900 = {\n        .name        = \"Neptune R900 flow meters\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 30,\n        .long_width  = 30,\n        .reset_limit = 320, // a bit longer than packet gap\n        .decode_fn   = &neptune_r900_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/new_template.c",
    "content": "/** @file\n    Template decoder for DEVICE, tested with BRAND, BRAND.\n\n    Copyright (C) 2016 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/*\n    Use this as a starting point for a new decoder.\n\n    Keep the Doxygen (slash-star-star) comment above to document the file and copyright.\n\n    Keep the Doxygen (slash-star-star) comment below to describe the decoder.\n    See http://www.doxygen.nl/manual/markdown.html for the formatting options.\n\n    Remove all other multiline (slash-star) comments.\n    Use single-line (slash-slash) comments to annontate important lines if needed.\n\n    To use this:\n    - Copy this template to a new file\n    - Change at least `new_template` in the source\n    - Add to include/rtl_433_devices.h\n    - Run ./maintainer_update.py (needs a clean git stage or commit)\n\n    Note that for simple devices doorbell/PIR/remotes a flex conf (see conf dir) is preferred.\n*/\n\n#include \"decoder.h\"\n\n/**\n(this is a markdown formatted section to describe the decoder)\n(the first line here should match the first documentation line of the file, e.g.)\nTemplate decoder for DEVICE, tested with BRAND, BRAND.\n\n(describe the modulation, timing, and transmission, e.g.)\nThe device uses PPM encoding,\n- 0 is encoded as 40 us pulse and 132 us gap,\n- 1 is encoded as 40 us pulse and 224 us gap.\nThe device sends a transmission every 63 seconds.\nA transmission starts with a preamble of 0xAA,\nthere a 5 repeated packets, each with a 1200 us gap.\n\n(describe the data and payload, e.g.)\nData layout:\n    (preferably use one character per bit)\n    FFFFFFFF PPPPPPPP PPPPPPPP IIIIIIII IIIIIIII IIIIIIII TTTTTTTT TTTTTTTT CCCCCCCC\n    (otherwise use one character per nibble if this fits well)\n    FF PP PP II II II TT TT CC\n\n- F: 8 bit flags, (0x40 is battery_low)\n- P: 16-bit little-endian Pressure\n- I: 24-bit little-endian id\n- T: 16-bit little-endian Unknown, likely Temperature\n- C: 8 bit Checksum, CRC-8 truncated poly 0x07 init 0x00\n*/\nstatic int new_template_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    /*\n     * Early debugging aid to see demodulated bits in buffer and\n     * to determine if your limit settings are matched and firing\n     * this decode callback.\n     *\n     * 1. Enable with -vvv (debug decoders)\n     * 2. Delete this block when your decoder is working\n     */\n    //    decoder_log_bitbuffer(decoder, 2, __func__, bitbuffer, \"\");\n\n    /*\n     * If you expect the bits flipped with respect to the demod\n     * invert the whole bit buffer.\n     */\n\n    bitbuffer_invert(bitbuffer);\n\n    /*\n     * The bit buffer will contain multiple rows.\n     * Typically a complete message will be contained in a single\n     * row if long and reset limits are set correctly.\n     * May contain multiple message repeats.\n     * Message might not appear in row 0, if protocol uses\n     * start/preamble periods of different lengths.\n     */\n\n    /*\n     * Either, if you expect just a single packet\n     * loop over all rows and collect or output data:\n     */\n\n    uint8_t *b; // bits of a row\n    int r;\n    for (r = 0; r < bitbuffer->num_rows; ++r) {\n        b = bitbuffer->bb[r];\n\n        /*\n         * Validate message and reject invalid messages as\n         * early as possible before attempting to parse data.\n         *\n         * Check \"message envelope\"\n         * - valid message length (use a minimum length to account\n         *   for stray bits appended or prepended by the demod)\n         * - valid preamble/device type/fixed bits if any\n         * - Data integrity checks (CRC/Checksum/Parity)\n         */\n\n        // Message is expected to be 68 bits long\n        if (bitbuffer->bits_per_row[r] < 68) {\n            continue; // not enough bits\n        }\n\n        if (b[0] != 0x42) {\n            continue; // magic header not found\n        }\n\n        /*\n         * ... see below and replace `return 0;` with `continue;`\n         */\n    }\n\n    /*\n     * Or, if you expect repeated packets\n     * find a suitable row:\n     */\n\n    // The message is repeated as 5 packets, require at least 3 repeated packets of 68 bits.\n    r = bitbuffer_find_repeated_row(bitbuffer, 3, 68);\n    if (r < 0 || bitbuffer->bits_per_row[r] > 68 + 16) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = bitbuffer->bb[r];\n\n    /*\n     * Either reject rows that don't start with the correct start byte:\n     * Example message should start with 0xAA\n     */\n    if (b[0] != 0xaa) {\n        return DECODE_ABORT_EARLY; // Messages start of 0xAA not found\n    }\n\n    /*\n     * Or (preferred) search for the message preamble:\n     * See bitbuffer_search()\n     */\n\n    /*\n     * Several tools are available to reverse engineer a message integrity\n     * check:\n     *\n     * - reveng for CRC: http://reveng.sourceforge.net/\n     *   - Guide: https://hackaday.com/2019/06/27/reverse-engineering-cyclic-redundancy-codes/\n     * - revdgst: https://github.com/triq-org/revdgst/\n     * - trial and error, e.g. via online calculators:\n     *   - https://www.scadacore.com/tools/programming-calculators/online-checksum-calculator/\n     */\n\n    /*\n     * Check message integrity (Parity example)\n     *\n     */\n    // parity check: odd parity on bits [0 .. 67]\n    // i.e. 8 bytes and a nibble.\n    int parity;\n    parity = b[0] ^ b[1] ^ b[2] ^ b[3] ^ b[4] ^ b[5] ^ b[6] ^ b[7]; // parity as byte\n    parity = (parity >> 4) ^ (parity & 0xF);                        // fold to nibble\n    parity ^= b[8] >> 4;                                            // add remaining nibble\n    parity = (parity >> 2) ^ (parity & 0x3);                        // fold to 2 bits\n    parity = (parity >> 1) ^ (parity & 0x1);                        // fold to 1 bit\n\n    if (!parity) {\n        // Enable with -vv (verbose decoders)\n        decoder_log(decoder, 1, __func__, \"parity check failed\");\n        return DECODE_FAIL_MIC;\n    }\n\n    /*\n     * Check message integrity (Checksum example)\n     */\n    if (((b[0] + b[1] + b[2] + b[3] - b[4]) & 0xFF) != 0) {\n        // Enable with -vv (verbose decoders)\n        decoder_log(decoder, 1, __func__, \"checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    /*\n     * Check message integrity (CRC example)\n     *\n     * Example device uses CRC-8\n     */\n    // There are 6 data bytes and then a CRC8 byte\n    int chk = crc8(b, 7, 0x07, 0x00);\n    if (chk != 0) {\n        // Enable with -vv (verbose decoders)\n        decoder_log(decoder, 1, __func__, \"bad CRC\");\n\n        // reject row\n        return DECODE_FAIL_MIC;\n    }\n\n    /*\n     * Now that message \"envelope\" has been validated,\n     * start parsing data.\n     */\n    int msg_type  = b[1];\n    int sensor_id = b[2] << 8 | b[3];\n    int value     = b[4] << 8 | b[5];\n\n    // A message type byte of 0x10 is expected\n    if (msg_type != 0x10) {\n        /*\n         * received an unexpected message type\n         * could be a bad message or a new message not\n         * previously seen.  Optionally log debug output.\n         */\n        return DECODE_FAIL_OTHER;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\", \"\", DATA_STRING, \"New-Template\",\n            \"id\",    \"\", DATA_INT,    sensor_id,\n            \"data\",  \"\", DATA_INT,    value,\n            \"mic\",   \"\", DATA_STRING, \"CHECKSUM\", // CRC, CHECKSUM, or PARITY\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    // Return 1 if message successfully decoded\n    return 1;\n}\n\n/*\n * List of fields that may appear in the output\n *\n * Used to determine what fields will be output in what\n * order for this device when using -F csv.\n *\n */\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"data\",\n        \"mic\", // remove if not applicable\n        NULL,\n};\n\n/*\n * r_device - registers device/callback. see rtl_433_devices.h\n *\n * Timings:\n *\n * short, long, and reset - specify pulse/period timings in [us].\n *     These timings will determine if the received pulses\n *     match, so your callback will fire after demodulation.\n *\n * Modulation:\n *\n * The function used to turn the received signal into bits.\n * See:\n * - pulse_slicer.h for descriptions\n * - r_device.h for the list of defined names\n *\n * This device is disabled and hidden, it can not be enabled.\n *\n * To enable your device, append it to the list in include/rtl_433_devices.h\n * and sort it into src/CMakeLists.txt or run ./maintainer_update.py\n *\n */\nr_device const new_template = {\n        .name        = \"Template decoder\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 132,  // short gap is 132 us\n        .long_width  = 224,  // long gap is 224 us\n        .gap_limit   = 300,  // some distance above long\n        .reset_limit = 1000, // a bit longer than packet gap\n        .decode_fn   = &new_template_decode,\n        .disabled    = 3, // disabled and hidden, use 0 if there is a MIC, 1 otherwise\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/newkaku.c",
    "content": "/** @file\n    Kaku decoder.\n*/\n/**\nKaku decoder.\nMight be similar to an x1527.\nS.a. Nexa, Proove.\n\nTwo bits map to 2 states, 0 1 -> 0 and 1 0 -> 1\nStatus bit can be 1 1 -> 1 which indicates DIM value. 4 extra bits are present with value\nstart pulse: 1T high, 10.44T low\n- 26 bit:  Address\n- 1  bit:  group bit\n- 1  bit:  Status bit on/off/[dim]\n- 4  bit:  unit\n- [4 bit:  dim level. Present if [dim] is used, but might be present anyway...]\n- stop pulse: 1T high, 40T low\n*/\n\n#include \"decoder.h\"\n\nstatic int newkaku_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b = bitbuffer->bb[0];\n\n    /* Reject missing sync */\n    if (bitbuffer->syncs_before_row[0] != 1)\n        return DECODE_ABORT_EARLY;\n\n    /* Reject codes of wrong length */\n    if (bitbuffer->bits_per_row[0] != 64 && bitbuffer->bits_per_row[0] != 72)\n        return DECODE_ABORT_LENGTH;\n\n    // 11 for command indicates DIM, 4 extra bits indicate DIM value\n    uint8_t dim_cmd = (b[6] & 0x03) == 0x03;\n    if (dim_cmd) {\n        b[6] &= 0xfe; // change DIM to ON to use Manchester\n    }\n\n    bitbuffer_t databits = {0};\n    // note: not manchester encoded but actually ternary\n    unsigned pos = bitbuffer_manchester_decode(bitbuffer, 0, 0, &databits, 80);\n    bitbuffer_invert(&databits);\n\n    /* Reject codes when Manchester decoding fails */\n    if (pos != 64 && pos != 72)\n        return DECODE_ABORT_LENGTH;\n\n    b = databits.bb[0];\n\n    uint32_t id        = (b[0] << 18) | (b[1] << 10) | (b[2] << 2) | (b[3] >> 6); // ID 26 bits\n    uint32_t group_cmd = (b[3] >> 5) & 1;\n    uint32_t on_bit    = (b[3] >> 4) & 1;\n    uint32_t unit      = (b[3] & 0x0f);\n    uint32_t dv        = (b[4] >> 4);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"KlikAanKlikUit-Switch\",\n            \"id\",           \"\",             DATA_INT,    id,\n            \"unit\",         \"Unit\",         DATA_INT,    unit,\n            \"group_call\",   \"Group Call\",   DATA_STRING, group_cmd ? \"Yes\" : \"No\",\n            \"command\",      \"Command\",      DATA_STRING, on_bit ? \"On\" : \"Off\",\n            \"dim\",          \"Dim\",          DATA_STRING, dim_cmd ? \"Yes\" : \"No\",\n            \"dim_value\",    \"Dim Value\",    DATA_INT,    dv,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"unit\",\n        \"group_call\",\n        \"command\",\n        \"dim\",\n        \"dim_value\",\n        NULL,\n};\n\nr_device const newkaku = {\n        .name        = \"KlikAanKlikUit Wireless Switch\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 300,  // 1:1\n        .long_width  = 1400, // 1:5\n        .sync_width  = 2650, // 1:10, tuned to widely match 2450 to 2850\n        .tolerance   = 200,\n        .reset_limit = 3200,\n        .decode_fn   = &newkaku_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/nexa.c",
    "content": "/** @file\n    Nexa decoder.\n\n    Copyright (C) 2017 Christian Juncker Brædstrup\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nNexa decoder.\nMight be similar to an x1527.\nS.a. Kaku, Proove.\n\nTested devices:\n- Magnetic sensor - LMST-606\n\nPacket gap is 10 ms.\n\nThis device is very similar to the proove magnetic sensor.\nThe proove decoder will capture the OFF-state but not the ON-state\nsince the Nexa uses two different bit lengths for ON and OFF.\n*/\n\n#include \"decoder.h\"\n\nstatic int nexa_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n\n    /* Reject missing sync */\n    if (bitbuffer->syncs_before_row[0] != 1)\n        return DECODE_ABORT_EARLY;\n\n    /* Reject codes of wrong length */\n    if (bitbuffer->bits_per_row[0] != 64 && bitbuffer->bits_per_row[0] != 72)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_t databits = {0};\n    // note: not manchester encoded but actually ternary\n    unsigned pos = bitbuffer_manchester_decode(bitbuffer, 0, 0, &databits, 80);\n    bitbuffer_invert(&databits);\n\n    /* Reject codes when Manchester decoding fails */\n    if (pos != 64 && pos != 72)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = databits.bb[0];\n\n    uint32_t id        = (b[0] << 18) | (b[1] << 10) | (b[2] << 2) | (b[3] >> 6); // ID 26 bits\n    uint32_t group_cmd = (b[3] >> 5) & 1;\n    uint32_t on_bit    = (b[3] >> 4) & 1;\n    uint32_t channel   = ((b[3] >> 2) & 0x03) ^ 0x03; // inverted\n    uint32_t unit      = (b[3] & 0x03) ^ 0x03;        // inverted\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Nexa-Security\",\n            \"id\",            \"House Code\",  DATA_INT,    id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"state\",         \"State\",       DATA_STRING, on_bit ? \"ON\" : \"OFF\",\n            \"unit\",          \"Unit\",        DATA_INT,    unit,\n            \"group\",         \"Group\",       DATA_INT,    group_cmd,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"state\",\n        \"unit\",\n        \"group\",\n        NULL,\n};\n\nr_device const nexa = {\n        .name        = \"Nexa\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 270,  // 1:1\n        .long_width  = 1300, // 1:5\n        .sync_width  = 2650, // 1:10, tuned to widely match 2450 to 2850\n        .tolerance   = 200,\n        .gap_limit   = 1500,\n        .reset_limit = 2800,\n        .decode_fn   = &nexa_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/nexus.c",
    "content": "/** @file\n    Nexus temperature and optional humidity sensor protocol.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n#include \"decoder.h\"\n\n/**\nNexus sensor protocol with ID, temperature and optional humidity.\n\nalso FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344,\nalso infactory/FreeTec (Pearl) NX-3980 sensors for infactory/FreeTec NX-3974 station,\nalso Solight TE82S sensors for Solight TE76/TE82/TE83/TE84 stations,\nalso TFA 30.3209.02 temperature/humidity sensor,\nalso Unmarked sensor form Rossmann Poland, board markings XS1043 REV02.\n\nThe sensor sends 36 bits 12 times,\nthe packets are ppm modulated (distance coding) with a pulse of ~500 us\nfollowed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a\n1 bit, the sync gap is ~4000 us.\n\nThe data is grouped in 9 nibbles:\n\n    [id0] [id1] [flags] [temp0] [temp1] [temp2] [const] [humi0] [humi1]\n\n- The 8-bit id changes when the battery is changed in the sensor.\n- flags are 4 bits B T C C\n  - B is the battery status: 1=OK, 0=LOW\n  - T is Test mode, 0=Normal, 1=Test\n  - CC is the channel: 0=CH1, 1=CH2, 2=CH3\n- temp is 12 bit signed scaled by 10\n- const is always 1111 (0x0F)\n- humidity is 8 bits\n\nTest mode is entered if the \"RES\"-button ist held pressed while inserting batteries.\nThe sensor will send continuously every 2-15 secs. until the battery is reset.\n\nThe sensors can be bought at Clas Ohlsen (Nexus) and Pearl (infactory/FreeTec).\n*/\nstatic int nexus_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // we expect 36 bits but there might be a trailing 0 bit\n    if (bitbuffer->bits_per_row[r] > 37) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if ((b[3] & 0xf0) != 0xf0) {\n        return DECODE_ABORT_EARLY; // const not 1111\n    }\n\n    // Reduce false positives.\n    if ((b[0] == 0 && b[2] == 0 && b[3] == 0)\n            || (b[0] == 0xff &&  b[2] == 0xff && b[3] == 0xFF)) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((b[1] & 0x30) == 0x30) {\n        return DECODE_ABORT_EARLY; // channel not 1-3\n    }\n    int id       = b[0];\n    int battery  = b[1] & 0x80;\n    int testmode = b[1] & 0x40;\n    int channel  = ((b[1] & 0x30) >> 4) + 1;\n    int temp_raw = (int16_t)((b[1] << 12) | (b[2] << 4)); // sign-extend\n    float temp_c = (temp_raw >> 4) * 0.1f;\n    int humidity = (((b[3] & 0x0F) << 4) | (b[4] >> 4));\n\n    data_t *data;\n    if (humidity == 0x00) { // Thermo\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",            DATA_STRING, \"Nexus-T\",\n                \"id\",            \"House Code\",  DATA_INT,    id,\n                \"channel\",       \"Channel\",     DATA_INT,    channel,\n                \"battery_ok\",    \"Battery\",     DATA_INT,    !!battery,\n                \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"test\",          \"Test?\",       DATA_COND,   testmode, DATA_INT,    !!testmode,\n                NULL);\n        /* clang-format on */\n    }\n    else { // Thermo/Hygro\n        /* clang-format off */\n        data = data_make(\n                \"model\",         \"\",            DATA_STRING, \"Nexus-TH\",\n                \"id\",            \"House Code\",  DATA_INT,    id,\n                \"channel\",       \"Channel\",     DATA_INT,    channel,\n                \"battery_ok\",    \"Battery\",     DATA_INT,    !!battery,\n                \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",      \"Humidity\",    DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                \"test\",          \"Test?\",       DATA_COND,   testmode, DATA_INT,    !!testmode,\n                NULL);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nNexus Sauna sensor with ID, temperature, battery and test flag.\n\nThe \"Sauna sensor\", sends 36 bits 6 times, the nibbles are:\n\n    [id0] [id1] [flags] [const] [temp0] [temp1] [temp2] [temp2] [const2]\n\n- The 8-bit id changes when the battery is changed in the sensor.\n- flags are 4 bits B T C C, where:\n  - B is the battery status: 1=OK, 0=LOW\n  - T is Test mode, 0=Normal, 1=Test.\n  To enter test mode, press and hold Tx/Send button while putting the last battery in, it will send values at ~2sec interval.\n  - CC is the channel: It is always 11 (0x3) for CH4\n- temp is 16 bit signed scaled by 10\n- const is always 1111 (0x0F)\n- const2 is always 0001 (0x1)\n  To be exact, the \"Sauna sensor\" seems to send niblets 6 times with const2=0x1, and then seventh time sends just 35 bits, so last nibble is 0b000.\n  Maybe this is \"data-end\" mark. I don't know. Anyway, it can be ignored here, cause data has been parsed already.\n\nSauna sensor kit is sold by IKH (CRX) and Motonet (Prego).\n*/\nstatic int nexus_sauna_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // we expect 36 bits but there might be a trailing 0 bit\n    if (bitbuffer->bits_per_row[r] > 37) {\n        return DECODE_ABORT_LENGTH;\n    }\n    if ((b[1] & 0xf) != 0xf) {\n        return DECODE_ABORT_EARLY; // const not 1111\n    }\n    // Reduce false positives.\n    if ((b[0] == 0) || ((b[4] & 0x10) != 0x10)) {\n        return DECODE_ABORT_EARLY;\n    }\n    if ((b[1] & 0x30) != 0x30) {\n        return DECODE_ABORT_EARLY; // channel not 4\n    }\n\n    int id       = b[0];\n    int battery  = b[1] & 0x80;\n    int testmode = b[1] & 0x40;\n    int channel  = ((b[1] & 0x30) >> 4) + 1; // always 0x3 = CH4\n    int temp_raw = (int16_t)((b[2] << 8) | (b[3])); // sign-extend\n    float temp_c = temp_raw * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Nexus-Sauna\",\n            \"id\",            \"House Code\",  DATA_INT,    id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !!battery,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"test\",          \"Test?\",       DATA_COND,   testmode,   DATA_INT,    !!testmode,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"test\",\n        NULL,\n};\n\nr_device const nexus = {\n        .name        = \"Nexus, FreeTec NC-7345, NX-3980, Solight TE82S, TFA 30.3209 temperature/humidity sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 3000,\n        .reset_limit = 5000,\n        .decode_fn   = &nexus_decode,\n        .priority    = 10, // Eliminate false positives by letting Rubicson-Temperature go earlier\n        .fields      = output_fields,\n};\n\nstatic char const *const sauna_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"test\",\n        NULL,\n};\n\nr_device const nexus_sauna = {\n        .name        = \"Nexus, CRX, Prego sauna temperature sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 3000,\n        .reset_limit = 5000,\n        .decode_fn   = &nexus_sauna_decode,\n        .priority    = 10, // Eliminate false positives by letting Rubicson-Temperature go earlier\n        .fields      = sauna_output_fields,\n};\n"
  },
  {
    "path": "src/devices/nice_flor_s.c",
    "content": "/** @file\n    Nice Flor-s remote for gates.\n\n    Copyright (C) 2020 Samuel Tardieu <sam@rfc1149.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nNice Flor-s remote for gates.\n\nProtocol description:\nThe protocol has been analyzed at this link: http://phreakerclub.com/1615\n\nA packet is made of 52 bits (13 nibbles S0 to S12):\n\n- S0: button ID from 1 to 4 (or 1 to 2 depending on the remote)\n- S1: retransmission count starting from 1, xored with ~S0\n- S2 and S7-S12: 28 bit encrypted serial number\n- S3-S6: 16 bits encrypted rolling code\n\nNice One remotes repeat the Nice Flor-s protocol with 20 additional bytes:\na packet is made of 72 bits\n*/\n\n#include \"decoder.h\"\n\nstatic int nice_flor_s_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 2 || bitbuffer->bits_per_row[1] != 0) {\n        return DECODE_ABORT_EARLY;\n    }\n    if (bitbuffer->bits_per_row[0] != 52 && bitbuffer->bits_per_row[0] != 72) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_invert(bitbuffer);\n    uint8_t *b = bitbuffer->bb[0];\n\n    uint8_t button_id = b[0] >> 4;\n    if (button_id < 1 || button_id > 4) {\n        return DECODE_ABORT_EARLY;\n    }\n    int count = 1 + (((b[0] ^ ~button_id) - 1) & 0xf);\n    uint32_t serial = ((b[1] & 0xf0) << 20) | ((b[3] & 0xf) << 20) |\n       (b[4] << 12) | (b[5] << 4) | (b[6] >> 4);\n    uint16_t code = (b[1] << 12) | (b[2] << 4) | (b[3] >> 4);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",  \"\",              DATA_STRING, \"Nice-FlorS\",\n            \"button\", \"Button ID\",     DATA_INT,     button_id,\n            \"serial\", \"Serial (enc.)\", DATA_FORMAT, \"%07x\",        DATA_INT, serial,\n            \"code\",   \"Code (enc.)\",   DATA_FORMAT, \"%04x\",        DATA_INT, code,\n            \"count\",  \"\",              DATA_INT,     count,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"button\",\n        \"serial\",\n        \"code\",\n        \"count\",\n        NULL,\n};\n\n// Example:\n// $ rtl_433 -R 169 -y \"{52} 0xe7a760b94372e {0}\"\n// time      : 2020-10-21 11:06:12\n// model     : Nice Flor-s  Button ID : 1             Serial (enc.): 56bc8d1    Code (enc.): 89f4\n// count     : 6\n\nr_device const nice_flor_s = {\n        .name        = \"Nice Flor-s remote control for gates\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 500,  // short pulse is ~500 us + ~1000 us gap\n        .long_width  = 1000, // long pulse is ~1000 us + ~500 us gap\n        .sync_width  = 1500, // sync pulse is ~1500 us + ~1500 us gap\n        .gap_limit   = 2000,\n        .reset_limit = 5000,\n        .tolerance   = 100,\n        .decode_fn   = &nice_flor_s_decode,\n        .disabled    = 1,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/norgo.c",
    "content": "/** @file\n    Norgo Energy NGE101 decoder.\n\n    Copyright (C) 2019 jamaron\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/** @fn int norgo_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nNorgo Energy NGE101 decoder.\n\nThe code is based on info and code from Jesper Hansen's pages (used with\nhis permission):\nhttp://blog.bitheap.net/p/this-is-overview-of-data-norge-nge101.html\n\n\nThe signal is FM encoded with clock cycle around x us, using\ninverted OOK_PULSE_DMC modulation, i.e.\n- No level shift within the clock cycle translates to a logic 1\n- One level shift within the clock cycle translates to a logic 0\nEach clock cycle begins with a level shift\n\n+---+   +---+   +-------+       +  high\n|   |   |   |   |       |       |\n|   |   |   |   |       |       |\n+   +---+   +---+       +-------+  low\n^       ^       ^       ^       ^  clock cycle\n|   0   |   0   |   1   |   1   |  translates as\n\nEach transmission is either 55 or 71 bits long.\n\nData is transmitted in pure binary values, LSbit first.\n\nEnergy meter transmits pulse duration and pulse count as separate messages.\nTransmissions also includes channel code and device ID. The sensor transmits\nevery 43 seconds 2 packets (55 bit packet twice or 71 bit packet together\nwith 55 bit packet).\n\n55 bit packet contents:\n\n    1111 1010 | 0000 1101 | 1010 1000 | 0000 1000 | 0000 0000 /\n    ssss ssss | fccc dddd | dddd tttt | tttt tttt | tttt tttu /\n    1010 1101 / 1010 000?\n    xxxx xxxx / pppp ppp?\n\n- s: sync byte, 0xfa\n- f: packet type (0 = 55 bit packet)\n- c: channel (LSbit first)\n- d: device ID (LSbit first)\n- t: time in 1/1024 seconds between the last two impulses (LSbit first)\n- u: unknown\n- x: xor sum (starting at byte 1)\n- p: parity\n\nCaptured time can be converted to momentary power usage (kW) using formula:\n(3686400/(n_imp_per_kwh)/captured_time\n\n71 bit packet contents:\n\n    1111 1010 | 1000 1101 | 1010 0001 | 0010 0001 | 1101 1111 /\n    ssss ssss | fccc dddd | dddd kkkk | kkkk kkkk | kkkk kkkk /\n    1100 0000 / 0000 0000 / 0001 0010 / 1101 111?\n    kkkk kkkk | kkkk kkbo / xxxx xxxx / pppp ppp?\n\n- s: sync byte, 0xfa\n- f: packet type (1 = 71 bit packet)\n- c: channel (LSbit first)\n- d: device ID (LSbit first)\n- k: impulse count since transmitter started (LSbit first)\n- b: low battery\n- o: overflow?\n- x: xor sum (starting at byte 1)\n- p: parity\n\nCaptured impulse count can be converted to energy usage (kWh) using formula:\npulse_count/(n_imp_per_kwh)\n*/\n\n#include \"decoder.h\"\n\nstatic uint16_t const checksum_taps[] = {\n        0x4880, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,\n        0x2080, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000,\n};\n\nstatic uint16_t next_mask(uint32_t mask)\n{\n    uint16_t i;\n    uint16_t n_mask;\n\n    n_mask = mask >> 1;\n    for (i = 0; i < 15; i++) {\n        if (mask & (1 << i)) {\n            n_mask ^= checksum_taps[i];\n        }\n    }\n    return n_mask;\n}\n\nstatic uint8_t calc_checksum(uint8_t *data, uint8_t datalen)\n{\n    uint16_t i;\n    uint32_t mask = 0x0001;\n    uint16_t chks = 0;\n\n    for (i = datalen - 1; i > 7; i--) {\n        mask = next_mask(mask);\n        if ((data[i / 8] >> (i % 8)) & 1)\n            chks ^= mask;\n    }\n    return chks >> 8;\n}\n\nstatic int norgo_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[0];\n\n    int device_id;\n    int channel;\n    int impulse_gap;\n    uint64_t impulses;\n    int low_battery;\n    //int maybe_overflow;\n    int checksum;\n    int calc_chk;\n\n    if (bitbuffer->bits_per_row[0] != 56\n            && bitbuffer->bits_per_row[0] != 72\n            && bitbuffer->bits_per_row[0] != 55\n            && bitbuffer->bits_per_row[0] != 71) {\n        decoder_logf(decoder, 1, __func__, \"wrong size of bit per row %d\",\n                    bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if (b[0] != (uint8_t)~0xFA) {\n        decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"wrong preamble\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    int xor_byte = xor_bytes(b + 1, (bitbuffer->bits_per_row[0] - 15) / 8);\n    if (xor_byte != 0xff) { // before invert 0 is ff\n        decoder_logf_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0], \"XOR fail (%02x)\",\n                    xor_byte);\n        return DECODE_FAIL_MIC;\n    }\n\n    bitbuffer_invert(bitbuffer); // inverted OOK_PULSE_DMC modulation\n    reflect_bytes(b, (bitbuffer->bits_per_row[0] + 1) / 8);\n\n    device_id = ((b[1] & 0xF0) >> 4) | ((b[2] & 0x0f) << 4);\n    channel   = ((b[1] & 0x0e) >> 1) + 1;\n    if (0 == (b[1] & 0x1)) {\n        calc_chk = calc_checksum(b, 5 * 8);\n        checksum = b[6];\n        if (calc_chk != checksum) {\n            decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"wrong checksum %02X vs. %02X\",\n                        calc_chk, checksum);\n            return DECODE_FAIL_MIC;\n        }\n\n        impulse_gap = (b[2] >> 4) | (b[3] << 4) | ((b[4] & 0x7F) << 12);\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"Norgo-NGE101\",\n                \"id\",           \"Device ID\",    DATA_INT,    device_id,\n                \"channel\",      \"Channel\",      DATA_INT,    channel,\n                \"gap\",          \"Impulse gap\",  DATA_INT,    impulse_gap,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else {\n        calc_chk = calc_checksum(b, 7 * 8);\n        checksum = b[8];\n        if (calc_chk != checksum) {\n            decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"wrong checksum %02X vs. %02X\",\n                        checksum, calc_chk);\n            return DECODE_FAIL_MIC;\n        }\n        impulses = (b[2] >> 4) | (b[3] << 4) | (b[4] << 12) | (b[5] << 20) | (((uint64_t)b[6] & 0x3F) << 28);\n\n        low_battery    = (b[6] & 0x40) >> 6;\n        //maybe_overflow = (b[6] & 0x80) >> 7;\n\n        // Pulse count is totally 34 bits but we report only 32 bits,\n        // should be enough for the duration of battery.\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"Norgo-NGE101\",\n                \"id\",           \"Id\",           DATA_INT,    device_id,\n                \"channel\",      \"Channel\",      DATA_INT,    channel,\n                \"impulses\",     \"Impulses\",     DATA_INT,    (uint32_t)impulses,\n                \"battery_ok\",   \"Battery\",      DATA_INT,    !low_battery,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"gap\",\n        \"impulses\",\n        \"battery_ok\",\n        \"mic\",\n        NULL,\n};\n\nr_device const norgo = {\n        .name        = \"Norgo NGE101\",\n        .modulation  = OOK_PULSE_DMC,\n        .short_width = 486,\n        .long_width  = 972,\n        .reset_limit = 2100,\n        .sync_width  = 0,\n        .tolerance   = 120,\n        .decode_fn   = &norgo_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oil_smart.c",
    "content": "/** @file\n    Oil tank monitor using manchester encoded FSK protocol with CRC.\n\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n    Device analysis by StarMonkey1\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nOil tank monitor using manchester encoded FSK protocol with CRC.\n\nTested devices:\n- Apollo Ultrasonic Smart liquid monitor (FSK, 433.92M) Issue #2244\n\nShould apply to similar Watchman, Beckett, and Apollo devices too.\n\nThere is a preamble plus de-sync of 555558, then MC coded an inner preamble of 5558 (raw 9999996a).\nEnd of frame is the last half-bit repeated additional 2 times, then 4 times mark.\n\nThe sensor sends a single packet once every half hour or twice a second\nfor 5 minutes when in pairing/test mode.\n\nDepth reading is in cm, lowest reading is unknown, highest is unknown, invalid is unknown.\n\nData Format:\n\n    PRE?16h ID?16h FLAGS?16h CM:8d CRC:8h\n\nData Layout:\n\n    PP PP II II FF CC DD XX\n\n- P: 16 bit Preamble of 0x5558\n- I: 16 bit Sensor ID\n- F: 8 bit Flags maybe\n- C: 8 bit Counter maybe\n- D: 8 bit Depth in cm, could have a MSB somewhere?\n- X: 8 bit CRC-8, poly 0x31 init 0x00, bit reflected\n\nexample packets are:\n\n- raw: {158}555558 9999 996a 6559aaa99996a55696a9a5963c\n- aligned: {134}9999996a 6559aaa999969aa6aa9a6995 fc\n- decoded: 5558 bd01 5642 0497\n\nTODO: this is not confirmed\nStart of frame full preamble is depending on first data bit either\n\n    0101 0101 0101 0101 0101 0111 01\n    0101 0101 0101 0101 0101 1000 10\n*/\nstatic int oil_smart_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t databits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &databits, 64);\n\n    if (databits.bits_per_row[0] < 64) {\n        return 0; // DECODE_ABORT_LENGTH; // TODO: fix calling code to handle negative return values\n    }\n\n    uint8_t *b = databits.bb[0];\n\n    if (b[0] != 0x55 || b[1] != 0x58) {\n        decoder_log(decoder, 2, __func__, \"Couldn't find preamble\");\n        return 0; // DECODE_FAIL_SANITY; // TODO: fix calling code to handle negative return values\n    }\n\n    if (crc8le(b, 8, 0x31, 0x00)) {\n        decoder_log(decoder, 2, __func__, \"CRC8 fail\");\n        return 0; // DECODE_FAIL_MIC; // TODO: fix calling code to handle negative return values\n    }\n\n    // We do not know if the unit ID changes when you rebind\n    // by holding a magnet to the sensor for long enough?\n    uint16_t unit_id = (b[2] << 8) | b[3];\n\n    // TODO: none of these are identified.\n    uint8_t unknown1 = b[4]; // idle seems to be 0x17\n    uint8_t unknown2 = b[5]; // appears to change variously to: 0c 0e 10 12\n\n    // TODO: the value for a bad reading has not been found?\n    // TODO: there could be more (MSB) bits to this?\n    uint16_t depth = b[6];\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Oil-Ultrasonic\",\n            \"id\",               \"\",             DATA_FORMAT, \"%04x\", DATA_INT, unit_id,\n            \"depth_cm\",         \"Depth\",        DATA_INT,    depth,\n            \"unknown_1\",        \"Unknown 1\",    DATA_FORMAT, \"%02x\", DATA_INT, unknown1,\n            \"unknown_2\",        \"Unknown 2\",    DATA_FORMAT, \"%02x\", DATA_INT, unknown2,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nOil tank monitor using manchester encoded FSK protocol with CRC.\n@sa oil_smart_decode()\n*/\nstatic int oil_smart_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[2] = {0x55, 0x58};\n    // End of frame is the last half-bit repeated additional 2 times, then 4 times mark.\n\n    unsigned bitpos = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 16)) + 128 <=\n            bitbuffer->bits_per_row[0]) {\n        events += oil_smart_decode(decoder, bitbuffer, 0, bitpos + 16);\n        bitpos += 2;\n    }\n\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"depth_cm\",\n        \"unknown_1\",\n        \"unknown_2\",\n        \"mic\",\n        NULL,\n};\n\nr_device const oil_smart = {\n        .name        = \"Oil Ultrasonic SMART FSK\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .reset_limit = 2000,\n        .decode_fn   = &oil_smart_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oil_standard.c",
    "content": "/** @file\n    Oil tank monitor using manchester encoded FSK/ASK protocol.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n    based on code Copyright (C) 2015 David Woodhouse\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nOil tank monitor using manchester encoded FSK/ASK protocol.\n\nTested devices:\n- APOLLO ULTRASONIC STANDARD (maybe also VISUAL but not SMART, FSK)\n- Tekelek TEK377E (E: European, A: American version)\n- Beckett Rocket TEK377A (915MHz, ASK)\n\nShould apply to similar Watchman, Beckett, and Apollo devices too.\n\nThe sensor sends a single packet once every hour or twice a second\nfor 11 minutes when in pairing/test mode (pairing needs 35 sec).\ndepth reading is in cm, lowest reading is ~3, highest is ~305, 0 is invalid\n\n    IIII IIII IIII IIII 0FFF L0OP DDDD DDDD\n\nThe TEK377E might send an additional 8 zero bits.\n\nexample packets are:\n\n    010101 01010101 01010111 01101001 10011010 10101001 10100101 10011010 01101010 10011001 10011010 0000\n    010101 01010101 01011000 10011010 01010110 01101010 10101010 10100101 01101010 10100110 10101001 1111\n\nStart of frame full preamble is depending on first data bit either\n\n    01 0101 0101 0101 0101 0111 01\n    01 0101 0101 0101 0101 1000 10\n*/\nstatic int oil_standard_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t databits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &databits, 41);\n\n    if (databits.bits_per_row[0] < 32 || databits.bits_per_row[0] > 40 || (databits.bb[0][4] & 0xfe) != 0)\n        return 0; // TODO: fix calling code to handle negative return values\n\n    uint8_t *b = databits.bb[0];\n\n    // The unit ID changes when you rebind by holding a magnet to the\n    // sensor for long enough.\n    uint16_t unit_id = (b[0] << 8) | b[1];\n\n    // 0x01: Rebinding (magnet held to sensor)\n    // 0x02: High-bit for depth\n    // 0x04: (always zero?)\n    // 0x08: Leak/theft alarm\n    // 0x10: (unknown toggle)\n    // 0x20: (unknown toggle)\n    // 0x40: (unknown toggle)\n    // 0x80: (always zero?)\n    uint8_t flags = b[2] & ~0x0A;\n    uint8_t alarm = (b[2] & 0x08) >> 3;\n\n    uint16_t depth             = 0;\n    uint16_t binding_countdown = 0;\n    if (flags & 1) {\n        // When binding, the countdown counts up from 0x40 to 0x4a\n        // (as long as you hold the magnet to it for long enough)\n        // before the device ID changes. The receiver unit needs\n        // to receive this *strongly* in order to change its\n        // allegiance.\n        binding_countdown = b[3];\n    }\n    else {\n        // A depth reading of zero indicates no reading.\n        depth = ((b[2] & 0x02) << 7) | b[3];\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\", DATA_STRING, \"Oil-SonicStd\",\n            \"id\",                   \"\", DATA_FORMAT, \"%04x\", DATA_INT, unit_id,\n            \"flags\",                \"\", DATA_FORMAT, \"%02x\", DATA_INT, flags,\n            \"alarm\",                \"\", DATA_INT,    alarm,\n            \"binding_countdown\",    \"\", DATA_INT,    binding_countdown,\n            \"depth_cm\",             \"\", DATA_INT,    depth,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nOil tank monitor using manchester encoded FSK/ASK protocol.\n@sa oil_standard_decode()\n*/\nstatic int oil_standard_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern0[2] = {0x55, 0x5D};\n    uint8_t const preamble_pattern1[2] = {0x55, 0x62};\n    // End of frame is the last half-bit repeated additional 4 times\n\n    unsigned bitpos = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern0, 16)) + 78 <=\n            bitbuffer->bits_per_row[0]) {\n        events += oil_standard_decode(decoder, bitbuffer, 0, bitpos + 14);\n        bitpos += 2;\n    }\n\n    bitpos = 0;\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern1, 16)) + 78 <=\n            bitbuffer->bits_per_row[0]) {\n        events += oil_standard_decode(decoder, bitbuffer, 0, bitpos + 14);\n        bitpos += 2;\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"flags\",\n        \"alarm\",\n        \"binding_countdown\",\n        \"depth_cm\",\n        NULL,\n};\n\nr_device const oil_standard = {\n        .name        = \"Oil Ultrasonic STANDARD FSK\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .reset_limit = 2000,\n        .decode_fn   = &oil_standard_callback,\n        .fields      = output_fields,\n};\n\nr_device const oil_standard_ask = {\n        .name        = \"Oil Ultrasonic STANDARD ASK\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .reset_limit = 2000,\n        .decode_fn   = &oil_standard_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oil_watchman.c",
    "content": "/** @file\n    Oil tank monitor using Si4320 framed FSK protocol.\n\n    Copyright (C) 2015 David Woodhouse\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nOil tank monitor using Si4320 framed FSK protocol.\n\nTested devices:\n- Sensor Systems Watchman Sonic\n- Kingspan Watchman Sonic Plus\n*/\nstatic int oil_watchman_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Start of frame preamble is 111000xx\n    uint8_t const preamble_pattern[] = {0xe0};\n\n    // End of frame is 00xxxxxx or 11xxxxxx depending on final data bit\n    uint8_t const postamble_pattern[2] = {0x00, 0xc0};\n\n    unsigned bitpos      = 0;\n    int events           = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 6)) + 136 <=\n            bitbuffer->bits_per_row[0]) {\n\n        // Skip the matched preamble bits to point to the data\n        bitpos += 6;\n\n        bitbuffer_t databits = {0};\n        bitpos = bitbuffer_manchester_decode(bitbuffer, 0, bitpos, &databits, 64);\n        if (databits.bits_per_row[0] != 64)\n            continue; // DECODE_ABORT_LENGTH\n\n        uint8_t *b = databits.bb[0];\n\n        // Check for postamble, depending on last data bit\n        if (bitbuffer_search(bitbuffer, 0, bitpos, &postamble_pattern[b[7] & 1], 2) != bitpos)\n            continue; // DECODE_ABORT_EARLY\n\n        if (b[7] != crc8le(b, 7, 0x31, 0))\n            continue; // DECODE_FAIL_MIC\n\n        // The unit ID changes when you rebind by holding a magnet to the\n        // sensor for long enough; it seems to be time-based.\n        uint32_t unit_id = ((unsigned)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];\n\n        // 0x01: Rebinding (magnet held to sensor)\n        // 0x08: Leak/theft alarm\n        // top three bits seem also to vary with temperature (independently of maybetemp)\n        uint8_t flags = b[4];\n\n        // Not entirely sure what this is but it might be inversely\n        // proportional to temperature.\n        uint8_t maybetemp  = b[5] >> 2;\n        double temperature = (double)(145.0 - 5.0 * maybetemp) / 3.0;\n\n        uint16_t depth             = 0;\n        uint16_t binding_countdown = 0;\n        if (flags & 1) {\n            // When binding, the countdown counts up from 0x51 to 0x5a\n            // (as long as you hold the magnet to it for long enough)\n            // before the device ID changes. The receiver unit needs\n            // to receive this *strongly* in order to change its\n            // allegiance.\n            binding_countdown = b[6];\n        }\n        else {\n            // A depth reading of zero indicates no reading. Even with\n            // the sensor flat down on a table, it still reads about 13.\n            depth = ((b[5] & 3) << 8) | b[6];\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                \"\", DATA_STRING, \"Oil-SonicSmart\",\n                \"id\",                   \"\", DATA_FORMAT, \"%06x\", DATA_INT, unit_id,\n                \"flags\",                \"\", DATA_FORMAT, \"%02x\", DATA_INT, flags,\n                \"maybetemp\",            \"\", DATA_INT,    maybetemp,\n                \"temperature_C\",        \"\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"binding_countdown\",    \"\", DATA_INT,    binding_countdown,\n                \"depth_cm\",             \"\", DATA_INT,    depth,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        events++;\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"flags\",\n        \"maybetemp\",\n        \"temperature_C\",\n        \"binding_countdown\",\n        \"depth_cm\",\n        NULL,\n};\n\nr_device const oil_watchman = {\n        .name        = \"Watchman Sonic / Apollo Ultrasonic / Beckett Rocket oil tank monitor\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 1000,\n        .long_width  = 1000, // NRZ\n        .reset_limit = 4000,\n        .decode_fn   = &oil_watchman_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oil_watchman_advanced.c",
    "content": "/** @file\n    Watchman Sonic Advanced/Plus oil tank level monitor.\n\n    Copyright (C) 2023 Gareth Potter\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nWatchman Sonic Advanced/Plus oil tank level monitor.\n\nTested devices:\n- Watchman Sonic Advanced, model code 0x0401 (seen on two devices)\n- Tekelek, model code 0x0106 (seen on two devices)\n\nThe devices uses GFSK with 500 us long and short pulses.\nUsing -Y minmax should be sufficient to get it to work.\n\nTotal length of message including preamble is 192 bits.\nThe format might be most easily summarised in a BitBench string:\n\n    PRE: 40b SYNC: 16h LEN:8d MODEL:16h ID:24d 8h TEMP:8h ?:16h DEPTH:8d VER:32h CRC:16h\n\nData Layout:\n\n- 40 bits of preamble, i.e. 10101010 etc.\n- 2 byte of 0x2dd4 - 'standard' sync word\n- 1 byte - message length, fixed 0x0e (14)\n- 2 byte - fixed 0x0401 or 0x0106 - presumably a model identifier, common at least to the devices we have tested\n- 3 byte integer serial number - as printed on a label attached to the device itself\n- 1 byte status:\n  - 0xC0 - during the first 20ish minutes after sync with the receiver when the device is transmitting once per second\n  - 0x80 - the first one or two transmissions after the sync period when the device seems to be calibrating itself\n  - 0x98 - normal, live value that you'll see on every transmission when the device is up and running\n- 1 byte temperature, in intervals of 0.5 degrees, offset by 0x48\n- 1 byte - varying bytes which could be the raw sensor reading\n- 4 bits - varying bytes which could be the raw sensor reading\n- 12 bits - integer depth (i.e. the distance between the sensor and the oil in the tank)\n- 4 byte of 0x01050300 - constant values which could be a version number? (1.5.3.0)\n- 2 byte CRC-16 poly 0x8005 init 0\n*/\n\nstatic int oil_watchman_advanced_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    static uint8_t const PREAMBLE_SYNC_LENGTH_BITS = 40;\n    static uint8_t const HEADER_LENGTH_BITS        = 8;\n    static uint8_t const BODY_LENGTH_BITS          = 144;\n    // no need to match all the preamble; 24 bits worth should do\n    // include part of preamble, sync-word, length, message identifier\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xaa, 0x2d, 0xd4, 0x0e};\n\n    unsigned bitpos = 0;\n    int events      = 0;\n\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, PREAMBLE_SYNC_LENGTH_BITS + HEADER_LENGTH_BITS)) + BODY_LENGTH_BITS <=\n            bitbuffer->bits_per_row[0]) {\n\n        bitpos += PREAMBLE_SYNC_LENGTH_BITS;\n        // get buffer including model ID, as we need this in CRC calculation\n        uint8_t msg[19];\n        bitbuffer_extract_bytes(bitbuffer, 0, bitpos, msg, BODY_LENGTH_BITS + HEADER_LENGTH_BITS);\n        bitpos += BODY_LENGTH_BITS + HEADER_LENGTH_BITS;\n\n        uint8_t *b = msg;\n        if (crc16(b, (BODY_LENGTH_BITS + HEADER_LENGTH_BITS) / 8, 0x8005, 0) != 0) {\n            decoder_log(decoder, 2, __func__, \"failed CRC check\");\n            return DECODE_FAIL_MIC;\n        }\n\n        int mcode = (b[1] << 8) | b[2];\n        if (mcode != 0x0401 && mcode != 0x0106) {\n            decoder_logf(decoder, 1, __func__, \"Unknown model code %04x\", mcode);\n            return DECODE_FAIL_SANITY;\n        }\n\n        // as printed on the side of the unit\n        uint32_t serial   = (b[3] << 16) | (b[4] << 8) | b[5];\n        uint8_t status    = b[6];\n        float temperature = (b[7] - 0x48) / 2; // truncate to whole number\n        uint16_t depth     = ((b[9] & 0x0f) << 8) | b[10];\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                \"Model\",        DATA_STRING, \"Oil-SonicAdv\",\n                \"id\",                   \"ID\",           DATA_FORMAT, \"%08d\", DATA_INT, serial,\n                \"temperature_C\",        \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n                \"depth_cm\",             \"Depth\",        DATA_INT,    depth,\n                \"status\",               \"Status\",       DATA_FORMAT, \"%02x\", DATA_INT, status,\n                \"mic\",                  \"Integrity\",    DATA_STRING, \"CRC\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        events++;\n    }\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"depth_cm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const oil_watchman_advanced = {\n        .name        = \"Watchman Sonic Advanced / Plus, Tekelek\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .reset_limit = 12500, // allow 24 sequential 0-bit's\n        .decode_fn   = &oil_watchman_advanced_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/opus_xt300.c",
    "content": "/** @file\n    Opus/Imagintronix XT300 Soil Moisture Sensor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nOpus/Imagintronix XT300 Soil Moisture Sensor.\n\nAlso called XH300 sometimes, this seems to be the associated display name\n\nhttps://www.plantcaretools.com/product/wireless-moisture-monitor/\n\nData is transmitted with 6 bytes row:\n\n     0. 1. 2. 3. 4. 5\n    FF ID SM TT ?? CC\n\n- FF: initial preamble\n- ID: 0101 01ID\n- SM: soil moisure (decimal 05 -> 99 %)\n- TT: temperature °C + 40°C (decimal)\n- ??: always FF... maybe spare bytes\n- CC: check sum (simple sum) except 0xFF preamble\n\n*/\n\n#include \"decoder.h\"\n\nstatic int opus_xt300_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int ret       = 0;\n    int fail_code = 0;\n    int row;\n    int chk;\n    uint8_t *b;\n    int channel, temp, moisture;\n    data_t *data;\n\n    for (row = 0; row < bitbuffer->num_rows; row++) {\n\n        if (bitbuffer->bits_per_row[row] != 48) {\n            fail_code = DECODE_ABORT_LENGTH;\n            continue;\n        }\n\n        b = bitbuffer->bb[row];\n\n        if (!b[0] && !b[1] && !b[2] && !b[3]) {\n            decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n            fail_code = DECODE_FAIL_SANITY;\n            continue;\n        }\n\n        if (b[0] != 0xFF && ((b[1] | 0x1) & 0xFD) == 0x55) {\n            fail_code = DECODE_ABORT_EARLY;\n            continue;\n        }\n        chk = add_bytes(b + 1, 4); // sum bytes 1-4\n        chk = chk & 0xFF;\n        if (chk != 0 && chk != b[5]) {\n            fail_code = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        channel  = (b[1] & 0x03);\n        temp     = b[3] - 40;\n        moisture = b[2];\n\n        // unverified sales advert say Outdoor temperature range: -40°C to +65°C\n        // test for Boiling water\n        // over 100% soil humidity ?\n        if (temp > 100 || moisture > 101) {\n            // decoder_logf(decoder, 0, __func__, \"temp %d moisture %d\", temp, moisture);\n            fail_code = DECODE_FAIL_SANITY;\n            continue;\n        }\n\n        /* clang-format off */\n        data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Opus-XT300\",\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temp,\n            \"moisture\",         \"Moisture\",     DATA_FORMAT, \"%d %%\", DATA_INT, moisture,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        ret++;\n    }\n    return ret > 0 ? ret : fail_code;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"channel\",\n        \"temperature_C\",\n        \"moisture\",\n        \"mic\",\n        NULL,\n};\n\nr_device const opus_xt300 = {\n        .name        = \"Opus/Imagintronix XT300 Soil Moisture\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 544,\n        .long_width  = 932,\n        .gap_limit   = 10000,\n        .reset_limit = 31000,\n        .decode_fn   = &opus_xt300_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oregon_scientific.c",
    "content": "/** @file\n    Various Oregon Scientific protocols.\n\n    Copyright (C) 2015 Helge Weissig, Denis Bodor, Tommy Vestermark, Karl Lattimer,\n    deennoo, pclov3r, onlinux, Pasquale Fiorillo.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n// Documentation for Oregon Scientific protocols can be found here:\n// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf\n//\n// note that at least for THN132N, THGR122N, THGR810 valid channel numbers are 1, 2, 4.\n// Sensors ID\n#define ID_THGR122N 0x1d20 // also THGR228N (#3121)\n#define ID_THGR968  0x1d30\n#define ID_BTHR918  0x5d50\n#define ID_BHTR968  0x5d60\n#define ID_RGR968   0x2d10\n#define ID_THR228N  0xec40\n#define ID_THN132N  0xec40 // same as THR228N but different packet size\n#define ID_AWR129   0xec41 // similar to THR228N, but an extra 100s digit\n#define ID_RTGN318  0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3\n#define ID_RTGN129  0x0cc3 // same as RTGN318 but different packet size\n#define ID_THGR810  0xf024 // This might be ID_THGR81, but what's true is lost in (git) history, #3221 Add new Oregon Scientific v3 models, original id is 0xf824, new rolling ids are 0xf024, 0xf224, 0xfa24 for rebranded sensor versions Newentor, Unni, Liorque.\n#define ID_THGR810a 0xf8b4 // unconfirmed version\n#define ID_THN802   0xc844\n#define ID_PCR800   0x2914\n#define ID_PCR800a  0x2d14 // Different PCR800 ID - AU version I think\n#define ID_WGR800   0x1984\n#define ID_WGR800a  0x1994 // unconfirmed version\n#define ID_WGR968   0x3d00\n#define ID_UV800    0xd874\n#define ID_THN129   0xcc43 // THN129 Temp only\n#define ID_RTHN129  0x0cd3 // RTHN129 Temp, clock sensors\n#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor\n#define ID_UVR128   0xec70\n#define ID_THGR328N   0xcc23  // Temp & Hygro sensor looks similar to THR228N but with 5 choice channel instead of 3\n#define ID_RTGR328N_1 0xdcc3  // RTGR328N_[1-5] RFclock (date &time) & Temp & Hygro sensor looks similar to THGR328N with RF clock (5 channels also) : Temp & hygro part\n#define ID_RTGR328N_2 0xccc3\n#define ID_RTGR328N_3 0xbcc3\n#define ID_RTGR328N_4 0xacc3\n#define ID_RTGR328N_5 0x9cc3\n#define ID_RTGR328N_6 0x8ce3  // RTGR328N_6&7 RFclock (date &time) & Temp & Hygro sensor looks similar to THGR328N with RF clock (5 channels also) : RF Time part\n#define ID_RTGR328N_7 0x8ae3\n\nstatic float get_os_temperature(uint8_t const *message)\n{\n    float temp_c = 0;\n    temp_c = (((message[5] >> 4) * 100) + ((message[4] & 0x0f) * 10) + ((message[4] >> 4) & 0x0f)) / 10.0F;\n    // The AWR129 BBQ thermometer has another digit to represent higher temperatures than what weather stations would observe.\n    temp_c += (message[5] & 0x07) * 100.0F;\n    // 0x08 is the sign bit\n    if (message[5] & 0x08) {\n        temp_c = -temp_c;\n    }\n    return temp_c;\n}\n\nstatic float get_os_rain_rate(uint8_t const *message)\n{\n    // Nibbles 11..8 rain rate, LSD = 0.1 units per hour, 4321 = 123.4 units per hour\n    float rain_rate = (((message[5] & 0x0f) * 1000) + ((message[5] >> 4) * 100) + ((message[4] & 0x0f) * 10) + (message[4] >> 4)) / 100.0F;\n    return rain_rate;\n}\n\nstatic float get_os_total_rain(uint8_t const *message)\n{\n    float total_rain = 0.0F; // Nibbles 17..12 Total rain, LSD = 0.001, 654321 = 123.456\n    total_rain = (message[8] & 0x0f) * 100.0F\n            + ((message[8] >> 4) & 0x0f) * 10.0F + (message[7] & 0x0f)\n            + ((message[7] >> 4) & 0x0f) / 10.0F + (message[6] & 0x0f) / 100.0F\n            + ((message[6] >> 4) & 0x0f) / 1000.0F;\n    return total_rain;\n}\n\nstatic unsigned int get_os_humidity(uint8_t const *message)\n{\n    int humidity = 0;\n    humidity = ((message[6] & 0x0f) * 10) + (message[6] >> 4);\n    return humidity;\n}\n\nstatic unsigned int get_os_uv(uint8_t const *message)\n{\n    int uvidx = 0;\n    uvidx = ((message[4] & 0x0f) * 10) + (message[4] >> 4);\n    return uvidx;\n}\n\nstatic unsigned cm180i_power(uint8_t const *msg, unsigned int offset)\n{\n    unsigned val = 0;\n    val = (msg[4 + offset * 2] << 8) | (msg[3 + offset * 2] & 0xF0);\n    // tested across situations varying from 700 watt to more than 8000 watt to\n    // get same value as showed in physical CM180 panel (exactly equals to 1+1/160)\n    val *= 1.00625f;\n    return val;\n}\n\nstatic uint64_t cm180i_total(uint8_t const *msg)\n{\n    uint64_t val = 0;\n    if ((msg[1] & 0x0F) == 0) {\n        // Sensor returns total only if nibble#4 == 0\n        val = ((uint64_t)msg[14] << 40)\n                | ((uint64_t)msg[13] << 32)\n                | ((uint32_t)msg[12] << 24)\n                | (msg[11] << 16)\n                | (msg[10] << 8)\n                | (msg[9]);\n    }\n    return val;\n}\n\nstatic uint8_t swap_nibbles(uint8_t byte)\n{\n    return (((byte & 0xf) << 4) | (byte >> 4));\n}\n\nstatic unsigned cm180_power(uint8_t const *msg)\n{\n    unsigned val = 0;\n    val = (msg[4] << 8) | (msg[3] & 0xF0);\n    // tested across situations varying from 700 watt to more than 8000 watt to\n    // get same value as showed in physical CM180 panel (exactly equals to 1+1/160)\n    val *= 1.00625f;\n    return val;\n}\n\nstatic uint64_t cm180_total(uint8_t const *msg)\n{\n    uint64_t val = 0;\n    if ((msg[1] & 0x0F) == 0) {\n        // Sensor returns total only if nibble#4 == 0\n        val = (uint64_t)msg[10] << 40;\n        val += (uint64_t)msg[9] << 32;\n        val += (uint32_t)msg[8] << 24;\n        val += msg[7] << 16;\n        val += msg[6] << 8;\n        val += msg[5];\n    }\n    return val;\n}\n\nstatic int validate_os_checksum(r_device *decoder, uint8_t const *msg, int checksum_nibble_idx)\n{\n    // Oregon Scientific v2.1 and v3 checksum is a    1 byte    'sum of nibbles' checksum.\n    // with the 2 nibbles of the checksum byte    swapped.\n    int i;\n    unsigned int checksum, sum_of_nibbles = 0;\n    for (i = 0; i < checksum_nibble_idx - 1; i += 2) {\n        uint8_t val = msg[i >> 1];\n        sum_of_nibbles += ((val >> 4) + (val & 0x0f));\n    }\n    if (checksum_nibble_idx & 1) {\n        sum_of_nibbles += (msg[checksum_nibble_idx >> 1] >> 4);\n        checksum = (msg[checksum_nibble_idx >> 1] & 0x0f) | (msg[(checksum_nibble_idx + 1) >> 1] & 0xf0);\n    }\n    else {\n        checksum = (msg[checksum_nibble_idx >> 1] >> 4) | ((msg[checksum_nibble_idx >> 1] & 0x0f) << 4);\n    }\n    sum_of_nibbles &= 0xff;\n\n    if (sum_of_nibbles == checksum) {\n        return 0;\n    }\n    else {\n        decoder_logf(decoder, 1, __func__, \"Checksum error, expected: %02x calculated: %02x\", checksum, sum_of_nibbles);\n        decoder_log_bitrow(decoder, 1, __func__, msg, ((checksum_nibble_idx + 4) >> 1) * 8, \"Message\");\n        return 1;\n    }\n}\n\nstatic int validate_os_v2_message(r_device *decoder, uint8_t const *msg, int bits_expected, int msg_bits,\n        int nibbles_in_checksum)\n{\n    // Compare number of valid bits processed vs number expected\n    if (bits_expected == msg_bits) {\n        return validate_os_checksum(decoder, msg, nibbles_in_checksum);\n    }\n    decoder_logf_bitrow(decoder, 1, __func__, msg, msg_bits, \"Bit validation error, expected %d bits, Message\", bits_expected);\n    return 1;\n}\n\n/**\nVarious Oregon Scientific protocols.\n\n@todo Documentation needed.\n*/\nstatic int oregon_scientific_v2_1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const *b = bitbuffer->bb[0];\n\n    // Check 2nd and 3rd bytes of stream for possible Oregon Scientific v2.1 sensor data (skip first byte to get past sync/startup bit errors)\n    if (((b[1] != 0x55) || (b[2] != 0x55))\n            && ((b[1] != 0xAA) || (b[2] != 0xAA))) {\n        if (b[3] != 0) {\n            decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0], \"Badly formatted OS v2.1 message\");\n        }\n        return DECODE_ABORT_EARLY;\n    }\n\n    bitbuffer_t databits = {0};\n    uint8_t const *msg = databits.bb[0];\n\n    // Possible    v2.1 Protocol message\n    unsigned int sync_test_val = ((unsigned)b[3] << 24) | (b[4] << 16) | (b[5] << 8) | (b[6]);\n    // Could be extra/dropped bits in stream.    Look for sync byte at expected position +/- some bits in either direction\n    for (int pattern_index = 0; pattern_index < 8; pattern_index++) {\n        unsigned int mask     = (unsigned int)(0xffff0000 >> pattern_index);\n        unsigned int pattern  = (unsigned int)(0x55990000 >> pattern_index);\n        unsigned int pattern2 = (unsigned int)(0xaa990000 >> pattern_index);\n\n        decoder_logf(decoder, 1, __func__, \"OS v2.1 sync search, test=%08x p=%08x m=%08x\", sync_test_val, pattern, mask);\n\n        if (((sync_test_val & mask) != pattern)\n                && ((sync_test_val & mask) != pattern2)) {\n            continue; // DECODE_ABORT_EARLY\n        }\n\n        // Found sync byte - start working on decoding the stream data.\n        // pattern_index indicates    where sync nibble starts, so now we can find the start of the payload\n        decoder_logf(decoder, 1, __func__, \"OS v2.1 sync %08x found, decode at bit %d\", sync_test_val, pattern_index);\n\n        //decoder_log_bitrow(decoder, 0, __func__, b, bitbuffer->bits_per_row[0], \"Raw OS v2 bits\");\n        bitbuffer_manchester_decode(bitbuffer, 0, pattern_index + 40, &databits, 173);\n        reflect_nibbles(databits.bb[0], (databits.bits_per_row[0]+7)/8);\n        //decoder_logf_bitbuffer(decoder, 0, __func__, &databits, \"MC OS v2 bits (from %d+40)\", pattern_index);\n\n        break;\n    }\n    int msg_bits = databits.bits_per_row[0];\n\n    int sensor_id   = (msg[0] << 8) | msg[1];\n    int channel     = (msg[2] >> 4) & 0x0f;\n    int device_id   = (msg[2] & 0x0f) | (msg[3] & 0xf0);\n    int battery_low = (msg[3] >> 2) & 0x01;\n\n    decoder_logf(decoder, 1, __func__,\"Found sensor type (%04x)\", sensor_id);\n    if ((sensor_id == ID_THGR122N) || (sensor_id == ID_THGR968)) {\n        if ((validate_os_v2_message(decoder, msg, 68, msg_bits, 15) != 0)\n                && (validate_os_v2_message(decoder, msg, 76, msg_bits, 15) != 0)) {\n            return 0;\n        }\n        int is_thgr228n = sensor_id == ID_THGR122N && msg_bits == 68;\n        int is_thgr122n = sensor_id == ID_THGR122N && msg_bits == 76; // the last byte is unused and not included in the checksum\n        int is_thgr968 = sensor_id == ID_THGR968;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                 \"\",                        DATA_COND, is_thgr968, DATA_STRING, \"Oregon-THGR968\",\n                \"model\",                 \"\",                        DATA_COND, is_thgr122n, DATA_STRING, \"Oregon-THGR122N\",\n                \"model\",                 \"\",                        DATA_COND, is_thgr228n, DATA_STRING, \"Oregon-THGR228N\",\n                \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                \"channel\",             \"Channel\",         DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, get_os_temperature(msg),\n                \"humidity\",            \"Humidity\",        DATA_FORMAT, \"%u %%\",     DATA_INT,        get_os_humidity(msg),\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_WGR968) {\n        if (validate_os_v2_message(decoder, msg, 94, msg_bits, 17) != 0) {\n            return 0;\n        }\n        float quadrant      = (msg[4] & 0x0f) * 10 + ((msg[4] >> 4) & 0x0f) * 1 + ((msg[5] >> 4) & 0x0f) * 100;\n        float avgWindspeed  = ((msg[7] >> 4) & 0x0f) / 10.0F + (msg[7] & 0x0f) * 1.0F + ((msg[8] >> 4) & 0x0f) / 10.0F;\n        float gustWindspeed = (msg[5] & 0x0f) / 10.0F + ((msg[6] >> 4) & 0x0f) * 1.0F + (msg[6] & 0x0f) / 10.0F;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                     DATA_STRING, \"Oregon-WGR968\",\n                \"id\",                 \"House Code\", DATA_INT,        device_id,\n                \"channel\",        \"Channel\",        DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"wind_max_m_s\", \"Gust\",             DATA_FORMAT, \"%.1f m/s\",DATA_DOUBLE, gustWindspeed,\n                \"wind_avg_m_s\", \"Average\",        DATA_FORMAT, \"%.1f m/s\",DATA_DOUBLE, avgWindspeed,\n                \"wind_dir_deg\",    \"Direction\",    DATA_FORMAT, \"%.1f degrees\",DATA_DOUBLE, quadrant,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_BHTR968) {\n        if (validate_os_v2_message(decoder, msg, 92, msg_bits, 19) != 0) {\n            return 0;\n        }\n        //unsigned int comfort = msg[7] >> 4;\n        //char *comfort_str = \"Normal\";\n        //if (comfort == 4) comfort_str = \"Comfortable\";\n        //else if (comfort == 8) comfort_str = \"Dry\";\n        //else if (comfort == 0xc) comfort_str = \"Humid\";\n        //unsigned int forecast = msg[9] >> 4;\n        //char *forecast_str = \"Cloudy\";\n        //if (forecast == 3) forecast_str = \"Rainy\";\n        //else if (forecast == 6) forecast_str = \"Partly Cloudy\";\n        //else if (forecast == 0xc) forecast_str = \"Sunny\";\n        float temp_c = get_os_temperature(msg);\n        float pressure = ((msg[7] & 0x0f) | (msg[8] & 0xf0)) + 856;\n        // decoder_logf(decoder, 0, __func__,\"Weather Sensor BHTR968    Indoor        Temp: %.1fC    %.1fF     Humidity: %d%%\", temp_c, ((temp_c*9)/5)+32, get_os_humidity(msg));\n        // decoder_logf(decoder, 0, __func__, \" (%s) Pressure: %dmbar (%s)\", comfort_str, ((msg[7] & 0x0f) | (msg[8] & 0xf0))+856, forecast_str);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                             DATA_STRING, \"Oregon-BHTR968\",\n                \"id\",                 \"House Code\",         DATA_INT,        device_id,\n                \"channel\",        \"Channel\",                DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",     \"Humidity\",             DATA_FORMAT, \"%u %%\",     DATA_INT,        get_os_humidity(msg),\n                \"pressure_hPa\",    \"Pressure\",        DATA_FORMAT, \"%.0f hPa\",     DATA_DOUBLE, pressure,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_BTHR918) {\n        // Similar to the BHTR968, but smaller message and slightly different pressure offset\n        if (validate_os_v2_message(decoder, msg, 84, msg_bits, 19) != 0) {\n            return 0;\n        }\n        float temp_c = get_os_temperature(msg);\n        float pressure = ((msg[7] & 0x0f) | (msg[8] & 0xf0)) + 795;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                 DATA_STRING,    \"Oregon-BTHR918\",\n                \"id\",               \"House Code\",       DATA_INT,       device_id,\n                \"channel\",          \"Channel\",          DATA_INT,       channel,\n                \"battery_ok\",       \"Battery\",          DATA_INT,       !battery_low,\n                \"temperature_C\",    \"Celsius\",          DATA_FORMAT,    \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",         \"Humidity\",         DATA_FORMAT,    \"%u %%\", DATA_INT, get_os_humidity(msg),\n                \"pressure_hPa\",     \"Pressure\",         DATA_FORMAT,    \"%.0f hPa\", DATA_DOUBLE, pressure,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_RGR968) {\n        if (validate_os_v2_message(decoder, msg, 80, msg_bits, 16) != 0) {\n            return 0;\n        }\n        float rain_rate  = ((msg[4] & 0x0f) * 100 + (msg[4] >> 4) * 10 + ((msg[5] >> 4) & 0x0f)) / 10.0F;\n        float total_rain = ((msg[7] & 0xf) * 10000 + (msg[7] >> 4) * 1000 + (msg[6] & 0xf) * 100 + (msg[6] >> 4) * 10 + (msg[5] & 0xf)) / 10.0F;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                     DATA_STRING, \"Oregon-RGR968\",\n                \"id\",                 \"House Code\", DATA_INT,        device_id,\n                \"channel\",        \"Channel\",        DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"rain_rate_mm_h\",    \"Rain Rate\",    DATA_FORMAT, \"%.2f mm/h\", DATA_DOUBLE, rain_rate,\n                \"rain_mm\", \"Total Rain\", DATA_FORMAT, \"%.2f mm\", DATA_DOUBLE, total_rain,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if ((sensor_id == ID_THR228N || sensor_id == ID_AWR129) && msg_bits == 76) {\n        if (validate_os_v2_message(decoder, msg, 76, msg_bits, 12) != 0) {\n            return 0;\n        }\n        float temp_c = get_os_temperature(msg);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\", \"\", DATA_COND, sensor_id == ID_THR228N, DATA_STRING, \"Oregon-THR228N\",\n                \"model\", \"\", DATA_COND, sensor_id == ID_AWR129, DATA_STRING, \"Oregon-AWR129\",\n                \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                \"channel\",             \"Channel\",         DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_THN132N && msg_bits == 64) {\n        if (validate_os_v2_message(decoder, msg, 64, msg_bits, 12) != 0) {\n            return 0;\n        }\n        // Sanity check BCD digits\n        if (((msg[5] >> 4) & 0x0F) > 9 || (msg[4] & 0x0F) > 9 || ((msg[4] >> 4) & 0x0F) > 9) {\n            decoder_log(decoder, 1, __func__, \"THN132N Message failed BCD sanity check.\");\n            return DECODE_FAIL_SANITY;\n        }\n        float temp_c = get_os_temperature(msg);\n        // Sanity check value\n        if (temp_c > 70 || temp_c < -50) {\n            decoder_logf(decoder, 1, __func__, \"THN132N failed value sanity check: temp %.1fC.\", temp_c);\n            return DECODE_FAIL_SANITY;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                 \"\",                        DATA_STRING, \"Oregon-THN132N\",\n                \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                \"channel\",             \"Channel\",         DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if ((sensor_id & 0x0fff) == ID_RTGN129 && msg_bits == 80) {\n        if (validate_os_v2_message(decoder, msg, 80, msg_bits, 15) != 0) {\n            return 0;\n        }\n        float temp_c = get_os_temperature(msg);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                 \"\",                        DATA_STRING, \"Oregon-RTGN129\",\n                \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                \"channel\",             \"Channel\",         DATA_INT,        channel, // 1 to 5\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",            \"Humidity\",        DATA_FORMAT, \"%u %%\",     DATA_INT,        get_os_humidity(msg),\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (((sensor_id == ID_RTGR328N_1) || (sensor_id == ID_RTGR328N_2) || (sensor_id == ID_RTGR328N_3) || (sensor_id == ID_RTGR328N_4) || (sensor_id == ID_RTGR328N_5)) && msg_bits == 173) {\n        if (validate_os_v2_message(decoder, msg, 173, msg_bits, 15) != 0) {\n             return 0;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Oregon-RTGR328N\",\n                \"id\",               \"House Code\",   DATA_INT,    device_id,\n                \"channel\",          \"Channel\",      DATA_INT,    channel, // 1 to 5\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, get_os_temperature(msg),\n                \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\",   DATA_INT,    get_os_humidity(msg),\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if ((sensor_id == ID_RTGR328N_6) || (sensor_id == ID_RTGR328N_7)) {\n        if (validate_os_v2_message(decoder, msg, 100, msg_bits, 21) != 0) {\n            return 0;\n        }\n\n        int year    = ((msg[9] & 0x0F) * 10) + ((msg[9] & 0xF0) >> 4) + 2000;\n        int month   = ((msg[8] & 0xF0) >> 4);\n        //int weekday = ((msg[8] & 0x0F));\n        int day     = ((msg[7] & 0x0F) * 10) + ((msg[7] & 0xF0) >> 4);\n        int hours   = ((msg[6] & 0x0F) * 10) + ((msg[6] & 0xF0) >> 4);\n        int minutes = ((msg[5] & 0x0F) * 10) + ((msg[5] & 0xF0) >> 4);\n        int seconds = ((msg[4] & 0x0F) * 10) + ((msg[4] & 0xF0) >> 4);\n\n        char clock_str[24];\n        snprintf(clock_str, sizeof(clock_str), \"%04d-%02d-%02dT%02d:%02d:%02d\",\n                year, month, day, hours, minutes, seconds);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Oregon-RTGR328N\",\n                \"id\",               \"House Code\",   DATA_INT,    device_id,\n                \"channel\",          \"Channel\",      DATA_INT,    channel, // 1 to 5\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"radio_clock\",      \"Radio Clock\",  DATA_STRING, clock_str,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if ((sensor_id & 0x0fff) == ID_RTGN318) {\n        if (msg_bits == 76 && (validate_os_v2_message(decoder, msg, 76, msg_bits, 15) == 0)) {\n            float temp_c = get_os_temperature(msg);\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",                 \"\",                        DATA_STRING, \"Oregon-RTGN318\",\n                    \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                    \"channel\",             \"Channel\",         DATA_INT,        channel, // 1 to 5\n                    \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                    \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                    \"humidity\",            \"Humidity\",        DATA_FORMAT, \"%u %%\",     DATA_INT,        get_os_humidity(msg),\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        else if (msg_bits == 100 && (validate_os_v2_message(decoder, msg, 100, msg_bits, 21) == 0)) {\n            // RF Clock message ??\n            return 0;\n        }\n    }\n    else if (sensor_id == ID_THN129 || (sensor_id & 0x0FFF) == ID_RTHN129) {\n        if ((validate_os_v2_message(decoder, msg, 68, msg_bits, 12) == 0)) {\n            float temp_c = get_os_temperature(msg);\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",                 \"\",                DATA_COND, sensor_id == ID_THN129, DATA_STRING, \"Oregon-THN129\",\n                    \"model\",                 \"\",                DATA_COND, sensor_id != ID_THN129, DATA_STRING, \"Oregon-RTHN129\",\n                    \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                    \"channel\",             \"Channel\",         DATA_INT,        channel, // 1 to 5\n                    \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                    \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                    NULL);\n            /* clang-format on */\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        else if (msg_bits == 104 && (validate_os_v2_message(decoder, msg, 104, msg_bits, 18) == 0)) {\n            // RF Clock message\n            return 0;\n        }\n    }\n    else if (sensor_id == ID_BTHGN129) {\n        if (validate_os_v2_message(decoder, msg, 92, msg_bits, 19) != 0) {\n            return 0;\n        }\n        // int comfort = msg[7] >> 4;\n        // char *comfort_str = \"Normal\";\n        // if      (comfort == 0x4)   comfort_str = \"Comfortable\";\n        // else if (comfort == 0x8)   comfort_str = \"Dry\";\n        // else if (comfort == 0xc) comfort_str = \"Humid\";\n        // int forecast = msg[9] >> 4;\n        // char *forecast_str = \"Cloudy\";\n        // if      (forecast == 0x3)   forecast_str = \"Rainy\";\n        // else if (forecast == 0x6)   forecast_str = \"Partly Cloudy\";\n        // else if (forecast == 0xc) forecast_str = \"Sunny\";\n        float temp_c = get_os_temperature(msg);\n        // Pressure is given in hPa. You may need to adjust the offset\n        // according to your altitude level (600 is a good starting point)\n        float pressure = ((msg[7] & 0x0f) | (msg[8] & 0xf0)) * 2 + (msg[8] & 0x01) + 600;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                 \"\",                        DATA_STRING, \"Oregon-BTHGN129\",\n                \"id\",                        \"House Code\",    DATA_INT,        device_id,\n                \"channel\",             \"Channel\",         DATA_INT,        channel, // 1 to 5\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",             \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, get_os_humidity(msg),\n                \"pressure_hPa\",    \"Pressure\",        DATA_FORMAT, \"%.2f hPa\", DATA_DOUBLE, pressure,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_UVR128 && msg_bits == 148) {\n        if (validate_os_v2_message(decoder, msg, 148, msg_bits, 12) != 0) {\n            return 0;\n        }\n        // Sanity check BCD digits\n        if (((msg[4] >> 4) & 0x0F) > 9 || (msg[4] & 0x0F) > 9) {\n            decoder_log(decoder, 1, __func__, \"UVR128 Message failed BCD sanity check.\");\n            return DECODE_FAIL_SANITY;\n        }\n        int uvidx = get_os_uv(msg);\n        // Sanity check value\n        if (uvidx < 0 || uvidx > 25) {\n            decoder_logf(decoder, 1, __func__, \"UVR128 failed values sanity check: uv %u.\", uvidx);\n            return DECODE_FAIL_SANITY;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                    \"\",                     DATA_STRING, \"Oregon-UVR128\",\n                \"id\",                         \"House Code\", DATA_INT,        device_id,\n                \"uvi\",                        \"UV Index\",     DATA_FORMAT, \"%.0f\", DATA_DOUBLE, (double)uvidx,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                //\"channel\",                \"Channel\",        DATA_INT,        channel,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_THGR328N) {\n        if (validate_os_v2_message(decoder, msg, 173, msg_bits, 15) != 0) {\n            return 0;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Oregon-THGR328N\",\n                \"id\",               \"House Code\",   DATA_INT,    device_id,\n                \"channel\",          \"Channel\",      DATA_INT,    channel, // 1 to 5\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, get_os_temperature(msg),\n                \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\",   DATA_INT,    get_os_humidity(msg),\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (msg_bits > 16) {\n        decoder_logf_bitrow(decoder, 1, __func__, msg, msg_bits, \"Unrecognized OS v2.1 message (type %04x)\", sensor_id);\n    }\n    else {\n        decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0], \"Possible OS v2.1 message, but sync nibble not found. Raw\");\n    }\n\n    return 0;\n}\n\n// ceil((335 + 11) / 8)\n#define EXPECTED_NUM_BYTES 44\n\n/**\nVarious Oregon Scientific protocols.\n\n@todo Documentation needed.\n*/\nstatic int oregon_scientific_v3_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b = bitbuffer->bb[0];\n\n    // Check stream for possible Oregon Scientific v3 protocol preamble\n    if ((((b[0] & 0xf) != 0x0f) || (b[1] != 0xff) || ((b[2] & 0xc0) != 0xc0))\n            && (((b[0] & 0xf) != 0x00) || (b[1] != 0x00) || ((b[2] & 0xc0) != 0x00))) {\n        if (b[3] != 0) {\n            decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0], \"Unrecognized Msg in OS v3\");\n        }\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t msg[EXPECTED_NUM_BYTES] = {0};\n    int msg_pos = 0;\n    int msg_len = 0;\n\n    // e.g. WGR800X has {335} 00 00 00 b1 22 40 0e 00 06 00 00 00 19 7c   00 00 00 b1 22 40 0e 00 06 00 00 00 19 7c   00 00 00 b1 22 40 0e 00 06 00 00 00 19 7c\n    // aligned (at 11) and reflected that's 3 packets:\n    // {324} 00 0a 19 84 00 e0 00 c0 00 00 00 3d 70   00 00 0a 19 84 00 e0 00 c0 00 00 00 3d 70   00 00 0a 19 84 00 e0 00 c0 00 00 00 3d 70\n\n    // full preamble is 00 00 00 5 (shorter for WGR800X)\n    uint8_t const os_pattern[] = {0x00, 0x05};\n    // CM180 preamble is 00 00 00 46, with 0x46 already data\n    uint8_t const cm180_pattern[] = {0x00, 0x46};\n    uint8_t const cm180i_pattern[] = {0x00, 0x4A};\n    // workaround for a broken manchester demod\n    // CM160 preamble might look like 7f ff ff aa, i.e. ff ff f5\n    uint8_t const alt_pattern[] = {0xff, 0xf5};\n\n    int os_pos     = bitbuffer_search(bitbuffer, 0, 0, os_pattern, 16) + 16;\n    int cm180_pos  = bitbuffer_search(bitbuffer, 0, 0, cm180_pattern, 16) + 8;  // keep the 0x46\n    int cm180i_pos = bitbuffer_search(bitbuffer, 0, 0, cm180i_pattern, 16) + 8; // keep the 0x46\n    int alt_pos    = bitbuffer_search(bitbuffer, 0, 0, alt_pattern, 16) + 16;\n\n    if (bitbuffer->bits_per_row[0] - os_pos >= 7 * 8) {\n        msg_pos = os_pos;\n        msg_len = bitbuffer->bits_per_row[0] - os_pos;\n    }\n\n    // 52 bits: secondary frame (instant watts only)\n    // 108 bits: primary frame (instant watts + cumulative wattshour)\n    else if (bitbuffer->bits_per_row[0] - cm180_pos >= 52) {\n        msg_pos = cm180_pos;\n        msg_len = bitbuffer->bits_per_row[0] - cm180_pos;\n    }\n\n    else if (bitbuffer->bits_per_row[0] - cm180i_pos >= 84) {\n        msg_pos = cm180i_pos;\n        msg_len = bitbuffer->bits_per_row[0] - cm180i_pos;\n    }\n\n    else if (bitbuffer->bits_per_row[0] - alt_pos >= 7 * 8) {\n        msg_pos = alt_pos;\n        msg_len = bitbuffer->bits_per_row[0] - alt_pos;\n    }\n\n    if (msg_len == 0 || msg_len > (int)sizeof(msg) * 8) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, msg_pos, msg, msg_len);\n    reflect_nibbles(msg, (msg_len + 7) / 8);\n\n    int sensor_id   = (msg[0] << 8) | msg[1];            // not for CM sensor types\n    int channel     = (msg[2] >> 4) & 0x0f;              // not for CM sensor types\n    int device_id   = (msg[2] & 0x0f) | (msg[3] & 0xf0); // not for CM sensor types\n    int battery_low = (msg[3] >> 2) & 0x01;              // not for CM sensor types\n\n    if ((sensor_id & 0xf0ff) == ID_THGR810 || sensor_id == ID_THGR810a) { // rolling ids 0xf024, 0xf224, 0xf824, 0xfa24, 3 from 4 nibbles are checked see #3221\n        if (validate_os_checksum(decoder, msg, 15) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n        // Sanity check BCD digits\n        if (((msg[5] >> 4) & 0x0F) > 9 || (msg[4] & 0x0F) > 9 || ((msg[4] >> 4) & 0x0F) > 9 || (msg[6] & 0x0F) > 9 || ((msg[6] >> 4) & 0x0F) > 9) {\n            decoder_log(decoder, 1, __func__, \"THGR810 Message failed BCD sanity check.\");\n            return DECODE_FAIL_SANITY;\n        }\n        float temp_c = get_os_temperature(msg);\n        int humidity = get_os_humidity(msg);\n        // Sanity check values\n        if (temp_c > 70 || temp_c < -50) {\n            decoder_logf(decoder, 1, __func__, \"THGR810 failed value sanity check: temp %.1fC hum %d%%.\", temp_c, humidity);\n            return DECODE_FAIL_SANITY;\n        }\n        int tx_button = msg[0] & 1; // unused sensor id bits\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Oregon-THGR810\",\n                \"id\",               \"House Code\",   DATA_INT,    device_id,\n                \"channel\",          \"Channel\",      DATA_INT,    channel,\n                \"button\",           \"Button\",       DATA_COND,   tx_button, DATA_INT, tx_button,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",      DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;                                    //msg[k] = ((msg[k] & 0x0F) << 4) + ((msg[k] & 0xF0) >> 4);\n    }\n    else if (sensor_id == ID_THN802) {\n        if (validate_os_checksum(decoder, msg, 12) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n        float temp_c = get_os_temperature(msg);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                    \"\",                     DATA_STRING, \"Oregon-THN802\",\n                \"id\",                         \"House Code\", DATA_INT,        device_id,\n                \"channel\",                \"Channel\",        DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Celsius\",        DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_UV800) {\n        if (validate_os_checksum(decoder, msg, 13) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n        int uvidx = get_os_uv(msg);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",                    \"\",                     DATA_STRING, \"Oregon-UV800\",\n                \"id\",                         \"House Code\", DATA_INT,        device_id,\n                \"channel\",                \"Channel\",        DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"uvi\",                        \"UV Index\",     DATA_FORMAT, \"%.0f\", DATA_DOUBLE, (double)uvidx,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_PCR800) {\n        if (validate_os_checksum(decoder, msg, 18) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n        // Sanity check BCD digits\n        if ((msg[8] & 0x0F) > 9\n                || ((msg[8] >> 4) & 0x0F) > 9\n                || (msg[7] & 0x0F) > 9\n                || ((msg[7] >> 4) & 0x0F) > 9\n                || (msg[6] & 0x0F) > 9\n                || ((msg[6] >> 4) & 0x0F) > 9\n                || (msg[5] & 0x0F) > 9\n                || ((msg[5] >> 4) & 0x0F) > 9\n                || (msg[4] & 0x0F) > 9\n                || ((msg[4] >> 4) & 0x0F) > 9) {\n            decoder_log(decoder, 1, __func__, \"PCR800 Message failed BCD sanity check.\");\n            return DECODE_FAIL_SANITY;\n        }\n\n        float rain_rate = get_os_rain_rate(msg);\n        float total_rain = get_os_total_rain(msg);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                     DATA_STRING, \"Oregon-PCR800\",\n                \"id\",                 \"House Code\", DATA_INT,        device_id,\n                \"channel\",        \"Channel\",        DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"rain_rate_in_h\",    \"Rain Rate\",    DATA_FORMAT, \"%5.1f in/h\", DATA_DOUBLE, rain_rate,\n                \"rain_in\", \"Total Rain\", DATA_FORMAT, \"%7.3f in\", DATA_DOUBLE, total_rain,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_PCR800a) {\n        if (validate_os_checksum(decoder, msg, 18) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n        float rain_rate = get_os_rain_rate(msg);\n        float total_rain = get_os_total_rain(msg);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                     DATA_STRING, \"Oregon-PCR800a\",\n                \"id\",                 \"House Code\", DATA_INT,        device_id,\n                \"channel\",        \"Channel\",        DATA_INT,        channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"rain_rate_in_h\",    \"Rain Rate\",    DATA_FORMAT, \"%.1f in/h\", DATA_DOUBLE, rain_rate,\n                \"rain_in\", \"Total Rain\", DATA_FORMAT, \"%.1f in\", DATA_DOUBLE, total_rain,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (sensor_id == ID_WGR800 || sensor_id == ID_WGR800a) {\n        if (validate_os_checksum(decoder, msg, 17) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n        // Sanity check BCD digits\n        if ((msg[5] & 0x0F) > 9\n                || ((msg[6] >> 4) & 0x0F) > 9\n                || (msg[6] & 0x0F) > 9\n                || ((msg[7] >> 4) & 0x0F) > 9\n                || (msg[7] & 0x0F) > 9\n                || ((msg[8] >> 4) & 0x0F) > 9) {\n            decoder_log(decoder, 1, __func__, \"WGR800 failed BCD sanity check.\");\n            return DECODE_FAIL_SANITY;\n        }\n\n        float gustWindspeed = (msg[5] & 0x0f) / 10.0f + ((msg[6] >> 4) & 0x0f) * 1.0f + (msg[6] & 0x0f) * 10.0f;\n        float avgWindspeed  = ((msg[7] >> 4) & 0x0f) / 10.0f + (msg[7] & 0x0f) * 1.0f + ((msg[8] >> 4) & 0x0f) * 10.0f;\n        float quadrant      = ((msg[4] >> 4) & 0x0f) * 22.5f;\n\n        // Sanity check values\n        if (gustWindspeed < 0 || gustWindspeed > 56 || avgWindspeed < 0 || avgWindspeed > 56) {\n            decoder_logf(decoder, 1, __func__, \"WGR800 failed value sanity check: wind_max_m_s %.1f wind_avg_m_s %.1f wind_dir_deg %.1f.\", gustWindspeed, avgWindspeed, quadrant);\n            return DECODE_FAIL_SANITY;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                     DATA_STRING,    \"Oregon-WGR800\",\n                \"id\",                 \"House Code\", DATA_INT,         device_id,\n                \"channel\",        \"Channel\",        DATA_INT,         channel,\n                \"battery_ok\",          \"Battery\",         DATA_INT,    !battery_low,\n                \"wind_max_m_s\",             \"Gust\",             DATA_FORMAT,    \"%.1f m/s\",DATA_DOUBLE, gustWindspeed,\n                \"wind_avg_m_s\",        \"Average\",        DATA_FORMAT,    \"%.1f m/s\",DATA_DOUBLE, avgWindspeed,\n                \"wind_dir_deg\",    \"Direction\",    DATA_FORMAT,    \"%.1f degrees\",DATA_DOUBLE, quadrant,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if ((msg[0] == 0x20) || (msg[0] == 0x21) || (msg[0] == 0x22) || (msg[0] == 0x23) || (msg[0] == 0x24)) { // Owl CM160 Readings\n        msg[0] = msg[0] & 0x0F;\n\n        if (validate_os_checksum(decoder, msg, 22) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n\n        int id = msg[1] & 0x0F;\n\n        unsigned int current_amps  = swap_nibbles(msg[3]) | ((msg[4] >> 4) << 8);\n        double current_watts = current_amps * 0.07 * 230; // Assuming device is running in 230V country\n\n        double total_amps = ((uint64_t)swap_nibbles(msg[10]) << 36) | ((uint64_t)swap_nibbles(msg[9]) << 28) |\n                    (swap_nibbles(msg[8]) << 20) | (swap_nibbles(msg[7]) << 12) |\n                    (swap_nibbles(msg[6]) << 4) | (msg[5]&0xf);\n\n        double total_kWh = total_amps * 230.0 / 3600.0 / 1000.0 * 1.12; // Assuming device is running in 230V country\n        //result compares to the CM160 LCD display values when * 1.12 between readings\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                     DATA_STRING,    \"Oregon-CM160\",\n                \"id\",               \"House Code\",           DATA_INT, id,\n //               \"current_A\",        \"Current Amps\",         DATA_FORMAT,   \"%d A\", DATA_INT, current_amps,\n //               \"total_As\",         \"Total Amps\",           DATA_FORMAT,   \"%d As\", DATA_INT, (int)total_amps,\n                \"power_W\",          \"Power\",                DATA_FORMAT,   \"%7.4f W\", DATA_DOUBLE, current_watts,\n                \"energy_kWh\",       \"Energy\",               DATA_FORMAT, \"%7.4f kWh\",DATA_DOUBLE, total_kWh,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (msg[0] == 0x26) { // Owl CM180 readings\n        msg[0]    = msg[0] & 0x0f;\n        if (validate_os_checksum(decoder, msg, 23) != 0) {\n            return DECODE_FAIL_MIC;\n        }\n\n        for (int k = 0; k < EXPECTED_NUM_BYTES; k++) { // Reverse nibbles\n            msg[k] = (msg[k] & 0xF0) >> 4 | (msg[k] & 0x0F) << 4;\n        }\n        // TODO: should we return if valid == 0?\n\n        int sequence = msg[1] & 0x0F;\n        int id       = msg[2] << 8 | (msg[1] & 0xF0);\n        int batt_low = (msg[3] & 0x1); // 8th bit instead of 6th commonly used for other devices\n\n        unsigned ipower = cm180_power(msg);\n        uint64_t itotal = cm180_total(msg);\n        float total_energy        = itotal / 3600.0f / 1000.0f;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"Oregon-CM180\",\n                \"id\",               \"House Code\",       DATA_INT,    id,\n                \"battery_ok\",       \"Battery\",          DATA_INT,    !batt_low,\n                \"power_W\",          \"Power\",            DATA_FORMAT, \"%d W\",DATA_INT, ipower,\n                \"energy_kWh\",       \"Energy\",           DATA_COND,   itotal != 0, DATA_FORMAT, \"%.2f kWh\",DATA_DOUBLE, total_energy,\n                \"sequence\",         \"sequence number\",  DATA_INT,    sequence,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if (msg[0] == 0x25) { // Owl CM180i readings\n        msg[0]    = msg[0] & 0x0f;\n        // to be done\n        // int valid = validate_os_checksum(decoder, msg, 23);\n        for (int k = 0; k < EXPECTED_NUM_BYTES; k++) { // Reverse nibbles\n            msg[k] = (msg[k] & 0xF0) >> 4 | (msg[k] & 0x0F) << 4;\n        }\n        // TODO: should we return if valid == 0?\n\n        int sequence = msg[1] & 0x0F;\n        int id       = msg[2] << 8 | (msg[1] & 0xF0);\n        int batt_low = (msg[3] & 0x40)?1:0; // 8th bit instead of 6th commonly used for other devices\n\n        unsigned ipower1 = cm180i_power(msg,0);\n        unsigned ipower2 = cm180i_power(msg,1);\n        unsigned ipower3 = cm180i_power(msg,2);\n        uint64_t itotal  = 0;\n        if (msg_len >= 140) {\n            itotal = cm180i_total(msg);\n        }\n\n        // Convert `itotal` which is in Ws (or J) to kWh unit.\n        float total_energy        = itotal / 3600.0f / 1000.0f;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",                 DATA_STRING, \"Oregon-CM180i\",\n                \"id\",               \"House Code\",       DATA_INT,    id,\n                \"battery_ok\",       \"Battery\",          DATA_INT,    !batt_low,\n                \"power1_W\",         \"Power1\",           DATA_FORMAT, \"%d W\",DATA_INT, ipower1,\n                \"power2_W\",         \"Power2\",           DATA_FORMAT, \"%d W\",DATA_INT, ipower2,\n                \"power3_W\",         \"Power3\",           DATA_FORMAT, \"%d W\",DATA_INT, ipower3,\n                \"energy_kWh\",       \"Energy\",           DATA_COND,   itotal != 0, DATA_FORMAT, \"%.2f kWh\",DATA_DOUBLE, total_energy,\n                \"sequence\",         \"sequence number\",  DATA_INT,    sequence,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    else if ((msg[0] != 0) && (msg[1] != 0)) { // sync nibble was found and some data is present...\n        decoder_log(decoder, 1, __func__, \"Message received from unrecognized Oregon Scientific v3 sensor.\");\n        decoder_log_bitrow(decoder, 1, __func__, msg, msg_len, \"Message\");\n        decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0], \"Raw\");\n    }\n    else if (b[3] != 0) {\n        decoder_log(decoder, 1, __func__, \"Possible OS v3 message, but sync nibble not found\");\n        decoder_log_bitrow(decoder, 1, __func__, b, bitbuffer->bits_per_row[0], \"Raw Data\");\n    }\n    return DECODE_FAIL_SANITY;\n}\n\n/**\nVarious Oregon Scientific protocols.\n@sa oregon_scientific_v2_1_decode() oregon_scientific_v3_decode()\n*/\nstatic int oregon_scientific_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int ret = oregon_scientific_v2_1_decode(decoder, bitbuffer);\n    if (ret <= 0) {\n        ret = oregon_scientific_v3_decode(decoder, bitbuffer);\n    }\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"button\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"rain_rate_mm_h\",\n        \"rain_rate_in_h\",\n        \"rain_mm\",\n        \"rain_in\",\n        \"wind_max_m_s\",\n        \"wind_avg_m_s\",\n        \"wind_dir_deg\",\n        \"pressure_hPa\",\n        \"uvi\",\n        \"power_W\",\n        \"energy_kWh\",\n        \"radio_clock\",\n        \"sequence\",\n        NULL,\n};\n\nr_device const oregon_scientific = {\n        .name        = \"Oregon Scientific Weather Sensor\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 440, // Nominal 1024Hz (488us), but pulses are shorter than pauses\n        .long_width  = 0,   // not used\n        .reset_limit = 2400,\n        .decode_fn   = &oregon_scientific_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oregon_scientific_sl109h.c",
    "content": "/** @file\n    Oregon Scientific SL109H decoder.\n*/\n/**\nOregon Scientific SL109H decoder.\n\nData layout (bits):\n\n    AAAA CC HHHH HHHH TTTT TTTT TTTT SSSS IIII IIII\n\n- A: 4 bit checksum (add)\n- C: 2 bit channel number\n- H: 8 bit BCD humidity\n- T: 12 bit signed temperature scaled by 10\n- S: 4 bit status, unknown\n- I: 8 bit a random id that is generated when the sensor starts\n\nS.a. http://www.osengr.org/WxShield/Downloads/OregonScientific-RF-Protocols-II.pdf\n\n\nThe device \"Bresser Thermo-/Hygro-Sensor Explore Scientific ST1005H\" works\nwith the same row length, but a completely different interpretation.\nAs such, if the bits align both decoders can misdetect data from the\nother sensor as valid from their sensor with \"plausable\" but usually\ncompletely wrong values.\n*/\n\n#include \"decoder.h\"\n\nstatic int oregon_scientific_sl109h_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *msg;\n    uint8_t b[5];\n\n    uint8_t sum, chk;\n    int channel;\n    uint8_t humidity;\n    int temp_raw;\n    float temp_c;\n    int status;\n    uint8_t id;\n\n    for (int row_index = 0; row_index < bitbuffer->num_rows; row_index++) {\n        if (bitbuffer->bits_per_row[row_index] != 38) // expected length is 38 bit\n            continue; // DECODE_ABORT_LENGTH\n\n        msg = bitbuffer->bb[row_index];\n\n        // No need to decode/extract values for simple test\n        // check id channel temperature humidity value not zero\n        if (!msg[0] && !msg[1] && !msg[2] && !msg[3]) {\n            decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n            continue; // DECODE_FAIL_SANITY\n        }\n\n        chk = msg[0] >> 4;\n\n        // align the channel \"half nibble\"\n        bitbuffer_extract_bytes(bitbuffer, row_index, 2, b, 36);\n        b[0] &= 0x3f;\n\n        // Prevent false positives from 'allzero'\n        // reject if Checksum channelhumidity and temperature are all zero\n        // No need to decode/extract values for simple test\n        if (chk == 0 && b[0] == 0 && b[1] == 0 && b[2] == 0)\n            continue; // DECODE_FAIL_SANITY\n\n        sum = add_nibbles(b, 5) & 0xf;\n        if (sum != chk) {\n            decoder_logf_bitbuffer(decoder, 2, __func__, bitbuffer, \"Checksum error. Expected: %01x Calculated: %01x\", chk, sum);\n            continue; // DECODE_FAIL_MIC\n        }\n\n        channel = b[0] >> 4;\n        channel = (channel % 3) ? channel : 3;\n\n        humidity = 10 * (b[0] & 0x0f) + (b[1] >> 4);\n\n        temp_raw = (int16_t)((b[1] & 0x0f) << 12) | (b[2] << 4); // uses sign-extend\n        temp_c   = (temp_raw >> 4) * 0.1f;\n\n        // reduce false positives by checking specified sensor range, this isn't great...\n        if (temp_c < -20 || temp_c > 60) {\n            decoder_logf(decoder, 2, __func__, \"temperature sanity check failed: %.1f C\", temp_c);\n            return DECODE_FAIL_SANITY;\n        }\n\n        // there may be more specific information here; not currently certain what information is encoded here\n        status = (b[3] >> 4);\n\n        // changes when thermometer reset button is pushed/battery is changed\n        id = ((b[3] & 0x0f) << 4) | (b[4] >> 4);\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"Model\",                                DATA_STRING, \"Oregon-SL109H\",\n                \"id\",               \"Id\",                                   DATA_INT,    id,\n                \"channel\",          \"Channel\",                              DATA_INT,    channel,\n                \"temperature_C\",    \"Celsius\",      DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temp_c,\n                \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\",   DATA_INT,    humidity,\n                \"status\",           \"Status\",                               DATA_INT,    status,\n                \"mic\",              \"Integrity\",                            DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"status\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const oregon_scientific_sl109h = {\n        .name        = \"Oregon Scientific SL109H Remote Thermal Hygro Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 5000,\n        .reset_limit = 10000, // packet gap is 8900\n        .decode_fn   = &oregon_scientific_sl109h_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oregon_scientific_v1.c",
    "content": "/** @file\n    OSv1 protocol.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nOSv1 protocol.\n\nMC with nominal bit width of 2930 us.\nPulses are somewhat longer than nominal half-bit width, 1748 us / 3216 us,\nGaps are somewhat shorter than nominal half-bit width, 1176 us / 2640 us.\nAfter 12 preamble bits there is 4200 us gap, 5780 us pulse, 5200 us gap.\n\nCare must be taken with the gap after the sync pulse since it\nis outside of the normal clocking.  Because of this a data stream\nbeginning with a 0 will have data in this gap.\n\n*/\n\n#include \"decoder.h\"\n\n#define OSV1_BITS   32\n\nstatic int oregon_scientific_v1_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int ret = 0;\n    int nibble[OSV1_BITS/4];\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] != OSV1_BITS)\n            continue; // DECODE_ABORT_LENGTH\n\n        int cs = 0;\n        for (int i = 0; i < OSV1_BITS / 8; i++) {\n            uint8_t byte = reverse8(bitbuffer->bb[row][i]);\n            nibble[i * 2    ] = byte & 0x0f;\n            nibble[i * 2 + 1] = byte >> 4;\n            if (i < ((OSV1_BITS / 8) - 1))\n                cs += nibble[i * 2] + 16 * nibble[i * 2 + 1];\n        }\n\n\n        // No need to decode/extract values for simple test\n        if (bitbuffer->bb[row][0] == 0xFF && bitbuffer->bb[row][1] == 0xFF\n                && bitbuffer->bb[row][2] == 0xFF && bitbuffer->bb[row][3] == 0xFF )  {\n            decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0xff\");\n            continue; //  DECODE_FAIL_SANITY\n        }\n\n        cs = (cs & 0xFF) + (cs >> 8);\n        int checksum = nibble[6] + (nibble[7] << 4);\n        /* reject 0x00 checksums to reduce false positives */\n        if (!checksum || (checksum != cs))\n            continue; // DECODE_FAIL_MIC\n\n        int sid      = nibble[0];\n        int channel  = ((nibble[1] >> 2) & 0x03) + 1;\n        //int uk1      = (nibble[1] >> 0) & 0x03; /* unknown.  Seen change every 60 minutes */\n        float temp_c =  nibble[2] * 0.1f + nibble[3] + nibble[4] * 10.0f;\n        int battery  = (nibble[5] >> 3) & 0x01;\n        //int uk2      = (nibble[5] >> 2) & 0x01; /* unknown.  Always zero? */\n        int sign     = (nibble[5] >> 1) & 0x01;\n        //int uk3      = (nibble[5] >> 0) & 0x01; /* unknown.  Always zero? */\n\n        if (sign)\n            temp_c = -temp_c;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING,    \"Oregon-v1\",\n                \"id\",               \"SID\",          DATA_INT,       sid,\n                \"channel\",          \"Channel\",      DATA_INT,       channel,\n                \"battery_ok\",       \"Battery\",      DATA_INT,       !battery,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT,    \"%.1f C\",              DATA_DOUBLE,    temp_c,\n                \"mic\",              \"Integrity\",    DATA_STRING,    \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        ret++;\n    }\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const oregon_scientific_v1 = {\n        .name        = \"OSv1 Temperature Sensor\",\n        .modulation  = OOK_PULSE_PWM_OSV1,\n        .short_width = 1465, // nominal half-bit width\n        .sync_width  = 5780,\n        .gap_limit   = 3500,\n        .reset_limit = 14000,\n        .decode_fn   = &oregon_scientific_v1_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/oria_wa150km.c",
    "content": "/** @file\n    Oria WA150KM temperature sensor decoder.\n\n    Copyright (C) 2025 Jan Niklaas Wechselberg\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nOria WA150KM temperature sensor decoder.\n\nThe device uses Manchester coding with G.E. Thomas convention.\nThe data is bit-reflected.\n\nData layout after decoding:\n\n    0  1  2  3  4  5  6  7  8  9  10 11 12 13\n    FF FF FF MM ?? CC DD TT II SS ?? ?? ?? BB\n\n- FF = Preamble: 3 bytes of 0xff\n- MM = Message type (unused)\n- CC = Channel (upper nibble + 1)\n- DD = Device ID\n- TT = Temperature decimal (upper nibble)\n- II = Temperature integer (BCD)\n- SS = Sign bit (bit 4, 1 = negative)\n- BB = Fixed value 0x65\n\nObservations currently not affecting implemetation:\n- In normal operation, the MSG_TYPE toggles between fa20 and fa28 with every send (interval is ~34 seconds)\n- Forced transmissions with the TX button have a MSG_TYPE=fa21\n- DEVICE_IDs stay consistent over powercycles\n- The devices transmit a \"battery low\" signal encoded in the byte after the temperature\n- Negative temperatures have another single bit set\n\n*/\n\n#define ORIA_WA150KM_BITLEN  227\n\nstatic int oria_wa150km_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Find a valid row (skipping short preamble rows)\n    int r;\n    for (r = 0; r < bitbuffer->num_rows; r++) {\n        if (bitbuffer->bits_per_row[r] == ORIA_WA150KM_BITLEN) {\n            break;\n        }\n    }\n    if (r >= bitbuffer->num_rows) {\n        decoder_logf(decoder, 2, __func__, \"No valid row found with %d bits\", ORIA_WA150KM_BITLEN);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Check warmup bytes before decoding\n    uint8_t *b = bitbuffer->bb[r];\n    if (b[0] != 0xAA || b[1] != 0xAA || b[2] != 0xAA) { // Check for alternating 1/0 pattern before Manchester decoding\n        decoder_log(decoder, 2, __func__, \"Warmup bytes are not 0xaaaaaa\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Check last byte (raw data before Manchester decoding)\n    if (b[bitbuffer->bits_per_row[r]/8 - 1] != 0x69) {\n        decoder_log(decoder, 2, __func__, \"Last byte is not 0x69\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Invert the buffer for G.E. Thomas decoding\n    bitbuffer_invert(bitbuffer);\n\n    // Manchester decode the row\n    bitbuffer_t manchester_buffer = {0};\n    bitbuffer_manchester_decode(bitbuffer, r, 0, &manchester_buffer, ORIA_WA150KM_BITLEN);\n\n    // Reflect bits in each byte\n    reflect_bytes(manchester_buffer.bb[0], (manchester_buffer.bits_per_row[0] + 7) / 8);\n\n    b = manchester_buffer.bb[0];\n\n    // Extract channel (upper nibble + 1)\n    uint8_t channel = ((b[5] >> 4) & 0x0F) + 1;\n\n    // Extract device ID\n    uint8_t device_id = b[6];\n\n    // Extract temperature\n    // BCD: Convert each nibble of byte 8 to decimal (tens+ones) and add decimal from byte 7\n    float temperature = (((b[8] >> 4) & 0x0F) * 10 + (b[8] & 0x0F)) + ((b[7] >> 4) & 0x0F) * 0.1f;\n    // Check sign byte (bit 4)\n    if (b[9] & 0x08) {\n        temperature = -temperature;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\", DATA_STRING, \"Oria-WA150KM\",\n            \"id\",           \"\", DATA_INT,    device_id,\n            \"channel\",      \"\", DATA_INT,    channel,\n            \"temperature\",  \"\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"temperature\",\n        NULL,\n};\n\nr_device const oria_wa150km = {\n        .name        = \"Oria WA150KM freezer and fridge thermometer\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 490,\n        .long_width  = 490,\n        .gap_limit   = 1500,\n        .reset_limit = 4000,\n        .decode_fn   = &oria_wa150km_decode,\n        .priority    = 10, // Reduce false positives with Oregon Scientific THGR810\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/philips_aj3650.c",
    "content": "/** @file\n    Philips AJ3650 outdoor temperature sensor.\n\n    Copyright (C) 2017 Chris Coffey <kpuc@sdf.org>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nPhilips outdoor temperature sensor -- used with various Philips clock\nradios (tested on AJ3650).\n\nNot tested, but these should also work: AJ260 ... maybe others?\n\nA complete message is 112 bits:\n- 4-bit initial preamble, always 0\n- 4-bit packet separator, always 0, followed by 32-bit data packet.\n- Packets are repeated 3 times for 108 bits total.\n\n32-bit data packet format:\n\n    0001cccc tttttttt tt000000 0b0?ssss\n\n- c: channel: 0=channel 2, 2=channel 1, 4=channel 3 (4 bits)\n- t: temperature in Celsius: subtract 500 and divide by 10 (10 bits)\n- b: battery status: 0 = OK, 1 = LOW (1 bit)\n- ?: unknown: always 1 in every packet I've seen (1 bit)\n- s: CRC: non-standard CRC-4, poly 0x9, init 0x1\n\nPulse width:\n- Short: 2000 us = 0\n- Long: 6000 us = 1\nGap width:\n- Short: 6000 us\n- Long: 2000 us\nGap width between packets: 29000 us\n\nPresumably the 4-bit preamble is meant to be a sync of some sort,\nbut it has the exact same pulse/gap width as a short pulse, and\ngets processed as data.\n*/\n\n#include \"decoder.h\"\n\n#define PHILIPS_BITLEN       112\n#define PHILIPS_PACKETLEN    4\n#define PHILIPS_STARTNIBBLE  0x0\n\nstatic int philips_aj3650_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    /* Map channel values to their real-world counterparts */\n    uint8_t const channel_map[] = {2, 0, 1, 0, 3};\n\n    uint8_t *bb;\n    unsigned int i;\n    uint8_t a, b, c;\n    uint8_t packet[PHILIPS_PACKETLEN];\n    uint8_t c_crc;\n    uint8_t channel, battery_low;\n    int temp_raw;\n    float temperature;\n    data_t *data;\n\n    /* Invert the data bits */\n    bitbuffer_invert(bitbuffer);\n\n    /* Correct number of rows? */\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 2, __func__, \"wrong number of rows (%d)\", bitbuffer->num_rows);\n        return DECODE_ABORT_EARLY;\n    }\n\n    /* Correct bit length? */\n    if (bitbuffer->bits_per_row[0] != PHILIPS_BITLEN) {\n        decoder_logf(decoder, 2, __func__, \"wrong number of bits (%d)\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bb = bitbuffer->bb[0];\n\n    /* Correct start sequence? */\n    if ((bb[0] >> 4) != PHILIPS_STARTNIBBLE) {\n        decoder_log(decoder, 2, __func__, \"wrong start nibble\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    /* Compare and combine the 3 repeated packets, with majority wins */\n    for (i = 0; i < PHILIPS_PACKETLEN; i++) {\n        a = bb[i+1]; /* First packet - on byte boundary */\n        b = (bb[i+5] << 4) | (bb[i+6] >> 4 & 0xf); /* Second packet - not on byte boundary */\n        c = bb[i+10]; /* Third packet - on byte boundary */\n\n        packet[i] = (a & b) | (b & c) | (a & c);\n    }\n\n    /* If debug enabled, print the combined majority-wins packet */\n    decoder_logf_bitrow(decoder, 2, __func__, packet, PHILIPS_PACKETLEN * 8, \"combined packet\");\n\n    /* Correct CRC? */\n    c_crc = crc4(packet, PHILIPS_PACKETLEN, 0x9, 1); /* Including the CRC nibble */\n    if (0 != c_crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC failed, calculated %x\", c_crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    /* Message validated, now parse the data */\n\n    /* Channel */\n    channel = packet[0] & 0x0f;\n    if (channel >= (sizeof(channel_map) / sizeof(channel_map[0])))\n        channel = 0;\n    else\n        channel = channel_map[channel];\n\n    /* Temperature */\n    temp_raw = (packet[1] << 2) | (packet[2] >> 6);\n    temperature = (temp_raw - 500) * 0.1f;\n\n    /* Battery status */\n    battery_low = packet[PHILIPS_PACKETLEN - 1] & 0x40;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Philips-Temperature\",\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !battery_low,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        NULL,\n};\n\nr_device const philips_aj3650 = {\n        .name        = \"Philips outdoor temperature sensor (type AJ3650)\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 2000,\n        .long_width  = 6000,\n//        .gap_limit   = 8000,\n        .reset_limit = 30000,\n        .decode_fn   = &philips_aj3650_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/philips_aj7010.c",
    "content": "/** @file\n    Philips outdoor temperature sensor.\n\n    Copyright (C) 2018 Nicolas Jourden <nicolas.jourden@laposte.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nPhilips outdoor temperature sensor -- used with various Philips clock\nradios (tested on AJ7010).\nThis is inspired from the other Philips driver made by Chris Coffey.\n\nA complete message is 40 bits:\n- 3 times sync of 1000us pulse + 1000us gap.\n- 40 bits, 2000 us short or 6000 us long\n- packet gap is 38 ms\n- Packets are repeated 3 times.\n\n40-bit data packet format:\n\n    00000000 01000101 00100010 00101001 01001110 : g_philips_21.1_ch2_B.cu8\n    00000000 01011010 01111100 00101001 00001111 : g_philips_21.4_ch1_C.cu8\n    00000000 01011010 00000101 00100110 01111001 : gph_bootCh1_17.cu8\n    00000000 01000101 00011110 00100110 01111101 : gph_bootCh2_17.cu8\n    00000000 00110110 11100011 00100101 11110000 : gph_bootCh3_17.cu8\n\nData format is:\n\n    00000000  0ccccccc tttttttt TTTTTTTT XXXXXXXX\n\n- c: 7 bit channel: 0x5A=channel 1, 0x45=channel 2, 0x36=channel 3\n- t: 16 bit temperature in ADC value that is then converted to deg. C.\n- X: XOR sum, every 2nd packet without last data byte (T).\n*/\n\n#include \"decoder.h\"\n\nstatic int philips_aj7010_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_invert(bitbuffer);\n\n    // Correct number of rows?\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 1, __func__, \"wrong number of rows (%d)\", bitbuffer->num_rows);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Correct bit length?\n    if (bitbuffer->bits_per_row[0] != 40) {\n        if (bitbuffer->bits_per_row[0] != 0) {\n            decoder_logf(decoder, 1, __func__, \"wrong number of bits (%d)\", bitbuffer->bits_per_row[0]);\n        }\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t *b = bitbuffer->bb[0];\n\n    // No need to decode/extract values for simple test\n    if (!b[0] && !b[2] && !b[3] && !b[4]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0xff\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Correct start sequence?\n    if (b[0] != 0x00) {\n        decoder_log(decoder, 1, __func__, \"wrong start nibble\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Correct checksum?\n    if (xor_bytes(b, 5) && xor_bytes(b, 3) ^ b[4]) {\n        decoder_log(decoder, 1, __func__, \"bad checksum\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Channel\n    int channel = (b[1]);\n    switch (channel) {\n    case 0x36:\n        channel = 3;\n        break;\n    case 0x45:\n        channel = 2;\n        break;\n    case 0x5A:\n        channel = 1;\n        break;\n    default:\n        channel = 0;\n        break;\n    }\n    decoder_logf(decoder, 1, __func__, \"channel decoded is %d\", channel);\n\n    // Temperature\n    int temp_raw = ((b[3] & 0x3f) << 8) | b[2];\n    float temp_c = (temp_raw / 353.0f) - 9.2f; // TODO: this is very likely wrong\n    decoder_logf(decoder, 1, __func__, \"temperature: raw: %d %08X converted: %.2f\", temp_raw, temp_raw, temp_c);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Philips-AJ7010\",\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"time\",\n        \"model\",\n        \"channel\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const philips_aj7010 = {\n        .name        = \"Philips outdoor temperature sensor (type AJ7010)\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 2000,\n        .long_width  = 6000,\n        .sync_width  = 1000,\n        .reset_limit = 30000,\n        .decode_fn   = &philips_aj7010_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/proflame2.c",
    "content": "/** @file\n    SmartFire Proflame 2 remote protocol.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n    based on protocol decode Copyright (C) 2020 johnellinwood\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/** @fn int proflame2_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nSmartFire Proflame 2 remote protocol.\n\nSee https://github.com/johnellinwood/smartfire\n\nThe command bursts are transmitted at 314,973 KHz using On-Off Keying (OOK).\nTransmission rate is 2400 baud. Packet is transmitted 5 times, repetitions are separated by 12 low amplitude bits (zeros).\n\nEncoded with a variant of Thomas Manchester encoding:\n0 is represented by 01, a 1 by 10, zero padding (Z) by 00, and synchronization words (S) as 11.\nThe encoded command packet is 182 bits, and the decoded packet is 91 bits.\n\nA packet is made up of 7 words, each 13 bits,\nstarts with a synchronization symbol, followed by a 1 as a guard bit,\nthen 8 bits of data, a padding bit, a parity bit, and finally a 1 as an end guard bit.\nThe padding bit is 1 for the first word and 0 for all other words.\nThe parity bit is calculated over the data bits and the padding bit,\nand is 0 if there are an even number of ones and 1 if there are an odd number of ones.\n\nThe payload data is 7 bytes:\n\n- Serial 1\n- Serial 2\n- Serial 3\n- Command 1\n- Command 2\n- Error Detection 1\n- Error Detection 2\n\n*/\n#include \"decoder.h\"\n\n/// out needs to be at least (bits / 26, usually 7) bytes long\nstatic int proflame2_mc(bitbuffer_t *bitbuffer, unsigned row, unsigned start, uint8_t *out)\n{\n    uint8_t *b   = bitbuffer->bb[row];\n    unsigned pos = start;\n    for (int f = 0;; ++f) {\n        if (bitbuffer->bits_per_row[row] - pos < 26)\n            return f;\n        // expect sync and start bit of \"1110\"\n        int sync = bitrow_get_bit(b, pos + 0) << 3\n                | bitrow_get_bit(b, pos + 1) << 2\n                | bitrow_get_bit(b, pos + 2) << 1\n                | bitrow_get_bit(b, pos + 3) << 0;\n        pos += 4;\n        if (sync != 0xe)\n            return f;\n\n        bitbuffer_t decoded = {0};\n        pos = bitbuffer_manchester_decode(bitbuffer, row, pos, &decoded, 11);\n        if (decoded.bits_per_row[0] != 11)\n            return f;\n\n        // invert IEEE MC to G.E.T. MC\n        uint8_t data = decoded.bb[0][0] ^ 0xff;\n        uint8_t flag = decoded.bb[0][1] ^ 0xe0;\n\n        int pad = (flag >> 7) & 1;\n        int par = (flag >> 6) & 1;\n        int end = (flag >> 5) & 1;\n\n        if (pad != (f == 0))\n            return f;\n\n        int par_chk = parity8(data) ^ pad ^ par;\n        if (par_chk)\n            return f;\n\n        if (end != 1)\n            return f;\n\n        out[f] = data;\n    }\n    return 0;\n}\n\nstatic int proflame2_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        uint8_t b[7] = {0};\n        int ret = proflame2_mc(bitbuffer, row, 0, b);\n\n        if (ret != 7)\n            continue;\n\n        int id   = b[0] << 16 | b[1] << 8 | b[2];\n        int cmd1 = b[3];\n        int cmd2 = b[4];\n        int err1 = b[5];\n        int err2 = b[6];\n\n        int pilot      = (b[3] >> 7);\n        int light      = (b[3] & 0x70) >> 4;\n        int thermostat = (b[3] & 0x02) >> 1;\n        int power      = (b[3] & 0x01);\n        int front      = (b[4] >> 7);\n        int fan        = (b[4] & 0x70) >> 4;\n        int aux        = (b[4] & 0x08) >> 3;\n        int flame      = (b[4] & 0x07);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"Proflame2-Remote\",\n                \"id\",           \"Id\",           DATA_FORMAT, \"%06x\", DATA_INT,    id,\n                \"cmd1\",         \"Cmd1\",         DATA_FORMAT, \"%02x\", DATA_INT,    cmd1, // add chk then remove this\n                \"cmd2\",         \"Cmd2\",         DATA_FORMAT, \"%02x\", DATA_INT,    cmd2, // add chk then remove this\n                \"err1\",         \"Err1\",         DATA_FORMAT, \"%02x\", DATA_INT,    err1, // add chk then remove this\n                \"err2\",         \"Err2\",         DATA_FORMAT, \"%02x\", DATA_INT,    err2, // add chk then remove this\n                \"pilot\",        \"Pilot\",        DATA_INT,    pilot,\n                \"light\",        \"Light\",        DATA_INT,    light,\n                \"thermostat\",   \"Thermostat\",   DATA_INT,    thermostat,\n                \"power\",        \"Power\",        DATA_INT,    power,\n                \"front\",        \"Front\",        DATA_INT,    front,\n                \"fan\",          \"Fan\",          DATA_INT,    fan,\n                \"aux\",          \"Aux\",          DATA_INT,    aux,\n                \"flame\",        \"Flame\",        DATA_INT,    flame,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"pilot\",\n        \"light\",\n        \"thermostat\",\n        \"power\",\n        \"front\",\n        \"fan\",\n        \"aux\",\n        \"flame\",\n        \"mic\",\n        NULL,\n};\n\nr_device const proflame2 = {\n        .name        = \"SmartFire Proflame 2 remote control\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 417, // 2400 baud\n        .long_width  = 417,\n        .gap_limit   = 1000, // 12 low amplitudes are 5000 us\n        .reset_limit = 6000,\n        .decode_fn   = &proflame2_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/prologue.c",
    "content": "/** @file\n    Prologue sensor protocol.\n*/\n/** @fn int prologue_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nPrologue sensor protocol,\nalso FreeTec NC-7104 sensor for FreeTec Weatherstation NC-7102,\nalso Pearl NC-7159-675,\nalso TFA pool thermometer 30.3240.10 #2651\nThe sensor can be bought at Clas Ohlson.\n\nNote: this is a false positive for AlectoV1.\n\nThe sensor sends 36 bits 7 times, before the first packet there is a sync pulse.\nThe packets are ppm modulated (distance coding) with a pulse of ~500 us\nfollowed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a\n1 bit, the sync gap is ~9000 us.\n\nThe data is grouped in 9 nibbles\n\n    [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1]\n\n- type: 4 bit fixed 1001 (9) or 0110 (5)\n- id: 8 bit a random id that is generated when the sensor starts, could include battery status\n  the same batteries often generate the same id\n- flags(3): is 0 when the battery is low, otherwise 1 (ok), first reading always says low\n- flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor\n- flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X)\n- temp: 12 bit signed scaled by 10\n- humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available\n\n*/\n\n#include \"decoder.h\"\n\nstatic int prologue_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b;\n    data_t *data;\n\n    int type;\n    int id;\n    int battery;\n    int button;\n    int channel;\n    int temp_raw;\n    int humidity;\n\n    if (bitbuffer->bits_per_row[0] <= 8 && bitbuffer->bits_per_row[0] != 0)\n        return DECODE_ABORT_EARLY; // Alecto/Auriol-v2 has 8 sync bits, reduce false positive\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 4, 36); // only 3 repeats will give false positives for Alecto/Auriol-v2\n    if (r < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[r] > 37) // we expect 36 bits but there might be a trailing 0 bit\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[r];\n\n    if ((b[0] & 0xF0) != 0x90 && (b[0] & 0xF0) != 0x50)\n        return DECODE_FAIL_SANITY;\n\n    // Prologue/ThermoPro-TX2 sensor\n    type     = b[0] >> 4;\n    id       = ((b[0] & 0x0F) << 4) | ((b[1] & 0xF0) >> 4);\n    battery  = b[1] & 0x08;\n    button   = (b[1] & 0x04) >> 2;\n    channel  = (b[1] & 0x03) + 1;\n    temp_raw = (int16_t)((b[2] << 8) | (b[3] & 0xF0)); // uses sign-extend\n    temp_raw = temp_raw >> 4;\n    humidity = ((b[3] & 0x0F) << 4) | (b[4] >> 4);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Prologue-TH\",\n            \"subtype\",       \"\",            DATA_INT,    type,\n            \"id\",            \"\",            DATA_INT,    id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !!battery,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_raw * 0.1,\n            \"humidity\",      \"Humidity\",    DATA_COND,   humidity != 0xcc, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"button\",        \"Button\",      DATA_INT,    button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"subtype\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        NULL,\n};\n\nr_device const prologue = {\n        .name        = \"Prologue, FreeTec NC-7104, NC-7159-675 temperature sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 7000,\n        .reset_limit = 10000,\n        .decode_fn   = &prologue_callback,\n        .priority    = 10, // Alecto collision, if Alecto checksum is correct it's not Prologue/ThermoPro-TX2\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/proove.c",
    "content": "/** @file\n    Proove decoder.\n\n    Copyright (C) 2016 Ask Jakobsen, Christian Juncker Brædstrup\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nProove/Nexa/Kaku decoder.\nMight be similar to an x1527.\nS.a. Kaku, Nexa.\n\nTested devices:\n- Magnetic door & window sensor\n  - \"Proove\" from 'Kjell & Company'\n  - \"Anslut\" from \"Jula\"\n  - \"Telecontrol Plus\" remote by \"REV Ritter GmbH\" (Germany) , model number \"008341C-1\"\n  - \"Nexa\"\n  - \"Intertechno ITLS-16\" (OEM model # \"ITAPT-821\")\n  - Nexa - LMST-606\n  - Smartwares SH4-90152\n\nFrom http://elektronikforumet.com/wiki/index.php/RF_Protokoll_-_Proove_self_learning\n\nProove packet structure (32 bits or 36 bits with dimmer value):\n\n    HHHH HHHH HHHH HHHH HHHH HHHH HHGO CCEE [DDDD]\n\n- H = The first 26 bits are transmitter unique codes, and it is this code that the receiver \"learns\" to recognize.\n- G = Group command. Set to 1 for on, 0 for off.\n- O = On/Off bit. Set to 1 for on, 0 for off.\n- C = Channel bits (inverted).\n- E = Unit bits (inverted). Device to be turned on or off. Unit #1 = 00, #2 = 01, #3 = 10.\n- D = Dimmer value (optional).\n\nPhysical layer:\nEvery bit in the packets structure is sent as two physical bits.\nWhere the second bit is the inverse of the first, i.e. 0 -> 01 and 1 -> 10.\nExample: 10101110 is sent as 1001100110101001\nThe sent packet length is thus 64 bits.\nA message is made up by a Sync bit followed by the Packet bits and ended by a Pause bit.\nEvery message is repeated about 5-15 times.\nPacket gap is 10 ms.\n*/\n\n#include \"decoder.h\"\n\nstatic int proove_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n\n    /* Reject missing sync */\n    if (bitbuffer->syncs_before_row[0] != 1)\n        return DECODE_ABORT_EARLY;\n\n    /* Reject codes of wrong length */\n    if (bitbuffer->bits_per_row[0] != 64)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_t databits = {0};\n    // note: not manchester encoded but actually ternary\n    bitbuffer_manchester_decode(bitbuffer, 0, 0, &databits, 80);\n\n    /* Reject codes when Manchester decoding fails */\n    /* 32 bits or 36 bits with dimmer value */\n    if (databits.bits_per_row[0] < 32)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_invert(&databits);\n\n    uint8_t *b = databits.bb[0];\n\n    uint32_t id        = (b[0] << 18) | (b[1] << 10) | (b[2] << 2) | (b[3] >> 6); // ID 26 bits\n    uint32_t group_cmd = (b[3] >> 5) & 1;\n    uint32_t on_bit    = (b[3] >> 4) & 1;\n    uint32_t channel   = ((b[3] >> 2) & 0x03) ^ 0x03; // inverted\n    uint32_t unit      = (b[3] & 0x03) ^ 0x03;        // inverted\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Proove-Security\",\n            \"id\",            \"House Code\",  DATA_INT,    id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"state\",         \"State\",       DATA_STRING, on_bit ? \"ON\" : \"OFF\",\n            \"unit\",          \"Unit\",        DATA_INT,    unit,\n            \"group\",         \"Group\",       DATA_INT,    group_cmd,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"state\",\n        \"unit\",\n        \"group\",\n        NULL,\n};\n\nr_device const proove = {\n        .name        = \"Proove / Nexa / KlikAanKlikUit Wireless Switch\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 270,  // 1:1\n        .long_width  = 1300, // 1:5\n        .sync_width  = 2650, // 1:10, tuned to widely match 2450 to 2850\n        .tolerance   = 200,\n        .gap_limit   = 1500,\n        .reset_limit = 2800,\n        .decode_fn   = &proove_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/quhwa.c",
    "content": "/** @file\n    Quhwa HS1527.\n\n    Copyright (C) 2016 Ask Jakobsen\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nQuhwa HS1527.\n\nTested devices:\nQH-C-CE-3V (which should be compatible with QH-832AC),\nalso sold as \"1 by One\" wireless doorbell\n*/\n\n#include \"decoder.h\"\n\nstatic int quhwa_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 5, 18);\n    if (r < 0)\n        return DECODE_ABORT_EARLY;\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // No need to decode/extract values for simple test\n    if (!b[0] && !b[1] && !b[2]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    b[0] = ~b[0];\n    b[1] = ~b[1];\n    b[2] = ~b[2];\n\n    if (bitbuffer->bits_per_row[r] != 18\n            || (b[1] & 0x03) != 0x03\n            || (b[2] & 0xC0) != 0xC0)\n        return DECODE_ABORT_LENGTH;\n\n    uint32_t id = (b[0] << 8) | b[1];\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",  \"\",    DATA_STRING, \"Quhwa-Doorbell\",\n            \"id\",     \"ID\",  DATA_INT, id,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        NULL,\n};\n\nr_device const quhwa = {\n        .name        = \"Quhwa\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 360,  // Pulse: Short 360µs, Long 1070µs\n        .long_width  = 1070, // Gaps: Short 360µs, Long 1070µs\n        .reset_limit = 6600, // Intermessage Gap 6500µs\n        .gap_limit   = 1200, // Long Gap 1120µs\n        .sync_width  = 0,    // No sync bit used\n        .tolerance   = 80,   // us\n        .decode_fn   = &quhwa_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/quinetic.c",
    "content": "/** @file\n    Quinetic Switches and Sensors.\n\n    Copyright (C) 2024 Nick Parrott\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nQuinetic Switches and Sensors.\n\n## Frame Layout\n\n    ...PPPP SS IISCC\n\n- P: 48-bits+ of Preamble\n- S: 16-bits of Sync-Word (0xA4, 0x23)\n- I: 16-bits of Device ID\n- S: 8-bits of Device Action\n- C: 16-bits of In-Packet Checksum (CRC-16 AUG-CCITT)\n\n## CRC Checksum Method\n\n- In-Packet Checksum: CC\n- 24-bits of data to CRC-check: IIS\n\n## Signal Summary\n\n- Frequency: 433.3 Mhz, +/- 50Khz\n- Nominal pulse width: 10us\n- Modulation: FSK_PCM\n- Checksum: CRC-16/AUG-CCITT\n\n## Device Characteristics\n\n- A switch emits 3-4 pulses when button is pressed.\n- A switch emits 3-4 pulses when button is released.\n- This duplication of packets is expected.\n- Device ID is preserved as 16-bit Hex.\n- It is printed on device rear-label (some models).\n*/\n\n#include \"decoder.h\"\n\nstatic int quinetic_switch_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n\n    if (bitbuffer->bits_per_row[0] < 110 || bitbuffer->bits_per_row[0] > 140) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    const uint8_t packet_syncword[] = {0xA4, 0x23};\n    unsigned syncword_bitindex;\n\n    syncword_bitindex = bitbuffer_search(bitbuffer, 0, 0, packet_syncword, 16);\n    if (syncword_bitindex >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 1, __func__, \"Sync-Word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t b[5];\n    bitbuffer_extract_bytes(bitbuffer, 0, syncword_bitindex + 16, b, sizeof(b) * 8);\n\n    int crc = crc16(b, 5, 0x1021, 0x1D0F);\n    if (crc != 0) {\n        decoder_logf(decoder, 1, __func__, \"CRC failure\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Process Switch-Channel (Button) nibble: b[2]\n    //\n    // Determine button number in switch (B1/B2/B3) when pressed.\n    // Typical Int Values:\n    //\n    // 192 = generic release\n    // 01 = press ( B1 )\n    // 02 = press ( B2 )\n    // 03 = press ( B3 )\n    int switch_channel = b[2];\n    if (switch_channel == 192) {\n        // Ignore \"button release\": button number unknown.\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Process Switch-ID nibbles: b[0] and b[1]\n    int id = (b[0] << 8) | (b[1]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",         \"Model\",           DATA_STRING, \"Quinetic\",\n        \"id\",            \"ID\",              DATA_FORMAT, \"%04x\", DATA_INT, id,\n        \"channel\",       \"Channel\",         DATA_INT,    switch_channel,\n        \"mic\",           \"Integrity\",       DATA_STRING, \"CRC\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channnel\",\n        \"mic\",\n        NULL,\n};\n\nr_device const quinetic = {\n        .name        = \"Quinetic\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 10,\n        .long_width  = 10,\n        .reset_limit = 120,\n        .tolerance   = 1,\n        .decode_fn   = &quinetic_switch_decode,\n        .fields      = output_fields,\n        .disabled    = 1, // disabled by default, due to required settings: frequency 433.4, sample_rate 1024k\n};\n"
  },
  {
    "path": "src/devices/radiohead_ask.c",
    "content": "/** @file\n    RadioHead ASK (generic) protocol.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/** @fn int radiohead_ask_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nRadioHead ASK (generic) protocol.\n\nDefault transmitter speed is 2000 bits per second, i.e. 500 us per bit.\nThe symbol encoding ensures a maximum run (gap) of 4x bit-width.\nSensible Living uses a speed of 1000, i.e. 1000 us per bit.\n*/\n\n#include \"decoder.h\"\n\n// Maximum message length (including the headers, byte count and FCS) we are willing to support\n// This is pretty arbitrary\n#define RH_ASK_MAX_PAYLOAD_LEN 67\n#define RH_ASK_HEADER_LEN 4\n#define RH_ASK_MAX_MESSAGE_LEN (RH_ASK_MAX_PAYLOAD_LEN - RH_ASK_HEADER_LEN - 3)\n\n// Note: all the \"4to6 code\" came from RadioHead source code.\n// see: http://www.airspayce.com/mikem/arduino/RadioHead/index.html\n\n// 4 bit to 6 bit symbol converter table\n// Used to convert the high and low nybbles of the transmitted data\n// into 6 bit symbols for transmission. Each 6-bit symbol has 3 1s and 3 0s\n// with at most 3 consecutive identical bits.\n// Concatenated symbols have runs of at most 4 identical bits.\nstatic uint8_t const symbols[] = {\n        0x0d, 0x0e, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c,\n        0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x32, 0x34\n};\n\n// Convert a 6 bit encoded symbol into its 4 bit decoded equivalent\nstatic uint8_t symbol_6to4(uint8_t symbol)\n{\n    uint8_t i;\n    // Linear search :-( Could have a 64 byte reverse lookup table?\n    // There is a little speedup here courtesy Ralph Doncaster:\n    // The shortcut works because bit 5 of the symbol is 1 for the last 8\n    // symbols, and it is 0 for the first 8.\n    // So we only have to search half the table\n    for (i = (symbol >> 2) & 8; i < 16; i++) {\n        if (symbol == symbols[i])\n            return i;\n    }\n    return 0xFF; // Not found\n}\n\n/**\nRadiohead ASK parser.\n*/\nstatic int radiohead_ask_extract(r_device *decoder, bitbuffer_t *bitbuffer, uint8_t row, /*OUT*/ uint8_t *payload)\n{\n    int len = bitbuffer->bits_per_row[row];\n    int msg_len = RH_ASK_MAX_MESSAGE_LEN;\n    int pos, nb_bytes;\n    uint8_t rxBits[2] = {0};\n\n    uint16_t crc, crc_recompute;\n\n    // Looking for preamble\n    uint8_t const init_pattern[] = {\n            0x55, // 8\n            0x55, // 16\n            0x55, // 24\n            0x51, // 32\n            0xcd, // 40\n    };\n    // The first 0 is ignored by the decoder, so we look only for 28 bits of \"01\"\n    // and not 32. Also \"0x1CD\" is 0xb38 (RH_ASK_START_SYMBOL) with LSBit first.\n    int init_pattern_len = 40;\n\n    pos = bitbuffer_search(bitbuffer, row, 0, init_pattern, init_pattern_len);\n    if (pos == len) {\n        decoder_log(decoder, 2, __func__, \"preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // read \"bytes\" of 12 bit\n    nb_bytes = 0;\n    pos += init_pattern_len;\n    for (; pos < len && nb_bytes < msg_len; pos += 12) {\n        bitbuffer_extract_bytes(bitbuffer, row, pos, rxBits, /*len=*/16);\n        // ^ we should read 16 bits and not 12, elsewhere last 4bits are ignored\n        rxBits[0] = reverse8(rxBits[0]);\n        rxBits[1] = reverse8(rxBits[1]);\n        rxBits[1] = ((rxBits[1] & 0x0F) << 2) + (rxBits[0] >> 6);\n        rxBits[0] &= 0x3F;\n        uint8_t hi_nibble = symbol_6to4(rxBits[0]);\n        if (hi_nibble > 0xF) {\n            decoder_logf(decoder, 1, __func__, \"Error on 6to4 decoding high nibble: %X\", rxBits[0]);\n            return DECODE_FAIL_SANITY;\n        }\n        uint8_t lo_nibble = symbol_6to4(rxBits[1]);\n        if (lo_nibble > 0xF) {\n            decoder_logf(decoder, 1, __func__, \"Error on 6to4 decoding low nibble: %X\", rxBits[1]);\n            return DECODE_FAIL_SANITY;\n        }\n        uint8_t byte = hi_nibble << 4 | lo_nibble;\n        payload[nb_bytes] = byte;\n        if (nb_bytes == 0) {\n            msg_len = byte;\n            // abort on invalid message length byte\n            if (msg_len < 2 || msg_len > RH_ASK_MAX_MESSAGE_LEN) {\n                break;\n            }\n        }\n        nb_bytes++;\n    }\n\n    // Prevent buffer underflow when calculating CRC\n    if (msg_len < 2) {\n        decoder_log(decoder, 2, __func__, \"message too short to contain crc\");\n        return DECODE_ABORT_LENGTH;\n    }\n    // Sanity check on excessive msg len\n    if (msg_len > RH_ASK_MAX_MESSAGE_LEN) {\n        decoder_logf(decoder, 2, __func__, \"message too long: %d\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Check CRC\n    crc = (payload[msg_len - 1] << 8) | payload[msg_len - 2];\n    crc_recompute = ~crc16lsb(payload, msg_len - 2, 0x8408, 0xFFFF);\n    if (crc_recompute != crc) {\n        decoder_logf(decoder, 1, __func__, \"CRC error: %04X != %04X\", crc_recompute, crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    return msg_len;\n}\n\nstatic int radiohead_ask_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t row = 0; // we are considering only first row\n    int msg_len, data_len, header_to, header_from, header_id, header_flags;\n\n    uint8_t rh_payload[RH_ASK_MAX_PAYLOAD_LEN] = {0};\n    int rh_data_payload[RH_ASK_MAX_MESSAGE_LEN];\n\n    msg_len = radiohead_ask_extract(decoder, bitbuffer, row, rh_payload);\n    if (msg_len <= 0) {\n        return msg_len; // pass error code on\n    }\n    data_len = msg_len - RH_ASK_HEADER_LEN - 3;\n    if (data_len <= 0)\n        return DECODE_FAIL_SANITY;\n\n    header_to    = rh_payload[1];\n    header_from  = rh_payload[2];\n    header_id    = rh_payload[3];\n    header_flags = rh_payload[4];\n\n    // Format data\n    for (int j = 0; j < data_len; j++) {\n        rh_data_payload[j] = (int)rh_payload[5 + j];\n    }\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"RadioHead-ASK\",\n            \"len\",          \"Data len\",     DATA_INT, data_len,\n            \"to\",           \"To\",           DATA_INT, header_to,\n            \"from\",         \"From\",         DATA_INT, header_from,\n            \"id\",           \"Id\",           DATA_INT, header_id,\n            \"flags\",        \"Flags\",        DATA_INT, header_flags,\n            \"payload\",      \"Payload\",      DATA_ARRAY, data_array(data_len, DATA_INT, rh_data_payload),\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nSensible Living Mini-Plant Moisture Sensor.\n\n@todo Documentation needed.\n*/\nstatic int sensible_living_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t row = 0; // we are considering only first row\n    int msg_len, house_id, sensor_type, sensor_count, alarms;\n    int module_id, sensor_value, battery_voltage;\n\n    uint8_t rh_payload[RH_ASK_MAX_PAYLOAD_LEN] = {0};\n\n    msg_len = radiohead_ask_extract(decoder, bitbuffer, row, rh_payload);\n    if (msg_len <= 0) {\n        return msg_len; // pass error code on\n    }\n\n    house_id        = rh_payload[1];\n    module_id       = (rh_payload[2] << 8) | rh_payload[3];\n    sensor_type     = rh_payload[4];\n    sensor_count    = rh_payload[5];\n    alarms          = rh_payload[6];\n    sensor_value    = (rh_payload[7] << 8) | rh_payload[8];\n    battery_voltage = (rh_payload[9] << 8) | rh_payload[10];\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING,  \"SensibleLiving-Moisture\",\n            \"house_id\",         \"House ID\",         DATA_INT,     house_id,\n            \"module_id\",        \"Module ID\",        DATA_INT,     module_id,\n            \"sensor_type\",      \"Sensor Type\",      DATA_INT,     sensor_type,\n            \"sensor_count\",     \"Sensor Count\",     DATA_INT,     sensor_count,\n            \"alarms\",           \"Alarms\",           DATA_INT,     alarms,\n            \"sensor_value\",     \"Sensor Value\",     DATA_INT,     sensor_value,\n            \"battery_mV\",       \"Battery Voltage\",  DATA_INT,     battery_voltage * 10,\n            \"mic\",              \"Integrity\",        DATA_STRING,  \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const radiohead_ask_output_fields[] = {\n        \"model\",\n        \"len\",\n        \"to\",\n        \"from\",\n        \"id\",\n        \"flags\",\n        \"payload\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const sensible_living_output_fields[] = {\n        \"model\",\n        \"house_id\",\n        \"module_id\",\n        \"sensor_type\",\n        \"sensor_count\",\n        \"alarms\",\n        \"sensor_value\",\n        \"battery_mV\",\n        \"mic\",\n        NULL,\n};\n\nr_device const radiohead_ask = {\n        .name        = \"Radiohead ASK\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .reset_limit = 5 * 500,\n        .decode_fn   = &radiohead_ask_callback,\n        .fields      = radiohead_ask_output_fields,\n};\n\nr_device const sensible_living = {\n        .name        = \"Sensible Living Mini-Plant Moisture Sensor\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 1000,\n        .long_width  = 1000,\n        .reset_limit = 5 * 1000,\n        .decode_fn   = &sensible_living_callback,\n        .fields      = sensible_living_output_fields,\n};\n"
  },
  {
    "path": "src/devices/rainpoint.c",
    "content": "/** @file\n    Decoder for RainPoint soil temperature and moisture sensor.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for RainPoint soil temperature and moisture sensor.\n\nSeen on 433.9 Mhz.\n\nDescription of the Sensor:\n- Humidity from 0 to 100 %\n- Temperature from -10 C to 50 C\n\nA Transmission contains three packets with Manchester coded data, note that the pause is a constant pulse, strangely.\n\nData layout:\n\n    0  1  2  3  4  5  6  7  8  9  10 11    Byte index\n    AA 81 F7 03 B1 04 00 12 08 00 51 15    Example data (reflected)\n    ^^ ^^           could be sync word\n          ^^        ID?\n             ^^     unknown?\n                ^^  channel (but maybe also other encoded data: 9F: CH1; B1: CH2; B7: CH3;)\n                   ^^ ^^ unknown? (second byte changes between 00 and 02)\n                         ^^ temperature (degrees, 2's complement)\n                            ^^ humidity (percentage)\n                               ^^ unknown?\n                                  ^^    Checksum, simple 4-bit addition over 20 nibbles (reflected)\n    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^       Section of data used to calculate the checksum (sum of nibbles)\n                                     ^^ unknown, fixed 0x15?\n\nRaw data:\n\n    rtl_433 -R 0 -X 'n=RainPoint,m=OOK_PCM,s=500,l=500,r=1500'\n\n*/\n\nstatic int rainpoint_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xaa, 0xa9}; // with sync perhaps aaaa 6666 9556\n\n    if (bitbuffer->num_rows != 1\n            || bitbuffer->bits_per_row[0] < 232 // 24 MC bits + some preamble\n            || bitbuffer->bits_per_row[0] > 3000) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof (preamble_pattern) * 8);\n\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n    start_pos += sizeof (preamble_pattern) * 8 - 2; // keep initial data bit\n\n    bitbuffer_t msg = {0};\n    unsigned len = bitbuffer_manchester_decode(bitbuffer, 0, start_pos, &msg, 12 * 8);\n    if (len - start_pos != 12 * 2 * 8) {\n        decoder_logf(decoder, 2, __func__, \"Manchester decode failed, got %u bits\", len - start_pos);\n        return DECODE_ABORT_LENGTH;\n    }\n    bitbuffer_invert(&msg);\n\n    uint8_t *b = msg.bb[0];\n    reflect_bytes(b, 12);\n    decoder_log_bitrow(decoder, 2, __func__, b, 12 * 8, \"\");\n\n    // Checksum, add nibbles with carry\n    int sum = add_nibbles(b, 10);\n    if ((sum & 0xff) != b[10]) {\n        decoder_logf(decoder, 2, __func__, \"Checksum failed %04x vs %04x\", b[10], sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    int sync     = (b[0] << 8) | b[1]; // just a guess\n    int id       = (b[2] << 8) | b[3]; // just a guess\n    int flags    = (b[4]);             // just a guess\n    int status   = (b[5] << 8) | b[6]; // just a guess\n    float temp_c = (int8_t)b[7];\n    int moisture = b[8];\n    int chan     = 0; // 9f: CH1, b1: CH2, b7: CH3\n    //int batt     = 0;\n\n    if (flags == 0x9f)\n        chan = 1;\n    else if (flags == 0xb1)\n        chan = 2;\n    else if (flags == 0xb7)\n        chan = 3;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"RainPoint-Soil\",\n            \"id\",               \"\",             DATA_FORMAT, \"%04x\", DATA_INT,    id,\n            \"channel\",          \"\",             DATA_INT,    chan,\n            \"sync\",             \"Sync?\",        DATA_FORMAT, \"%04x\", DATA_INT,    sync,\n            \"flags\",            \"Flags?\",       DATA_FORMAT, \"%02x\", DATA_INT,    flags,\n            \"status\",           \"Status?\",      DATA_FORMAT, \"%04x\", DATA_INT,    status,\n            //\"battery_ok\",       \"Battery\",      DATA_INT,    batt,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"moisture\",         \"Moisture\",     DATA_FORMAT, \"%d %%\", DATA_INT, moisture,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"sync\",   // TODO: remove this\n        \"flags\",  // TODO: remove this\n        \"status\", // TODO: remove this\n        //\"battery_ok\",\n        \"temperature_C\",\n        \"moisture\",\n        \"mic\",\n        NULL,\n};\n\nr_device const rainpoint = {\n        .name        = \"RainPoint soil temperature and moisture sensor\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .reset_limit = 1500,\n        .decode_fn   = &rainpoint_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rainpoint_hcs012arf.c",
    "content": "/** @file\n    RainPoint HCS012ARF Rain Gauge sensor.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n    Copyright (C) 2025 Bruno OCTAU (ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nRainPoint HCS012ARF Rain Gauge sensor.\n\nManufacturer:\n- Fujian Baldr Technology Co., Ltd\n\nRF Information:\n- Seen on 433.92 Mhz.\n- FCC ID : 2AWDBHCS008FRF\n- RF Module used in other RainPoint / Fujian Baldr Technology Co., Ltd devices : HCS008FRF, HCS012ARF, HTV113FRF, HTV213FRF, TTC819, TCS008B\n\nDescription of the HCS012ARF Rain Gauge Sensor:\n- Rainfall Range: 0-9999mm , but 2 bytes identified, missing 1 bit MSB somewhere in the data layout flags\n- Accuracy: ±0.1mm\n- Data Reporting: Every 3 mins\n\nA Transmission contains ten packets with Manchester coded data, reflected\n\nData layout:\n\n    Byte Index  0  1  2  3  4  5  6  7  8  9\n    Sample     a5 08 54 03 04 61 03 00 00 c7 [Batt inserted]\n               HH[II II II II FB FF RR RR]SS\n\n- HH: {8} Header, fixed 0xa5 (or 0xa4 when MC Zero bit decoded)\n- ID: {32} Sensor ID, does not change with new battery, looks unique\n- FF: {6} Fixed value, 0x18, may contains 1 bit MSB Rain Gauge\n- B:{1} Low Battery flag = 1, Good Battery = 0\n- B:{1} Powered on, batteries are inserted = 1, then always = 0\n- FF:{8} Fixed value, 0x03, may contains 1 bit MSB Rain Gauge\n- RR:{16} little-endian rain gauge value, scale 10 (1 Tip = 0,1 mm), 2 bytes are not enough, max 0xFFFF = 6553.5 mm, 1 bit MSB somewhere in flags ?\n- SS:{8} Byte sum of previous bytes except header [value in the hooks, from ID to Rain gauge].\n\nRaw data:\n\n    rtl_433 -X 'n=HCS012ARF,m=OOK_PCM,s=320,l=320,r=1000,g=700,repeats>=5,unique'\n\n\n*/\n\nstatic int rainpoint_hcs012arf_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n\n    // Find repeats\n    int row = bitbuffer_find_repeated_row(bitbuffer, 4, 163);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] > 163)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_t msg = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, 0, &msg, 10 * 2 * 8); // including header\n    bitbuffer_invert(&msg);\n\n    uint8_t *b = msg.bb[0];\n    reflect_bytes(b, 10);\n\n    if (b[0] != 0xa5) // header = 0xa5, could be 0xa4 when MC Zero bit decoded\n        return DECODE_ABORT_EARLY;\n\n    decoder_log_bitrow(decoder, 2, __func__, b, 10 * 8, \"MC and Reflect decoded\");\n\n    // Checksum\n    int sum = add_bytes(&b[1], 8); // header not part of the sum\n    if ((sum & 0xff) != b[9]) {\n        decoder_logf(decoder, 2, __func__, \"Checksum failed %04x vs %04x\", b[9], sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    int id       = (b[4] << 24) | (b[3] << 16) | (b[2] << 8) | b[1]; // just a guess, little-endian\n    int flags1   = b[5]; // may contains 1 bit MSB for Rain Gauge\n    int bat_low  = (flags1 & 0x02) >> 1;\n    //int bat_in   = (flags1 & 0x01); // power up, battery inserted = 1, then always 0\n    int flags2   = b[6];        // may contains 1 bit MSB for Rain Gauge\n    int rain_raw  = (b[8] << 8) | b[7]; // little-endian\n    float rain_mm = rain_raw * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"RainPoint-HCS012ARF\",\n            \"id\",               \"\",                 DATA_INT,    id,     // decimal value reported at Rainpoint application\n            \"flags1\",           \"Flags 1\",          DATA_FORMAT, \"%02x\", DATA_INT,  (flags1 >> 2), // remove battery flags\n            \"flags2\",           \"Flags 2\",          DATA_FORMAT, \"%02x\", DATA_INT,  flags2,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !bat_low,\n            \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"flags1\",\n        \"flags2\",\n        \"battery_ok\",\n        \"rain_mm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const rainpoint_hcs012arf = {\n        .name        = \"RainPoint HCS012ARF Rain Gauge sensor\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 320,\n        .long_width  = 320,\n        .reset_limit = 1000,\n        .gap_limit   = 700,\n        .decode_fn   = &rainpoint_hcs012arf_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/regency_fan.c",
    "content": "/** @file\n    Decoder for Regency fan remotes.\n\n    Copyright (C) 2020-2022 David E. Tiller\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for Regency fan remotes.\n\nRegency fans use OOK_PULSE_PPM encoding.\nThe packet starts with 576 uS start pulse.\n- 0 is defined as a 375 uS gap followed by a 970 uS pulse.\n- 1 is defined as a 880 uS gap followed by a 450 uS pulse.\n\nTransmissions consist of the start bit followed by bursts of 20 bits.\nThese packets ar repeated up to 11 times.\n\nAs written, the PPM code always interprets a narrow gap as a 1 and a\nlong gap as a 0, however the actual data over the air is inverted,\ni.e. a short gap is a 0 and a long gap is a 1. In addition, the data\nis 5 nibbles long and is represented in Little-Endian format. In the\ncode I invert the bits and also reflect the bytes. Reflection introduces\nan additional nibble at bit offsets 16-19, so the data is expressed a 3\ncomplete bytes.\n\nThe examples below are _after_ inversion and reflection (MSB's are on\nthe left).\n\nPacket layout:\n\n     Bit number\n     0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23\n      CHANNEL  |  COMMAND  |            VALUE       | 0  0  0  0| 4 bit checksum\n\nCHANNEL is determined by the bit switches in the battery compartment. All\nswitches in the 'off' position result in a channel of 15, implying that the\nswitches pull the address lines down when in the on position.\n\nCOMMAND is one of the following:\n\n- 1 (0x01)\n    value: (0xc0, unused).\n\n- 2 (0x02)\n    value: 0x01-0x07. On my remote, the speeds are shown as 8 - value.\n\n- 4 (0x04)\n    value: 0x00-0xc3. The value is the intensity percentage.\n           0x00 is off, 0xc3 is 99% (full).\n\n- 5 (0x05)\n    value: 0x00 is 'off', 0x01 is on.\n\n- 6 (0x06)\n    value: 0x07 is one way, 0x83 is the other.\n\nThe CHECKSUM is calculated by adding the nibbles of the first two bytes\nand ANDing the result with 0x0f.\n\n*/\n\nstatic int regency_fan_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    char const *const command_names[] = {\n            /* 0  */ \"invalid\",\n            /* 1  */ \"fan_speed\",\n            /* 2  */ \"fan_speed\",\n            /* 3  */ \"invalid\",\n            /* 4  */ \"light_intensity\",\n            /* 5  */ \"light_delay\",\n            /* 6  */ \"fan_direction\",\n            /* 7  */ \"invalid\",\n            /* 8  */ \"invalid\",\n            /* 9  */ \"invalid\",\n            /* 10 */ \"invalid\",\n            /* 11 */ \"invalid\",\n            /* 12 */ \"invalid\",\n            /* 13 */ \"invalid\",\n            /* 14 */ \"invalid\",\n            /* 15 */ \"invalid\",\n    };\n\n    int return_code = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        int num_bits = bitbuffer->bits_per_row[row];\n\n        if (num_bits != 21) { // Max number of bits is 21\n            decoder_logf(decoder, 2, __func__, \"Expected %d bits, got %d.\", 21, num_bits);\n            continue;\n        }\n\n        uint8_t bytes[3]; // Max number of bytes is 3\n        bitbuffer_extract_bytes(bitbuffer, row, 1, bytes, 21); // Valid byte offset is 1, Max number of bits is 21\n        reflect_bytes(bytes, 3); // Max number of bytes is 3\n\n        // Calculate nibble sum and compare\n        int checksum = add_nibbles(bytes, 2) & 0x0f;\n        if (checksum != bytes[2]) { // Sum is in byte 2\n            decoder_logf(decoder, 2, __func__, \"Checksum failure: expected %x, got %x\", bytes[2], checksum);\n            continue;\n        }\n\n        // Now that message \"envelope\" has been validated, start parsing data.\n        int command = bytes[0] >> 4;    // Command and Channel are in byte 0\n        int channel = ~bytes[0] & 0x0f; // Command and Channel are in byte 0\n        int value   = bytes[1];         // Value is in byte 1\n\n        char value_string[64] = {0};\n\n        switch (command) {\n        case 1: // 1 is the command to STOP\n            snprintf(value_string, sizeof(value_string), \"stop\");\n            break;\n\n        case 2: // 2 is the command to change fan speed\n            snprintf(value_string, sizeof(value_string), \"speed %d\", value);\n            break;\n\n        case 4: // 4 is the command to change the light intensity\n            snprintf(value_string, sizeof(value_string), \"%d %%\", value);\n            break;\n\n        case 5: // 5 is the command to set the light delay\n            snprintf(value_string, sizeof(value_string), \"%s\", value == 0 ? \"off\" : \"on\");\n            break;\n\n        case 6: // 6 is the command to change fan direction\n            snprintf(value_string, sizeof(value_string), \"%s\", value == 0x07 ? \"clockwise\" : \"counter-clockwise\");\n            break;\n\n        default:\n            decoder_logf(decoder, 2, __func__, \"Unknown command: %d\", command);\n            continue;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",     DATA_STRING,    \"Regency-Remote\",\n                \"channel\",          \"\",     DATA_INT,       channel,\n                \"command\",          \"\",     DATA_STRING,    command_names[command],\n                \"value\",            \"\",     DATA_STRING,    value_string,\n                \"mic\",              \"\",     DATA_STRING,    \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return_code++;\n    }\n\n    return return_code;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"channel\",\n        \"command\",\n        \"value\",\n        \"mic\",\n        NULL,\n};\n\nr_device const regency_fan = {\n        .name        = \"Regency Ceiling Fan Remote (-f 303.75M to 303.96M)\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 580,\n        .long_width  = 976,\n        .gap_limit   = 8000,\n        .reset_limit = 14000,\n        .decode_fn   = &regency_fan_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/revolt_nc5462.c",
    "content": "/** @file\n    Revolt NC-5462 Energy Meter.\n\n    Copyright (C) 2023 Nicolai Hess\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n#include \"decoder.h\"\n\n/**\nRevolt NC-5462 Energy Meter.\n\n- Sends on 433.92 MHz.\n- Pulse Width Modulation with startbit/delimiter\n\nTwo Modes:\n\n## Normal data mode:\n\n- 105 pulses\n- first pulse sync\n- 104 data pulse (11 times 8 bit data + 8 bit checksum + 8 bit unknown)\n- 11 byte data:\n\n| data         | byte      |\n|--------------|-----------|\n| detect flag  | first bit |\n| id           | 0,1       |\n| voltage      | 2         |\n| current      | 3,4       |\n| frequency    | 5         |\n| power        | 6,7       |\n| power factor | 8         |\n| energy       | 9,10      |\n\n\n## \"Register\" mode (after pushing button on energy meter):\n\nSame 104 data pulses as in data mode, but first bit high and multiple rows of (the same) data.\n\nPulses\n- sync ~ 10 ms high / 280 us low\n- 1-bit ~ 320 us high / 160 us low\n- 0-bit ~ 180 us high / 160 us low\n- message end 180 us high / 100 ms low\n\n*/\n\nstatic int revolt_nc5462_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_invert(bitbuffer);\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n    if (bitbuffer->bits_per_row[0] != 104) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[0];\n\n    int sum = add_bytes(b, 11);\n    if (sum == 0) {\n        return DECODE_FAIL_SANITY;\n    }\n    int chk = b[11];\n    if ((sum & 0xff) != chk) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int button    = b[0] >> 7;\n    int id        = ((b[0] & 0x7f) << 8) | (b[1]);\n    int voltage   = b[2];\n    int current   = b[4] | b[3] << 8;\n    int frequency = b[5];\n    int power     = b[7] | b[6] << 8;\n    int pf        = b[8];\n    int energy    = b[10] | b[9] << 8;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Revolt-NC5462\",\n            \"id\",               \"House Code\",   DATA_INT,    id,\n            \"voltage_V\",        \"Voltage\",      DATA_FORMAT, \"%d V\",        DATA_INT,    voltage,\n            \"current_A\",        \"Current\",      DATA_FORMAT, \"%.2f A\",      DATA_DOUBLE, current * 0.01,\n            \"frequency_Hz\",     \"Frequency\",    DATA_FORMAT, \"%d Hz\",       DATA_INT,    frequency,\n            \"power_W\",          \"Power\",        DATA_FORMAT, \"%.2f W\",      DATA_DOUBLE, power * 0.1,\n            \"power_factor_VA\",  \"Power factor\", DATA_FORMAT, \"%.2f VA\",     DATA_DOUBLE, pf * 0.01,\n            \"energy_kWh\",       \"Energy\",       DATA_FORMAT, \"%.2f kWh\",    DATA_DOUBLE, energy * 0.01,\n            \"button\",           \"Button\",       DATA_INT, button,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"voltage_V\",\n        \"current_A\",\n        \"frequency_Hz\",\n        \"power_W\",\n        \"power_factor_VA\",\n        \"energy_kWh\",\n        \"button\",\n        \"mic\",\n        NULL,\n};\n\nr_device const revolt_nc5462 = {\n        .name        = \"Revolt NC-5642 Energy Meter\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 200,\n        .long_width  = 320,\n        .sync_width  = 10024,\n        .reset_limit = 272,\n        .decode_fn   = &revolt_nc5462_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/revolt_zx7717.c",
    "content": "/** @file\n    Revolt ZX-7717-675 433 MHz power meter.\n\n    Copyright (C) 2024 Christian W. Zuckschwerdt <zany@triq.net>\n    Copyright (C) 2024 Boing <dhs_mobil@google.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nRevolt ZX-7717-675 433 MHz power meter.\n\n- Used with Revolt ZX-7716 Monitor.\n- Other names: HPM-27717, ZX-7717-919\n- Up to 6 channels\n- First seen: 12-2024\n- https://www.revolt-power.de/TOP-KAT161-Zusaetzliche-Steckdose-ZX-7717-919.shtml\n\nOutputs are: (in this order)\n- Current (A) max 15.999 A , Minimum is >= 0.001 A\n- Voltage (V) max 250.0 V\n- Power  (VA) max 3679.9 VA\n- PF (Powerfactor not in message, but calculated)\n- 8 bit checksum\n- some unknown bytes/flags\n\nModulation: ASK/OOK with Manchester coding.\nSend interval: 5 secs and/or when current changes.\n\nHF Output is 10 mW, but appears much higher (due to antenna maybe),\nWith RSSI -0.1 dB, SNR 33.0 dB at 31 m distance!\n\nThe packet is 14 manchester encoded bytes with a Preamble of 0x2A and\nan 8-bit checksum (last byte).\n\nRaw data:\n\n    2ab0abe05a15603a14005710840011\n    2ab0abe05a15603a13005710df0040\n    2ab0abe05a15603ae2c0e710ca20bb\n    2ab0abe05a15603a1ac0e710c12078\n    2ab0abe05a15603a7d007b104f00c7\n    2a88abe05a950026b880603af5c05710d9a018\n    2a48abe05a950026b880e000000040000000003e\n    2ab0abe05a15603a6ec0b7103f20dd\n    2a70abe05a05e08000001c80000000a4\n\nExample messages:\n\n    0d d507 5aa8 06 5c 2800 ea08 2100 88\n    0d d507 5aa8 06 5c c800 ea08 fb00 02\n    0d d507 5aa8 06 5c 4703 e708 5304 dd\n    0d d507 5aa8 06 5c 5803 e708 8304 1e\n    0d d507 5aa8 06 5c be00 de08 f200 e3\n    11 d507 5aa9 00 64 1d01 065c af03 ea08 9b05 18\n    12 d507 5aa9 00 64 1d01 0700 0000 0200 0000 00 7c\n    0d d507 5aa8 06 5c 7603 ed08 fc04 bb\n    0e d507 5aa0 07 01 0000 3801 0000 00 25\n\nData layout:\n\n    LL IIII UUUU CC FF AAAA VVVV WWWW XX\n\n- L: (8 bit) 0d    : payload_length (13), excluding the length byte, including checksum\n- I: (16 bit) d507    : id\n- U: (16 bit) 5aa8    : unknown1\n- C: (8 bit) 06    : channel (6) // TODO\n- F: (8 bit) 5c    ;  unknown2\n- A: (16 bit) be00    : current (0.190)\n- V: (16 bit) de08    : voltage    (227.0)\n- W: (16 bit) f200    : power    (24.2)\n- X: (8 bit) e3    : checksum\n\nAll data is little endian.\nThere are 2 message types: 06 is power, 07 is energy.\n\npower:\n    0d 7e05 5a 68 06 5c 2100 e108 0700 c5\n    000 : (8bit)    0d      Length:13\n    001 : (16bit)   7e05    id:1406\n    003 : (8bit)    5a      version:90\n    004 : (8bit)    68      flags:\"01101000\"\n    005 : (8bit)    06      type:6 (power)\n    006 : (8bit)    5c      92?\n    007 : (16bit)   2100    current_A:0.033\n    009 : (16bit    e108    voltage_V:227.3\n    011 : (16bit)   0700    power_W:0.7\n    013 : (8bit)    c5        checksum\n\ncoldstart power:\n    11 d507 5a a9 00 64 1d01 06 5c af03 ea08 9b05 18\n    000 : (8bit)    11      Length:17 (coldstart power)\n    001 : (16bit)   d507    id:2005\n    003 : (8bit)    5a      version:90\n    004 : (8bit)    a9      flags:\"10101001\"\n    005 : (8bit)    00      ?0\n    006 : (8bit)    64      ?100\n    007 : (16bit    1d01    ?285\n    009 : (8bit)    06      type:6 (power)\n    010 : (8bit)    5c      ?92\n    011 : (16bit)   af03    current_A:0.943\n    013 : (16bit    ea08    voltage_V:228.2\n    015 : (16bit)   9b05    power_W:143.5\n    017 : (8bit)    c5      checksum\n\nenergy:\n    0e d507 5a a0 07 030000 a503 020000 98\n    000 : (8bit)    0e      Length:14\n    001 : (16bit)   7e05    id:2005\n    003 : (8bit)    5a      version:90\n    004 : (8bit)    a0      flags:\"10100000\"\n    005 : (8bit)    07      type:7 (energy)\n    006 : (24bit)   030000  energy_kWh:0.03\n    009 : (16bit    a503    tsec:933 (15min 33sec)\n    011 : (24bit)   020000  energy_kWh:0.02\n    013 : (8bit)    98      checksum\n\ncoldstart energy:\n    12 7e 05 5a 69 00 64 1d 01 07 000000 0200 000000 e3\n    000 : (8bit)    0e      Length:18\n    001 : (16bit)   7e05    id:2005\n    003 : (8bit)    5a      version:90\n    004 : (8bit)    69      flags:\"1101001\"\n    005 : (16bit)   0064    uke1:    ?0064    (?100)\n    007 : (16bit)   1d01    uke2:    ?1d01    (?285)\n    009 : (8bit)    07      type:7    (energy)\n    010 : (24bit)   000000  energy_kWh:0.00\n    013 : (16bit)   0200    tsec:2 (2sec)\n    015 : (24bit)   020000  energy_kWh:0.00\n    018 : (8bit)    98      checksum\n\n*/\nstatic int revolt_zx7717_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x2a}; // sync is 0x2a\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n    // valid message lengths are 0d, 0e, 11, 12, i.e. 13, 14, 17, 18 plus sync and length byte\n    unsigned row_len = bitbuffer->bits_per_row[0];\n    if (row_len < 15 * 8 || row_len > 22 * 8) {\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n\n    unsigned pos = bitbuffer_search(bitbuffer, 0, 0, preamble, 8);\n    pos += 8; // skip preamble\n\n    if (pos > 16) { // match only near the start\n        return DECODE_ABORT_LENGTH; // preamble not found\n    }\n    int len = bitbuffer->bits_per_row[0] - pos;\n\n    uint8_t b[32];\n    bitbuffer_extract_bytes(bitbuffer, 0, pos, b, len);\n    reflect_bytes(b, (len + 7) / 8);\n\n    int msg_len = b[0]; // expected: 0d, 0e, 11, 12\n    if (msg_len < 1) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Is there enough data for a given length of message?\n    if (len < (msg_len + 1) * 8) {\n        return DECODE_ABORT_LENGTH; // short buffer\n    }\n\n    int sum = add_bytes(b, msg_len);\n    if (b[msg_len] != (sum & 0xff)) {\n        return DECODE_FAIL_MIC; // bad checksum\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, len, \"message\");\n\n    int is_power   = 0;\n    int is_energy  = 0;\n    int id         = (b[2] << 8) | (b[1]);\n    int version    = (b[3]);\n    // int flags      = (b[4]);\n    // int type       = 0;\n    int current    = 0;\n    int voltage    = 0;\n    int power      = 0;\n    int energy_kWh = 0;\n    // int tsec       = 0; // time in secs energy changed\n\n    if (msg_len == 13) {\n        // power 0x0d\n        // 0d d507 5a a8 06 5c 2800 ea08 2100 88\n        is_power = 1;\n        // type      = (b[5]);\n        // unknown_1 = (b[6]);\n        current = (b[8] << 8) | b[7];\n        voltage = (b[10] << 8) | b[9];\n        power   = (b[12] << 8) | b[11];\n    }\n    else if (msg_len == 14) {\n        // energy 0x0e\n        is_energy  = 1;\n        // type       = (b[5]);\n        energy_kWh = (b[8] << 16) | (b[7] << 8) | b[6];\n        // tsec       = (b[10] << 8 | b[9]);\n        // energy_kwh_l = (b[13] << 16) | (b[12] << 8) | b[11];\n    }\n    else if (msg_len == 17) {\n        // 0x11 power at coldstart = initial power\n        is_power = 1;\n        // type      = (b[9]);\n        current   = (b[12] << 8) | b[11];\n        voltage   = (b[14] << 8) | b[13];\n        power     = (b[16] << 8) | b[15];\n    }\n    else if (msg_len == 18) {\n        // 0x12 energy at coldstart / initial energy\n        is_energy  = 1;\n        // type       = (b[9]);\n        energy_kWh = (b[12] << 16) | (b[11] << 8) | b[10];\n        // tsec =  (b[14] << 8 | b[13]);\n        // tsec is FAULTY and useless by design, should be: \"time since coldstart\" in seconds or even better in minutes for 24bit\n    }\n    else {\n        decoder_log_bitrow(decoder, 1, __func__, b, len, \"unhandled message\");\n        return DECODE_FAIL_OTHER; // unhandled message\n    }\n\n    // calculation for PF (Powerfactor) is invalid if current is < 0.02 A\n    // e.g. a standby device will show bad readings\n    // double va = current * voltage * 0.001; // computed value\n    // double powerf = va > 1.0 ? power / va : 1.0; // computed value\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Revolt-ZX7717\",\n            \"id\",               \"Device ID\",        DATA_INT,  id,\n            \"version\",          \"Version\",          DATA_INT,  version,\n            \"current_A\",        \"Current\",          DATA_COND, is_power, DATA_FORMAT, \"%.3f A\", DATA_DOUBLE, current * 0.001,\n            \"voltage_V\",        \"Voltage\",          DATA_COND, is_power, DATA_FORMAT, \"%.1f V\", DATA_DOUBLE, voltage * 0.1,\n            \"power_W\",          \"Power\",            DATA_COND, is_power, DATA_FORMAT, \"%.1f W\", DATA_DOUBLE, power * 0.1,\n            \"energy_kWh\",       \"energy_kWh\",       DATA_COND, is_energy, DATA_FORMAT, \"%.2f kWh\", DATA_DOUBLE, energy_kWh * 0.01,\n            // \"apparentpower_VA\", \"Apparent Power\",   DATA_FORMAT, \"%.1f VA\", DATA_DOUBLE, va * 0.1, // computed value\n            // \"powerfactor\",      \"Power Factor\",     DATA_DOUBLE, powerf, // computed value\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"version\",\n        \"current_A\",\n        \"voltage_V\",\n        \"power_W\",\n        \"energy_kWh\",\n        // \"apparentpower_VA\", // computed value\n        // \"powerfactor\", // computed value\n        \"mic\",\n        NULL,\n};\n\nr_device const revolt_zx7717 = {\n        .name        = \"Revolt ZX-7717 power meter\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 310, // Nominal width of clock half period [us]\n        .long_width  = 310,\n        .reset_limit = 900, // Maximum gap size before End Of Message [us]\n        .decode_fn   = &revolt_zx7717_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rftech.c",
    "content": "/** @file\n    RF-tech decoder (INFRA 217S34).\n\n    Copyright (C) 2016 Erik Johannessen\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nRF-tech decoder (INFRA 217S34).\n\nAlso marked INFRA 217S34\nEwig Industries Macao\n\nExample of message:\n\n    01001001 00011010 00000100\n\n- First byte is unknown, but probably id.\n- Second byte is the integer part of the temperature.\n- Third byte bits 0-3 is the fraction/tenths of the temperature.\n- Third byte bit 7 is 1 with fresh batteries.\n- Third byte bit 6 is 1 on button press.\n\nMore sample messages:\n\n    {24} ad 18 09 : 10101101 00011000 00001001\n    {24} 3e 17 09 : 00111110 00010111 00001001\n    {24} 70 17 03 : 01110000 00010111 00000011\n    {24} 09 17 01 : 00001001 00010111 00000001\n\nWith fresh batteries and button pressed:\n\n    {24} c5 16 c5 : 11000101 00010110 11000101\n\n*/\n\n#include \"decoder.h\"\n\nstatic int rftech_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 24);\n\n    if (r < 0 || bitbuffer->bits_per_row[r] != 24)\n        return DECODE_ABORT_LENGTH;\n    uint8_t *b = bitbuffer->bb[r];\n\n    int sensor_id = b[0];\n    float temp_c  = (b[1] & 0x7f) + (b[2] & 0x0f) * 0.1f;\n    if (b[1] & 0x80)\n        temp_c = -temp_c;\n\n    int battery = (b[2] & 0x80) == 0x80;\n    int button  = (b[2] & 0x60) != 0;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"RF-tech\",\n            \"id\",               \"Id\",           DATA_INT,    sensor_id,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    battery,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"button\",           \"Button\",       DATA_INT,    button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const csv_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"button\",\n        NULL,\n};\n\nr_device const rftech = {\n        .name        = \"RF-tech\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 5000,\n        .reset_limit = 10000,\n        .decode_fn   = &rftech_callback,\n        .disabled    = 1,\n        .fields      = csv_output_fields,\n};\n"
  },
  {
    "path": "src/devices/risco_agility.c",
    "content": "/** @file\n    Risco 2 way Agility protocol.\n\n    Copyright (C) 2024 Bruno OCTAU (ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nRisco 2 way Agility protocol.\n\nManufacturer :\n- Risco Ltd.\n\nReference:\n- Risco PIR RWX95PA Agility sensor,\n\nFCC extract:\n- The module is a transceiver which consist of a small PCB with an integral helical antenna,\nwhich operates in the frequency of 433.92MHz Modulation is On-Off Keying using Manchester code with max bit rate of 2400Bps.\nThis module is installed only in RISCO 2-way wireless units, and it's behavior is determined by the host unit, as tested by ITL.\n- Being bi-directional enables the detectors to receive an acknowledgment from the panel for every transmission.\n\nThis module, p/n RWRT433R000A, is a 433.92Mhz 2-way wireless module manufactured by RISCO Ltd.\nThe model consists of a small PCB, a header for connection to the host unit, and a helical integral antenna.\nThis model is not sold separately, and is not installed in any units other then RISCO 2-way wireless units, and currently it is used\nin the following hosts:\n- Agility Security panel       P/N: RW132x4t0zzA\n- 2-Way I/O Expander           P/N: RW132I04000H\n- 2-Way Wireless PIR Detector  P/N: RWX95043300A\n- 2-Way Wireless PET Detector  P/N: RWX95P43300A\n\nS.a. issue #3062\n\nData Layout:\n- 2 types of message have been identified.\n- 16 bytes\n- or 33 bytes\n\nPreamble/Syncword  .... : 0x555a\n\nShort 16 bytes message:\n                   0  8  16 24 34 40 48 56 64 72 80 88 96104112120\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15\n    Sample         ff 60 01 e1 9c b6 01 74 fe 28 0c 60 60 00 50 be\n                   AA AA BB BC DD DD EE EE EE FF FF GG HI JJ ZZ ZZ\n\n- AA:{16} flag 1, fixed 0xFF60\n- BB:{12} flag 2, fixed 0x01E\n- C: {4}  0 or 1 flag 3\n- D: {16} Counter, 8 bits reversed and reflected binary coded, one bit change between message, each byte increases to maximum then decreases.\n- EE:{24} Possible ID, not yet decoded from Wxxxxxxxxxxx number on the QR sticker.\n- FF:{16} Fixed 0x280c value\n- GG:{8}  flag 4, 0x60 from PIR sensor, 0xA0 from other type frame\n- H: {4}  Alarm state, 0x6 (0x4 Gray decoded) = Tampered, 0xA (0x6) = Tampered_motion, 0xC (0x2) = Motion, 0x0 = Clear, not detection.\n- I: {4}  0x0 = Normal, 0x3 (0x8) = Low Bat ?\n- J: {4}  0 or 1\n- ZZ:{16} CRC-16, poly 0x8005, init 0x8181\n\nBitbench:\n\n    ? 16h ? 12h ZERO_OR_ONE 4h COUNTER ? 4h CMD ? 4h ID ? 32h FIX 16h ? 8h ? 8h ZERO_OR_ONE 8h CRC 16h 8h\n\nLong 33 bytes message: (draft, to be reviewed)\n\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32\n    Sample         fe f8 01 d1 ba 18 01 ac 89 28 0c a0 03 01 e0 a3 19 01 06 00 00 c0 c0 00 df 3e 2f a5 f4 1e 00 82 1b\n                   AA AA BB BC DE FF FF FF FF GG GG HH II JJ J? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ZZ ZZ\n\n- AA:{16} flag 1, fixed 0xFEF8\n- BB:{12} flag 2, fixed 0x01D\n- C: {4} 0 or 1 flag 3\n- D: {4} Counter ?\n- E: {4} Command ? (send / acknowledge ?)\n- FF:{32} Possible ID, not yet decoded from Wxxxxxxxxxxx number on the QR sticker.\n- GG:{16} Fixed 0x280c value\n- HH:{8} flag 4, 0x60 from PIR sensor, 0xA0 from other type frame\n- II:{8} flag 5, 0x60 = Tampered, 0xA0 = Tampered_motion, 0xC0 = Motion, 0x03 from other type frame.\n- ??: Unknown\n- JJ:{12} flag 6, fixed 0x01E\n- ZZ:{16} CRC-16, poly 0x8005, init 0x8181\n\nBitbench:\n\n    ? 16h ? 12h ZERO_OR_ONE 4h ? COUNTER ? 4h CMD ? 4h ID ? 32h FIX 16h ? 8h 8h 12h 12h 32h 8h 72h ? 8h CRC 16h\n\n*/\n\nstatic int gray_decode(int n) {\n    int p = n;\n    while (n >>= 1) p ^= n;\n    return p;\n}\n\nstatic int risco_agility_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_t decoded = { 0 };\n    uint8_t *b;\n    uint8_t const preamble_pattern[] = {0x55, 0x5a};\n    uint8_t len_msg = 16; // default for sensor message, could be 33 bytes for other Agility message not yet decoded\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n    if (pos >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 1, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, bitbuffer->bb[0], bitbuffer->bits_per_row[0], \"MSG\");\n\n    bitbuffer_differential_manchester_decode(bitbuffer, 0, pos + sizeof(preamble_pattern) * 8, &decoded, len_msg * 8);\n\n    decoder_log_bitrow(decoder, 1, __func__, decoded.bb[0], decoded.bits_per_row[0], \"DMC\");\n\n    // check msg length\n\n    if (decoded.bits_per_row[0] < len_msg * 8) {\n        decoder_logf(decoder, 1, __func__, \"Too short\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = decoded.bb[0];\n\n    // verify checksum\n    if (crc16(b, len_msg, 0x8005, 0x8181)) {\n        decoder_logf(decoder, 1, __func__, \"crc error\");\n        return DECODE_FAIL_MIC; // crc mismatch\n    }\n\n    // expected 0xFF60 short message, 0xFEF8 message not yet decoded properly\n    int message_type = (b[0] << 8)| b[1];\n    if ( message_type != 0xFF60) {\n        decoder_logf(decoder, 1, __func__, \"Wrong message type %04x\", message_type);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // ID is probably not well decoded as bit not reverse and not Gray decoded\n    int id = (b[6] << 16) | (b [7] << 8) | b[8];\n\n    reflect_bytes(b,16);\n\n    // Alarm state, 0x4 = Tampered, 0x6 = Tampered_motion, 0x2 = Motion, 0x0 = Clear, not detection.\n    int state        = gray_decode(b[12] & 0xF);\n    int tamper       = (state & 0x4) >> 2;\n    int motion       = (state & 0x2) >> 1;\n    int low_batt     = (gray_decode((b[12] & 0xF0) >> 4) & 0x8)>> 3;\n    int counter_raw  = (b[5] << 8) | b[4];\n    int counter   = gray_decode(counter_raw);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",       \"\",                 DATA_STRING, \"Risco-RWX95P\",\n            \"id\",          \"\",                 DATA_INT, id,\n            \"counter\",     \"Counter\",          DATA_INT, counter,\n            \"tamper\",      \"Tamper\",           DATA_COND,   tamper, DATA_INT, 1,\n            \"motion\",      \"Motion\",           DATA_COND,   motion, DATA_INT, 1,\n            \"battery_ok\",  \"Battery_OK\",       DATA_INT,    !low_batt,\n            \"mic\",         \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"counter\",\n        \"tamper\",\n        \"motion\",\n        \"battery_ok\",\n        \"mic\",\n        NULL,\n};\n\nr_device const risco_agility = {\n        .name        = \"Risco 2 Way Agility protocol, Risco PIR/PET Sensor RWX95P\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 175,\n        .long_width  = 175,\n        .reset_limit = 1000,\n        .decode_fn   = &risco_agility_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rojaflex.c",
    "content": "/** @file\n    RojaFlex shutter and remote devices.\n\n    Copyright (c) 2021 Sebastian Hofmann <sebastian.hofmann+rtl433@posteo.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n#include \"decoder.h\"\n\n/**\nRojaFlex shutter and remote devices.\n\n- Frequency: 433.92 MHz\n\nData layout:\n\n    0xaaaaaaaa d391d391 SS KKKKKK ?CDDDD TTTT CCCC\n\n- 4 byte Preamble   : \"0xaaaaaaaa\"\n- 4 byte Sync Word  : \"9391d391\"\n- 1 byte Size       : \"S\" is always \"0x08\"\n- 3 byte ID         : Seems to be the static ID for the Homeinstallation\n- 3 byte Data       : See below\n- 1 byte Token I    : It seems to be an internal message token which is used for the shutter answer.\n- 1 byte Token II   : Is the sum of 3 Bytes ID + 3 Bytes Data + 1 Byte token\n- 2 byte CRC-16/CMS : poly 0x8005 init 0xffff, seems optional, missing from commands via bridge P2D.\n\nOverall 19 byte packets, only 17 byte without CRC (from bridge).\n\nData documentation:\n\n- 0xFF     - Size always \"0x8\"\n- 0xFFFFFF - ID, I assume that differs per installation, but is static then\n- 0xF      - Unknown (is static 0x2) - Not sure if it is also the HomeID\n- 0xF      - Channel: 1-15 single channels (one shutter is registered to one channel), 0 means all\n- 0xFF     - Command ID    (0x0a = stop, 0x1a = up,0x8a = down, 0xea = Request)\n- 0xFF     - Command Value (in status from shutter this is the percent value. 0% for open 100% for close)\n\nTo get raw data:\n\n    ./rtl_433 -f 433920000 -X n=RojaFlex,m=FSK_PCM,s=100,l=100,r=102400\n*/\n\n// Message Defines\n#define DATAFRAME_BITCOUNT_INCL_CRC 88\n#define DATAFRAME_BYTECOUNT_INCL_CRC 11 //Including CRC but no pramble\n#define LENGTH_OFFSET 0\n#define LENGTH_BITCOUNT 8\n#define ID_OFFSET 1 // HomeID which I assume is static for one Remote Device\n#define ID_BITCOUNT 28\n#define CHANNEL_OFFSET 4         // Mask 0x0F\n#define UNKNOWN_CHANNEL_OFFSET 5 // Mask 0xF0\n#define COMMAND_ID_OFFSET 5\n#define COMMAND_ID_BITCOUNT 8\n#define COMMAND_VALUE_OFFSET 6\n#define COMMAND_VALUE_BITCOUNT 8\n#define MESSAGE_TOKEN_OFFSET 7\n#define MESSAGE_TOKEN_BITCOUNT 16\n#define MESSAGE_CRC_OFFSET 9\n#define MESSAGE_CRC_BITCOUNT 16\n\n// Command Defindes\n#define COMMAND_ID_STOP 0x0a\n#define COMMAND_ID_UP 0x1a\n#define COMMAND_ID_DOWN 0x8a\n#define COMMAND_ID_SAVE_UNSAVE_POS 0x9a\n#define COMMAND_ID_GO_SAVED_POS 0xda\n#define COMMAND_ID_REQUESTSTATUS 0xea\n\n#define DEVICE_TYPE_UNKNOWN 0x0\n#define DEVICE_TYPE_SHUTTER 0x5\n#define DEVICE_TYPE_REMOTE 0xa\n#define DEVICE_TYPE_BRIDGE 0xb\n\nstatic int rojaflex_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const message_preamble[] = {\n            /*0xaa, 0xaa,*/ 0xaa, 0xaa, // preamble\n            0xd3, 0x91, 0xd3, 0x91      // sync word\n    };\n\n    data_t *data;\n    uint8_t msg[DATAFRAME_BYTECOUNT_INCL_CRC];\n    uint8_t dataframe_bitcount = 0;\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int row = 0;\n    // Validate message and reject it as fast as possible : check for preamble\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, message_preamble, sizeof(message_preamble) * 8);\n\n    if (start_pos < bitbuffer->bits_per_row[row]) {\n        // Save bitcount of total message including preamble\n        dataframe_bitcount = (bitbuffer->bits_per_row[row] - start_pos - sizeof(message_preamble) * 8) & 0xFE;\n    }\n    else {\n        return DECODE_ABORT_EARLY; // no preamble detected\n    }\n\n    if (dataframe_bitcount < (DATAFRAME_BITCOUNT_INCL_CRC - MESSAGE_CRC_BITCOUNT) || (dataframe_bitcount > DATAFRAME_BITCOUNT_INCL_CRC)) {\n        // check min and max length\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Extract raw line\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos + sizeof(message_preamble) * 8, msg, dataframe_bitcount);\n    decoder_log_bitrow(decoder, 2, __func__, msg, dataframe_bitcount, \"frame data\");\n\n    // Check CRC if available\n    if (dataframe_bitcount == DATAFRAME_BITCOUNT_INCL_CRC) {\n        uint16_t crc_message = (msg[MESSAGE_CRC_OFFSET] << 8 | msg[MESSAGE_CRC_OFFSET + 1]);\n        uint16_t crc_calc    = crc16(&msg[LENGTH_OFFSET], 9, 0x8005, 0xffff); // \"CRC-16/CMS\"\n\n        if (crc_message != crc_calc) {\n            decoder_logf(decoder, 1, __func__, \"CRC invalid message:%04x != calc:%04x\", crc_message, crc_calc);\n\n            return DECODE_FAIL_MIC;\n        }\n    }\n\n    // Data output\n    int has_crc = dataframe_bitcount == DATAFRAME_BITCOUNT_INCL_CRC;\n    int id      = (msg[ID_OFFSET] << 20) | (msg[ID_OFFSET + 1] << 12) | (msg[ID_OFFSET + 2] << 4) | (msg[ID_OFFSET + 3] >> 4);\n    int token   = (msg[MESSAGE_TOKEN_OFFSET] << 8) | (msg[MESSAGE_TOKEN_OFFSET + 1]);\n\n    int device_type = DEVICE_TYPE_UNKNOWN;\n    if ((msg[COMMAND_ID_OFFSET] & 0xF) == 0x5) {\n        device_type = DEVICE_TYPE_SHUTTER;\n    }\n    else if ((msg[COMMAND_ID_OFFSET] & 0xF) == 0xa) {\n        // RojaFlex Bridge clones a remote signal but does not send an CRC!?!?\n        // So we can detect if it a real remote or a bridge on the message length.\n        if (has_crc) {\n            device_type = DEVICE_TYPE_REMOTE;\n        }\n        else {\n            device_type = DEVICE_TYPE_BRIDGE;\n        }\n    }\n\n    char const *cmd_str = \"unknown\";\n    switch (msg[COMMAND_ID_OFFSET]) {\n    case COMMAND_ID_STOP:\n        cmd_str = \"Stop\"; break;\n    case COMMAND_ID_UP:\n        cmd_str = \"Up\"; break;\n    case COMMAND_ID_DOWN:\n        cmd_str = \"Down\"; break;\n    case COMMAND_ID_SAVE_UNSAVE_POS:\n        // 5 x Stop on remote set inclined pos.\n        // Command is complete identical for set and unset\n        // - If nothing is saved it will set.\n        // - If something is saved and the position is identical it will reset.\n        //   The P2D bridge is beeping in that case.\n        cmd_str = \"Save/Unsave position\"; break;\n    case COMMAND_ID_GO_SAVED_POS:\n        // Hold Stop for 5 seconds to drive to saved pos.\n        cmd_str = \"Go saved position\"; break;\n    case COMMAND_ID_REQUESTSTATUS:\n        // I am not sure if that is true.\n        // I know that the remote is sending the message and not the shutter.\n        // I know that the bridge is not sending this message after e.g.0x1a.\n        // I know that the shutter sends a Position status right after this message.\n        // After the normal 0x1a command from a bridge, the position status\n        // will be send wenn the stutter is completely up but not before.\n        // So I think this is a \"Request Shutter Status Now\".\n        cmd_str = \"Request Status\"; break;\n    case 0x85: //  0%\n        cmd_str = \"Pos. Status 0%\"; break;\n    case 0x95: // 20%\n        cmd_str = \"Pos. Status 20%\"; break;\n    case 0xA5: // 40%\n        cmd_str = \"Pos. Status 40%\"; break;\n    case 0xB5: // 60%\n        cmd_str = \"Pos. Status 60%\"; break;\n    case 0xC5: // 80%\n        cmd_str = \"Pos. Status 80%\"; break;\n    case 0xD5: //100%\n        cmd_str = \"Pos. Status 100%\"; break;\n    }\n\n    /* clang-format off */\n    data = data_make(\n                \"model\",        \"Model\",        DATA_COND, device_type == DEVICE_TYPE_UNKNOWN, DATA_STRING, \"RojaFlex-Other\",\n                \"model\",        \"Model\",        DATA_COND, device_type == DEVICE_TYPE_SHUTTER, DATA_STRING, \"RojaFlex-Shutter\",\n                \"model\",        \"Model\",        DATA_COND, device_type == DEVICE_TYPE_REMOTE, DATA_STRING, \"RojaFlex-Remote\",\n                \"model\",        \"Model\",        DATA_COND, device_type == DEVICE_TYPE_BRIDGE, DATA_STRING, \"RojaFlex-Bridge\",\n                \"id\",           \"ID\",           DATA_FORMAT, \"%07x\", DATA_INT,    id,\n                \"channel\",      \"Channel\",      DATA_INT,    msg[CHANNEL_OFFSET] & 0xF,\n                \"token\",        \"Msg Token\",    DATA_FORMAT, \"%04x\", DATA_INT,    token,\n                \"cmd_id\",       \"Value\",        DATA_FORMAT, \"%02x\", DATA_INT,    msg[COMMAND_ID_OFFSET],\n                \"cmd_name\",     \"Command\",      DATA_STRING, cmd_str,\n                \"cmd_value\",    \"Value\",        DATA_INT,    msg[COMMAND_VALUE_OFFSET],\n                \"mic\",          \"Integrity\",    DATA_COND,   has_crc, DATA_STRING, \"CRC\",\n                NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n// You can use this defines to clone / generate all commands for other bridges\n#define GENERATE_COMMANDS_FOR_CURRENT_CHANNEL 0\n#define GENERATE_COMMANDS_FOR_ALL_CHANNELS 0\n\n    if (GENERATE_COMMANDS_FOR_CURRENT_CHANNEL || GENERATE_COMMANDS_FOR_ALL_CHANNELS) {\n        uint8_t const remote_commands[] = {\n                COMMAND_ID_STOP,\n                COMMAND_ID_UP,\n                COMMAND_ID_DOWN,\n                COMMAND_ID_SAVE_UNSAVE_POS,\n                COMMAND_ID_GO_SAVED_POS,\n                COMMAND_ID_REQUESTSTATUS};\n\n        uint8_t channel = GENERATE_COMMANDS_FOR_CURRENT_CHANNEL ? msg[CHANNEL_OFFSET] & 0xF : 0;\n        uint8_t command;\n        uint8_t msg_new[19];\n        uint16_t sum = 0;\n\n        decoder_log(decoder, 2, __func__, \"Signal cloner\");\n\n        do {\n            for (uint8_t i = 0; i < sizeof(remote_commands); ++i) {\n                command = remote_commands[i];\n\n                // Create complete message preamble\n                msg_new[0] = 0xaa;\n                msg_new[1] = 0xaa;\n                msg_new[2] = 0xaa;\n                msg_new[3] = 0xaa;\n                msg_new[4] = 0xd3;\n                msg_new[5] = 0x91;\n                msg_new[6] = 0xd3;\n                msg_new[7] = 0x91;\n\n                // Set length\n                msg_new[8 + LENGTH_OFFSET] = 0x8;\n\n                // Clone ID from received message\n                msg_new[8 + ID_OFFSET + 0] = msg[ID_OFFSET + 0];\n                msg_new[8 + ID_OFFSET + 1] = msg[ID_OFFSET + 1];\n                msg_new[8 + ID_OFFSET + 2] = msg[ID_OFFSET + 2];\n\n                // Clone 4bit ID + Channel\n                msg_new[8 + ID_OFFSET + 3] = (msg[ID_OFFSET + 3] & 0xF0) + channel;\n\n                // Set command id + command value\n                msg_new[8 + COMMAND_ID_OFFSET]    = command;\n                msg_new[8 + COMMAND_VALUE_OFFSET] = 0x1;\n\n                // Generate message token\n                // TODO: This value is not completely known\n                msg_new[8 + MESSAGE_TOKEN_OFFSET + 0] = (command == COMMAND_ID_REQUESTSTATUS) ? 0x02 : command;\n\n                // Calculate sum\n                sum = 0;\n                for (uint8_t j=0; j <= 6; ++j) {\n                    sum += msg_new[8 + ID_OFFSET + j];\n                }\n                msg_new[8 + MESSAGE_TOKEN_OFFSET + 1] = sum & 0xff;\n\n                // Generate CRC\n                // Thanks to: ./reveng -w 16 -s $msg1 $msg2 $msg3\n                // width=16  poly=0x8005  init=0xffff  refin=false  refout=false  xorout=0x0000  check=0xaee7  residue=0x0000  name=\"CRC-16/CMS\"\n                uint16_t crc_calc                   = crc16(&msg_new[8 + LENGTH_OFFSET], 9, 0x8005, 0xffff);\n                msg_new[8 + MESSAGE_CRC_OFFSET + 0] = crc_calc >> 8;\n                msg_new[8 + MESSAGE_CRC_OFFSET + 1] = crc_calc & 0xFF;\n\n                /*\n                // Print final command\n                */\n                decoder_logf_bitrow(decoder, 2, __func__, &msg_new[0], sizeof(msg_new) * 8, \"CH:%01x Command:0x%02x\", channel, command);\n            }\n\n            decoder_log(decoder, 2, __func__, \"\");\n            ++channel;\n        } while ((channel <= 0xF) && GENERATE_COMMANDS_FOR_ALL_CHANNELS);\n    }\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"token\",\n        \"cmd_id\",\n        \"cmd_name\",\n        \"cmd_value\",\n        \"mic\",\n        NULL,\n};\n\nr_device const rojaflex = {\n        .name        = \"RojaFlex shutter and remote devices\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100,\n        .long_width  = 100,\n        .reset_limit = 102400,\n        .sync_width  = 0,\n        .decode_fn   = &rojaflex_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rosstech_dcu706.c",
    "content": "/** @file\n    Rosstech Digital Control Unit DCU-706/Sundance.\n\n    Copyright (C) 2023 suaveolent\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int rosstech_dcu706_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nRosstech Digital Control Unit DCU-706/Sundance/Jacuzzi.\n\nSupported Models:\nSundance DCU-6560-131, SD-880 Series, PN 6560-131\nJacuzzi DCU-2560-131, Jac-J300/J400 and SD-780 series, PN 6560-132/2560-131\n\nData coding:\n\nUART 8o1: 11 bits/byte: 1 start bit (1), odd parity, 1 stop bit (0).\n\nData layout:\n\n    SS IIII TT CC\n\n- S: 8 bit sync byte and type of transmission\n- I: 16 bit ID\n- T: 8 bit temp packet in degrees F\n- C: 8 bit Checksum: Count 1s for each bit of each element:\n                     Set bit to 1 if number is even 0 if odd\n\n*/\n\nstatic int rosstech_dcu706_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_data_transmission[] = {0xDD, 0x40};\n    // The Bond command also contains the temperature\n    uint8_t const preamble_Bond[] = {0xCD, 0x00};\n    int const preamble_length = 11;\n\n    // We need 55 bits\n    uint8_t msg[7];\n\n    if (bitbuffer->num_rows != 1\n            || bitbuffer->bits_per_row[0] < 55\n            || bitbuffer->bits_per_row[0] > 300) {\n        decoder_logf(decoder, 2, __func__, \"bit_per_row %u out of range\", bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_EARLY; // Unrecognized data\n    }\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble_data_transmission, preamble_length);\n\n    if (start_pos == bitbuffer->bits_per_row[0]) {\n\n        start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble_Bond, preamble_length);\n\n        if (start_pos == bitbuffer->bits_per_row[0]) {\n            return DECODE_ABORT_LENGTH;\n        }\n    }\n\n    if (start_pos + 55 > bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);\n\n    uint8_t b[5] = {0};\n    unsigned r   = extract_bytes_uart_parity(msg, 0, 55, b);\n    if (r != 5) {\n        decoder_logf(decoder, 2, __func__, \"UART decoding failed. Got %d of 5 bytes\", r);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int msg_type = b[0];                 // S\n    int id       = (b[1] << 8) | (b[2]); // I\n    int temp_f   = b[3];                 // T\n    int checksum = b[4];                 // C\n\n    uint8_t calculated = 0xff ^ xor_bytes(b, 4);\n    if (calculated != checksum) {\n        decoder_logf(decoder, 2, __func__, \"Checksum failed. Expected: %02x, Calculated: %02x\", checksum, calculated);\n        return DECODE_FAIL_MIC;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",            \"Model\",              DATA_STRING,   \"Rosstech-Spa\",\n        \"id\",               \"ID\",                 DATA_FORMAT,   \"%04x\",    DATA_INT,   id,\n        \"msg_type\",         \"Transmission Type\",  DATA_STRING,   msg_type == 0xba ? \"Data\" : \"Bond\",\n        \"temperature_F\",    \"Temperature\",        DATA_FORMAT,   \"%d F\",    DATA_INT,   temp_f,\n        \"mic\",              \"Integrity\",          DATA_STRING,   \"CHECKSUM\",\n        NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"msg_type\",\n        \"temperature_F\",\n        \"mic\",\n        NULL,\n};\n\nr_device const rosstech_dcu706 = {\n        .name        = \"Rosstech Digital Control Unit DCU-706/Sundance/Jacuzzi\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 200,\n        .long_width  = 200,\n        .reset_limit = 2000,\n        .decode_fn   = &rosstech_dcu706_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rubicson.c",
    "content": "/** @file\n    Rubicson or InFactory PT-310 temperature sensor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n#include \"decoder.h\"\n\n/**\nRubicson temperature sensor.\n\nAlso older TFA 30.3197 sensors.\nAlso ZX-7382-675 sensor for inFactory FWS-325.pro, first seen 2025-01 (Pearl).\nAlso inFactory ZX-7614-675 (replaces ZX-7382-675).\n\nAlso InFactory PT-310 pool temperature sensor (AKA ZX-7074/7073). This device\nhas longer packet lengths of 37 or 38 bits but is otherwise compatible. See more at\nhttps://github.com/merbanan/rtl_433/issues/2119\n\nThe sensor sends 12 packets of  36 bits PPM modulated data.\n\ndata is grouped into 9 nibbles\n\n    [id0] [id1] [bat|unk1|chan1|chan2] [temp0] [temp1] [temp2] [0xf] [crc1] [crc2]\n\n- The id changes when the battery is changed in the sensor.\n- bat bit is 1 if battery is ok, 0 if battery is low\n- unk1 is always 0 probably unused\n- chan1 and chan2 forms a 2bit value for the used channel\n- temp is 12 bit signed scaled by 10\n- F is always 0xf\n- crc1 and crc2 forms a 8-bit crc, polynomial 0x31, initial value 0x6c, final value 0x0\n\nThe sensor can be bought at Kjell&Co. The Infactory pool sensor can be bought at Pearl.\n*/\nstatic int rubicson_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    // Infactory devices report 38 (or for last repetition) 37 bits\n    if (bitbuffer->bits_per_row[r] < 36 || bitbuffer->bits_per_row[r] > 38) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if ((b[3] & 0xf0) != 0xf0) {\n        return DECODE_ABORT_EARLY; // const not 1111\n    }\n\n    // CRC check\n    uint8_t tmp[5];\n    tmp[0] = b[0];                // Byte 0 is nibble 0 and 1\n    tmp[1] = b[1];                // Byte 1 is nibble 2 and 3\n    tmp[2] = b[2];                // Byte 2 is nibble 4 and 5\n    tmp[3] = b[3] & 0xf0;         // Byte 3 is nibble 6 and 0-padding\n    tmp[4] = (b[3] & 0x0f) << 4 | // CRC is nibble 7 and 8\n             (b[4] & 0xf0) >> 4;\n\n    int chk = crc8(tmp, 5, 0x31, 0x6c);\n    if (chk) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int id       = b[0];\n    int battery  = (b[1] & 0x80);\n    int channel  = ((b[1] & 0x30) >> 4) + 1;\n    int temp_raw = (int16_t)((b[1] << 12) | (b[2] << 4)); // sign-extend\n    float temp_c = (temp_raw >> 4) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Rubicson-Temperature\",\n            \"id\",               \"House Code\",   DATA_INT,    id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !!battery,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\n// timings based on samp_rate=1024000\nr_device const rubicson = {\n        .name        = \"Rubicson, TFA 30.3197 or InFactory PT-310 Temperature Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000, // Gaps:  Short 976us, Long 1940us, Sync 4000us\n        .long_width  = 2000, // Pulse: 500us (Initial pulse in each package is 388us)\n        .gap_limit   = 3000,\n        .reset_limit = 4800, // Two initial pulses and a gap of 9120us is filtered out\n        .decode_fn   = &rubicson_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rubicson_48659.c",
    "content": "/** @file\n    Rubicson 48659 meat thermometer.\n\n    Copyright (C) 2019 Benjamin Larsson.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nRubicson 48659 meat thermometer.\n\n    {32} II 12 TT CC\n\n- I = power on id\n- 1 = 0UUU U follows temperature [0-7]\n- 2 = XSTT S = sign, TT = temp high bits (2) X=unknown\n- T = Temp in Farenhight\n- C = Checksum, add 3 bytes - byte 4 should give a6\n\n\n    {32} 01 08 71 d4    45\n    {32} 01 18 73 e6    46\n    {32} 01 28 75 f8    47\n    {32} 01 38 77 0a    48\n    {32} 01 48 79 1c    49\n    {32} 01 50 7a 25    50C  122F\n    {32} 01 60 7c 37    51C  124F\n    {32} 01 70 7e 49    52\n    {32} 01 00 80 db    53\n    {32} 01 10 82 ed    54\n    {32} 01 18 83 f6    55\n    {32} 01 28 85 08    56\n    {32} 01 38 87 1a    57          {32} 0b 68 4d 1a    25\n    {32} 01 48 89 2c    58\n    {32} 01 58 8b 3e    59\n    {32} 01 60 8c 47    60\n    {32} 01 70 8e 59    61\n    {32} 01 00 90 eb    62\n    {32} 01 10 92 fd    63\n    {32} 01 20 94 0f    64\n    {32} 01 28 95 18    65\n    {32} 01 38 97 2a    66\n    {32} 01 48 99 3c    67\n    {32} 01 58 9b 4e    68\n    {32} 01 68 9d 60    69\n    {32} 01 70 9e 69    70\n    {32} 01 00 a0 fb    71\n    {32} 01 10 a2 0d    72\n    {32} 01 20 a4 1f    73\n    {32} 01 30 a6 31    74\n    {32} 01 38 a7 3a    75\n    {32} 01 48 a9 4c    76\n\n\nbattery\n\n    {32} cc 38 67 c5    39\n    {32} cc 08 61 8f    36\n    {32} cc 78 5f fd    34\n    {32} cc 70 5e f4    33\n    {32} cc 50 5a d0    32\n    {32} cc 30 56 ac    30\n    {32} cc 18 53 91    28\n    {32} cc 08 51 7f    27\n    {32} cc 78 4f ed    26\n\n\nbattery\n\n    {32} f0 18 43 a5    19\n    {32} f0 30 46 c0    21\n    {32} f0 40 48 d2    22\n    {32} f0 50 4a e4    23\n    {32} f0 60 4c f6    24\n    {32} f0 78 4f 11    26\n\nbattery change for each value\n\n    {32} d2 60 4c d8    24\n    {32} 01 60 4c 07    24\n    {32} 20 60 4c 26    24\n    {32} c3 60 4c c9    24\n    {32} ae 60 4c b4    24\n    {32} 98 60 4c 9e    24\n    {32} 27 60 4c 2d    24\n    {32} 5d 60 4c 63    24\n\n    {32} 49 68 4d 58    25\n    {32} d9 68 4d e8    25\n    {32} 36 68 4d 45    25\n    {32} 0b 68 4d 1a    25\n    {32} 63 68 4d 72    25\n    {32} 80 68 4d 8f\n    {32} 3c 68 4d 4b\n    {32} 97 68 4d a6\n    {32} 37 68 4d 46\n    {32} 64 68 4d 73\n    {32} 76 68 4d 85\n    {32} f6 68 4d 05\n    {32} fa 68 4d 09\n    {32} d6 68 4d e5\n    {32} d3 68 4d e2\n    {32} 01 68 4d 10\n    {32} 25 68 4d 34\n    {32} e0 68 4d ef\n    {32} 22 68 4d 31\n    {32} 56 68 4d 65\n    {32} 53 68 4d 62\n\n    {32} 0b 78 4f 2c    26\n\n    {32} 23 28 65 0a    38\n    {32} 23 70 6e 5b    43\n    {32} 23 00 70 ed    44\n\n    {32} 23 60 dc b9    104\n    {32} 23 78 df d4    106\n    {32} 23 28 e5 8a    109\n    {32} 23 08 f1 76    116\n    {32} 23 40 f8 b5    120\n    {32} 23 60 fc d9    122\n    {32} 23 19 03 99    128\n    {32} 23 61 0c ea    131\n    {32} 23 19 13 a9    135\n    {32} 23 39 17 cd    138\n    {32} 23 01 20 9e    142\n    {32} 23 69 2d 13    149\n    {32} 23 01 30 ae    151\n    {32} 23 21 34 d2    153\n    {32} 23 31 36 e4    154\n    {32} 23 39 37 ed    155\n    {32} 23 59 3b 11    157\n    {32} 23 69 3d 23    158\n    {32} 23 79 3f 35    159\n\n    {32} 23 79 3f 35    159\n    {32} 1a 70 0e f2    -10\n    {32} 1a 18 03 8f    -16\n    {32} 1a 8c 01 01    -18\n    {32} 1a 9c 03 13    -19\n    {32} 1a b4 06 2e    -22\n    {32} 1a d4 0a 52    -23\n    {32} 1a e4 0c 64    -24\n*/\n\nstatic int rubicson_48659_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Compare first four bytes of rows that have 32 or 33 bits.\n    // more then 25 repeats are not uncommon\n    int row = bitbuffer_find_repeated_row(bitbuffer, 10, 32);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n    uint8_t *b = bitbuffer->bb[row];\n\n    if ((bitbuffer->bits_per_row[row] > 33) || (bitbuffer->bits_per_row[row] < 10))\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t checksum = add_bytes(b, 3) - b[3];\n    if (checksum != 0xa6) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int id = b[0];\n    // 1 sign bit and 10 bits for the value\n    float temp_f = ((b[1] & 0x04) >> 2) ? -1 : 1 * (((b[1] & 0x3) << 8) | b[2]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Rubicson-48659\",\n            \"id\",            \"Id\",          DATA_INT,    id,\n            \"temperature_F\", \"Temperature\", DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_f,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_F\",\n        \"mic\",\n        NULL,\n};\n\nr_device const rubicson_48659 = {\n        .name        = \"Rubicson 48659 Thermometer\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 940,\n        .long_width  = 1900,\n        .gap_limit   = 2000,\n        .reset_limit = 4000,\n        .decode_fn   = &rubicson_48659_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/rubicson_pool_48942.c",
    "content": "/** @file\n    Rubicson pool thermometer 48942 decoder.\n\n    Copyright (C) 2022 Robert Högberg\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nRubicson pool thermometer 48942 decoder.\n\nThe device uses OOK and fixed period PWM.\n- 0 is encoded as 240 us pulse and 480 us gap,\n- 1 is encoded as 480 us pulse and 240 us gap.\n\nA transmission consists of an initial preamble followed by sync\npulses and the data. Sync pulses and data are sent twice.\n\nPreamble:\n     __      ____      ____      ____      ____\n    |  |____|    |____|    |____|    |____|    |__________\n    480 980  980  980  980  980  980  980  980  3880     [us]\n\nSync pulses:\n     ___     ___     ___     ___\n    |   |___|   |___|   |___|   |___\n    730  730 730 730 730 730 730 730    [us]\n\nThe device's transmission interval depends on the configured\nchannel. The interval is 55 + `device channel` seconds.\n\nData format:\n    71       ba       4e       60       ba       0\n    01110001 10111010 01001110 01100000 10111010 0\n    CCCCRRRR RRRRRR10 BTTTTTTT TTTT0000 XXXXXXXX 0\n\n- C: channel - offset by 1; 0000 means channel 1\n               The device can be configured to use channels 1-8\n- R: random power on id\n- 1: constant 1\n- 0: constant 0\n- B: low battery indicator\n- T: temperature - offset by 1024 and scaled by 10\n- X: CRC\n*/\n\n#include \"decoder.h\"\n\nstatic int rubicson_pool_48942_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 41);\n    if (row < 0 || bitbuffer->bits_per_row[row] != 41)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = bitbuffer->bb[row];\n    bitbuffer_invert(bitbuffer);\n\n    // validate some static bits\n    if (b[3] & 0xF || b[5])\n        return DECODE_ABORT_EARLY;\n\n    // reduce false positives\n    if (b[0] == 0 && b[2] == 0 && b[4] == 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (crc8(b, 4, 0x31, 0x00) != b[4])\n        return DECODE_FAIL_MIC;\n\n    int channel = (b[0] >> 4) + 1;\n    int random_id = (b[0] & 0x0F) << 6 | (b[1] & 0xFC) >> 2;\n    int battery_low = b[2] >> 7;\n    float temp_c = ((((b[2] & 0x7F) << 4) | (b[3] >> 4)) - 1024) * 0.1f;\n\n    decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"\");\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",          \"\",             DATA_STRING,  \"Rubicson-48942\",\n            \"channel\",        \"Channel\",      DATA_INT,     channel,\n            \"id\",             \"Random ID\",    DATA_INT,     random_id,\n            \"battery_ok\",     \"Battery\",      DATA_INT,     !battery_low,\n            \"temperature_C\",  \"Temperature\",  DATA_FORMAT,  \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",            \"Integrity\",    DATA_STRING,  \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"channel\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const rubicson_pool_48942 = {\n        .name        = \"Rubicson Pool Thermometer 48942\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 280,\n        .long_width  = 480,\n        .reset_limit = 6000,\n        .gap_limit   = 5000,\n        .sync_width  = 730,\n        .decode_fn   = &rubicson_pool_48942_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/s3318p.c",
    "content": "/** @file\n    Conrad Electronics S3318P outdoor sensor.\n\n    Copyright (C) 2016 Martin Hauke\n    Enhanced (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nLargely the same as esperanza_ews, kedsum.\n@sa esperanza_ews.c kedsum.c\n\nAlso NC-5849-913 from Pearl (for FWS-310 station).\n\nAlso ST389 sensor for ORIA WA50 Wireless Digital Freezer Thermometer (no humidity)\n\nTransmit Interval: every ~50s.\nMessage Format: 40 bits (10 nibbles).\n\n    Byte:      0        1        2        3        4\n    Nibble:    1   2    3   4    5   6    7   8    9   10\n    Type:   00 IIIIIIII ??CCTTTT TTTTTTTT HHHHHHHH WB??XXXX\n\n- 0: Preamble\n- I: sensor ID (changes on battery change)\n- C: channel number\n- T: temperature\n- H: humidity\n- W: tx-button pressed\n- B: low battery\n- ?: unknown meaning\n- X: CRC-4 poly 0x3 init 0x0 xor last 4 bits\n\nExample data:\n\n    [01] {42} 04 15 66 e2 a1 00 : 00000100 00010101 01100110 11100010 10100001 00 ---> Temp/Hum/Ch:23.2/46/1\n\nTemperature:\n- Sensor sends data in °F, lowest supported value is -90°F\n- 12 bit unsigned and scaled by 10 (Nibbles: 6,5,4)\n- in this case `011001100101` =  1637/10 - 90 = 73.7 °F (23.17 °C)\n\nHumidity:\n- 8 bit unsigned (Nibbles 8,7)\n- in this case `00101110` = 46\n\nChannel number: (Bits 10,11) + 1\n- in this case `00` --> `00` +1 = Channel1\n\nBattery status: (Bit 33) (0 normal, 1 voltage is below ~2.7 V)\n- TX-Button: (Bit 32) (0 indicates regular transmission, 1 indicates requested by pushbutton)\n\nRandom Code / Device ID: (Nibble 1)\n- changes on every battery change\n*/\n\n#include \"decoder.h\"\n\nstatic int s3318p_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[5];\n    data_t *data;\n\n    // ignore if two leading sync pulses (Esperanza EWS)\n    if (bitbuffer->bits_per_row[0] == 0 && bitbuffer->bits_per_row[1] == 0)\n        return DECODE_ABORT_EARLY;\n\n    // the signal should have 6 repeats with a sync pulse between\n    // require at least 4 received repeats\n    int r = bitbuffer_find_repeated_row(bitbuffer, 4, 42);\n    if (r < 0 || bitbuffer->bits_per_row[r] != 42)\n        return DECODE_ABORT_LENGTH;\n\n    // remove the two leading 0-bits and align the data\n    bitbuffer_extract_bytes(bitbuffer, r, 2, b, 40);\n\n    // No need to decode/extract values for simple test\n    // check id channel temperature humidity value not zero\n    if (!b[0] && !b[1] && !b[2] && !b[3]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    // CRC-4 poly 0x3, init 0x0 over 32 bits then XOR the next 4 bits\n    int crc = crc4(b, 4, 0x3, 0x0) ^ (b[4] >> 4);\n    if (crc != (b[4] & 0xf))\n        return DECODE_FAIL_MIC;\n\n    int id          = b[0];\n    int channel     = ((b[1] & 0x30) >> 4) + 1;\n    int temp_raw    = ((b[2] & 0x0f) << 8) | (b[2] & 0xf0) | (b[1] & 0x0f);\n    float temp_f    = (temp_raw - 900) * 0.1f;\n    int humidity    = ((b[3] & 0x0f) << 4) | ((b[3] & 0xf0) >> 4);\n    int button      = b[4] >> 7;\n    int battery_low = (b[4] & 0x40) >> 6;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Conrad-S3318P\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",         \"Humidity\",     DATA_COND,   humidity != 0, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"button\",           \"Button\",       DATA_INT,    button,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_F\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const s3318p = {\n        .name        = \"Conrad S3318P, FreeTec NC-5849-913 temperature humidity sensor, ORIA WA50 ST389 temperature sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1900,\n        .long_width  = 3800,\n        .gap_limit   = 4400,\n        .reset_limit = 9400,\n        .decode_fn   = &s3318p_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/sainlogic_sa8.c",
    "content": "/** @file\n    Sainlogic SA8 Weather Station.\n\n    Copyright (C) 2026 Bruno OCTAU (\\@ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nSainlogic SA8 Weather Station.\n\nDesciption:\n- All in one Weather station, with indor display and outdor Weather sensors for Wind Speed/Gust/Direction, Temp/Humidity and Rain Gauge\n\nCompatible rebrand:\n- Gevanti SA8\n\nFCC ID:\n- 2BP5V-8SA8P\n\nBrand from FCC ID information:\n- Dong Guan Zhen Ke Technology Co., LTD - Original Equipment\n\nS.a. issue #3445 open by \\@lrbison\n\nRF Information:\n- 433.92 Mhz, OOK PCM signal, UART coded.\n- flex decoder:\n\n    rtl_433 -X 'n=SA8,m=OOK_PCM,s=200,l=200,r=2500,bits>=800,bits<=1100,preamble=fc95,decode_uart'\n\nData layout:\n\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40\n    Sample         46 54 24 cd ab 26 0c d0 bd c3 75 39 e3 39 e3 e8 44 f3 00 6f 00 3d 00 00 00 00 00 b4 00 7e 00 41 00 53 00 00 00 f1 10 17 1d\n                   SS SS SS[II II II II II II ?? ?? ?? ?? ?? ?? CC CC ?? ?? TT TT HH 00 00 00 00 00 GG GG WW WW DD DD RR RR ?? ?? BB BB]XX XX\n\n\n- SS: {24} Fixed value 0x465424, synchro word, not part of the CRC16.\n- II: {48} Fixed value, ID / MAC address of the Outdor Weather Station, to be confirmed\n- ??: {16} fixed value, 0xc375\n- ??: {16} fixed value, 0x39e3\n- ??: {16} fixed value, 0x39e3, repeated value above\n- CC: {16} little endian LSB/MSB, Counter, +1 each message transmit\n- ??: {16} fixed value 0xf300\n- TT: {16} little endian LSB/MSB, signed value, Temp °C, scale 10\n- HH:  {8} Humidity %\n- 00: {40} Fixed value to 0\n- GG: {16} little endian LSB/MSB, Wind Gust in m/s, scale 100\n- WW: {16} little endian LSB/MSB, Wind Average in m/s, scale 100\n- DD: {16} little endian LSB/MSB, Wind Direction in °, 0 = North, 180 = South\n- RR: {16} little endian LSB/MSB, Rain Gauge in mm scale 0.42893617f\n- ??: {16} little endian LSB/MSB, another unknown counter\n- BB: {16} little endian LSB/MSB, looks battery level in mV. From first byte, battery flags 0x10 = battery OK, 0x01 = battery KO or missing\n- XX: {16} little endian LSB/MSB, CRC 16 of [previous bytes except 3 first ones], poly 0x8005, init 0xffff, XOROUT 0x0000\n\n*/\n\nstatic int sainlogic_sa8_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n\n    uint8_t const preamble_pattern[] = {0xfc, 0x95};\n    uint8_t b[41];\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 2, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 16) + 16;\n\n    if (offset >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    int num_bits = bitbuffer->bits_per_row[0] - offset;\n    num_bits = MIN((size_t)num_bits, sizeof (b) * 10);\n    int len = extract_bytes_uart(bitbuffer->bb[0], offset, num_bits, b);\n\n    if (len < 41) {\n        decoder_log(decoder, 2, __func__, \"Message too short\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, b, sizeof (b) * 8, \"UART decoded MSG\");\n\n    // crc checksum here when guessed\n    uint16_t crc_calculated = crc16(&b[3], 36, 0x8005, 0xffff);\n    if ( crc_calculated != (b[40] << 8 | b[39])) {\n        decoder_log(decoder, 2, __func__, \"CRC error\");\n    }\n\n    // ID b[0] to b[14], 15 bytes\n    char ID[6 * 2 + 1];\n    snprintf(ID, sizeof(ID), \"%02x%02x%02x%02x%02x%02x\", b[4], b[3], b[6], b[5], b[8], b[7]);\n    uint16_t counter   = b[16] << 8 | b[15];\n    int16_t temp_raw   = b[20] << 8 | b[19];\n    int humidity       = b[21];\n    int gust_raw       = b[28] << 8 | b[27];\n    int wind_raw       = b[30] << 8 | b[29];\n    int dir_degree     = b[32] << 8 | b[31];\n    uint16_t rain_raw  = b[34] << 8 | b[33];\n    uint16_t unknown   = b[36] << 8 | b[35]; // may be rain/h counter\n    int battery_ok     = (b[38] & 0x10) >> 4;\n    uint16_t bat_mv    = b[38] << 8 | b[37]; // bat level not confirmed, kept as battery flags\n\n    float temp_c      = temp_raw * 0.1f;\n    float gust_km_h   = gust_raw * 0.036f; // orignal value is m/s but for Customary conversion, km/h is better\n    float wind_km_h   = wind_raw * 0.036f; // orignal value is m/s but for Customary conversion, km/h is better\n    float rain_mm     = rain_raw * 0.42893617f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",           \"\",               DATA_STRING, \"Sainlogic-SA8\",\n            \"id\",              \"\",               DATA_STRING, ID,\n            \"battery_ok\",      \"Battery_OK\",     DATA_INT,    battery_ok,\n            //\"battery_mV\",      \"Battery Voltage\",DATA_FORMAT, \"%u mV\",     DATA_INT,    bat_mv, // not confirmed\n            \"counter\",         \"Counter\",        DATA_INT,    counter,\n            \"temperature_C\",   \"Temperature\",    DATA_FORMAT, \"%.1f C\",    DATA_DOUBLE, temp_c,\n            \"humidity\",        \"Humidity\",       DATA_FORMAT, \"%u %%\",     DATA_INT,    humidity,\n            \"wind_avg_km_h\",   \"Wind avg speed\", DATA_FORMAT, \"%.1f km/h\", DATA_DOUBLE, wind_km_h,\n            \"wind_max_km_h\",   \"Wind max speed\", DATA_FORMAT, \"%.1f km/h\", DATA_DOUBLE, gust_km_h,\n            \"wind_dir_deg\",    \"Wind Direction\", DATA_INT,    dir_degree,\n            \"rain_mm\",         \"Total rainfall\", DATA_FORMAT, \"%.1f mm\",   DATA_DOUBLE, rain_mm,\n            \"unknown\",         \"Unknown\",        DATA_FORMAT, \"%04x\",      DATA_INT,    unknown,\n            \"flags\",           \"Flags\",          DATA_FORMAT, \"%04x\",      DATA_INT,    bat_mv,\n            \"mic\",             \"Integrity\",      DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_mV\",\n        \"counter\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"wind_dir_deg\",\n        \"rain_mm\",\n        \"unknown\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const sainlogic_sa8 = {\n        .name        = \"Sainlogic SA8, Gevanti SA8 Weather Station\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 200,\n        .long_width  = 200,\n        .reset_limit = 2500,\n        .decode_fn   = &sainlogic_sa8_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/schou_72543_rain.c",
    "content": "/** @file\n    Schou 72543 Day Rain Gauge.\n\n    contributed by Jesper M. Nielsen\n    discovered by Jesper M. Nielsen\n    based upon ambient_weather.c\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecode Schou 72543 Rain Gauge, DAY series.\n\nDevices supported:\n\n- Schou 72543 Rain Gauge, DAY Series.\n- Motonet MTX rain gauge (Product code: 86-01352) sold in Finland.\n- MarQuant Wireless Rain Gauge (Product code: 014369) sold by JULA AB, Sweden.\n\nThis decoder handles the 433mhz rain-thermometer.\n\nCodes example: {66}50fc467b7f9a832a8, {65}a1f88cf6ff3506550, {70}a1f88cf6ff3506557c\n\n    {66}: [ 0 ] [ 1010 0001 1111 1000 ] [ 1000 ] [ 1100 ] [ 1111 0110 ] [ 1111 1111 ] [ 0011 0101 ] [ 0000 0110 ] [ 0101 0101 ] [ 0       ]\n    {65}:       [ 1010 0001 1111 1000 ] [ 1000 ] [ 1100 ] [ 1111 0110 ] [ 1111 1111 ] [ 0011 0101 ] [ 0000 0110 ] [ 0101 0101 ] [ 0       ]\n    {70}:       [ 1010 0001 1111 1000 ] [ 1000 ] [ 1100 ] [ 1111 0110 ] [ 1111 1111 ] [ 0011 0101 ] [ 0000 0110 ] [ 0101 0101 ] [ 0111 11 ]\n    KEY:  [ 0 ] [ IIII IIII IIII IIII ] [ SSSS ] [ NNNN ] [ rrrr rrrr ] [ RRRR RRRR ] [ tttt tttt ] [ TTTT TTTT ] [ CCCC CCCC ] [ 0??? ?? ]\n\n- 0:  Always zero\n- ?:  Either 1 or 0\n- I:  16 bit random ID. Resets to new value after every battery change\n- S:  Status bits\n      [ X--- ]: Battery status:  0: OK,  1: Low battery\n      [ -X-- ]: Repeated signal: 0: New, 1: Repeat of last message (4 repeats will happen after battery replacement)\n      [ --XX ]: Assumed always to be 0\n- N:  4 bit running count. Increased by 2 every value incremented by 2 every message, i.e. 0, 2, 4, 6, 8, a, c, e, 0, 2...\n- Rr: 16 bit Rainfall in 1/10 millimeters per count. Initial value fff6 = 6552.6 mm rain\n      r: lower 8 bit, initializes to f6\n      R: Upper 8 bit, initializes to ff\n- Tt: 16 bit temperature.\n      t: lower 8 bit\n      T: Upper 8 bit\n- C:  Checksum. Running 8 bit sum of the data left of the checksum.\n      E.g. {65}a1f88cf6ff3506'55'0 Checksum is 55 obtained as ( a1 + f8 + 8c + f6 + ff + 35 + 06 ) = 455 i.e. 55\n\n*/\n\nstatic int schou_72543_rain_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Full data is 3 rows, two are required for data validation\n    if (bitbuffer->num_rows < 2) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Check if the first 64 bits of at least two rows are alike\n    int row = bitbuffer_find_repeated_prefix(bitbuffer, 2, 64);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Load bitbuffer data and validate checksum\n    uint8_t *b = bitbuffer->bb[row];\n    int chk    = b[7];            // Checksum as read\n    int sum    = add_bytes(b, 7); // Checksum as calculated\n\n    // reduce false positives\n    if (sum == 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (chk != (sum & 0xff)) {\n        decoder_logf_bitrow(decoder, 1, __func__, b, 65, \"Checksum error, expected: %02x calculated: %02x\", chk, sum);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Decode message\n    int device_id       = (b[0] << 8) | b[1];                  // Assuming little endian, but it not important as the value is random\n    int battery_low     = (b[2] & 0x80) > 0;                   // if one, battery is low\n    int message_repeat  = (b[2] & 0x40) > 0;                   // if one, message is a repeat (startup after batteries are replaced)\n    int message_counter = (b[2] & 0x0e) >> 1;                  // 3 bit counter (rather than 4 bit incrementing by 2 each time\n    float rain_mm       = ((b[4] << 8) | b[3]) * 0.1f;         //   0.0 to  6553.5  mm\n    float temperature_F = (((b[6] << 8) | b[5]) - 900) * 0.1f; // -40.0 to +158     degF\n\n    /* clang-format off */\n    data_t   *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Schou-72543\",\n            \"id\",               \"ID\",           DATA_INT,    device_id,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.1f F\",  DATA_DOUBLE, temperature_F,\n            \"rain_mm\",          \"Rain\",         DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n            \"battery_ok\",       \"Battery_ok\",   DATA_INT,    !battery_low,\n            \"msg_counter\",      \"Counter\",      DATA_INT,    message_counter,\n            \"msg_repeat\",       \"Msg_repeat\",   DATA_INT,    message_repeat,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_F\",\n        \"rain_mm\",\n        \"battery_ok\",\n        \"msg_counter\",\n        \"msg_repeat\",\n        \"mic\",\n        NULL,\n};\n\nr_device const schou_72543_rain = {\n        .name        = \"Schou 72543 Day Rain Gauge, Motonet MTX Rain, MarQuant Rain Gauge, TFA Dostmann 30.3252.01/47.3006.01 Rain Gauge and Thermometer, ADE WS1907\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 972,\n        .long_width  = 2680,\n        .sync_width  = 7328,\n        .reset_limit = 2712,\n        .decode_fn   = &schou_72543_rain_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/schraeder.c",
    "content": "/** @file\n    Schrader TPMS protocol.\n\n    Copyright (C) 2016 Benjamin Larsson\n    and 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nSchrader TPMS decoder.\n\nFCC-Id: MRXGG4\n\nPacket payload: 1 sync nibble and 8 bytes data, 17 nibbles:\n\n    0 12 34 56 78 9A BC DE F0\n    7 f6 70 3a 38 b2 00 49 49\n    S PF FI II II II PP TT CC\n\n- S: sync\n- P: preamble (0xf)\n- F: flags\n- I: id (28 bit)\n- P: pressure from 0 bar to 6.375 bar, resolution of 25 mbar/hectopascal per bit\n- T: temperature from -50 C to 205 C (1 bit = 1 temperature count 1 C)\n- C: CRC8 from nibble 1 to E\n*/\n\n#include \"decoder.h\"\n\nstatic int schraeder_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[8];\n    int serial_id;\n    int flags;\n    int pressure;    // mbar/hectopascal\n    int temperature; // deg C\n\n    // Reject wrong amount of bits\n    if (bitbuffer->bits_per_row[0] != 68)\n        return DECODE_ABORT_LENGTH;\n\n    // Shift the buffer 4 bits to remove the sync bits\n    bitbuffer_extract_bytes(bitbuffer, 0, 4, b, 64);\n\n    // Calculate the crc\n    if (b[7] != crc8(b, 7, 0x07, 0xf0)) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // Get data\n    serial_id   = (b[1] & 0x0F) << 24 | b[2] << 16 | b[3] << 8 | b[4];\n    flags       = (b[0] & 0x0F) << 4 | b[1] >> 4;\n    pressure    = b[5] * 25;\n    temperature = b[6] - 50;\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%07X\", serial_id);\n    char flags_str[3];\n    snprintf(flags_str, sizeof(flags_str), \"%02x\", flags);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Schrader\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"flags\",            \"\",             DATA_STRING, flags_str,\n            \"id\",               \"ID\",           DATA_STRING, id_str,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, pressure * 0.1f,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temperature,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nTPMS Model: Schrader Electronics EG53MA4.\nContributed by: Leonardo Hamada (hkazu).\n\nAlso Schrader Opel OEM No. 13348393 TPMS Sensor (might be found in Saab, Opel, Vauxhall, Chevrolet).\nGM (Chevrolet) OEM No. 13540600 for 2006-2025 GM.\n\nProbable packet payload:\n\n    SSSSSSSSSS ???????? IIIIII TT PP CC\n\n- S: sync\n- ?: might contain the preamble, status and battery flags\n- I: id (24 bits), could extend into flag bits (?)\n- P: pressure, 25 mbar per bit\n- T: temperature, degrees Fahrenheit\n- C: checksum, sum of byte data modulo 256\n*/\nstatic int schrader_EG53MA4_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t b[10];\n    int serial_id;\n    char id_str[9];\n    unsigned flags;\n    char flags_str[9];\n    int pressure;    // mbar\n    int temperature; // degree Fahrenheit\n    int checksum;\n\n    // Check for incorrect number of bits received\n    if (bitbuffer->bits_per_row[0] != 120)\n        return DECODE_ABORT_LENGTH;\n\n    // Discard the first 40 bits\n    bitbuffer_extract_bytes(bitbuffer, 0, 40, b, 80);\n\n    // No need to decode/extract values for simple test\n    // check serial flags pressure temperature value not zero\n    if (!b[1] && !b[2] && !b[4] && !b[5] && !b[7] && !b[8]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Calculate the checksum\n    checksum = add_bytes(b, 9) & 0xff;\n    if (checksum != b[9]) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // Get data\n    serial_id   = (b[4] << 16) | (b[5] << 8) | b[6];\n    flags       = ((unsigned)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];\n    pressure    = b[7] * 25;\n    temperature = b[8];\n    snprintf(id_str, sizeof(id_str), \"%06X\", serial_id);\n    snprintf(flags_str, sizeof(flags_str), \"%08x\", flags);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Schrader-EG53MA4\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"flags\",            \"\",             DATA_STRING, flags_str,\n            \"id\",               \"ID\",           DATA_STRING, id_str,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, pressure * 0.1f,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, (double)temperature,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nSMD3MA4 Schrader TPMS used in Subaru.\nContributed by: RonNiles.\n\nAlso Schrader 3039 TPMS for Infiniti, Nissan, Renault.\nContributed by: MotorvateDIY.\n\nRefer to https://github.com/JoeSc/Subaru-TPMS-Spoofing\n\nSCHRADER 3039 TPMS for Infiniti Nissan Renault (407001AY0A) (40700JY00B ?)\n- https://catalogue.schradertpms.com/de-DE/ProductDetails/3039.html\n- https://catalogue.schradertpms.com/en-GB/ProductDetails/3039.html\n- Art.-Nr. 3039\n- OE Art.-Nr: 407001AY0A\n- EAN-Code: 5054208000275\n- INFINITI, NISSAN, RENAULT (407001AY0A)\n\nUsed with:\n- Nissan 370Z Z34 until 06/2014\n- Infiniti FX until 12/2013\n- Infiniti EX P53B (from 2007-10 until 2016-03)\n- Infiniti FX (LCV) P53C (from 2008-03 until 2014-08)\n- Infiniti FX P53C (from 2008-03 until 2014-08)\n- Infiniti G L53A (from 2006-08 until 2013-03)\n- Renault Koleos H45 (from 2008-02 until 2013-12)\n\nData layout:\n\n    ^^^^_^_^_^_^_^_^_^_^_^_^_^_^_^_^^^^_FFFFFFIIIIIIIIIIIII\n    IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIPPPPPPPPPPPPPPPPCCCC\n\n- PREAMBLE: 36-bits 0xF5555555E\n- F: FLAGS, 3 Manchester encoded bits\n- I: ID, 24 Manchester encoded bits\n- P: PRESSURE, 8 Manchester encoded bits (PSI * 5)\n- C: CHECK, 2 Manchester encoded bits some kind of Parity\n\nNOTE: there is NO temperature data transmitted\nTODO: the checksum is unknown\n\nWe use OOK_PULSE_PCM to get the bitstream above.\nThen we use bitbuffer_manchester_decode() which will alert us to any\nbit sequence which is not a valid Manchester transition. This enables a sanity\ncheck on the Manchester pulses which is important for detecting possible\ncorruption since there is no CRC.\n\nThe Manchester bits are encoded as 01 => 0 and 10 => 1, which is\nthe reverse of bitbuffer_manchester_decode(), so we invert the result.\n\nExample payloads:\n\n    {37}0000000030 {37}1000000020 {37}0800000028 {37}0400000020 {37}0200000028\n    {37}0100000020 {37}0080000028 {37}0040000020 {37}0020000028 {37}0010000020\n    {37}0008000028 {37}0004000020 {37}0002000028 {37}1400000030 {37}0a00000020\n    {37}698e08eb48 {37}698e08ec68 {37}698e08ee60 {37}698e08edf0 {37}098e08edb8\n    {37}098e08eca8 {37}098e08eb88 {37}098e08eb78 {37}098e08eb40 {37}098e08eb28\n    {37}098e08eae0 {37}098e08eac8 {37}098e08eab0 {37}098e08ea98 {37}098e08ea68\n    {37}098e08e8d0 {37}098e08e8b8 {37}098e08e880 {37}098e08e660 {37}098e08e3f8\n    {37}698e08e2a0 {37}698e08e1e8 {37}098e08e028 {37}099b56e028 {37}099798e038\n\n*/\n#define NUM_BITS_PREAMBLE (36)\n#define NUM_BITS_FLAGS (3)\n#define NUM_BITS_ID (24)\n#define NUM_BITS_PRESSURE (10)\n#define NUM_BITS_DATA (NUM_BITS_FLAGS + NUM_BITS_ID + NUM_BITS_PRESSURE)\n#define NUM_BITS_TOTAL (NUM_BITS_PREAMBLE + 2 * NUM_BITS_DATA)\n\nstatic int schrader_SMD3MA4_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Reject wrong length, with margin of error for extra bits at the end\n    if (bitbuffer->bits_per_row[0] < NUM_BITS_TOTAL\n            || bitbuffer->bits_per_row[0] >= NUM_BITS_TOTAL + 8) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Check preamble\n    uint8_t *b = bitbuffer->bb[0];\n    if (b[0] != 0xf5 || b[1] != 0x55 || b[2] != 0x55 || b[3] != 0x55\n            || (b[4] >> 4) != 0xe) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Check and decode the Manchester bits\n    bitbuffer_t decoded = {0};\n    int ret = bitbuffer_manchester_decode(bitbuffer, 0, NUM_BITS_PREAMBLE,\n            &decoded, NUM_BITS_DATA);\n    if (ret != NUM_BITS_TOTAL) {\n        decoder_log(decoder, 2, __func__, \"invalid Manchester data\");\n        return DECODE_FAIL_MIC;\n    }\n    bitbuffer_invert(&decoded);\n    b = decoded.bb[0];\n\n    // Compute parity\n    int parity = xor_bytes(b, 4) ^ (b[4] & 0xe0);\n    parity     = (parity >> 4) ^ (parity & 0x0f);\n    parity     = (parity >> 2) ^ (parity & 0x03);\n\n    // Get the decoded data fields\n    // FFFSSSSS SSSSSSSS SSSSSSSS SSSPPPPP PPPCCxxx\n    int flags     = b[0] >> 5;\n    int serial_id = ((b[0] & 0x1f) << 19) | (b[1] << 11) | (b[2] << 3) | (b[3] >> 5);\n    int pressure  = ((b[3] & 0x1f) <<  3) | (b[4] >> 5);\n    int check     = ((b[4] & 0x18) >> 3);\n\n    decoder_logf_bitbuffer(decoder, 3, __func__, &decoded, \"Parity: %d%d Check: %d%d\", parity >> 1, parity & 1, check >> 1, check & 1);\n\n    // reject all-zero data\n    if (!flags && !serial_id && !pressure) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%06X\", serial_id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Schrader-SMD3MA4\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"flags\",            \"\",             DATA_INT,    flags,\n            \"id\",               \"ID\",           DATA_STRING, id_str,\n            \"pressure_PSI\",     \"Pressure\",     DATA_FORMAT, \"%.1f PSI\", DATA_DOUBLE, pressure * 0.2f,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const output_fields_EG53MA4[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_F\",\n        \"mic\",\n        NULL,\n};\n\nstatic char const *const output_fields_SMD3MA4[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_PSI\",\n        NULL,\n};\n\nr_device const schraeder = {\n        .name        = \"Schrader TPMS\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 120,\n        .long_width  = 0,\n        .reset_limit = 480,\n        .decode_fn   = &schraeder_decode,\n        .fields      = output_fields,\n};\n\nr_device const schrader_EG53MA4 = {\n        .name        = \"Schrader TPMS EG53MA4, Saab, Opel, Vauxhall, Chevrolet\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 123,\n        .long_width  = 0,\n        .reset_limit = 300,\n        .decode_fn   = &schrader_EG53MA4_decode,\n        .fields      = output_fields_EG53MA4,\n};\n\nr_device const schrader_SMD3MA4 = {\n        .name        = \"Schrader TPMS SMD3MA4 (Subaru) 3039 (Infiniti, Nissan, Renault)\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 120,\n        .long_width  = 120,\n        .reset_limit = 480,\n        .decode_fn   = &schrader_SMD3MA4_decode,\n        .fields      = output_fields_SMD3MA4,\n};\n"
  },
  {
    "path": "src/devices/scmplus.c",
    "content": "/** @file\n    ERT SCM+ sensors.\n\n    Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nERT SCM+ sensors.\n\n- Freq 912600155\n\nRandom information:\n\nhttps://github.com/bemasher/rtlamr/wiki/Protocol\nhttp://www.gridinsight.com/community/documentation/itron-ert-technology/\n\nUnits: \"Some meter types transmit consumption in 1 kWh units, while others use more granular 10 Wh units\"\n\n*/\n\nstatic int scmplus_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t b[16];\n    data_t *data;\n    unsigned sync_index;\n    const uint8_t scmplus_frame_sync[] = {0x16, 0xA3, 0x1E};\n\n    if (bitbuffer->bits_per_row[0] < 128) {\n        return (DECODE_ABORT_LENGTH);\n    }\n\n    sync_index = bitbuffer_search(bitbuffer, 0, 0, scmplus_frame_sync, 24);\n\n    if (sync_index >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((bitbuffer->bits_per_row[0] - sync_index) < 128) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    decoder_logf(decoder, 1, __func__, \"row len=%hu sync_index=%u\", bitbuffer->bits_per_row[0], sync_index);\n\n    // bitbuffer_debug(bitbuffer);\n    bitbuffer_extract_bytes(bitbuffer, 0, sync_index, b, 16 * 8);\n\n    // uint32_t t_16; // temp vars\n    // uint32_t t_32;\n\n    uint16_t crc, pkt_checksum;\n    // memcpy(&t_16, &b[14], 2);\n    // pkt_checksum = ntohs(t_16);\n    pkt_checksum = (b[14] << 8 | b[15]);\n\n    crc = crc16(&b[2], 12, 0x1021, 0x0971);\n    // decoder_logf(decoder, 0, __func__, \"CRC = %d %04X == %d %04X\", pkt_checksum,pkt_checksum,  crc, crc);\n    if (crc != pkt_checksum) {\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, b, 16 * 8, \"aligned\");\n\n    // uint8_t protocol_id;\n    uint32_t endpoint_id;\n    // uint8_t endpoint_type;\n    uint32_t consumption_data;\n    uint16_t physical_tamper;\n\n    char crc_str[8];\n    char protocol_id_str[5];\n    char endpoint_type_str[5];\n    char physical_tamper_str[8];\n\n    // protocol_id = b[2];\n    snprintf(protocol_id_str, sizeof(protocol_id_str), \"0x%02X\", b[2]); // protocol_id);  // b[2]\n\n    // endpoint_type = b[3];\n    snprintf(endpoint_type_str, sizeof(endpoint_type_str), \"0x%02X\", b[3]); // endpoint_type);  // b[3]\n\n    // memcpy(&t_32, &b[4], 4);\n    // endpoint_id = ntohl(t_32);\n    endpoint_id = ((uint32_t)b[4] << 24) | (b[5] << 16) | (b[6] << 8) | (b[7]);\n\n    // memcpy(&t_32, &b[8], 4);\n    // consumption_data = ntohl(t_32);\n    consumption_data = ((uint32_t)b[8] << 24) | (b[9] << 16) | (b[10] << 8) | (b[11]);\n\n    // memcpy(&t_16, &b[12], 2);\n    // physical_tamper = ntohs(t_16);\n    // physical_tamper = ((t_16 & 0xFF00) >> 8 | (t_16 & 0x00FF) << 8);\n    physical_tamper = (b[12] << 8 | b[13]);\n    snprintf(physical_tamper_str, sizeof(physical_tamper_str), \"0x%04X\", physical_tamper);\n\n    snprintf(crc_str, sizeof(crc_str), \"0x%04X\", crc);\n\n    // Least significant nibble of endpoint_type is  equivalent to SCM's endpoint type field\n    // id info from https://github.com/bemasher/rtlamr/wiki/Compatible-Meters\n    char const *meter_type;\n\n    switch (b[3] & 0x0f) {\n    case 4:\n    case 5:\n    case 7:\n    case 8:\n        meter_type = \"Electric\";\n        break;\n    case 0:\n    case 1:\n    case 2:\n    case 9:\n    case 12:\n        meter_type = \"Gas\";\n        break;\n    case 3:\n    case 11:\n    case 13:\n        meter_type = \"Water\";\n        break;\n    default:\n        meter_type = \"unknown\";\n        break;\n    }\n\n    // decoder_logf(decoder, 0, __func__, \"meter_type = %s\", meter_type);\n\n    /*\n        Field key names and format set to  match rtlamr field names\n\n        {Time:2020-06-20T09:58:19.074 Offset:49152 Length:49152\n        SCM+:{ProtocolID:0x1E EndpointType:0xAB EndpointID:  68211547 Consumption:  6883 Tamper:0x4900 PacketCRC:0x39BE}}\n    */\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"SCMplus\",\n            \"id\",               \"\",                 DATA_INT,    endpoint_id,\n            \"ProtocolID\",       \"Protocol_ID\",      DATA_STRING, protocol_id_str, // TODO: this should be int\n            \"EndpointType\",     \"Endpoint_Type\",    DATA_STRING, endpoint_type_str, // TODO: this should be int\n            \"EndpointID\",       \"Endpoint_ID\",      DATA_INT,    endpoint_id, // TODO: remove this (see \"id\")\n            \"Consumption\",      \"\",                 DATA_INT,    consumption_data,\n            \"Tamper\",           \"\",                 DATA_STRING, physical_tamper_str, // TODO: should be int\n            \"PacketCRC\",        \"crc\",              DATA_STRING, crc_str, // TODO: remove this\n            \"MeterType\",        \"Meter_Type\",       DATA_STRING, meter_type,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"ProtocolID\",\n        \"EndpointType\",\n        \"EndpointID\",\n        \"Consumption\",\n        \"Tamper\",\n        \"PacketCRC\",\n        \"MeterType\",\n        \"mic\",\n        NULL,\n};\n\n//      Freq 912600155\n//     -X n=L58,m=OOK_MC_ZEROBIT,s=30,l=30,g=20000,r=20000,match={24}0x16a31e,preamble={1}0x00\n\nr_device const scmplus = {\n        .name        = \"Standard Consumption Message Plus (SCMplus)\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 30,\n        .long_width  = 0, // not used\n        .gap_limit   = 0,\n        .reset_limit = 64,\n        .decode_fn   = &scmplus_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/secplus_v1.c",
    "content": "/** @file\n    Security+ 1.0 rolling code\n\n    Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>\n    Based on code by Clayton Smith https://github.com/argilo/secplus\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/** @fn int secplus_v1_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nSecurity+ 1.0 rolling code\n\n@warning This decoder is not stateless.\n@warning This decoder is dependent on elapsed time.\n\nFreq 310, 315 and 390 MHz.\n\nSecurity+ 1.0  is described in [US patent application US6980655B2](https://patents.google.com/patent/US6980655B2/)\n\n*/\n\n#include \"decoder.h\"\n#include \"compat_time.h\"\n\n/**\nData comes in two bursts/packets, each bursts/packet is then separately passed to secplus_v1_decode_v1_half.\n\nDecodes transmitted binary into trinary data\n\nBinary Bits are read from bits and stored as an array of uint8_t in result[]\n\nThe trinary value of the first nibble is also returned\n\nThe trinary conversion is accomplished done by counting the number of '1' in a group\n\nBinary | Trinary\n--- | ---\n`0 0 0 0` | invalid\n`0 0 0 1` | 0\n`0 0 1 1` | 1\n`0 1 1 1` | 2\n`1 1 1 1` | invalid\n\n000100110111011100110001 -> 0001 0011 0111 0111 0011 0001 -> 1 11 111 111 11 1 -> [0, 1,2, 2, 1, 0]\n\nThe patterns `1 1 1 1` or `0 0 0 0` should never happen\n\nnote: due to implementation this needs 44 bytes output in worst case of invalid data.\n*/\n\nstatic int secplus_v1_decode_v1_half(r_device *decoder, uint8_t *bits, uint8_t *result)\n{\n    uint8_t *r;\n    int x = 0;\n\n    r = result;\n\n    for (int i = 0; i < 11; i++) {\n        // fprintf(stderr, \"\\nbin X = {%ld} %s\\n\", strlen(binstr), binstr);\n        for (int j = 0; j < 8; j++) {\n            int k = (bits[i] << j) & 0x80;\n            // fprintf(stderr, \"k == %d\\n\", k);\n            if (k) {\n                x++;\n            }\n            else {\n                if (x == 0) {\n                    continue;\n                }\n                else if (x == 1) {\n                    *r++ = 0;\n                    // fprintf(stderr, \"\\nbin 0 = {%ld} %s\\n\", strlen(binstr), binstr);\n                }\n                else if (x == 2) {\n                    *r++ = 1;\n                    // fprintf(stderr, \"\\nbin 1 = {%ld} %s\\n\", strlen(binstr), binstr);\n                }\n                else if (x == 3) {\n                    *r++ = 2;\n                    // fprintf(stderr, \"\\nbin 2 = {%ld} %s\\n\", strlen(binstr), binstr);\n                }\n                else { // x > 3\n                    decoder_logf(decoder, 1, __func__, \"Error x == %d\", x);\n                    return -1; // DECODE_FAIL_SANITY\n                }\n                x = 0;\n            }\n        }\n    }\n\n    return (int)result[0];\n}\n\nstatic const uint8_t preamble_1[1] = {0x02};\nstatic const uint8_t preamble_2[1] = {0x07};\n\n/**\nFind index of next bursts/packets in bitbuffer.\n\nThe transmissions do not have a magic number or preamble.\n\nThey all start with a '0' or a '2' represented at 0001. and 0111.\nsince all nibbles start with 0 we can look for bytes\n000 + 0001 + 0 and 000 + 0111 + 0 for the start of a transmission\n(or just the 0001 and 0111 at the start of a bitbuffer)\n*/\n\nstatic int find_next(bitbuffer_t *bitbuffer, int cur_index)\n{\n\n    // int search_index;\n    int search_index_1;\n    int search_index_2;\n\n    if (cur_index == 0 && ((bitbuffer->bb[0][0] & 0xf0) == 0x10 || (bitbuffer->bb[0][0] & 0xf0) == 0x70))\n        return 0;\n\n    if (cur_index == 0 && ((bitbuffer->bb[0][0] & 0xE0) == 0xe0 || (bitbuffer->bb[0][0] & 0xc0) == 0x80))\n        return 0;\n\n    search_index_1 = bitbuffer_search(bitbuffer, 0, cur_index, preamble_1, 8);\n    search_index_1 += 3;\n\n    search_index_2 = bitbuffer_search(bitbuffer, 0, cur_index, preamble_2, 8);\n    search_index_2 += 3;\n\n    // return first match in buffer\n    return (search_index_1 < search_index_2 ? search_index_1 : search_index_2);\n}\n\n// max age for cache in us\n#define CACHE_MAX_AGE 800000\n\nstatic uint8_t cached_result[24] = {0};\nstatic struct timeval cached_tv  = {0};\n\nstatic int secplus_v1_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t result_1[24] = {0};\n    uint8_t result_2[24] = {0};\n    int status           = 0;\n    int search_index;\n\n    // the max of 130 is just a guess\n    if (bitbuffer->bits_per_row[0] < 84 || bitbuffer->bits_per_row[0] > 130) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    decoder_logf(decoder, 1, __func__, \"num rows = %u len %u\", bitbuffer->num_rows, bitbuffer->bits_per_row[0]);\n\n    search_index = 0;\n    while (search_index < bitbuffer->bits_per_row[0] && status == 0) {\n        int dr            = 0;\n        uint8_t buffy[44] = {0}; // actually we expect 22 bytes on valid decode\n        uint8_t buffi[11] = {0};\n\n        search_index = find_next(bitbuffer, search_index);\n\n        decoder_logf(decoder, 2, __func__, \"find_next return : bits_per_row - search_index = %d\", bitbuffer->bits_per_row[0] - search_index);\n\n        // nothing found\n        if (search_index == -1 || (search_index + 84) > bitbuffer->bits_per_row[0]) {\n            break;\n        }\n\n        bitbuffer_extract_bytes(bitbuffer, 0, search_index, buffi, 84);\n\n        dr = secplus_v1_decode_v1_half(decoder, buffi, buffy);\n\n        if (dr < 0 || dr == 1) {\n            // decoder_log(decoder, 0, __func__, \"decode error\");\n            search_index += 4;\n            continue;\n        }\n        else if (dr == 0) {\n            // decoder_log(decoder, 0, __func__, \"decode result_1\");\n            memcpy(result_1, buffy, 22);\n            status ^= 0x001;\n            search_index += 88;\n        }\n        else if (dr == 2) {\n            // decoder_log(decoder, 0, __func__, \"decode result_2\");\n            memcpy(result_2, buffy, 22);\n            status ^= 0x002;\n            search_index += 88;\n        }\n\n        // this should not happen\n        if (status == 3)\n            break;\n\n    } // while\n\n    decoder_logf(decoder, 2, __func__, \"exited  loop status = %02X\", status);\n\n    // if we have both parts, move on and report data\n    // if have only one part cache it for later.\n\n    // if we have no parts, quit\n    if (status == 0) {\n        return -1; // found nothing\n    }\n\n    // is there data in cache?\n    if (cached_tv.tv_sec) {\n        struct timeval cur_tv;\n        struct timeval res_tv;\n        gettimeofday(&cur_tv, NULL);\n        timeval_subtract(&res_tv, &cur_tv, &cached_tv);\n\n        decoder_logf(decoder, 2, __func__, \"res %12ld %8ld\", (long)res_tv.tv_sec, (long)res_tv.tv_usec);\n\n        // is the data not expired\n        if (res_tv.tv_sec == 0 && res_tv.tv_usec < CACHE_MAX_AGE) {\n\n            // if we have part 2 AND part 1 cached\n            if (status == 2 && cached_result[0] == 0) {\n                memcpy(result_1, cached_result, 21);\n                status = 3;\n                decoder_log(decoder, 1, __func__, \"Load cache  part 1\");\n            }\n            // if we have part 1 AND part 2 cached\n            else if (status == 1 && cached_result[0] == 2) {\n                memcpy(result_2, cached_result, 21);\n                status = 3;\n                decoder_log(decoder, 1, __func__, \"Load cache  part 2\");\n            }\n        }\n\n        // clear cache because it is expired or used\n        memset(cached_result, 0, sizeof(cached_result));\n        timerclear(&cached_tv);\n\n    } // if cache contains data\n\n    if (status == 1) {\n        gettimeofday(&cached_tv, NULL);\n        memcpy(cached_result, result_1, 21);\n        decoder_log(decoder, 1, __func__, \"caching part 1\");\n        return -2; // found only 1st part\n    }\n    else if (status == 2) {\n        gettimeofday(&cached_tv, NULL);\n        memcpy(cached_result, result_2, 21);\n        decoder_log(decoder, 1, __func__, \"caching part 2\");\n        return -2; // found only 2nd part\n    }\n    else if (status == 3) {\n        // decoder_log(decoder, 0, __func__, \"got both\");\n    }\n    else {\n        return -1; // should never get here\n    }\n\n    // if we are here we have received both packets, stored in result_1 & result_2\n    // we now generate values for rolling_temp & fixed\n    // using the trinary data stored in result_1 & result_2\n\n    uint32_t rolling;          // max 2**32\n    uint32_t rolling_temp = 0; // max 2**32\n    uint32_t fixed        = 0; // max 3^20 (~32 bits)\n\n    uint8_t *res;\n    res = result_1;\n    res++;\n\n    uint32_t acc = 0;\n    for (int i = 0; i < 20; i += 2) {\n        uint8_t digit = 0;\n\n        digit        = res[i];\n        rolling_temp = (rolling_temp * 3) + digit;\n        acc += digit;\n\n        digit = (60 + res[i + 1] - acc) % 3;\n        fixed = (fixed * 3) + digit;\n        acc += digit;\n    }\n\n    res = result_2;\n    res++;\n\n    acc = 0;\n    for (int i = 0; i < 20; i += 2) {\n        uint8_t digit = 0;\n\n        digit        = res[i];\n        rolling_temp = (rolling_temp * 3) + digit;\n        acc += digit;\n\n        digit = (60 + res[i + 1] - acc) % 3;\n        fixed = (fixed * 3) + digit;\n        acc += digit;\n    }\n\n    rolling = reverse32(rolling_temp);\n\n    /*\n        we now have values for rolling & fixed\n        next we extract status info stored in the value for 'fixed'\n    */\n    int switch_id = fixed % 3;\n    int id;\n    int id0        = (fixed / 3) % 3;\n    int id1        = (int)(fixed / 9) % 3;\n    int pad_id     = 0;\n    int pin        = 0;\n    char pin_s[24] = {0};\n\n    int remote_id = 0;\n    char const *button  = \"\";\n\n    if (id1 == 0) {\n        //  pad_id = (fixed // 3**3) % (3**7)     27  3^72187\n        pad_id = (fixed / 27) % 2187;\n        id     = pad_id;\n        // pin = (fixed // 3**10) % (3**9)  3^10= 59049 3^9=19683\n        pin = (fixed / 59049) % 19683;\n\n        if (0 <= pin && pin <= 9999) {\n            snprintf(pin_s, sizeof(pin_s), \"%04d\", pin);\n        }\n        else if (10000 <= pin && pin <= 11029) {\n            strcat(pin_s, \"enter\"); // NOLINT\n        }\n\n        int pin_suffix = 0;\n        // pin_suffix = (fixed // 3**19) % 3   3^19=1162261467\n        pin_suffix = (fixed / 1162261467) % 3;\n\n        if (pin_suffix == 1)\n            strcat(pin_s, \"#\"); // NOLINT\n        else if (pin_suffix == 2)\n            strcat(pin_s, \"*\"); // NOLINT\n\n        // decoder_logf(decoder, 1, __func__, \"pad_id=%d pin=%d pin_s=%s\", pad_id, pin, pin_s);\n    }\n    else {\n        remote_id = (int)fixed / 27;\n        id        = remote_id;\n        if (switch_id == 1)\n            button = \"left\";\n        else if (switch_id == 0)\n            button = \"middle\";\n        else if (switch_id == 2)\n            button = \"right\";\n\n        // decoder_logf(decoder, 1, __func__, \"remote_id=%d button=%s\", remote_id, button);\n    }\n\n    // preformat unsigned int\n    char rolling_str[16];\n    snprintf(rolling_str, sizeof(rolling_str), \"%u\", rolling);\n\n    // preformat unsigned int\n    char fixed_str[16]; // should be 10 chars max\n    snprintf(fixed_str, sizeof(fixed_str), \"%u\", fixed);\n\n    // decoder_logf(decoder, 0, __func__,  \"# Security+:  rolling=2320615320  fixed=1846948897  (id1=2 id0=0 switch=1 remote_id=68405514 button=left)\");\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"Secplus-v1\",\n            \"id\",           \"\",             DATA_INT,    id,\n            \"id0\",          \"ID_0\",         DATA_INT,    id0,\n            \"id1\",          \"ID_1\",         DATA_INT,    id1,\n            \"switch_id\",    \"Switch-ID\",    DATA_INT,    switch_id,\n            \"pad_id\",       \"Pad-ID\",       DATA_COND,   pad_id,    DATA_INT,    pad_id,\n            \"pin\",          \"Pin\",          DATA_COND,   pin,       DATA_STRING, pin_s,\n            \"remote_id\",    \"Remote-ID\",    DATA_COND,   remote_id, DATA_INT,    remote_id,\n            \"button_id\",    \"Button-ID\",    DATA_COND,   remote_id, DATA_STRING, button,\n            // \"fixed\",        \"Fixed_Code\",   DATA_INT,    fixed,\n            \"fixed\",        \"Fixed_Code\",   DATA_STRING, fixed_str,\n            // \"rolling\",      \"Rolling_Code\", DATA_INT,    rolling,\n            \"rolling\",      \"Rolling_Code\", DATA_STRING, rolling_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"id0\",\n        \"id1\",\n        \"switch_id\",\n        \"pad_id\",\n        \"pin\",\n        \"remote_id\",\n        \"button_id\",\n        \"fixed\",\n        \"rolling\",\n        NULL,\n};\n\n//      Freq 310.01M\n//   -X \"n=v1,m=OOK_PCM,s=500,l=500,t=40,r=10000,g=7400\"\n\nr_device const secplus_v1 = {\n        .name        = \"Security+ (Keyfob)\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 500,\n        .long_width  = 500,\n        .tolerance   = 20,\n        .gap_limit   = 15000,\n        .reset_limit = 80000,\n        .decode_fn   = &secplus_v1_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/secplus_v2.c",
    "content": "/** @file\n    Security+ 2.0 rolling code.\n\n    Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>\n    Based on code by Clayton Smith https://github.com/argilo/secplus\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nFreq 310, 315 and 390 MHz.\n\nSecurity+ 2.0  is described in [US patent application US20110317835A1](https://patents.google.com/patent/US20110317835A1/)\n\n\n*/\n\n#include \"decoder.h\"\n\n/**\nSecurity+ 2.0 rolling code.\n\nData comes in two bursts/packets.\n\nLayout:\n\n    bits = `AA BB IIII OOOO X*30`\n\n- AA = payload type  (2 bits 00 or 01)\n- BB = FrameID (2 bits always 00)\n- IIII = inversion indicator (4 bits)\n- OOOO = Order indicator (4 bits).\n- XXXX....  = data (30 bits)\n\n---\n\ndata is broken up into 3 parts (p0 p1 p2)\neg:\n\ndata = `ABCABCABCABCABCABCABCABCABCABC`\nbecomes:\n\n    `p0 = AAAAAAAAAA`\n    `p1 = BBBBBBBBBB`\n    `p2 = CCCCCCCCCC`\n\nthese three parts are then inverted and reordered based on the 4bit Order and Inversion indicators\n\nfixed generated from concatenate  p0 + p1\n\nroll_array is generated from the 8 bit used for Order and Inversion indicators + p3\nby reading the buffer in binary bit pairs forming trinary values\n\nEG:\n`1 0 0 1 1 0 1 0 0 1 1 0=> [1 0] [0 1] [1 0] [1 0] [0 1] [1 0] => 2 1 2 2 1 2`\n\nReturns data in :\n  * roll_array as an array of trinary values  0, 1, 2) the value 3 is invalid\n  * fixed_p as an bitbuffer_t with 20 bits of data\n\n\nOnce the above has been run twice the two are merged\n\n---\n\n*/\n\nstatic int secplus_v2_decode_v2_half(r_device *decoder, bitbuffer_t *bits, uint8_t roll_array[], bitbuffer_t *fixed_p)\n{\n    uint8_t invert = 0;\n    uint8_t order  = 0;\n    uint32_t x    = 0;\n    unsigned int start_pos = 2; //\n    uint8_t buffy[10];\n\n    uint8_t part_id = (bits->bb[0][0] >> 6);\n\n    decoder_log_bitrow(decoder, 1, __func__, bits->bb[0], bits->bits_per_row[0], \"\");\n\n    bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 2);\n    start_pos += 2;\n\n    bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 8);\n    start_pos += 8;\n    order = buffy[0] >> 4;\n\n    invert = buffy[0] & 0x0f;\n    // bitrow_debug(&invert, 8);\n\n    bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 30);\n    // start_pos += 30;\n\n    // copy 30 bits of data into 32bit int then shift >> 2\n    // memcpy(&dat, buffy, 4);\n    x = ((uint32_t)buffy[0] << 24) | (buffy[1] << 16) | (buffy[2] << 8) | (buffy[3]);\n\n    x >>= 2;\n\n    // using short to store 10bit values\n    uint16_t p0 = 0, p1 = 0, p2 = 0;\n\n    // sort 30 bits of interleaved data into three 10 bit buffers\n    for (int i = 0; i < 10; i++) {\n        p2 ^= (x & 0x00000001) << i; // 9-\n        x >>= 1;\n        p1 ^= (x & 0x00000001) << i;\n        x >>= 1;\n        p0 ^= (x & 0x00000001) << i;\n        x >>= 1;\n    }\n\n    // selectively invert buffers\n    switch (invert) {\n    case 0x00: // 0b0000 (True, True, False),\n        p0 = ~p0 & 0x03FF;\n        p1 = ~p1 & 0x03FF;\n        break;\n    case 0x01: // 0b0001 (False, True, False),\n        p1 = ~p1 & 0x03FF;\n        break;\n    case 0x02: // 0b0010 (False, False, True),\n        p2 = ~p2 & 0x03FF;\n        break;\n    case 0x04: // 0b0100 (True, True, True),\n        p0 = ~p0 & 0x03FF;\n        p1 = ~p1 & 0x03FF;\n        p2 = ~p2 & 0x03FF;\n        break;\n    case 0x05: // 0b0101 (True, False, True),\n    case 0x0a: // 0b1010 (True, False, True),\n        p0 = ~p0 & 0x03FF;\n        p2 = ~p2 & 0x03FF;\n        break;\n    case 0x06: // 0b0110 (False, True, True),\n        p1 = ~p1 & 0x03FF;\n        p2 = ~p2 & 0x03FF;\n        break;\n    case 0x08: // 0b1000 (True, False, False),\n        p0 = ~p0 & 0x03FF;\n        break;\n    case 0x09: // 0b1001 (False, False, False),\n        break;\n    default:\n        decoder_log(decoder, 1, __func__, \"Invert FAIL\");\n        return 1;\n    }\n\n    uint16_t a = p0, b = p1, c = p2;\n\n    // selectively reorder buffers\n    switch (order) {\n    case 0x06: // 0b0110  2, 1, 0],\n    case 0x09: // 0b1001  2, 1, 0],\n        p2 = a;\n        p1 = b;\n        p0 = c;\n        break;\n\n    case 0x08: // 0b1000  1, 2, 0],\n    case 0x04: // 0b0100  1, 2, 0],\n        p1 = a;\n        p2 = b;\n        p0 = c;\n        break;\n\n    case 0x01: // 0b0001 2, 0, 1],\n        p2 = a;\n        p0 = b;\n        p1 = c;\n        break;\n\n    case 0x00: // 0b0000  0, 2, 1],\n        p0 = a;\n        p2 = b;\n        p1 = c;\n        break;\n\n    case 0x05: // 0b0101 1, 0, 2],\n        p1 = a;\n        p0 = b;\n        p2 = c;\n        break;\n\n    case 0x02: // 0b0010 0, 1, 2],\n    case 0x0A: // 0b1010 0, 1, 2],\n        p0 = a;\n        p1 = b;\n        p2 = c;\n        break;\n\n    default:\n        decoder_log(decoder, 1, __func__, \"Order FAIL\");\n        return 2;\n    }\n\n    bitbuffer_extract_bytes(bits, 0, 4, buffy, 8);\n    x     = buffy[0];\n    int k = 0;\n    for (int i = 6; i >= 0; i -= 2) {\n        roll_array[k++] = (x >> i) & 0x03;\n    }\n\n    // decoder_log_bitrow(decoder, 3, __func__, buffy, 8, \"\")\n\n    // assemble binary bits into trinary\n    x = p2;\n    for (int i = 8; i >= 0; i -= 2) {\n        roll_array[k++] = (x >> i) & 0x03;\n    }\n\n    decoder_logf(decoder, 1, __func__, \"roll_array : (%d) %d %d %d %d %d %d %d %d %d\", part_id,\n                roll_array[0], roll_array[1], roll_array[2], roll_array[3],\n                roll_array[4], roll_array[5], roll_array[6], roll_array[7], roll_array[8]);\n\n    // SANITY check trinary values, 00/01/10 are valid,  11 is not\n    for (int i = 0; i < 9; i++) {\n        if (roll_array[i] == 3) {\n            decoder_log(decoder, 0, __func__, \"roll_array val FAIL\");\n            return 1; // DECODE_FAIL_SANITY;\n        }\n    }\n\n    // fixed_p = p0 + p1\n    for (int i = 9; i >= 0; i--) {\n        bitbuffer_add_bit(fixed_p, (p0 >> i) & 0x01);\n    }\n    for (int i = 9; i >= 0; i--) {\n        bitbuffer_add_bit(fixed_p, (p1 >> i) & 0x01);\n    }\n\n    return 0;\n}\n\nstatic const uint8_t _preamble[] = {0xaa, 0xaa, 0x95, 0x60};\nunsigned _preamble_len           = 28;\n\n/**\nSecurity+ 2.0 rolling code.\n@sa secplus_v2_decode_v2_half()\n*/\nstatic int secplus_v2_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    unsigned search_index = 0;\n    bitbuffer_t bits = {0};\n    // int i            = 0;\n\n    //bitbuffer_t bits_1    = {0};\n    bitbuffer_t fixed_1   = {0};\n    uint8_t rolling_1[16] = {0};\n\n    //bitbuffer_t bits_2    = {0};\n    bitbuffer_t fixed_2   = {0};\n    uint8_t rolling_2[16] = {0};\n\n    for (uint16_t row = 0; row < bitbuffer->num_rows; ++row) {\n        if (bitbuffer->bits_per_row[row] < 110) {\n            continue;\n        }\n\n        search_index = bitbuffer_search(bitbuffer, row, 0, _preamble, _preamble_len);\n\n        if (search_index >= bitbuffer->bits_per_row[row]) {\n            break;\n        }\n\n        bitbuffer_clear(&bits);\n        bitbuffer_manchester_decode(bitbuffer, row, search_index + 26, &bits, 80);\n        // search_index += 20;\n        if (bits.bits_per_row[0] < 42) {\n            continue; // DECODE_ABORT_LENGTH;\n        }\n\n        decoder_log_bitrow(decoder, 1, __func__, bits.bb[0], bits.bits_per_row[0], \"manchester decoded\");\n\n        // valid = 0X00XXXX\n        // 1st 3rs and 4th bits should always be 0\n        if (bits.bb[0][0] & 0xB0) {\n            continue; // DECODE_FAIL_SANITY;\n        }\n\n        // 2nd bit indicates with half of the data\n        if (bits.bb[0][0] & 0xC0) {\n            decoder_log(decoder, 1, __func__, \"Set 2\");\n            secplus_v2_decode_v2_half(decoder, &bits, rolling_2, &fixed_2);\n        }\n        else {\n            decoder_log(decoder, 1, __func__, \"Set 1\");\n            secplus_v2_decode_v2_half(decoder, &bits, rolling_1, &fixed_1);\n        }\n\n        // break if we've received both halves\n        if (fixed_1.bits_per_row[0] > 1 && fixed_2.bits_per_row[0] > 1) {\n            break;\n        }\n    }\n\n    // Do we have what we need ??\n    if (fixed_1.bits_per_row[0] == 0 || fixed_2.bits_per_row[0] == 0) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Assemble rolling_1[] and rolling_2[] into rolling_digits[]\n    uint8_t rolling_digits[24] = {0};\n    uint8_t *r;\n\n    r    = rolling_digits;\n    *r++ = rolling_2[8];\n    *r++ = rolling_1[8];\n    for (int i = 4; i < 8; i++) {\n        *r++ = rolling_2[i];\n    }\n    for (int i = 4; i < 8; i++) {\n        *r++ = rolling_1[i];\n    }\n\n    for (int i = 0; i < 4; i++) {\n        *r++ = rolling_2[i];\n    }\n    for (int i = 0; i < 4; i++) {\n        *r++ = rolling_1[i];\n    }\n\n    // compute rolling_total from rolling_digits[]\n    uint32_t rolling_total = 0;\n    uint32_t rolling_temp  = 0;\n\n    for (int i = 0; i < 18; i++) {\n        rolling_temp = (rolling_temp * 3) + rolling_digits[i];\n    }\n\n    // Max value = 2^28 (268435456)\n    if (rolling_temp >= 0x10000000) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    // value is 28 bits thus need to shift over 4 bit\n    rolling_total = reverse32(rolling_temp);\n    rolling_total = rolling_total >> 4;\n\n    // Assemble \"fixed\" data part\n    uint64_t fixed_total = 0;\n    uint8_t *bb;\n    bb = fixed_1.bb[0];\n    fixed_total ^= ((uint64_t)bb[0]) << 32;\n    fixed_total ^= ((uint64_t)bb[1]) << 24;\n    fixed_total ^= ((uint64_t)bb[2]) << 16;\n\n    bb = fixed_2.bb[0];\n    fixed_total ^= ((uint64_t)bb[0]) << 12;\n    fixed_total ^= ((uint64_t)bb[1]) << 4;\n    fixed_total ^= (bb[2] >> 4) & 0x0f;\n\n    // int button    = fixed_total >> 32;\n    // int remote_id = fixed_total & 0xffffffff;\n    char fixed_str[16];\n    char rolling_str[16];\n\n    // rolling_total is a 28 bit unsigned number\n    // fixed_totals is 40 bit in a uint64_t\n    snprintf(fixed_str, sizeof(fixed_str), \"%llu\", (long long unsigned)fixed_total);\n    snprintf(rolling_str, sizeof(rolling_str), \"%u\", rolling_total);\n\n    /* clang-format off */\n    data_t *data;\n    data = data_make(\n            \"model\",       \"Model\",    DATA_STRING, \"Secplus-v2\",\n            \"id\",          \"\",       DATA_INT, (fixed_total & 0xffffffff),\n            \"button_id\",   \"Button-ID\",    DATA_INT,    (fixed_total >> 32),\n            \"remote_id\",   \"Remote-ID\",    DATA_INT,    (fixed_total & 0xffffffff),\n            // \"fixed\",       \"\",    DATA_INT,    fixed_total,\n            \"fixed\",       \"Fixed_Code\",    DATA_STRING,    fixed_str,\n            \"rolling\",     \"Rolling_Code\",    DATA_STRING,    rolling_str,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        // Common fields\n        \"model\",\n        \"id\",\n        \"rolling\",\n        \"fixed\",\n        \"button_id\",\n        \"remote_id\",\n        NULL,\n};\n\n//      Freq 310.01M\n//  -X \"n=vI3,m=OOK_PCM,s=230,l=230,t=40,r=10000,g=7400,match={24}0xaaaa9560\"\n\nr_device const secplus_v2 = {\n        .name        = \"Security+ 2.0 (Keyfob)\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 250,\n        .long_width  = 250,\n        .tolerance   = 50,\n        .gap_limit   = 1500,\n        .reset_limit = 9000,\n        .decode_fn   = &secplus_v2_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/sharp_spc775.c",
    "content": "/** @file\n    Decoder for Sharp SPC775 weather station.\n\n    Copyright (C) 2020 Daniel Drown\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nDecoder for Sharp SPC775 weather station.\n\n- Modulation: FSK PWM\n- Frequency: 917.2 MHz\n- 3900 us long single frequency preamble signal\n- 4800 us 2x high to low transitions\n- 725 us per symbol, 225 us high for 0, 425 us high for 1\n- ends with 3000 us low, then back to the 2x high/low transitions\n- data is repeated 3x per transmission\n- 48 bits worth of data\n- 8 bits of fixed sync (0xa5)\n- 8 bits of ID\n- 1 bit of battery state\n- 3 bits of \"unused\"?\n- 12 bits of signed 0.1C units\n- 8 bits of humidity %\n- 8 bits of digest checksum\n\ngeneric parser version:\nrtl_433 -f 917.2M -s 250k -R 0 -X n=sharp,m=FSK_PWM,s=225,l=425,y=4000,g=2900,r=150000,invert,bits=48,preamble={8}a5\n*/\n\n#include \"decoder.h\"\n\nstatic int sharp_spc775_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xa5};\n\n    data_t *data;\n    uint8_t b[6];\n    int length_match   = 0;\n    int preamble_match = 0;\n\n    // Invert data for processing\n    bitbuffer_invert(bitbuffer);\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] >= 48) {\n            length_match++;\n            unsigned pos = bitbuffer_search(bitbuffer, row, 0, preamble, sizeof(preamble) * 8);\n            if (pos + 6 * 8 <= bitbuffer->bits_per_row[row]) {\n                preamble_match++;\n                bitbuffer_extract_bytes(bitbuffer, row, pos, b, 6 * 8);\n            }\n        }\n    }\n\n    if (!length_match)\n        return DECODE_ABORT_LENGTH;\n    if (!preamble_match)\n        return DECODE_FAIL_SANITY;\n\n    int id          = b[1];                                           // changes on each power cycle\n    int battery_low = (b[2] & 0x80);                                  // High bit is low battery indicator\n    int temp_raw    = (int16_t)(((b[2] & 0x0f) << 12) | (b[3] << 4)); // uses sign-extend\n    float temp_c    = (temp_raw >> 4) * 0.1f;                         // Convert sign extended int to float\n    int humidity    = b[4];                                           // Simple 0-100 RH\n    int chk_digest  = b[5];\n\n    uint8_t chk_calc = xor_bytes(b, 5);\n    int chk_expected = lfsr_digest8_reflect(&chk_calc, 1, 0x31, 0x31);\n\n    if (chk_expected != chk_digest)\n        return DECODE_FAIL_MIC;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Sharp-SPC775\",\n            \"id\",               \"\",                 DATA_INT,    id,\n            \"battery_ok\",       \"Battery\",          DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",    DATA_INT,    humidity,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const sharp_spc775 = {\n        .name        = \"Sharp SPC775 weather station\",\n        .modulation  = FSK_PULSE_PWM,\n        .short_width = 225,\n        .long_width  = 425,\n        .gap_limit   = 2900,\n        .reset_limit = 10000,\n        .decode_fn   = &sharp_spc775_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/silvercrest.c",
    "content": "/** @file\n    Silvercrest remote decoder.\n\n    Copyright (C) 2018 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nSilvercrest remote decoder.\n\n@todo Documentation needed.\n*/\nstatic int silvercrest_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const cmd_lu_tab[16] = {2, 3, 0, 1, 4, 5, 7, 6, 0xC, 0xD, 0xF, 0xE, 8, 9, 0xB, 0xA};\n\n    uint8_t *b; // bits of a row\n    uint8_t cmd;\n    data_t *data;\n\n    if (bitbuffer->bits_per_row[1] != 33)\n        return DECODE_ABORT_LENGTH;\n\n    /* select second row, first might be bad */\n    b = bitbuffer->bb[1];\n    if ((b[0] == 0x7c) && (b[1] == 0x26)) {\n        cmd = b[2] & 0xF;\n        // Validate button\n        if ((b[3] & 0xF) != cmd_lu_tab[cmd])\n            return DECODE_ABORT_EARLY;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",    \"\", DATA_STRING, \"Silvercrest-Remote\",\n                \"button\",   \"\", DATA_INT,    cmd,\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n\n        return 1;\n    }\n    return DECODE_ABORT_EARLY;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"button\",\n        NULL,\n};\n\nr_device const silvercrest = {\n        .name        = \"Silvercrest Remote Control\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 264,\n        .long_width  = 744,\n        .reset_limit = 12000,\n        .gap_limit   = 5000,\n        .decode_fn   = &silvercrest_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/simplisafe.c",
    "content": "/** @file\n    Protocol of the SimpliSafe Sensors.\n\n    Copyright (C) 2018 Adam Callis <adam.callis@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    License: GPL v2+ (or at your choice, any other OSI-approved Open Source license)\n*/\n/** @fn int ss_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer)\nProtocol of the SimpliSafe Sensors.\n\n@sa ss_sensor_parser()\n@sa ss_pinentry_parser()\n@sa ss_keypad_commands()\n\nThe data is sent leveraging a PiWM Encoding where a long is 1, and a short is 0\n\nAll bytes are sent with least significant bit FIRST (1000 0111 = 0xE1)\n\n 2 Bytes   | 1 Byte       | 5 Bytes   | 1 Byte  | 1 Byte  | 1 Byte       | 1 Byte\n Sync Word | Message Type | Device ID | CS Seed | Command | SUM CMD + CS | Epilogue\n\n*/\n\n#include \"decoder.h\"\n\nstatic void ss_get_id(char *id, uint8_t *b)\n{\n    char *p = id;\n\n    // Change to least-significant-bit last (protocol uses least-significant-bit first) for hex representation:\n    for (uint16_t k = 3; k <= 7; k++) {\n        char c = b[k];\n        c = reverse8(c);\n        // If the character is not representable with a valid-ish ascii character, replace with ?.\n        // This probably means the message is invalid.\n        // This is at least better than spitting out non-printable stuff :).\n        if (c < 32 || c > 126) {\n          sprintf(p++, \"%c\", '?');\n          continue;\n        }\n        sprintf(p++, \"%c\", (char)c);\n    }\n    *p = '\\0';\n}\n\n/**\nSimpliSafe protocol for sensors.\n*/\nstatic int ss_sensor_parser(r_device *decoder, bitbuffer_t *bitbuffer, int row)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[row];\n\n    // each row needs to have exactly 92 bits\n    if (bitbuffer->bits_per_row[row] != 92)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t seq = reverse8(b[8]);\n    uint8_t state = reverse8(b[9]);\n    uint8_t csum = reverse8(b[10]);\n    if (((seq + state) & 0xff) != csum)\n        return DECODE_FAIL_MIC;\n\n    char id[6];\n    ss_get_id(id, b);\n\n    char extradata[30];\n    if (state == 1) {\n        snprintf(extradata, sizeof(extradata), \"Contact Open\");\n    } else if (state == 2) {\n        snprintf(extradata, sizeof(extradata), \"Contact Closed\");\n    } else if (state == 3) {\n        snprintf(extradata, sizeof(extradata), \"Alarm Off\");\n    } else {\n        //snprintf(extradata, sizeof(extradata), \"\");\n        *extradata = '\\0';\n    }\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"SimpliSafe-Sensor\",\n            \"id\",           \"Device ID\",    DATA_STRING, id,\n            \"seq\",          \"Sequence\",     DATA_INT,    seq,\n            \"state\",        \"State\",        DATA_INT,    state,\n            \"extradata\",    \"Extra Data\",   DATA_STRING, extradata,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nSimpliSafe protocol for pinentry.\n*/\nstatic int ss_pinentry_parser(r_device *decoder, bitbuffer_t *bitbuffer, int row)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[row];\n    // In a keypad message the pin is encoded in bytes 10 and 11 with the the digits each using 4 bits\n    // However the bits are low order to high order\n    int digits[5];\n    int pina = reverse8(b[10]);\n    int pinb = reverse8(b[11]);\n\n    digits[0] = (pina & 0xf);\n    digits[1] = ((pina & 0xf0) >> 4);\n    digits[2] = (pinb & 0xf);\n    digits[3] = ((pinb & 0xf0) >> 4);\n\n    char id[6];\n    ss_get_id(id, b);\n\n    char extradata[30];\n    snprintf(extradata, sizeof(extradata), \"Disarm Pin: %x%x%x%x\", digits[0], digits[1], digits[2], digits[3]);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"SimpliSafe-Keypad\",\n            \"id\",           \"Device ID\",    DATA_STRING, id,\n            \"seq\",          \"Sequence\",     DATA_INT,    b[9],\n            \"extradata\",    \"Extra Data\",   DATA_STRING, extradata,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nSimpliSafe protocol for keypad commands.\n*/\nstatic int ss_keypad_commands(r_device *decoder, bitbuffer_t *bitbuffer, int row)\n{\n    data_t *data;\n    uint8_t *b = bitbuffer->bb[row];\n    char extradata[30]; // = \"Arming: \";\n\n    if (b[10] == 0x6a) {\n        snprintf(extradata, sizeof(extradata), \"Arm System - Away\");\n    } else if (b[10] == 0xca) {\n        snprintf(extradata, sizeof(extradata), \"Arm System - Home\");\n    } else if (b[10] == 0x3a) {\n        snprintf(extradata, sizeof(extradata), \"Arm System - Canceled\");\n    } else if (b[10] == 0x2a) {\n        snprintf(extradata, sizeof(extradata), \"Keypad Panic Button\");\n    } else if (b[10] == 0x86) {\n        snprintf(extradata, sizeof(extradata), \"Keypad Menu Button\");\n    } else {\n        snprintf(extradata, sizeof(extradata), \"Unknown Keypad: %02x\", b[10]);\n    }\n\n    char id[6];\n    ss_get_id(id, b);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"SimpliSafe-Keypad\",\n            \"id\",           \"Device ID\",    DATA_STRING, id,\n            \"seq\",          \"Sequence\",     DATA_INT,    b[9],\n            \"extradata\",    \"Extra Data\",   DATA_STRING, extradata,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic int ss_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Require two identical rows.\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 90);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    // The row must start with 0xcc5f (0x33a0 inverted).\n    uint8_t *b = bitbuffer->bb[row];\n    if (b[0] != 0xcc || b[1] != 0x5f)\n        return DECODE_ABORT_EARLY;\n\n    bitbuffer_invert(bitbuffer);\n\n    if (b[2] == 0x88) {\n        return ss_sensor_parser(decoder, bitbuffer, row);\n    } else if (b[2] == 0x66) {\n        return ss_pinentry_parser(decoder, bitbuffer, row);\n    } else if (b[2] == 0x44) {\n        return ss_keypad_commands(decoder, bitbuffer, row);\n    } else {\n        decoder_logf(decoder, 1, __func__, \"Unknown Message Type: %02x\", b[2]);\n        return DECODE_ABORT_EARLY;\n    }\n}\n\nstatic char const *const sensor_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"seq\",\n        \"state\",\n        \"extradata\",\n        NULL,\n};\n\nr_device const ss_sensor = {\n        .name        = \"SimpliSafe Home Security System (May require disabling automatic gain for KeyPad decodes)\",\n        .modulation  = OOK_PULSE_PIWM_DC,\n        .short_width = 500,  // half-bit width 500 us\n        .long_width  = 1000, // bit width 1000 us\n        .reset_limit = 2200,\n        .tolerance   = 100, // us\n        .decode_fn   = &ss_sensor_callback,\n        .fields      = sensor_output_fields,\n};\n"
  },
  {
    "path": "src/devices/simplisafe_gen3.c",
    "content": "/** @file\n    SimpliSafe Gen 3 protocol.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nSimpliSafe Gen 3 protocol.\n\nThe data is sent at 433.9MHz using FSK at 4800 baud with a preamble and sync of `aaaaaaa 930b 51de`.\n\nKnown message length/types:\n- Arm: 15 01\n- Disarm: 18 01\n- Sensors: 16 02\n\nData Layout:\n\n    LEN:8h TYP:8h ID:32h CTR:24h CMAC:32h ENCR:80h CHK:16h\n\nExample codes:\n\n    55555554985a8ef0b01004fa89af407800c32b888bff61098d3627bdd5d369ca1800000000\n    d55555552616a3bc2c04013ea26bd01e0030cae222ffd842634d89ef7574da728600000000\n    d55555552616a3bc2c04013ea26bd21e0103b1a07f861673b5d1c531fa0bcd269c00000000\n    55555554985a8ef0b01004fa89af4878040ec681fe1859ced74714c7e82f349a7000000000\n    55555554985a8ef0b01004fa89af4878040ec681fe1859ced74714c7e82f349a7000000000\n\n*/\nstatic int simplisafe_gen3_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x93, 0x0b, 0x51, 0xde}; // 32 bit\n\n    int bitpos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 32) + 32;\n    if (bitpos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // a row needs to have at least 1+21+2 bytes\n    if (bitpos + 24 * 8 > bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t b[27]; // for length 21 to 24 (plus 3)\n    bitbuffer_extract_bytes(bitbuffer, 0, bitpos, b, 27 * 8);\n\n    // The row must start with length indicator of 21, 22, or 24 (0x15, 0x16, 0x18)\n    if (b[0] != 0x15 && b[0] != 0x16 && b[0] != 0x18)\n        return DECODE_ABORT_EARLY;\n\n    int len      = (b[0]); // verified to be 21, 22, or 24\n    int msg_type = (b[1]);\n    int id       = ((unsigned)b[2] << 24) | (b[3] << 16) | (b[4] << 8) | (b[5]);\n    int ctr      = (b[8] << 16) | (b[7] << 8) | (b[6]); // note: little endian\n    int cmac     = ((unsigned)b[9] << 24) | (b[10] << 16) | (b[11] << 8) | (b[12]);\n    // int crc      = (b[23] << 8) | (b[24]);\n    char encr[12 * 2 + 1]; // 9, 10, or 12 hex bytes\n    bitrow_snprint(&b[13], (len - 12) * 8, encr, sizeof(encr));\n\n    int chk = crc16(b, len + 3, 0x8005, 0xffff);\n    if (chk) {\n        decoder_logf_bitrow(decoder, 1, __func__, b, (len + 3) * 8, \"crc failed (%04x)\", chk);\n        return DECODE_FAIL_MIC;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"SimpliSafe-Gen3\",\n            \"id\",               \"ID\",               DATA_FORMAT, \"%08x\", DATA_INT, id,\n            \"msg_type\",         \"Type\",             DATA_FORMAT, \"%02x\", DATA_INT, msg_type,\n            \"ctr\",              \"Counter\",          DATA_FORMAT, \"%06x\", DATA_INT, ctr,\n            \"cmac\",             \"CMAC\",             DATA_FORMAT, \"%08x\", DATA_INT, cmac,\n            \"encr\",             \"Encrypted\",        DATA_STRING, encr,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"msg_type\",\n        \"ctr\",\n        \"cmac\",\n        \"encr\",\n        \"mic\",\n        NULL,\n};\n\nr_device const simplisafe_gen3 = {\n        .name        = \"SimpliSafe Gen 3 Home Security System\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 208, // 4800 baud\n        .long_width  = 208,\n        .reset_limit = 7000,\n        .decode_fn   = &simplisafe_gen3_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/smoke_gs558.c",
    "content": "/** @file\n    Wireless Smoke & Heat Detector.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nWireless Smoke & Heat Detector.\n\nNingbo Siterwell Electronics  GS 558  Sw. V05  Ver. 1.3  on 433.885MHz\nVisorTech RWM-460.f  Sw. V05, distributed by PEARL, seen on 433.674MHz\n\nA short wakeup pulse followed by a wide gap (11764 us gap),\nfollowed by 24 data pulses and 2 short stop pulses (in a single bit width).\nThis is repeated 8 times with the next wakeup directly following\nthe preceding stop pulses.\n\nBit width is 1731 us with\nShort pulse: -___ 436 us pulse + 1299 us gap\nLong pulse:  ---_ 1202 us pulse + 526 us gap\nStop pulse:  -_-_ 434us pulse + 434us gap + 434us pulse + 434us gap\n= 2300 baud pulse width / 578 baud bit width\n\n24 bits (6 nibbles):\n- first 5 bits are unit number with bits reversed\n- next 15(?) bits are group id, likely also reversed\n- last 4 bits are always 0x3 (maybe hardware/protocol version)\nDecoding will reverse the whole packet.\nShort pulses are 0, long pulses 1, need to invert the demod output.\n\nEach device has it's own group id and unit number as well as a\nshared/learned group id and unit number.\nIn learn mode the primary will offer it's group id and the next unit number.\nThe secondary device acknowledges pairing with 16 0x555555 packets\nand copies the offered shared group id and unit number.\nThe primary device then increases it's unit number.\nThis means the primary will always have the same unit number as the\nlast learned secondary, weird.\nAlso you always need to learn from the same primary.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int smoke_gs558_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b;\n    int r;\n    int learn = 0;\n    int unit; // max 30\n    int id;\n\n    if (bitbuffer->num_rows < 3)\n        return DECODE_ABORT_EARLY; // truncated transmission\n\n    bitbuffer_invert(bitbuffer);\n\n    for (r = 0; r < bitbuffer->num_rows; ++r) {\n        b = bitbuffer->bb[r];\n\n        // count learn pattern and strip\n        if (bitbuffer->bits_per_row[r] >= 24\n                && b[0] == 0x55 && b[1] == 0x55 && b[2] == 0x55) {\n            ++learn;\n            bitbuffer->bits_per_row[r] = 0;\n        }\n\n        // strip end-of-packet pulse\n        if ((bitbuffer->bits_per_row[r] == 26 || bitbuffer->bits_per_row[r] == 27)\n                && b[3] == 0)\n            bitbuffer->bits_per_row[r] = 24;\n    }\n\n    r = bitbuffer_find_repeated_row(bitbuffer, 3, 24);\n\n    if (r < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[r] > 4 * 8)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[r];\n\n    // if ((b[2] & 0x0f) != 0x03)\n    //     return DECODE_ABORT_EARLY; // last nibble is always 0x3?\n\n    b[0] = reverse8(b[0]);\n    b[1] = reverse8(b[1]);\n    b[2] = reverse8(b[2]);\n\n    unit = b[0] & 0x1f; // 5 bits\n    id = ((b[2] & 0x0f) << 11) | (b[1] << 3) | (b[0] >> 5); // 15 bits\n\n    if (id == 0 || id == 0x7fff)\n        return DECODE_FAIL_SANITY; // reject min/max to reduce false positives\n\n    char code_str[7];\n    snprintf(code_str, sizeof(code_str), \"%02x%02x%02x\", b[2], b[1], b[0]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"Smoke-GS558\",\n            \"id\",           \"\",             DATA_INT, id,\n            \"unit\",         \"\",             DATA_INT, unit,\n            \"learn\",        \"\",             DATA_INT, learn > 1,\n            \"code\",         \"Raw Code\",     DATA_STRING, code_str,\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"unit\",\n        \"learn\",\n        \"code\",\n        NULL,\n};\n\nr_device const smoke_gs558 = {\n        .name        = \"Wireless Smoke and Heat Detector GS 558\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 436,          // Threshold between short and long pulse [us]\n        .long_width  = 1202,         // Maximum gap size before new row of bits [us]\n        .gap_limit   = 1299 * 1.5f,  // Maximum gap size before new row of bits [us]\n        .reset_limit = 11764 * 1.2f, // Maximum gap size before End Of Message [us]\n        .decode_fn   = &smoke_gs558_callback,\n        .fields      = output_fields,\n        .disabled    = 1, // false positives with generic EV1527 devices\n};\n"
  },
  {
    "path": "src/devices/solight_te44.c",
    "content": "/** @file\n    Solight TE44 temperature sensor.\n\n    Copyright (C) 2017 Miroslav Oujesky\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nSolight TE44 -- Generic wireless thermometer, which might be sold as part of different kits.\n\nNote: this is identical with Rubicson and is only active when Rubicson is disabled.\n\nSo far these were identified (mostly sold in central/eastern europe)\n- Solight TE44\n- Solight TE66\n- EMOS E0107T\n- NX-6876-917 from Pearl (for FWS-70 station).\n- newer TFA 30.3197\n\nRated -50 C to 70 C, frequency 433,92 MHz, three selectable channels.\n\nData structure:\n\n12 repetitions of the same 36 bit payload, 1bit zero as a separator between each repetition.\n\n    36 bit payload format: iiiiiiii b0cctttt tttttttt 1111xxxx xxxx\n\n- i: 8 bit random key (changes after device reset)\n- b 1 bit battery flag: 1 if battery is ok, 0 if battery is low\n- c: 2 bit channel (0-2)\n- t: 12 bit temperature in celsius, signed integer, scale 10\n- x: 8 bit checksum\n\n*/\nstatic int solight_te44_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int r = bitbuffer_find_repeated_row(bitbuffer, 3, 36);\n    if (r < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[r];\n\n    if (bitbuffer->bits_per_row[r] != 37) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if ((b[3] & 0xf0) != 0xf0) {\n        return DECODE_ABORT_EARLY; // const not 1111\n    }\n\n    // CRC check\n    uint8_t tmp[5];\n    tmp[0] = b[0];                // Byte 0 is nibble 0 and 1\n    tmp[1] = b[1];                // Byte 1 is nibble 2 and 3\n    tmp[2] = b[2];                // Byte 2 is nibble 4 and 5\n    tmp[3] = b[3] & 0xf0;         // Byte 3 is nibble 6 and 0-padding\n    tmp[4] = (b[3] & 0x0f) << 4 | // CRC is nibble 7 and 8\n             (b[4] & 0xf0) >> 4;\n\n    int chk = crc8(tmp, 5, 0x31, 0x6c);\n    if (chk) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int id       = b[0];\n    // int battery  = (b[1] & 0x80);\n    int channel  = ((b[1] & 0x30) >> 4);\n    int temp_raw = (int16_t)((b[1] << 12) | (b[2] << 4)); // sign-extend\n    float temp_c = (temp_raw >> 4) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Solight-TE44\",\n            \"id\",               \"Id\",           DATA_INT,    id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel + 1,\n//            \"battery_ok\",       \"Battery\",      DATA_INT,    !!battery,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        //\"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const solight_te44 = {\n        .name        = \"Solight TE44/TE66, EMOS E0107T, NX-6876-917\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 972,  // short gap = 972 us\n        .long_width  = 1932, // long gap = 1932 us\n        .gap_limit   = 3000, // packet gap = 3880 us\n        .reset_limit = 6000,\n        .decode_fn   = &solight_te44_decode,\n        .priority    = 10, // Eliminate false positives by letting Rubicson-Temperature go earlier\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/somfy_iohc.c",
    "content": "/** @file\n    Somfy io-homecontrol devices.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nSomfy io-homecontrol devices.\n\nE.g. Velux remote controller KI 313.\n\n    rtl_433 -c 0 -R 0 -g 40 -X \"n=uart,m=FSK_PCM,s=26,l=26,r=300,preamble={24}0x5555ff,decode_uart\" -f 868.89M\n\nProtocol description:\n\n- Preamble is 55..55.\n- The message, including the sync word is UART encoded, 8 data bits equal 10 packet bits.\n- 16 bit sync word of ff33, UART encoded: 0 ff 1 0 cc 1 = 7fd99.\n- 4+4 bit message type/length indicator byte.\n- 32 bit destination address (little endian presumably).\n- 32 bit source address (little endian presumably).\n- n bytes variable length message payload bytes\n- 16 bit MAC counter value\n- 48 bit MAC value\n- 16 bit CRC-16, poly 0x1021, init 0x0000, reflected.\n\nExample packets:\n\n    ff33 f6 2000003f dacdea00 016100000000     0bdd fd8ef56f15ad aa1e\n    ff33 f6 0000003f dacdea00 016100000000     0bdd fd8ef56f15ad 4f9c\n    ff33 f6 2000003f dacdea00 0161c8000000     0bbd 8aa3a9732e10 26d2\n    ff33 f6 0000003f dacdea00 0161c8000000     0bbd 8aa3a9732e10 c350\n    ff33 f6 2000003f dacdea00 0161d2000000     0b99 15decacf7e0e 8069\n    ff33 f6 0000003f dacdea00 0161d2000000     0b99 15decacf7e0e 65eb\n    ff33 f6 0000003f dacdea00 0161d2000000     0ba1 05175a82dfae 8bbf\n\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d6c 2c3a3123e6ab 7f1e [UP RIGHT]\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d6e e448de7d4e03 62d1 [UP RIGHT]\n    ff33 f8 0000007f c5896700 0161d40080c80000 0c63 04e867ed64ad f055 [UP LEFT]\n    ff33 f8 0000007f c5896700 0161d40080c80000 0c65 8414991e8b06 b82b [UP LEFT]\n    ff33 f8 0000007f 70875800 0161d40080c80000 3bd5 05526875499c 7e72 [UP PSA]\n    ff33 f6 0000003f e1f57300 0161d2000000     0d6f 708d89781e43 bc24 [STOP RIGHT]\n    ff33 f6 0000003f e1f57300 0161d2000000     0d71 d1b10f26e1c1 8a9d [STOP RIGHT]\n    ff33 f6 0000003f c5896700 0161d2000000     0c66 4fcf56fb1c72 d31e [STOP LEFT]\n    ff33 f6 0000003f c5896700 0161d2000000     0c68 2025e049f331 b64a [STOP LEFT]\n    ff33 f6 0000003f 70875800 0161d2000000     3bd2 e6b62cef54c8 a937 [STOP PSA]\n    ff33 f6 0000003f 70875800 0161d2000000     3bd6 630743f0530d dc24 [STOP PSA]\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d74 9fb9a0665ff4 77a6 [DOWN RIGHT]\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d76 71b81065a2e2 0616 [DOWN RIGHT]\n    ff33 f8 0000007f c5896700 0161d40080c80000 0c6b 56fcf691e6a9 2c74 [DOWN LEFT]\n    ff33 f8 0000007f c5896700 0161d40080c80000 0c6d daf020864668 8fad [DOWN LEFT]\n    ff33 f8 0000007f 70875800 0161d40080c80000 3bdf 1ee7a0e30448 7a6b [DOWN PSA]\n\n    ff33 f6 0000003f 17f52300 0147c8000000     18c4 38789cb680cc bc74\n    ff33 f8 0000003f 17f52320 02ff0143010e0000 18c5 045ee107363d 59b4\n    ff33 f8 0000003f 17f52320 02ff01430105ff00 18c6 a34715cbe012 4f7f\n    ^    ^  ^        ^        ^                ^    ^            ^CRC\n    ^    ^  ^        ^        ^                ^    ^MAC\n    ^    ^  ^        ^        ^                ^counter\n    ^    ^  ^        ^        ^payload\n    ^    ^  ^        ^source\n    ^    ^  ^destination\n    ^    ^length of payload\n    ^sync, not included in CRC\n\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d9a 4c0e4a0e45aa 995d\n    ff33 f8 0000007f c5896700 0161d40080c80000 0d9a 87ac970b53d1 d80d\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d9c ec44bf478a06 0cad\n    ff33 f8 0000007f c5896700 0161d40080c80000 0d9c f610e5e0eb9f 6ba1\n    ff33 f8 0000007f c5896700 0161d40080c80000 0d9e 10707b9a1f4a ed84\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0d9e de2cc3c1117a f8fd\n    ff33 f8 0000007f c5896700 0161d40080c80000 0da0 11280dad6fe7 b06a\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0da0 209550b7e353 0a35\n    ff33 f8 0000007f c5896700 0161d40080c80000 0da2 445f4c017d24 0cde\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0da2 877cc2f46a38 8a5c\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0da4 4412916d3fe6 42b8\n    ff33 f8 0000007f c5896700 0161d40080c80000 0da4 787e20402f19 6b43\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0da6 0eb34055b18f 1209\n    ff33 f8 0000007f c5896700 0161d40080c80000 0da6 33a9c2c0cc66 5e5d\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0da8 07c9a103761b 1f5b\n    ff33 f8 0000007f c5896700 0161d40080c80000 0da8 b3f7dd7a366f de5e\n    ff33 f8 0000007f c5896700 0161d40080c80000 0daa 80075cf2e7bc 6064\n    ff33 f8 0000007f e1f57300 0161d40080c80000 0daa eb6adf4f8fad 3404\n\n*/\n\nstatic int somfy_iohc_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x57, 0xfd, 0x99};\n\n    uint8_t b[1 + 31 + 2]; // Length, payload, CRC\n\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_EARLY;\n\n    unsigned offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 24) + 24;\n    if (offset >= bitbuffer->bits_per_row[0])\n        return DECODE_ABORT_EARLY;\n    int num_bits = bitbuffer->bits_per_row[0] - offset;\n\n    num_bits = MIN((size_t)num_bits, sizeof (b) * 10);\n\n    int len = extract_bytes_uart(bitbuffer->bb[0], offset, num_bits, b);\n    if (len < 11)\n        return DECODE_ABORT_LENGTH;\n\n    // Mandatory fields\n    // Control byte 1\n    // end_flag : 1\n    // start_flag : 1\n    // protocol_mode : 1\n    // frame_length : 5\n    int msg_len = b[0] & 0x1f;\n    if (len < msg_len + 3)\n        return DECODE_ABORT_LENGTH;\n    if (msg_len < 8)\n        return DECODE_ABORT_LENGTH;\n    len = msg_len + 3;\n\n    int msg_end_flag      = (b[0] & 0x80) >> 7;\n    int msg_start_flag    = (b[0] & 0x40) >> 6;\n    int msg_protocol_mode = (b[0] & 0x20) >> 5;\n\n    // Control byte 2\n    // use_beacon : 1\n    // is_routed : 1\n    // low_power_mode : 1\n    // protocol_version : 3\n    int msg_use_beacon       = (b[1] & 0x80) >> 7;\n    int msg_is_routed        = (b[1] & 0x40) >> 6;\n    int msg_low_power_mode   = (b[1] & 0x20) >> 5;\n    int msg_protocol_version = b[1] & 0x03;\n\n    // Addresses\n    // dst_addr : 24\n    // src_addr : 24\n    int msg_dst_addr = (b[2] << 16) | (b[3] << 8) | b[4];\n    int msg_src_addr = (b[5] << 16) | (b[6] << 8) | b[7];\n\n    // Command ID\n    // cmd_id : 8\n    int msg_cmd_id = b[8];\n\n    // optional fields\n    int msg_seq_nr = 0;\n    char msg_mac[13] = {0};\n\n    char msg_data[31 * 2 + 1]; // variable length, converted to hex string\n    unsigned int data_length = msg_len - 8;\n    if (msg_protocol_mode == 0 || data_length < 8) {\n        bitrow_snprint(&b[9], data_length * 8, msg_data, sizeof (msg_data));\n    } else {\n        data_length -= 8;\n        bitrow_snprint(&b[9], data_length * 8, msg_data, sizeof (msg_data));\n        msg_seq_nr = (b[9 + data_length] << 8) | b[9 + data_length + 1];\n        bitrow_snprint(&b[9 + data_length + 2], 6 * 8, msg_mac, sizeof msg_mac);\n    }\n\n    // crc : 16;\n    //int msg_crc = (b[len - 2] << 8) | b[len - 1];\n\n    // calculate and verify checksum\n    if (crc16lsb(b, len, 0x8408, 0x0000) != 0) // unreflected poly 0x1021\n        return DECODE_FAIL_MIC;\n\n    decoder_logf_bitrow(decoder, 2, __func__, b, len * 8, \"offset %u, num_bits %u, len %d, msg_len %d\", offset, num_bits, len, msg_len);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Somfy-IOHC\",\n            \"id\",               \"Source\",           DATA_FORMAT, \"%06x\", DATA_INT, msg_src_addr,\n            \"dst_id\",           \"Target\",           DATA_FORMAT, \"%06x\", DATA_INT, msg_dst_addr,\n            \"msg_type\",         \"Command\",          DATA_FORMAT, \"%02x\", DATA_INT, msg_cmd_id,\n            \"msg\",              \"Message\",          DATA_STRING, msg_data,\n            \"mode\",             \"Mode\",             DATA_STRING, msg_protocol_mode ? \"One-way\" : \"Two-way\",\n            \"version\",          \"Version\",          DATA_INT,    msg_protocol_version,\n            \"counter\",          \"Counter\",          DATA_COND, msg_protocol_mode == 1, DATA_INT,    msg_seq_nr,\n            \"mac\",              \"MAC\",              DATA_COND, msg_protocol_mode == 1, DATA_STRING, msg_mac,\n            \"flag_end\",         \"End flag\",         DATA_INT,    msg_end_flag,\n            \"flag_start\",       \"Start flag\",       DATA_INT,    msg_start_flag,\n            \"flag_mode\",        \"Mode flag\",        DATA_INT,    msg_protocol_mode,\n            \"flag_beacon\",      \"Beacon flag\",      DATA_INT,    msg_use_beacon,\n            \"flag_routed\",      \"Routed flag\",      DATA_INT,    msg_is_routed,\n            \"flag_lpm\",         \"LPM flag\",         DATA_INT,    msg_low_power_mode,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"dst_id\",\n        \"msg_type\",\n        \"msg\",\n        \"mode\",\n        \"version\",\n        \"counter\",\n        \"mac\",\n        \"flag_end\",\n        \"flag_start\",\n        \"flag_mode\",\n        \"flag_beacon\",\n        \"flag_routed\",\n        \"flag_lpm\",\n        \"mic\",\n        NULL,\n};\n\n// rtl_433 -c 0 -R 0 -g 40 -X \"n=uart,m=FSK_PCM,s=26,l=26,r=300,preamble={24}0x57fd99,decode_uart\" -f 868.89M\nr_device const somfy_iohc = {\n        .name        = \"Somfy io-homecontrol\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 26,\n        .long_width  = 26,\n        .reset_limit = 300, // UART encoding has at most 9 0's, nominal 234 us.\n        .decode_fn   = &somfy_iohc_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/somfy_rts.c",
    "content": "/** @file\n    Somfy RTS.\n\n    Copyright (C) 2020 Matthias Schulz <mschulz@seemoo.tu-darmstadt.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nSomfy RTS.\n\nProtocol description:\nThe protocol is very well defined under the following two links:\n[1] https://pushstack.wordpress.com/somfy-rts-protocol/\n[2] https://patentimages.storage.googleapis.com/bd/ae/4f/bf24e41e0161ca/US8189620.pdf\n\nEach frame consists of a preamble with hardware and software sync pulses followed by the manchester encoded data pulses.\nA rising edge describes a data bit 1 and a falling edge a data bit 0. The preamble is different for the first frame and\nfor retransmissions. In the end, the signal is first decoded using an OOK PCM decoder and within the callback, only the\ndata bits will be manchester decoded.\n\nIn the following, each character representing a low level \"_\" and a high level \"^\" is roughly 604 us long.\n\nFirst frames' preamble:\n\n    ^^^^^^^^^^^^^^^^___________^^^^____^^^^____^^^^^^^^_\n\nThe first long pulse is often wrongly detected, so I just make sure that it ends up in another row during decoding and\nthen only consider the rows containing the second part of the first frame preamble.\n\nRetransmission frames' preamble:\n\n    ^^^^____^^^^____^^^^____^^^^____^^^^____^^^^____^^^^____^^^^^^^^_\n\nOn some devices (see #2356) the preamble is two bytes shorter apparently?\n\nThe data is manchester encoded _^ represents a 1 and ^_ represents a 0. The data section consists of 56 bits that equals\n7 bytes of scrambled data. The data is scrambled by XORing each following byte with the last scrambled byte. After\ndescrambling, the 7 bytes have the following meaning conting byte from left to right as in big endian byte order:\n\n- byte 0:   called \"random\" in [1] and \"key\" in [2], in the end it is just the seed for the scrambler\n- byte 1:   The higher nibble represents the control command, the lower nibble is the frame's checksum calculated by XORing\n            all nibbles\n- byte 2-3: Replay counter value in big endian byte order\n- byte 4-6: Remote control channel's address\n\nOn some devices (see #2356) there are two extra bytes for a total of 80 bits apparently?\n\n## TEL-FIX wall-mounted remote control for RadioLoop Motor\n\nThere is a quirk with TEL-FIX wall-mounted remote control for RadioLoop Motor:\nIt looks like the seed isn't random but actually the button code: 0x88 DOWN, 0x85 STOP, 0x86 UP.\nThe command is fixed to 0xf, which we use as indication that an actual command is in the seed.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int somfy_rts_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    char const *const control_strs[] = {\n            \"? (0)\",\n            \"My (1)\",\n            \"Up (2)\",\n            \"My + Up (3)\",\n            \"Down (4)\",\n            \"My + Down (5)\",\n            \"Up + Down (6)\",\n            \"My + Up + Down (7)\",\n            \"Prog (8)\",\n            \"Sun + Flag (9)\",\n            \"Flag (10)\",\n            \"? (11)\",\n            \"? (12)\",\n            \"? (13)\",\n            \"? (14)\",\n            \"? (15)\",\n    };\n\n    char const *const seed_strs[] = {\n            \"? (0)\",\n            \"? (1)\",\n            \"? (2)\",\n            \"? (3)\",\n            \"? (4)\",\n            \"Stop (5)\",\n            \"Up (6)\",\n            \"? (7)\",\n            \"Down (8)\",\n            \"? (9)\",\n            \"? (10)\",\n            \"? (11)\",\n            \"? (12)\",\n            \"? (13)\",\n            \"? (14)\",\n            \"? (15)\",\n    };\n\n    // full retransmission pattern is {65}f0f0f0f0f0f0f0ff0\n    // some devices only have 49 bit preamble, don't require the first 16 bit\n    uint8_t const preamble_pattern_long[] = {0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xff, 0x00};\n    uint8_t const preamble_length_long = 49;\n    // alternate pattern if the bitrate wrongly shortens the 8x 1's to 7x.\n    uint8_t const preamble_pattern_rate[] = {0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xfe, 0x00};\n    uint8_t const preamble_length_rate = 48;\n    // full fist transmission pattern after sync is {25}f0f0ff0\n    uint8_t const preamble_pattern_short[] = {0xf0, 0xf0, 0xff, 0x00};\n    uint8_t const preamble_length_short = 25;\n\n    int is_retransmission = 0;\n    int decode_row = -1;\n    int bitpos = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] > 170) {\n            is_retransmission = 1;\n            bitpos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern_long, preamble_length_long) + preamble_length_long;\n            // Retry for wrong bitrate if needed\n            if (bitpos + 56 * 2 > bitbuffer->bits_per_row[row]) {\n                bitpos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern_rate, preamble_length_rate) + preamble_length_rate;\n            }\n            // Are there at least 56 MC bits in this row?\n            if (bitpos + 56 * 2 <= bitbuffer->bits_per_row[row]) {\n                decode_row = row;\n                break;\n            }\n        }\n        else if (bitbuffer->bits_per_row[row] > 130) {\n            is_retransmission = 0;\n            bitpos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern_short, preamble_length_short) + preamble_length_short;\n            if (bitpos + 56 * 2 <= bitbuffer->bits_per_row[row]) {\n                decode_row = row;\n                break;\n            }\n        }\n    }\n\n    if (decode_row < 0)\n        return DECODE_ABORT_EARLY;\n\n    // Are there at least 56 MC bits in this row?\n    if (bitpos + 56 * 2 > bitbuffer->bits_per_row[decode_row])\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_t decoded = {0};\n    bitbuffer_manchester_decode(bitbuffer, decode_row, bitpos, &decoded, 80);\n    if (decoded.num_rows == 0 || decoded.bits_per_row[0] < 56)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t *b = decoded.bb[0];\n\n    // descramble\n    for (int i = 6; i > 0; i--)\n        b[i] = b[i] ^ b[i - 1];\n\n    // calculate and verify checksum\n    int chksum_calc = xor_bytes(b, 7);\n    chksum_calc = (chksum_calc & 0xf) ^ (chksum_calc >> 4); // fold to nibble\n    if (chksum_calc != 0)\n        return DECODE_FAIL_MIC;\n\n    int seed    = b[0];\n    int control = (b[1] & 0xf0) >> 4;\n    int chksum  = b[1] & 0xf;\n    int counter = (b[2] << 8) | b[3];\n    // assume little endian as multiple addresses used by one remote control increase the address value in little endian byte order.\n    int address = (b[6] << 16) | (b[5] << 8) | b[4];\n\n    // lookup control\n    char const *control_str = control_strs[control];\n    if (control == 0xf) {\n        // TEL-FIX quirk\n        control_str = seed_strs[seed & 0xf];\n    }\n\n    decoder_logf(decoder, 2, __func__, \"seed=0x%02x, chksum=0x%x\", seed, chksum);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",          \"\",               DATA_STRING, \"Somfy-RTS\",\n            \"id\",             \"\",               DATA_FORMAT, \"%06X\", DATA_INT, address,\n            \"control\",        \"Control\",        DATA_STRING, control_str,\n            \"counter\",        \"Counter\",        DATA_INT,    counter,\n            \"retransmission\", \"Retransmission\", DATA_INT,    is_retransmission,\n            \"mic\",            \"Integrity\",      DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"control\",\n        \"counter\",\n        \"retransmission\",\n        \"mic\",\n        NULL,\n};\n\n// rtl_433 -r g001_433.414M_250k.cu8 -X \"n=somfy-test,m=OOK_PCM,s=604,l=604,t=40,r=10000,g=3000,y=2416\"\n// Nominal bit width is ~604 us, RZ, short=long\n\nr_device const somfy_rts = {\n        .name        = \"Somfy RTS\",\n        .modulation  = OOK_PULSE_PCM,\n        .short_width = 604,   // each pulse is ~604 us (nominal bit width)\n        .long_width  = 604,   // each pulse is ~604 us (nominal bit width)\n//        .sync_width     = 2416,  // hardware sync pulse is ~2416 us (4 x nominal bit width), software sync pulse is ~4550 us. Commented, as sync_width has no effect on the PCM decoder.\n        .gap_limit   = 3000,  // largest off between two pulses is ~2416 us during sync. Gap between start pulse (9664 us) and first frame is 6644 us (11 x nominal bit width), 3000 us will split first message into two rows one with start pulse and one with first frame\n        .reset_limit = 10000, // larger than gap between start pulse and first frame (6644 us = 11 x nominal bit width) to put start pulse and first frame in two rows, but smaller than inter-frame space of 30415 us\n        .tolerance   = 20,\n        .decode_fn   = &somfy_rts_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/springfield.c",
    "content": "/** @file\n    Springfield PreciseTemp Wireless Temperature and Soil Moisture Station.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nSpringfield PreciseTemp Wireless Temperature and Soil Moisture Station.\n\nNote: this is a false positive for AlectoV1.\n\nhttp://www.amazon.com/Springfield-Digital-Moisture-Meter-Freeze/dp/B0037BNHLS\n\nData is transmitted in 9 nibbles\n\n    [id0] [id1] [flags] [temp0] [temp1] [temp2] [moist] [chk] [unkn]\n\n- id: 8 bit a random id that is generated when the sensor starts\n- flags(3): Battery low flag, 1 when the battery is low, otherwise 0 (ok)\n- flags(2): TX Button Pushed, 1 when the sensor sends a reading while pressing the button on the sensor\n- flags(1,0): Channel number that can be set by the sensor (1, 2, 3, X)\n- temp: 12 bit Temperature Celsius x10 in 3 nibbles 2s complement\n- moist: 4 bit Moisture Level of 0 - 10\n- chk: 4 bit Checksum of nibbles 0 - 6 (simple xor of nibbles)\n- unkn: 4 bit Unknown\n\nActually 37 bits for all but last transmission which is 36 bits.\n*/\n\n#include \"decoder.h\"\n\nstatic int springfield_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int ret = 0;\n    unsigned tmpData;\n    unsigned savData = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        if (bitbuffer->bits_per_row[row] != 36 && bitbuffer->bits_per_row[row] != 37)\n            continue; // DECODE_ABORT_LENGTH\n        uint8_t *b = bitbuffer->bb[row];\n        tmpData = ((unsigned)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];\n        if (tmpData == 0xffffffff)\n            continue; // DECODE_ABORT_EARLY\n        if (tmpData == savData)\n            continue;\n        savData = tmpData;\n\n        int chk = xor_bytes(b, 4); // sum nibble 0-7\n        chk = (chk >> 4) ^ (chk & 0x0f); // fold to nibble\n        if (chk != 0)\n            continue; // DECODE_FAIL_MIC\n\n        int sid      = (b[0]);\n        int battery  = (b[1] >> 7) & 1;\n        int button   = (b[1] >> 6) & 1;\n        int channel  = ((b[1] >> 4) & 0x03) + 1;\n        int temp     = (int16_t)(((b[1] & 0x0f) << 12) | (b[2] << 4)); // uses sign extend\n        float temp_c = (temp >> 4) * 0.1f;\n        int moisture = (b[3] >> 4) * 10; // Moisture level is 0-10\n        //int uk1      = b[4] >> 4; /* unknown. */\n\n        // reduce false positives by checking specified sensor range, this isn't great...\n        if (temp_c < -30 || temp_c > 70) {\n            decoder_logf(decoder, 2, __func__, \"temperature sanity check failed: %.1f C\", temp_c);\n            return DECODE_FAIL_SANITY;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Springfield-Soil\",\n                \"id\",               \"SID\",          DATA_INT,    sid,\n                \"channel\",          \"Channel\",      DATA_INT,    channel,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery,\n                \"transmit\",         \"Transmit\",     DATA_STRING, button ? \"MANUAL\" : \"AUTO\", // TODO: delete this\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                \"moisture\",         \"Moisture\",     DATA_FORMAT, \"%d %%\", DATA_INT, moisture,\n                \"button\",           \"Button\",       DATA_INT,    button,\n//                \"uk1\",            \"uk1\",          DATA_INT,    uk1,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        ret++;\n    }\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"transmit\", // TODO: delete this\n        \"temperature_C\",\n        \"moisture\",\n        \"button\",\n        \"mic\",\n        NULL,\n};\n\nr_device const springfield = {\n        .name        = \"Springfield Temperature and Soil Moisture\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 5000,\n        .reset_limit = 9200,\n        .decode_fn   = &springfield_decode,\n        .priority    = 10, // Alecto collision, if Alecto checksum is correct it's not Springfield-Soil\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/srsmith_pool_srs_2c_tx.c",
    "content": "/** @file\n    SRSmith Pool Light Remote Control, Model SRS-2C-TX.\n\n    Copyright (C) 2022 gcohen55\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nSRSmith Pool Light Remote Control, Model SRS-2C-TX.\n\nThe SR Smith remote control sends broadcasts of ~144 bits and it comes in shifted (similar to the Maverick XR30 BBQ Sensor)\n- Frequency: 915MHz\n\nData Layout:\n\n    PPPP WWWW S UUUU C B T PP\n\n- P: 32 bit preamble (0xaaaaaaaa; 7 or 8 bits shifted left for analysis)\n- W: 32 bit sync word (0xd391d391)\n- S: 8 bit size (so far I've only seen 0x07)\n- U: 32 bit unknown (I always see 0x01fffff5 here)\n- C: 8 bit pin code is located in the bottom nibble of this byte, inverted and reversed.\n- B: 8 bit contains the ID of the button that was pushed on the remote\n- T: 8 bit CRC-8, poly 1, init 1 from the 16 bytes that we don't know (U) until the button that was pressed (B)\n- P: 16 bit CRC-16, poly 0x8005, init 0xFFFF, of the packet from the size (S) until the CRC-8 (T)\n\nFormat String:\n\n    PRE:32h SYNC: 32h SIZE: hh UNSURE:32h | UNSURE: 4b | PIN ~^4b |  BTN: hh | CRC-8: hh | CRC-16: hhhh\n\nCapture raw:\n\n    -f 915M -X n=SRSmith,m=FSK_PCM,s=100,l=100,r=4096,preamble=d391d391\n*/\n\n// size byte + 7 byte message + two byte crc == 10 bytes\n#define TOTAL_PACKET_SIZE_BYTES 10\n#define BUTTON_ID_ONE           0x0d\n#define BUTTON_ID_TWO           0x1f\n#define BUTTON_ID_S             0x07\n#define BUTTON_ID_M             0x0b\n\nstatic int srsmith_pool_srs_2c_tx_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // part of preamble + sync word\n    uint8_t const preamble[] = {0xaa, 0xd3, 0x91, 0xd3, 0x91};\n    int const preamble_length = sizeof(preamble) * 8;\n\n    if (bitbuffer->num_rows != 1)\n        return DECODE_ABORT_EARLY;\n\n    // minimum: TOTAL_PACKET_SIZE_BYTES * 8 + sync word length (4)*8 + preamble byte (1*8) == 120\n    // maximum: TOTAL_PACKET_SIZE_BYTES * 8 + sync word length (4)*8 + preamble bytes (4*8) == 144\n    if (bitbuffer->bits_per_row[0] < 120 || bitbuffer->bits_per_row[0] > 144)\n        return DECODE_ABORT_LENGTH;\n\n    // next line does the search for the preamble+sync bits, returns the bit position where the preamble+sync bits START\n    // so we shift that by the number of the preamble+sync bits\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0, preamble, preamble_length) + preamble_length;\n    if (start_pos >= bitbuffer->bits_per_row[0]) {\n        return DECODE_ABORT_EARLY; // preamble/sync missing\n    }\n\n    // bytes are there, length is right, let's extract into b\n    uint8_t b[TOTAL_PACKET_SIZE_BYTES];\n    // now we're extracting the bytes -- we know what the total size /should/ be\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, b, TOTAL_PACKET_SIZE_BYTES * 8);\n    int total_length      = bitbuffer->bits_per_row[0];\n    int sub_packet_length = b[0];\n\n    // sub-packet (packet within packet that has commands and baby parity) actually starts at b[1]\n    uint32_t unknown_field = ((uint32_t)b[1] << 24) | (b[2] << 16) | (b[3] << 8) | (b[4]); // not sure what these four bytes are.\n                                                                                           //\n    // get xmitted pin (byte 12, inverted, reversed, bottom nibble)\n    uint8_t pin_container          = b[5];\n    uint8_t inverted_pin_container = ~pin_container;                   // invert the pin\n    uint8_t reversed_pin           = reverse8(inverted_pin_container); // reverse the bits\n\n    // convert just the first four bits to string that contains 4 bits that the pin is\n    char pin_string[5] = {0};\n    snprintf(pin_string, sizeof(pin_string), \"%d%d%d%d\",\n            (reversed_pin & 0x80 ? 1 : 0),\n            (reversed_pin & 0x40 ? 1 : 0),\n            (reversed_pin & 0x20 ? 1 : 0),\n            (reversed_pin & 0x10 ? 1 : 0));\n\n    // button that was pressed\n    uint8_t button_id = b[6];\n\n    // get label for the button that was pressed\n    char const *button_string;\n\n    switch (button_id) {\n    case BUTTON_ID_ONE:\n        button_string = \"On/Off Channel 1\";\n        break;\n    case BUTTON_ID_TWO:\n        button_string = \"On/Off Channel 2\";\n        break;\n    case BUTTON_ID_S:\n        button_string = \"Color Sync\";\n        break;\n    case BUTTON_ID_M:\n        button_string = \"ON/OFF Control - M\";\n        break;\n    default:\n        button_string = \"Unknown\";\n        break;\n    }\n\n    // grab CRCs/parity\n    // the \"sub packet parity\" is the parity for the sub-packet within the modem packet (e.g., between the size byte and the start of the crc-16)\n    // see above for description.\n    uint8_t sub_packet_parity            = b[7];\n    uint8_t calculated_sub_packet_parity = crc8(&b[1], 6, 1, 1);\n\n    // total CRC-16\n    // this CRC represents the entire packet sent by the modem in the remote\n    uint16_t total_crc            = (b[8] << 8) | (b[9]);\n    uint16_t calculated_total_crc = crc16(&b[0], 8, 0x8005, 0xFFFF);\n    decoder_logf(decoder, 1, __func__,\n            \"total_length: %d, sub_packet_length: %d, sub_packet_parity: %hhx, calculated_sub_packet_parity: %hhx, total_crc: %04x, calculated_total_crc: %04x, button_id: %hhx, button_string: %s, pin_string: %s\",\n            total_length, sub_packet_length, sub_packet_parity, calculated_sub_packet_parity, total_crc, calculated_total_crc, button_id, button_string, pin_string);\n\n    if (total_crc != calculated_total_crc) {\n        // parity fail.\n        return DECODE_FAIL_MIC;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                  \"\",                         DATA_STRING, \"SRSmith-SRS2CTX\",\n            \"id\",                     \"Id\",                       DATA_INT, reversed_pin, // technically peoples pins should be different on each remote\n            \"button_press\",           \"Pushed Button ID\",         DATA_FORMAT, \"%02x\", DATA_INT, button_id,\n            \"button_press_name\",      \"Pushed Button String\",     DATA_STRING, button_string,\n            \"unknown\",                \"Unknown\",                  DATA_FORMAT, \"%08x\", DATA_INT, unknown_field,\n            \"mic\",                    \"Integrity\",                DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"mic\",\n        \"id\",\n        \"button_press\",\n        \"button_press_name\",\n        \"unknown\",\n        NULL,\n};\n\nr_device const srsmith_pool_srs_2c_tx = {\n        .name        = \"SRSmith Pool Light Remote Control SRS-2C-TX (-f 915M)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100,\n        .long_width  = 100,\n        .reset_limit = 4096,\n        .decode_fn   = &srsmith_pool_srs_2c_tx_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/steelmate.c",
    "content": "/** @file\n    Steelmate TPMS FSK protocol.\n\n    Copyright (C) 2016 Benjamin Larsson\n    Copyright (C) 2016 John Jore\n    Copyright (C) 2025 Bruno OCTAU (ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nSteelmate TPMS FSK protocol.\n\nReference:\n\n- model TP-S15\n\nBrand:\n\n- Steelmate\n- R-Lake\n\nS.a. issue #3200 Pressure issue :\n\n- The originally guessed formula was : Pressure in PSI scale 2, but more the pressure is important more the value diverged between the TPMS display and rtl_433.\n- New analysis : Based on data collected by \\@e100 + the technical specification ( 0~7.9Bar ) + analysis by \\@e100 and refined by \\@ProfBoc75, the pressure is given in Bar at scale 32.\n\nPacket payload:\n\n- 9 bytes.\n\nBytes 2 to 9 are inverted Manchester with swapped MSB/LSB:\n\n                                  0  1  2  3  4  5  6  7  8\n                       [00] {72} 00 00 7f 3c f0 d7 ad 8e fa\n    After translating            00 00 01 c3 f0 14 4a 8e a0\n                                 SS SS AA II II PP TT BB CC\n\n- S = sync, (0x00)\n- A = preamble, (0x01)\n- I = id, 0xc3f0\n- P = Pressure in Bar, scale 32, 0xA0 / 32 = 5 Bar, or 0xA0 * 3.125 = 500 kPA, see issue #3200\n- T = Temperature in Celcius + 50, 0x4a = 24 'C\n- B = Battery, where mV = 3900-(value*10). E.g 0x8e becomes 3900-(1420) = 2480mV.\n-     This calculation is approximate fit from sample data, any improvements are welcome.\n-   > If this field is set to 0xFF, a \"fast leak\" alarm is triggered.\n-   > If this field is set to 0xFE, a \"slow leak\" alarm is triggered.\n- C = Checksum, adding bytes 2 to 7 modulo 256 = byte 8,(0x01+0xc3+0xf0+0x14+0x4a+0x8e) modulus 256 = 0xa0\n\n*/\n\n#include \"decoder.h\"\n\nstatic int steelmate_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x00, 0x00, 0x7f}; // inverted, raw value is 0x5a\n    //Loop through each row of data\n    for (int row = 0; row < bitbuffer->num_rows; row++) {\n        //Payload is inverted Manchester encoded, and reversed MSB/LSB order\n        unsigned row_len = bitbuffer->bits_per_row[row];\n\n        //Length must be 72, 73, 208 or 209 bits to be considered a valid packet\n        if (row_len != 72 && row_len != 73 && row_len != 209 && row_len != 208)\n            continue; // DECODE_ABORT_LENGTH\n\n        //Valid preamble? (Note, the data is still wrong order at this point. Correct pre-amble: 0x00 0x00 0x01)\n        unsigned bitpos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, 24);\n        if (bitpos > row_len - 72)\n            continue; // DECODE_ABORT_EARLY\n        bitbuffer_invert(bitbuffer);\n        uint8_t b[9];\n        bitbuffer_extract_bytes(bitbuffer, row, bitpos, b, 72);\n\n        reflect_bytes(b, 9);\n\n        //Checksum is a sum of all the other values\n        if ((add_bytes(&b[2], 6) & 0xFF) != b[8])\n            continue; // DECODE_FAIL_MIC\n\n        //Sensor ID\n        uint8_t id1 = b[3];\n        uint8_t id2 = b[4];\n\n        //Pressure is stored as 32 * the Bar\n        uint8_t p1 = b[5];\n\n        //Temperature is sent as degrees Celcius + 50.\n        int temp_c = b[6] - 50;\n\n        //Battery voltage is stored as 100*(3.9v-<volt>).\n        uint8_t b1     = b[7];\n        int battery_mv = 3900 - b1 * 10;\n\n        int sensor_id      = (id1 << 8) | id2;\n        float pressure_kpa = p1 * 3.125f;                         // as guessed in #3200\n\n        char sensor_idhex[7];\n        snprintf(sensor_idhex, sizeof(sensor_idhex), \"0x%04x\", sensor_id);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"type\",             \"\",             DATA_STRING,  \"TPMS\",\n                \"model\",            \"\",             DATA_STRING,  \"Steelmate\",\n                \"id\",               \"\",             DATA_STRING,  sensor_idhex,\n                \"pressure_kPa\",     \"\",             DATA_FORMAT,  \"%.0f kPa\", DATA_DOUBLE,  pressure_kpa,\n                \"temperature_C\",    \"\",             DATA_FORMAT,  \"%d C\",     DATA_INT,     temp_c,\n                \"battery_mV\",       \"\",             DATA_COND,    b1 < 0xFE,  DATA_INT,     battery_mv,\n                \"alarm\",            \"\",             DATA_COND,    b1 == 0xFF, DATA_STRING, \"fast leak\",\n                \"alarm\",            \"\",             DATA_COND,    b1 == 0xFE, DATA_STRING, \"slow leak\",\n                \"mic\",              \"Integrity\",    DATA_STRING,  \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    //Was not a Steelmate TPMS after all\n    // TODO: improve return codes by aborting early\n    return DECODE_FAIL_SANITY;\n}\n\nstatic char const *const output_fields[] = {\n        \"type\",\n        \"model\",\n        \"id\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"battery_mV\",\n        \"alarm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const steelmate = {\n        .name        = \"Steelmate TPMS\",\n        .modulation  = FSK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 50,\n        .long_width  = 50,\n        .reset_limit = 120,\n        .decode_fn   = &steelmate_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/telldus_ft0385r.c",
    "content": "/** @file\n    Telldus weather station indoor unit FT0385R.\n\n    Copyright (C) 2021 Jarkko Sonninen <kasper@iki.fi>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nTelldus weather station indoor unit.\n\nAs the indoor unit receives a message from the outdoor unit,\nit sends 3 radio messages\n- Oregon-WGR800\n- Oregon-THGR810 or Oregon-PCR800\n- Telldus-FT0385R (this one)\n\nThe outdoor unit is the same as SwitchDoc Labs WeatherSense FT020T\nand Cotech 36-7959 Weatherstation.\n\n433Mhz, OOK modulated with Manchester encoding, halfbit-width 500 us.\nMessage length is 5 + 296 bit.\nEach message starts with bits 10100 1110. First 9 bits is considered as a preamble.\nThe first 5 bits of the preamble is ignored and the rest of the message is used in CRC calculation.\n\nExample raw message:\n\n    {298} e1 23 00 0c 17 2b 0b 5a 09 34 00 00 00 00 00 03 00 1b 03 90 12 1b 12 1b 43 6e 4c 92 23 27 49 28 c8 ff fa fa 4b\n\nExample raw message, if outdoor data is unavailable:\n\n    {298} e0 73 7f fb fb fb fb fb fb fb ff fb ff fb 3f fb ff fb ff fb ff fb ff fb 47 fb 7b 6c 26 27 0a 27 93 ff fb fb 97\n\nIntegrity check is done using CRC8 using poly=0x31  init=0xc0\n\nMessage layout\n\n    AAAABBBB BBBBCCCC ZJIHGFED DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG HHHHHHHH IIIIIIII JJJJJJJJ\n    KKKKKKKK KKKKKKKK LLLLLLLL LLLLLLLL MMMMMMMM MMMMMMMM NNNNNNNN NNNNNNNN OOOOOOOO OOOOOOOO PPPPPPPP PPPPPPPP\n    SSSSQQQQ QQQQQQQQ RRRRRRRR SSSSSSSS TTTTTTTT UUUUUUUU UUUUUUUU VVVVVVVV VVVVVVVV\n    WWWWWWWW WWWWWWWW XXXXXXXX YYYYYYYY\n\n- A : 4 bit: ? Type code ?, fixed 0xe\n- B : 8 bit: ? Indoor serial number or flags. Changes in reset.\n- C : 4 bit: ? Flags, normally 0x3, Battery indicator 0 = Ok, 4 = Battery low ?\n- Z : 1 bit: ? Unknown, possibly not used\n- D : 9 bit: Wind Avg, scaled by 10. MSB in byte 2\n- E : 9 bit: Wind Gust, scaled by 10. MSB in byte 2\n- F : 9 bit: Wind direction in degrees. MSB in byte 2\n- G : 9 bit: ? Wind 2, scaled by 10. MSB in byte 2\n- H : 9 bit: ? Wind direction 2 in degrees. MSB in byte 2\n- I : 9 bit: ? Wind 3, scaled by 10. MSB in byte 2\n- J : 9 bit: ? Wind direction 3 in degrees. MSB in byte 2\n- K : 16 bit: ? Rain rate in mm, scaled by 10\n- L : 16 bit: Rain 1h mm, scaled by 10\n- M : 16 bit: Rain 24h mm, scaled by 10. Unavailable value = 0x3ffb.\n- N : 16 bit: Rain week mm, scaled by 10\n- O : 16 bit: Rain month mm, scaled by 10\n- P : 16 bit: Rain total in mm, scaled by 10\n- Q : 12 bit: Temperature in Fahrenheit, offset 400, scaled by 10\n- R : 8 bit: Humidity\n- S : 12 bit: Temperature indoor in Fahrenheit, offset 400, scaled by 10. MSB in byte 24.\n- T : 8 bit: Humidity indoor\n- U : 16 bit: Pressure absolute in hPa\n- V : 16 bit: Pressure relative in hPa\n- W : 16 bit: ? Light intensity. No sensor: 0xfffa, outdoor data is unavailable: 0xfffb\n- X : 8 bit: ? UV index. No sensor: 0xfa, outdoor data is unavailable: 0xfb\n- Y : 8 bit: CRC, poly 0x31, init 0xc0\n\nIf outdoor data is unavailable, the value is 0xfb, 0x1fb, 0x7fb or 0xfffb\nTelldus outdoor unit is missing Light and UV sensors, but they may be seen in the messages.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int telldus_ft0385r_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x14, 0xe0}; // 9 bits\n\n    int r = -1;\n    uint8_t b[37]; // 296 bits, 37 bytes\n    data_t *data;\n\n    if (bitbuffer->num_rows > 2) {\n        return DECODE_ABORT_EARLY;\n    }\n    if (bitbuffer->bits_per_row[0] < 296 && bitbuffer->bits_per_row[1] < 296) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    for (int i = 0; i < bitbuffer->num_rows; ++i) {\n        unsigned pos = bitbuffer_search(bitbuffer, i, 0, preamble, 9);\n        pos += 8;\n\n        if (pos + 296 > bitbuffer->bits_per_row[i])\n            continue; // too short or not found\n\n        r = i;\n        bitbuffer_extract_bytes(bitbuffer, i, pos, b, 296);\n        break;\n    }\n\n    if (r < 0) {\n        decoder_log(decoder, 2, __func__, \"Couldn't find preamble\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    if (crc8(b, 37, 0x31, 0xc0)) {\n        decoder_log(decoder, 2, __func__, \"CRC8 fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    // Extract data from buffer\n    //int header    = (b[0] & 0xf0) >> 4;                           // [0:4]\n    //int serial    = ((b[0] & 0x0f) << 4) | ((b[1] & 0xf0) >> 4);  // [8:8]\n    //int flags     = (b[1] & 0x0f);                                // [12:4]\n    //int unk16     = (b[1] & 0x80) >> 7;                           // [16:1]\n    //int deg3_msb  = (b[2] & 0x40) >> 6;                           // [17:1]\n    //int wind3_msb = (b[2] & 0x20) >> 5;                           // [18:1]\n    //int deg2_msb  = (b[2] & 0x10) >> 4;                           // [19:1]\n    //int wind2_msb = (b[2] & 0x08) >> 3;                           // [20:1]\n    int deg_msb   = (b[2] & 0x04) >> 2;                           // [21:1]\n    int gust_msb  = (b[2] & 0x02) >> 1;                           // [22:1]\n    int wind_msb  = (b[2] & 0x01);                                // [23:1]\n    int wind      = (wind_msb << 8) | b[3];                       // [24:8]\n    int gust      = (gust_msb << 8) | b[4];                       // [32:8]\n    int wind_dir  = (deg_msb << 8) | b[5];                        // [40:8]\n    //int wind2     = (wind2_msb << 8) | b[6];                      // [48:8]\n    //int wind2_dir = (deg2_msb << 8) | b[7];                       // [56:8]\n    //int wind3     = (wind3_msb << 8) | b[8];                      // [64:8]\n    //int wind3_dir = (deg3_msb << 8) | b[9];                       // [72:8]\n    //int rain_rate = ((b[10]) << 8) | (b[11]);                     // [80:12]\n    //int rain_1h   = ((b[12]) << 8) | (b[13]);                     // [96:12]\n    //int rain_24h  = ((b[14]) << 8) | (b[15]);                     // [112:12]\n    //int rain_week = ((b[16]) << 8) | (b[17]);                     // [128:12]\n    //int rain_mon  = ((b[18]) << 8) | (b[19]);                     // [144:12]\n    int rain_tot  = ((b[20]) << 8) | (b[21]);                     // [160:16]\n    //int rain_tot2 = ((b[22]) << 8) | (b[23]);                     // [176:16]\n    int temp2_msb  = (b[24] & 0xf0) >> 4;                         // [192:4]\n    int temp_raw  = ((b[24] & 0x0f) << 8) | (b[25]);              // [196:12]\n    int humidity  = (b[26]);                                      // [208:8]\n    int temp2_raw = (temp2_msb << 8) | (b[27]);                   // [216:8]\n    int humidity2 = (b[28]);                                      // [224:8]\n    int pressure  = ((b[29]) << 8) | (b[30]);                     // [232:16]\n    //int pressure2 = ((b[31]) << 8) | (b[32]);                     // [248:16]\n    //int light_lux = ((b[33]) << 8) | (b[34]);                     // [264:16]\n    //int uv        = (b[35]);                                      // [280:8]\n    //int crc       = (b[36]);                                      // [288:8]\n\n    //int batt_low  = (flags & 0x04) >> 3;\n    float temp_f = (temp_raw - 400) * 0.1f;\n    float temp2_f = (temp2_raw - 400) * 0.1f;\n\n    /* clang-format off */\n    if (temp_raw != 0x7fb) {\n        data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Telldus-FT0385R\",\n            //\"battery_ok\",       \"Battery\",          DATA_INT,    !batt_low,\n            \"temperature_F\",    \"Temperature\",      DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"temperature_2_F\",  \"Temperature in\",   DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp2_f,\n            \"humidity_2\",       \"Humidity in\",      DATA_FORMAT, \"%u %%\", DATA_INT, humidity2,\n            \"pressure_hPa\",     \"Pressure\",         DATA_FORMAT, \"%.1f hPa\", DATA_DOUBLE, pressure * 0.1f,\n            //\"rain_rate_mm_h\",   \"Rain Rate\",        DATA_FORMAT, \"%.2f mm/h\", DATA_DOUBLE, rain_rate * 0.1f,\n            \"rain_mm\",          \"Rain\",             DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_tot * 0.1f,\n            \"wind_dir_deg\",     \"Wind direction\",   DATA_INT,    wind_dir,\n            \"wind_avg_m_s\",     \"Wind\",             DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, wind * 0.1f,\n            \"wind_max_m_s\",     \"Gust\",             DATA_FORMAT, \"%.1f m/s\", DATA_DOUBLE, gust * 0.1f,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    } else {\n        // No outdoor data\n        data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"Telldus-FT0385R\",\n            //\"battery_ok\",       \"Battery\",          DATA_INT,    !batt_low,\n            \"temperature_2_F\",  \"Temperature in\",   DATA_FORMAT, \"%.1f F\", DATA_DOUBLE, temp2_f,\n            \"humidity_2\",       \"Humidity in\",      DATA_FORMAT, \"%u %%\", DATA_INT, humidity2,\n            \"pressure_hPa\",     \"Pressure\",         DATA_FORMAT, \"%.1f hPa\", DATA_DOUBLE, pressure * 0.1f,\n            \"mic\",              \"Integrity\",        DATA_STRING, \"CRC\",\n            NULL);\n    }\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const telldus_ft0385r_output_fields[] = {\n        \"model\",\n        \"battery_ok\",\n        \"temperature_F\",\n        \"humidity\",\n        \"temperature_2_F\",\n        \"humidity_2\",\n        \"pressure_hPa\",\n        \"rain_rate_mm_h\",\n        \"rain_mm\",\n        \"wind_dir_deg\",\n        \"wind_avg_m_s\",\n        \"wind_max_m_s\",\n        \"mic\",\n        NULL,\n};\n\nr_device const telldus_ft0385r = {\n        .name        = \"Telldus weather station FT0385R sensors\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 500,\n        .long_width  = 0, // not used\n        .gap_limit   = 1200, // Not used\n        .reset_limit = 2400,\n        .decode_fn   = &telldus_ft0385r_decode,\n        .fields      = telldus_ft0385r_output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_14_1504_v2.c",
    "content": "/** @file\n    Decoder for TFA Dostmann 14.1504.V2 (30.3254.01)\n    Radio-controlled grill and meat thermometer\n\n    Copyright (C) 2022-2023 Joël Bourquard <joel.bourquard@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for TFA Dostmann 14.1504.V2 (30.3254.01)\n\nCAUTION: Do not confuse with TFA Dostmann 14.1504 (30.3201) which had a completely different protocol => [71] Maverick ET-732/733 BBQ Sensor\n\nPayload format:\n- Preamble         {36} 0x7aaaaaa5c (for robustness we only use the tail: {24}0xaaaa5c)\n- Flags            {4}  OR between: 0x2=battery ok, 0x5=resync button\n- Temperature      {12} Raw temperature value. Temperature in C = (value/4)-532. Example: 0x8a0 = 20 C\n- Separator        {8}  0xff (differs if resync)\n- Digest           {16} 16-bit LFSR digest + final XOR\n\nTo get raw data:\n\n    rtl_433 -R 0 -X 'n=TFA-141504v2,m=FSK_PCM,s=360,l=360,r=4096,preamble={24}aaaa5c'\n\nExample payloads (excluding preamble):\n- Resync   = 7052f9cee3 (encoding differs from temperature readings => not handled)\n- No probe = 2700ffb791 (just like a temperature reading => in this case we report the appropriate probe status and no temperature reading)\n- ...\n- 20 C     = 28a0ffce69\n- 21 C     = 28a4ffa0f5\n- ...\n- 24 C     = 28b0ff6438\n- ...\n- 44 C     = 2900ff8c9d\n- ...\n*/\n\n#define NUM_BITS_PREAMBLE 24\n#define NUM_BYTES_DATA    5\n#define OFFSET_MIC        (NUM_BYTES_DATA - 2)\n#define NUM_BITS_DATA     (NUM_BYTES_DATA * 8)\n#define NUM_BITS_TOTAL    (NUM_BITS_PREAMBLE + NUM_BITS_DATA)\n#define NUM_BITS_MAX      (NUM_BITS_TOTAL + 12)\n\nstatic int tfa_14_1504_v2_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0xaa, 0xaa, 0x5c};\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n    unsigned const row = 0;\n    // signed value for safety\n    int available_bits = bitbuffer->bits_per_row[row];\n\n    // optional optimization: early exit if row too short\n    if (available_bits < NUM_BITS_TOTAL) {\n        return DECODE_ABORT_EARLY; // considered \"early\" because preamble not checked\n    }\n\n    // sync on preamble\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble, NUM_BITS_PREAMBLE);\n    available_bits -= start_pos;\n    if (available_bits < NUM_BITS_PREAMBLE) {\n        return DECODE_ABORT_EARLY; // no preamble found\n    }\n\n    // check min & max length\n    if (available_bits < NUM_BITS_TOTAL ||\n            available_bits > NUM_BITS_MAX) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t b[NUM_BYTES_DATA];\n    bitbuffer_extract_bytes(bitbuffer, row, start_pos + NUM_BITS_PREAMBLE, b, NUM_BITS_DATA);\n\n    uint8_t flags = b[0] >> 4;\n    // ignore resync button\n    if ((flags & 0x5) == 0x5) {\n        return DECODE_FAIL_SANITY;\n    }\n    unsigned battery_ok = (flags & 0x2) != 0;\n\n    if (b[2] != 0xff) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    uint16_t calc_mic = lfsr_digest16(b, OFFSET_MIC, 0x8810, 0x0d42) ^ 0x16eb;\n    uint16_t data_mic = (b[OFFSET_MIC] << 8) + b[OFFSET_MIC+1];\n    if (calc_mic != data_mic) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // we discard the last 2 bits as those are always zero\n    uint16_t raw_temp_c         = ((b[0] & 0xf) << 6) + (b[1] >> 2);\n    unsigned is_probe_connected = (raw_temp_c != 0x1c0);\n    float temp_c                = ((int)raw_temp_c) - 532;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING,    \"TFA-141504v2\",\n            \"battery_ok\",       \"Battery\",          DATA_INT,       battery_ok,\n            \"probe_fail\",       \"Probe failure\",    DATA_INT,       !is_probe_connected,\n            \"temperature_C\",    \"Temperature\",      DATA_COND,      is_probe_connected,     DATA_FORMAT,    \"%.0f C\",   DATA_DOUBLE,    temp_c,\n            \"mic\",              \"Integrity\",        DATA_STRING,    \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"battery_ok\",\n        \"probe_fail\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_14_1504_v2 = {\n        .name        = \"TFA Dostmann 14.1504.V2 Radio-controlled grill and meat thermometer\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 360,\n        .long_width  = 360,\n        .reset_limit = 4096,\n        .decode_fn   = &tfa_14_1504_v2_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_30_3196.c",
    "content": "/** @file\n    TFA Dostmann 30.3196 T/H outdoor sensor.\n\n    Copyright (c) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n    Documented by Ekkehart Tessmer.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nTFA Dostmann 30.3196 T/H outdoor sensor at 868.33M.\n\nhttps://www.tfa-dostmann.de/en/produkt/temperature-humidity-transmitter-11/\nhttps://clientmedia.trade-server.net/1768_tfadost/media/7/86/3786.pdf\n\nThe device comes with 'TFA Modus Plus' (indoor) base station.\nUp to three outdoor sensors can be operated (ch 1, 2, or 3).\n\n- At the start there is a 6 ms gap (FSK space)\n- Data is Manchester coded with a half-bit width of 245 us\n- The data row is repeated four times with 7 ms gaps (FSK space)\n\n- A second layer of manchester coding yields 16 bit preamble and 48 bits data\n- The 64 bits of preamble 0xcccccccccccccccc, after first MC 0xaaaaaaaa, after second MC 0xffff\n- A data row consists of 48 bits (6 Bytes).\n\nData layout:\n\n    FFFFFFFF ??CCTTTT TTTTTTTT BHHHHHHH AAAAAAAA AAAAAAAA\n\n- F: 8 bit Fixed message type 0xA8. d2d2d333 -> 9995 -> 57 (~ A8)\n- C: 2 bit Channel number (1,2,3,X)\n- T: 12 bit Temperature (Celsius) offset 40 scaled 10\n- B: 1 bit Low battery indicator\n- H: 7 bit Humidity\n- A: 16 bit LFSR hash, gen 0x8810, key 0x22d0\n- e.g. TYPE:8h ?2h CH:2d TEMP:12d BATT:1b HUM:7d CHK?16h\n\nExample data:\n\n    a8 21 fa 5b 38 54 : 10101000 00100001 11111010 01011011 00111000 01010100\n    a8 22 22 5e 90 48 : 10101000 00100010 00100010 01011110 10010000 01001000\n\n*/\n\n#include \"decoder.h\"\n\nstatic int tfa_303196_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x55, 0x56}; // 12 bit preamble + 4 bit data\n    int row;\n    data_t *data;\n    uint8_t *b;\n    bitbuffer_t databits = {0};\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 2, 48 * 2 + 12); // expected are 4 rows, require 2\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, 16);\n    start_pos += 12; // skip preamble\n\n    if (bitbuffer->bits_per_row[row] - start_pos < 48 * 2)\n        return DECODE_ABORT_LENGTH; // short buffer or preamble not found\n\n    bitbuffer_manchester_decode(bitbuffer, row, start_pos, &databits, 48);\n\n    if (databits.bits_per_row[0] < 48)\n        return DECODE_ABORT_LENGTH; // payload malformed MC\n\n    b = databits.bb[0];\n\n    if (b[0] != 0xa8)\n        return DECODE_FAIL_SANITY;\n\n    uint16_t digest = (b[4] << 8) | (b[5]);\n    int chk         = lfsr_digest16(b, 4, 0x8810, 0x22d0) ^ digest;\n\n    //decoder_logf_bitrow(decoder, 0, __func__, b, 48, \"TFA-303196 (%08x  %04x  %04x)\", chk_data, digest, session);\n\n    int channel     = (b[1] >> 4) + 1;\n    int temp_raw    = ((b[1] & 0x0F) << 8) | b[2];\n    float temp_c    = (temp_raw - 400) * 0.1f;\n    int battery_low = b[3] >> 7;\n    int humidity    = b[3] & 0x7F;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"TFA-303196\",\n            \"id\",               \"\",             DATA_INT,    chk,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"missing\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_303196 = {\n        .name        = \"TFA Dostmann 30.3196 T/H outdoor sensor\",\n        .modulation  = FSK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 245,\n        .long_width  = 0, // unused\n        .tolerance   = 60,\n        .reset_limit = 22000,\n        .decode_fn   = &tfa_303196_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_30_3221.c",
    "content": "/** @file\n    Temperature/Humidity outdoor sensor TFA 30.3221.02.\n\n    Copyright (C) 2020 Odessa Claude\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nTemperature/Humidity outdoor sensor TFA 30.3221.02.\n\nOther compatible sensors: 30.3249.02\nThis is the same as LaCrosse-TX141THBv2 and should be merged.\n\nS.a. https://github.com/RFD-FHEM/RFFHEM/blob/master/FHEM/14_SD_WS.pm\n\n    0    4    | 8    12   | 16   20   | 24   28   | 32   36\n    --------- | --------- | --------- | --------- | ---------\n    0000 1001 | 0001 0110 | 0001 0000 | 0000 0111 | 0100 1001\n    IIII IIII | BSCC TTTT | TTTT TTTT | HHHH HHHH | XXXX XXXX\n\n- I:  8 bit random id (changes on power-loss)\n- B:  1 bit battery indicator (0=>OK, 1=>LOW)\n- S:  1 bit sendmode (0=>auto, 1=>manual)\n- C:  2 bit channel valid channels are 0-2 (1-3)\n- T: 12 bit unsigned temperature, offset 500, scaled by 10\n- H:  8 bit relative humidity percentage\n- X:  8 bit checksum digest 0x31, 0xf4\n\nThe sensor sends 3 repetitions at intervals of about 60 seconds.\n*/\n\n#include \"decoder.h\"\n\nstatic int tfa_303221_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row, sendmode, channel, battery_low, temp_raw, humidity;\n    float temp_c;\n    data_t *data;\n    uint8_t *b;\n    unsigned int device;\n\n    // Device send 4 row, checking for two repeated\n    row = bitbuffer_find_repeated_row(bitbuffer, (bitbuffer->num_rows > 4) ? 4 : 2, 40);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    // Checking for right number of bits per row\n    if (bitbuffer->bits_per_row[row] > 41)\n        return DECODE_ABORT_LENGTH;\n\n    bitbuffer_invert(bitbuffer);\n    b = bitbuffer->bb[row];\n\n    device = b[0];\n\n    // Sanity Check\n    if (device == 0)\n        return DECODE_FAIL_SANITY;\n\n    // Validate checksum\n    int observed_checksum = b[4];\n    int computed_checksum = lfsr_digest8_reflect(b, 4, 0x31, 0xf4);\n    if (observed_checksum != computed_checksum) {\n        return DECODE_FAIL_MIC;\n    }\n\n    temp_raw    = ((b[1] & 0x0F) << 8) | b[2];\n    temp_c      = (temp_raw - 500) * 0.1f;\n    humidity    = b[3];\n    battery_low = b[1] >> 7;\n    channel     = ((b[1] >> 4) & 3) + 1;\n    sendmode    = (b[1] >> 6) & 1;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"TFA-303221\",\n            \"id\",               \"Sensor ID\",    DATA_INT,    device,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"sendmode\",         \"Test mode\",    DATA_INT,    sendmode,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"sendmode\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_30_3221 = {\n        .name        = \"TFA Dostmann 30.3221.02 T/H Outdoor Sensor (also 30.3249.02)\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 235,\n        .long_width  = 480,\n        .reset_limit = 850,\n        .sync_width  = 836,\n        .decode_fn   = &tfa_303221_callback,\n        .priority    = 10, // This is the same as LaCrosse-TX141THBv2\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_drop_30.3233.c",
    "content": "/** @file\n    Decoder for TFA Drop 30.3233.01.\n\n    Copyright (C) 2020 Michael Haas\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/**\nTFA Drop is a rain gauge with a tipping bucket mechanism.\n\nLinks:\n\n - Product page:\n   - https://www.tfa-dostmann.de/en/produkt/wireless-rain-gauge-drop/\n - Manual 2019:\n   - https://clientmedia.trade-server.net/1768_tfadost/media/2/66/16266.pdf\n - Manual 2020:\n   - https://clientmedia.trade-server.net/1768_tfadost/media/3/04/16304.pdf\n - Discussion of protocol:\n   - https://github.com/merbanan/rtl_433/issues/1240\n\nThe sensor has part number 30.3233.01. The full package, including the\nbase station, has part number 47.3005.01.\n\nThe device uses PWM encoding:\n\n- 0 is encoded as 250 us pulse and a 500us gap\n- 1 is encoded as 500 us pulse and a 250us gap\n\nNote that this encoding scheme is inverted relative to the default\ninterpretation of short/long pulses in the PWM decoder in rtl_433.\nThe implementation below thus inverts the buffer. The protocol is\ndescribed below in the correct space, i.e. after the buffer has been\ninverted.\n\nNot every tip of the bucket triggers a message immediately. In some\ncases, artificially tipping the bucket many times lead to the base\nstation ignoring the signal completely until the device was reset.\n\nData layout:\n\n```\nCCCCIIII IIIIIIII IIIIIIII BCUU XXXX RRRRRRRR CCCCCCCC SSSSSSSS MMMMMMMM\nKKKK\n```\n\n- C: 4 bit message prefix, always 0x3\n- I: 2.5 byte ID\n- B: 1 bit, battery_low. 0 if battery OK, 1 if battery is low.\n- C: 1 bit, device reset. Set to 1 briefly after battery insert.\n- X: Transmission counter\n     - Possible values: 0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xE, 0xE.\n     - Rolls over.\n- R: LSB of 16-bit little endian rain counter\n- S: MSB of 16-bit little endian rain counter\n- C: Fixed to 0xaa\n- M: Checksum.\n   - Compute with reverse Galois LFSR with byte reflection, generator\n     0x31 and key 0xf4.\n- K: Unknown. Either b1011 or b0111.\n     - Distribution: 50:50.\n\n[Bitbench](http://triq.org/bitbench) string:\n\n```\nID:hh ID:hh ID:hh BAT_LOW:b RESET:b UNKNOWN:bb XMIT_COUNTER:h RAIN_A:d\nCONST:hh RAIN_B:d CHECK:8b UNKNOWN:bbxx xxxx\n```\n\nSome example data:\n\n```\nc240aaff09550021c\nc240aabf095500e04\nc240aafd095500b64\nc240aafb0955003e4\nc240aaf9095500a9c\nc212b7f9035500e5c\nc212b7f703550053c\nc212b7f5035500c44\n```\n\n\nThe rain bucket counter represents the number of tips of the rain\nbucket. Each tip of the bucket corresponds to 0.254mm of rain.\n\nThe rain bucket counter does not start at 0. Instead, the counter\nstarts at 65526 to indicate 0 tips of the bucket. The counter rolls\nover at 65535 to 0, which corresponds to 9 and 10 tips of the bucket.\n\nIf no change is detected, the sensor will continue broadcasting\nidentical values. This lasts at least for 20 minutes,\npotentially forever.\n\nThe second nibble of byte 3 is a transmission counter: 0x0, 0x2, 0x4,\n0x6, 0x8, 0xa, 0xc, 0xe. After the transmission with counter 0xe, the\ncounter rolls over to 0x0 on the next transmission and the cycle starts\nover.\n\nAfter battery insertion, the sensor will transmit 7 messages in rapid\nsuccession, one message every 3 seconds. After the first message,\nthe remaining 6 messages have bit 1 of byte 3 set to 1. This could be\nsome sort of reset indicator.\nFor these 6 messages, the transmission counter does not increase.\n\nAfter the full 7 messages, one regular message is sent after 30s.\nAfterwards, messages are sent every 45s.\n*/\n\n#include \"decoder.h\"\n\n#define TFA_DROP_BITLEN 66\n#define TFA_DROP_STARTBYTE 0x3 /* Inverted already */\n#define TFA_DROP_MINREPEATS 2\n\nstatic int tfa_drop_303233_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_invert(bitbuffer);\n\n    int row_index = bitbuffer_find_repeated_row(bitbuffer, TFA_DROP_MINREPEATS,\n            TFA_DROP_BITLEN);\n    if (row_index < 0 || bitbuffer->bits_per_row[row_index] > TFA_DROP_BITLEN + 16) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t *row_data = bitbuffer->bb[row_index];\n\n    /*\n     * Reject rows that don't start with the correct start byte.\n     */\n    if ((row_data[0] & 0xf0) != (TFA_DROP_STARTBYTE << 4)) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    /*\n     * Validate checksum\n     */\n    uint8_t observed_checksum = row_data[7];\n    uint8_t computed_checksum = lfsr_digest8_reflect(row_data, 7, 0x31, 0xf4);\n    if (observed_checksum != computed_checksum) {\n        return DECODE_FAIL_MIC;\n    }\n\n    /*\n     * After validation, start parsing data.\n     */\n\n    /*\n     * Mask first nibble in row_data[0] it is a constant message prefix.\n     */\n    int sensor_id = (row_data[0] & 0x0f) << 16 | row_data[1] << 8 | row_data[2];\n\n    uint16_t rain_counter = row_data[6] << 8 | row_data[4];\n    rain_counter = rain_counter + 10; // wrapping intentional\n\n    float rain_mm = rain_counter * 0.254f;\n\n    int battery_low = (row_data[3] & 0x80) >> 7;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",      \"\",           DATA_STRING, \"TFA-Drop\",\n            \"id\",         \"\",           DATA_FORMAT, \"%5x\", DATA_INT,  sensor_id,\n            \"battery_ok\", \"Battery\",    DATA_INT,    !battery_low,\n            \"rain_mm\",    \"Rain total\", DATA_FORMAT, \"%.1f mm\", DATA_DOUBLE, rain_mm,\n            \"mic\",        \"Integrity\",  DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"rain_mm\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_drop_303233 = {\n        .name        = \"TFA Drop Rain Gauge 30.3233.01\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 255,\n        .long_width  = 510,\n        .gap_limit   = 1300,\n        .reset_limit = 2500,\n        .sync_width  = 750,\n        .decode_fn   = &tfa_drop_303233_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_marbella.c",
    "content": "/** @file\n    TFA Dostmann Marbella (30.3238.06).\n\n    Copyright (C) 2021 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nTFA Dostmann Marbella (30.3238.06).\n\nMain display cat no: 3066.01\n\nExternal links:\n- https://www.tfa-dostmann.de/produkt/funk-poolthermometer-marbella-30-3066/\n- https://clientmedia.trade-server.net/1768_tfadost/media/3/52/21352.pdf\n\nThe Marbella sensor operates at 868MHz frequency band.\n\nFSK_PCM with 105 us long high durations\n\nAA 2D D4 68 3F 16 0A 31 9A AA XX\nPP SS SS RR RR RR ZC TT TA AA LL\n\n\nP - preamble 0xA\nS - common sync 0x2dd4\nR - serial number of sensor\nZ - always zero\nC - 3 bit counter\nT - 12 bit temperature in degree celsius\nA - always 0xA\nL - lsfr, byte reflected reverse galois with 0x31 key and generator\n    7 bytes starting from the serial number\n*/\n\n#include \"decoder.h\"\n\nstatic int tfa_marbella_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    unsigned bitpos = 0;\n    uint8_t msg[11];\n\n    uint8_t const preamble_pattern[] = {0xaa, 0x2d, 0xd4};\n\n    unsigned start_pos = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof (preamble_pattern) * 8);\n\n    if (bitpos == bitbuffer->bits_per_row[0])\n        return DECODE_FAIL_SANITY;\n\n    bitbuffer_extract_bytes(bitbuffer, 0, start_pos, msg, sizeof(msg) * 8);\n\n    if (msg[9] != 0xAA)\n        return DECODE_FAIL_SANITY;\n\n    // Rev-Galois with gen 0x31 and key 0x31\n    uint8_t ic = lfsr_digest8_reflect(&msg[3], 7, 0x31, 0x31);\n    if (ic != msg[10]) {\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"\");\n\n    int temp_raw = (msg[7] << 4) | (msg[8] >> 4);\n    float temp_c = (temp_raw - 400) * 0.1f;\n    int counter  = (msg[6] & 0xF) >> 1;\n    int serialnr = msg[3] << 16 | msg[4] << 8 | msg[5];\n\n    char serialnr_str[6 * 2 + 1];\n    snprintf(serialnr_str, sizeof(serialnr_str), \"%06x\", serialnr);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"TFA-Marbella\",\n            \"id\",               \"\",             DATA_STRING, serialnr_str,\n            \"counter\",          \"\",             DATA_INT,    counter,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"counter\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_marbella = {\n        .name        = \"TFA Marbella Pool Thermometer\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 105,\n        .long_width  = 105,\n        .reset_limit = 2000,\n        .decode_fn   = &tfa_marbella_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_pool_thermometer.c",
    "content": "/** @file\n    TFA pool temperature sensor.\n\n    Copyright (C) 2015 Alexandre Coffignal\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nTFA pool temperature sensor.\n\nTested with TFA-Pool-thermometer 30.3160.\n\nSends 10 24 bits frames.\n\nData layout:\n\n    CCCCIIII IIIITTTT TTTTTTTT DDBF\n\n- C: checksum, sum of nibbles - 1\n- I: device id (changing only after reset)\n- T: temperature\n- D: channel number\n- B: battery status\n- F: first transmission\n*/\n\nstatic int tfa_pool_thermometer_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // require 7 of 10 repeats\n    int row = bitbuffer_find_repeated_row(bitbuffer, 7, 28);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY; // no repeated row found\n    }\n    if (bitbuffer->bits_per_row[row] != 28) {\n        return DECODE_ABORT_LENGTH; // prevent false positives\n    }\n\n    uint8_t *b = bitbuffer->bb[row];\n\n    int checksum_rx = ((b[0] & 0xF0) >> 4);\n    int checksum    = ((b[0] & 0x0F)\n            + (b[1] >> 4)\n            + (b[1] & 0x0F)\n            + (b[2] >> 4)\n            + (b[2] & 0x0F)\n            + (b[3] >> 4) - 1);\n\n    if (checksum_rx != (checksum & 0x0F)) {\n        decoder_logf_bitrow(decoder, 2, __func__, b, bitbuffer->bits_per_row[row], \"checksum fail (%02x)\", checksum);\n        return DECODE_FAIL_MIC;\n    }\n\n    int device   = ((b[0] & 0x0F) << 4) | ((b[1] & 0xF0) >> 4);\n    int temp_raw = ((b[1] & 0x0F) << 8) | b[2];\n    float temp_f = (temp_raw > 2048 ? temp_raw - 4096 : temp_raw) * 0.1f;\n    int channel  = ((b[3] & 0xC0) >> 6);\n    int battery  = ((b[3] & 0x20) >> 5);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",                 DATA_STRING,    \"TFA-Pool\",\n            \"id\",               \"Id\",               DATA_INT,       device,\n            \"channel\",          \"Channel\",          DATA_INT,       channel,\n            \"battery_ok\",       \"Battery\",          DATA_INT,       battery,\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT,    \"%.1f C\",  DATA_DOUBLE,    temp_f,\n            \"mic\",              \"Integrity\",        DATA_STRING,    \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_pool_thermometer = {\n        .name        = \"TFA pool temperature sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4600,\n        .gap_limit   = 7800,\n        .reset_limit = 10000,\n        .decode_fn   = &tfa_pool_thermometer_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tfa_twin_plus_30.3049.c",
    "content": "/** @file\n    TFA-Twin-Plus-30.3049\n    also Conrad KW9010 (perhaps just rebranded), Ea2 BL999.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n    original implementation 2015 Paul Ortyl\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nDecode TFA-Twin-Plus-30.3049, Conrad KW9010 (perhaps just rebranded), Ea2 BL999.\n\nProtocol as reverse engineered by https://github.com/iotzo\n\n36 Bits (9 nibbles)\n\n| Type: | IIIICCII | B???TTTT | TTTTTSSS | HHHHHHH1 | XXXX |\n| ----- | -------- | -------- | -------- | -------- | ---- |\n| BIT/8 | 76543210 | 76543210 | 76543210 | 76543210 | 7654 |\n| BIT/A | 01234567 | 89012345 | 57890123 | 45678901 | 2345 |\n|       | 0        | 1        | 2        | 3        |      |\n\n- I: sensor ID (changes on battery change)\n- C: Channel number\n- B: low battery\n- T: temperature\n- S: sign\n- X: checksum\n- ?: unknown meaning\n- all values are LSB-first, so need to be reversed before presentation\n\n    [04] {36} e4 4b 70 73 00 : 111001000100 101101110 000 0111001 10000 ---> temp/hum:23.7/50\n    temp num-->13-21bit(9bits) in reverse order in this case \"011101101\"=237\n    positive temps (with 000 in bits 22-24) : temp=num/10 (in this case 23.7 C)\n    negative temps (with 111 in bits 22-24) : temp=(512-num)/10\n    negative temps example:\n    [03] {36} e4 4c 1f 73 f0 : 111001000100 110000011 111 0111001 11111 temp: -12.4\n\n    Humidity:\n    hum num-->25-32bit(7bits) in reverse order : in this case \"1001110\"=78\n    humidity=num-28 --> 78-28=50\n\nI have channel number bits(5,6 in reverse order) and low battery bit(9).\nIt seems that the 1,2,3,4,7,8 bits changes randomly on every reset/battery change.\n*/\n\n#include \"decoder.h\"\n\nstatic int tfa_twin_plus_303049_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    int row;\n    uint8_t *b;\n\n    row = bitbuffer_find_repeated_row(bitbuffer, 2, 36);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[row] != 36)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[row];\n\n    if (!(b[0] || b[1] || b[2] || b[3] || b[4])) /* exclude all zeros */\n        return DECODE_ABORT_EARLY;\n\n    // reverse bit order\n    uint8_t rb[5] = { reverse8(b[0]), reverse8(b[1]), reverse8(b[2]),\n            reverse8(b[3]), reverse8(b[4]) };\n\n    int sum_nibbles =\n        (rb[0] >> 4) + (rb[0] & 0xF)\n      + (rb[1] >> 4) + (rb[1] & 0xF)\n      + (rb[2] >> 4) + (rb[2] & 0xF)\n      + (rb[3] >> 4) + (rb[3] & 0xF);\n\n    int checksum = rb[4] & 0x0F;  // just make sure the 10th nibble does not contain junk\n    if (checksum != (sum_nibbles & 0xF))\n        return DECODE_FAIL_MIC; // wrong checksum\n\n  /* IIIICCII B???TTTT TTTTTSSS HHHHHHH1 XXXX */\n    int negative_sign = (b[2] & 7);\n    int temp          = ((rb[2]&0x1F) << 4) | (rb[1]>> 4);\n    int humidity      = (rb[3] & 0x7F) - 28;\n    int sensor_id     = (rb[0] & 0x0F) | ((rb[0] & 0xC0)>>2);\n    int battery_low   = b[1] >> 7;\n    int channel       = (b[0]>>2) & 3;\n\n    float tempC = (negative_sign ? -((1 << 9) - temp) : temp) * 0.1F;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"TFA-TwinPlus\",\n            \"id\",            \"Id\",          DATA_INT,    sensor_id,\n            \"channel\",       \"Channel\",     DATA_INT,    channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !battery_low,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, tempC,\n            \"humidity\",      \"Humidity\",    DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tfa_twin_plus_303049 = {\n        .name        = \"TFA-Twin-Plus-30.3049, Conrad KW9010, Ea2 BL999\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 6000,\n        .reset_limit = 10000,\n        .decode_fn   = &tfa_twin_plus_303049_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tp11.c",
    "content": "/** @file\n    ThermoPro TP-11 Thermometer.\n\n    Copyright (C) 2017 Google Inc.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TP-11 Thermometer.\n\nnormal sequence of bit rows:\n\n    [00] {33} db 41 57 c2 80\n    [01] {33} db 41 57 c2 80\n    [02] {33} db 41 57 c2 80\n    [03] {32} db 41 57 c2\n\n*/\n\nstatic int thermopro_tp11_sensor_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Compare first four bytes of rows that have 32 or 33 bits.\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 32);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n    uint8_t *b = bitbuffer->bb[row];\n\n    if (bitbuffer->bits_per_row[row] > 33)\n        return DECODE_ABORT_LENGTH;\n\n    uint8_t ic = lfsr_digest8_reflect(b, 3, 0x51, 0x04);\n    if (ic != b[3]) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // No need to decode/extract values for simple test\n    if ((!b[0] && !b[1] && !b[2] && !b[3])\n            || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff && b[3] == 0xff)) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00 or 0xFF\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    int device   = (b[0] << 4) | (b[1] >> 4);\n    int temp_raw = ((b[1] & 0x0f) << 8) | b[2];\n    float temp_c = (temp_raw - 200) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Thermopro-TP11\",\n            \"id\",            \"Id\",          DATA_INT,    device,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp11 = {\n        .name        = \"Thermopro TP11 Thermometer\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 500,\n        .long_width  = 1500,\n        .gap_limit   = 2000,\n        .reset_limit = 4000,\n        .decode_fn   = &thermopro_tp11_sensor_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tp12.c",
    "content": "/** @file\n    ThermoPro TP-12 Thermometer.\n\n    Copyright (C) 2017 Google Inc.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TP-12 Thermometer.\n\nA normal sequence for the TP12:\n\n    [00] {0} :\n    [01] {41} 38 73 21 bb 81 80\n    [02] {41} 38 73 21 bb 81 80\n    [03] {41} 38 73 21 bb 81 80\n    [04] {41} 38 73 21 bb 81 80\n    [05] {41} 38 73 21 bb 81 80\n    [06] {41} 38 73 21 bb 81 80\n    [07] {41} 38 73 21 bb 81 80\n    [08] {41} 38 73 21 bb 81 80\n    [09] {41} 38 73 21 bb 81 80\n    [10] {41} 38 73 21 bb 81 80\n    [11] {41} 38 73 21 bb 81 80\n    [12] {41} 38 73 21 bb 81 80\n    [13] {41} 38 73 21 bb 81 80\n    [14] {41} 38 73 21 bb 81 80\n    [15] {41} 38 73 21 bb 81 80\n    [16] {41} 38 73 21 bb 81 80\n    [17] {40} 38 73 21 bb 81\n\nLayout appears to be:\n\n    [01] {41} 38 73 21 bb 81 80 : 00111000 01110011 00100001 10111011 10000001 1\n                                  device   temp 1   temp     temp 2   checksum\n                                           low bits 1   2    low bits\n                                                    hi bits\n\n*/\n\n#define BITS_IN_VALID_ROW 41\n\nstatic int thermopro_tp12_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int temp1_raw, temp2_raw, row;\n    float temp1_c, temp2_c;\n    uint8_t *bytes;\n    unsigned int device;\n    data_t *data;\n    uint8_t ic;\n\n    // The device transmits 16 rows, let's check for 3 matching.\n    // (Really 17 rows, but the last one doesn't match because it's missing a trailing 1.)\n    // Update for TP08: same is true but only 2 rows.\n    row = bitbuffer_find_repeated_prefix(\n            bitbuffer,\n            (bitbuffer->num_rows > 5) ? 5 : 2,\n            BITS_IN_VALID_ROW - 1); // allow 1 bit less to also match the last row\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    bytes = bitbuffer->bb[row];\n    if (!bytes[0] && !bytes[1] && !bytes[2] && !bytes[3]) {\n        return DECODE_ABORT_EARLY; // reduce false positives\n    }\n\n    if (bitbuffer->bits_per_row[row] != BITS_IN_VALID_ROW)\n        return DECODE_ABORT_LENGTH;\n\n    ic = lfsr_digest8_reflect(bytes, 4, 0x51, 0x04);\n    if (ic != bytes[4]) {\n        return DECODE_FAIL_MIC;\n    }\n    // Note: the device ID changes randomly each time you replace the battery, so we can't early out based on it.\n    // This is probably to allow multiple devices to be used at once.  When you replace the receiver batteries\n    // or long-press its power button, it pairs with the first device ID it hears.\n    device = bytes[0];\n\n    temp1_raw = ((bytes[2] & 0xf0) << 4) | bytes[1];\n    temp2_raw = ((bytes[2] & 0x0f) << 8) | bytes[3];\n\n    temp1_c = (temp1_raw - 200) * 0.1f;\n    temp2_c = (temp2_raw - 200) * 0.1f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",            DATA_STRING, \"Thermopro-TP12\",\n            \"id\",               \"Id\",          DATA_INT,    device,\n            \"temperature_1_C\",  \"Temperature 1 (Food)\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp1_c,\n            \"temperature_2_C\",  \"Temperature 2 (Barbecue)\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp2_c,\n            \"mic\",              \"Integrity\",   DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_1_C\",\n        \"temperature_2_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp12 = {\n        .name        = \"ThermoPro TP08/TP12/TP20 thermometer\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 500,\n        .long_width  = 1500,\n        .gap_limit   = 2000,\n        .reset_limit = 4000,\n        .decode_fn   = &thermopro_tp12_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tp211b.c",
    "content": "/** @file\n    ThermoPro TP211B Thermometer.\n\n    Copyright (C) 2026, Ali Rahimi, Bruno OCTAU, Christian W. Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TP211B Thermometer.\n\nRF:\n- 915 MHz FSK temperature sensor.\n\nBased on issue #3435 open by \\@splobsterman, and thanks to the analysis conducted there by Ali, Bruno, Christian\nAnd contributors with lot of samples from \\@splobsterman, \\@moryckaz, and Ali\n\nFlex decoder:\n\n    rtl_433 -f 915M -X \"n=tp211b,m=FSK_PCM,s=105,l=105,r=1500,preamble=552dd4\"\n\nData layout after preamble:\n\n    Byte Position   0  1  2  3  4  5  6  7\n    Sample          01 1e d6 03 6c aa 14 ff\n    Sample          01 1e d6 02 fa aa c4 1e\n                    II II II fT TT aa CC CC\n\n- III: {24} Sensor ID\n- f:   {4}  Flags or unused, always 0\n- TTT: {12} Temperature, raw value, °C = (raw - 500) / 10\n- aa:  {8}  Fixed value 0xAA\n- CC:  {16} Checksum, XOR bit with a specific WORD to get the 16 bit values, and final XOR with 0x411B, see table below.\n- Followed by trailing d2 d2 d2 d2 d2 00 00 (not used).\n\nXOR Table by bit position into the frame:\n\n    0xC881 [ Bit 0 Byte 0 ]\n    0xC441 [ Bit 1 Byte 0 ]\n    0xC221 [ Bit 2 Byte 0 ]\n    0xC111 [ Bit 3 Byte 0 ]\n\n    0xC089 [ Bit 4 Byte 0 ]\n    0xC045 [ Bit 5 Byte 0 ]\n    0xC023 [ Bit 6 Byte 0 ]\n    0xC010 [ Bit 7 Byte 0 ]\n\n    0xC01F [ Bit 8 Byte 1 ]\n    0xC00E [ Bit 9 Byte 1 ]\n    0x6007 [ Bit 10 Byte 1 ]\n    0x9002 [ Bit 11 Byte 1 ]\n\n    0x4801 [ Bit 12 Byte 1 ]\n    0x8401 [ Bit 13 Byte 1 ]\n    0xE201 [ Bit 14 Byte 1 ]\n    0xD101 [ Bit 15 Byte 1 ]\n\n    0xDE01 [ Bit 16 Byte 2 ]\n    0xCF01 [ Bit 17 Byte 2 ]\n    0xC781 [ Bit 18 Byte 2 ]\n    0xC3C1 [ Bit 19 Byte 2 ]\n\n    0xC1E1 [ Bit 20 Byte 2 ]\n    0xC0F1 [ Bit 21 Byte 2 ]\n    0xC079 [ Bit 22 Byte 2 ]\n    0xC03D [ Bit 23 Byte 2 ]\n\n    0xC029 [ Bit 24 Byte 3 ]\n    0xC015 [ Bit 25 Byte 3 ]\n    0xC00B [ Bit 26 Byte 3 ]\n    0xC004 [ Bit 27 Byte 3 ]\n\n    0x6002 [ Bit 28 Byte 3 ]\n    0x3001 [ Bit 29 Byte 3 ]\n    0xB801 [ Bit 30 Byte 3 ]\n    0xFC01 [ Bit 31 Byte 3 ]\n\n    0xE801 [ Bit 32 Byte 4 ]\n    0xD401 [ Bit 33 Byte 4 ]\n    0xCA01 [ Bit 34 Byte 4 ]\n    0xC501 [ Bit 35 Byte 4 ]\n\n    0xC281 [ Bit 36 Byte 4 ]\n    0xC141 [ Bit 37 Byte 4 ]\n    0xC0A1 [ Bit 38 Byte 4 ]\n    0xC051 [ Bit 39 Byte 4 ]\n\n    0xC061 [ Bit 40 Byte 5 ]\n    0xC031 [ Bit 41 Byte 5 ]\n    0xC019 [ Bit 42 Byte 5 ]\n    0xC00D [ Bit 43 Byte 5 ]\n\n    0xC007 [ Bit 44 Byte 5 ]\n    0xC002 [ Bit 45 Byte 5 ]\n    0x6001 [ Bit 46 Byte 5 ]\n    0x9001 [ Bit 47 Byte 5 ]\n\n*/\n\nstatic uint16_t tp211b_checksum(uint8_t const *b)\n{\n    static uint16_t const xor_table[] = {\n            0xC881, 0xC441, 0xC221, 0xC111, 0xC089, 0xC045, 0xC023, 0xC010,\n            0xC01F, 0xC00E, 0x6007, 0x9002, 0x4801, 0x8401, 0xE201, 0xD101,\n            0xDE01, 0xCF01, 0xC781, 0xC3C1, 0xC1E1, 0xC0F1, 0xC079, 0xC03D,\n            0xC029, 0xC015, 0xC00B, 0xC004, 0x6002, 0x3001, 0xB801, 0xFC01,\n            0xE801, 0xD401, 0xCA01, 0xC501, 0xC281, 0xC141, 0xC0A1, 0xC051,\n            0xC061, 0xC031, 0xC019, 0xC00D, 0xC007, 0xC002, 0x6001, 0x9001};\n    uint16_t checksum = 0x411b;       // modified below with final checksum.\n    for (int n = 0; n < 6; n++) {     // iterate over the bytes in the row.\n        for (int i = 0; i < 8; i++) { // iterate over the bits in the byte.\n            const int bit = (b[n] << (i + 1)) & 0x100;\n            if (bit) {\n                checksum ^= xor_table[(n * 8) + i];\n            }\n        }\n    }\n    return checksum;\n}\n\nstatic int thermopro_tp211b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0x55, 0x2d, 0xd4};\n    uint8_t b[8];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    const int msg_len = bitbuffer->bits_per_row[0];\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((msg_len - offset) < 64) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 8 * 8);\n\n    // Sanity check: byte 5 should be fixed 0xAA\n    if (b[5] != 0xaa) {\n        decoder_log(decoder, 1, __func__, \"Fixed byte mismatch (expected 0xAA at byte 5)\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    if ((!b[0] && !b[1] && !b[2] && !b[3] && !b[4]) || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff && b[3] == 0xff && b[4] == 0xff)) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00 or 0xFF\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    const uint16_t checksum_calc     = tp211b_checksum(b);\n    const uint16_t checksum_from_row = b[6] << 8 | b[7];\n    if (checksum_from_row != checksum_calc) {\n        decoder_logf(decoder, 2, __func__, \"Checksum error, calculated %04x, expected %04x\", checksum_calc, checksum_from_row);\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, 64, \"MSG\");\n\n    int id       = (b[0] << 16) | (b[1] << 8) | b[2];\n    int temp_raw = ((b[3] & 0x0f) << 8) | b[4];\n    float temp_c = (temp_raw - 500) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"ThermoPro-TP211B\",\n            \"id\",            \"Id\",          DATA_FORMAT, \"%06x\",   DATA_INT,    id,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, (double)temp_c,\n            \"mic\",           \"Integrity\",   DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp211b = {\n        .name        = \"ThermoPro TP211B Thermometer\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 105,\n        .long_width  = 105,\n        .reset_limit = 1500,\n        .decode_fn   = &thermopro_tp211b_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tp28b.c",
    "content": "/** @file\n    ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill.\n\n    Copyright (C) 2024 Josh Pearce <joshua.pearce@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill.\n\nExample data:\n\n    rtl_433 -f 915M -F json -X \"n=tp28b,m=FSK_PCM,s=105,l=105,r=5500,preamble=d2aa2dd4\" | jq --unbuffered -r '.codes[0]'\n    (spaces below added manually)\n\n    {259}2802 0626 0000 2802 1107 0000 a290 6d70 a702 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 23C, over2: 71C, Temp2: 23C]\n    {259}2217 0626 0000 3102 1107 0000 a290 6d70 bf02 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 172, over2: 71C, Temp2: 23C]\n    {259}4421 1026 9009 3002 1012 4410 a298 6d70 5a03 000000000000 aaaa 0000000000000 [over1: 261C, Temp1: 214, over2: 121C, Temp2: 23C]\n\nData layout:\n\n    [p1_temp] [p1_set_hi] [p1_set_lo] [p2_temp] [p2_set_hi] [p2_set_lo] [flags] [id] [cksum] 000000000000 aaaa 0000000000000\n\n- p1_temp: probe 1 current temp. 16 bit BCD\n- p1_set_hi: probe 1 high alarm temp. 16 bit BCD\n- p1_set_lo: probe 1 low alarm temp. 16 bit BCD\n- p2_temp: probe 2 current temp. 16 bit BCD\n- p2_set_hi: probe 2 high alarm temp. 16 bit BCD\n- p2_set_lo: probe 2 low alarm temp. 16 bit BCD\n- flags: 16 bit status flags\n- id: 16 bit identifier\n- cksum: 16 bit checksum\n\nBit bench format:\n\n    A_TEMP:hhhh A_HI:hhhh A_LO:hhhh B_TEMP:hhhh B_HI:hhhh B_LO:hhhh FLAGS:hhhh ID:hhhh CHK:hhhh hhhhhhhhhhhh hhhh hhhhhhhhhhhhh\n\nTemps are little-endian 16 bit Binary Coded Decimals (BCD), LLHH. They are always in Celsius.\n\n    Example: 2821,\n        28 => 2.8 deg C\n        21 => 210 deg C\n        210 + 2.8 = 212.8 C (displayed rounded to 213)\n\nBelow is some work on status/alarm flags, but I can't quite make sense of them all:\n\n    02d8 => F,  p1: in-range,    p2: in-range\n    02f9 => F,  p1: low,         p2: in-range\n    02dd => F,  p1: in-range,    p2: low\n    02de => F,  p1: in-range,    p2: hi\n    02fa => F,  p1: hi,          p2: in-range\n    86f9 => F,  p1: low,         p2: low\n    82f9 => F,  p1: low,         p2: low        ack'd\n    a2f9 => C,  p1: low,         p2: low        ack'd\n    a6f9 => C,  p1: low,         p2: low        unack'd\n\n- flags & 0x2000 => Display in Celcius, otherwise Fahrenheit\n- flags & 0x0400 => Alarm unacknowledged, otherwise acknowledged\n- flags & 0x0020 => P1 in alarm, otherwise normal\n- flags & 0x0004 => P2 in alarm, otherwise normal\n- flags & 0x0001 => P2 in alarm low\n*/\n\n// Convert BCD encoded temp to float\nstatic float bcd2float(uint8_t lo, uint8_t hi)\n{\n    return ((hi & 0xF0) >> 4) * 100.0f + (hi & 0x0F) * 10.0f + ((lo & 0xF0) >> 4) * 1.0f + (lo & 0x0F) * 0.1f;\n}\n\nstatic int thermopro_tp28b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xd2, 0xaa, 0x2d, 0xd4};\n\n    uint8_t b[18];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n    if (msg_len < 240) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n    if (msg_len > 451) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 18 * 8);\n\n    int sum = (add_bytes(b, 16) & 0xff) - b[16];\n    if (sum) {\n        decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, \"Checksum error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, bitbuffer->bits_per_row[0] - offset, \"\");\n\n    uint16_t id     = b[15] | b[14] << 8;\n    uint16_t flags  = b[13] | b[12] << 8;\n    float p1_temp   = bcd2float(b[0], b[1]);\n    float p1_set_hi = bcd2float(b[2], b[3]);\n    float p1_set_lo = bcd2float(b[4], b[5]);\n    float p2_temp   = bcd2float(b[6], b[7]);\n    float p2_set_hi = bcd2float(b[8], b[9]);\n    float p2_set_lo = bcd2float(b[10], b[11]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",                             DATA_STRING,    \"ThermoPro-TP28b\",\n            \"id\",                   \"\",                             DATA_FORMAT,    \"%04x\",   DATA_INT,    id,\n            \"temperature_1_C\",      \"Temperature 1\",                DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, p1_temp,\n            \"alarm_high_1_C\",       \"Temperature 1 alarm high\",     DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, p1_set_hi,\n            \"alarm_low_1_C\",        \"Temperature 1 alarm low\",      DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, p1_set_lo,\n            \"temperature_2_C\",      \"Temperature 2\",                DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, p2_temp,\n            \"alarm_high_2_C\",       \"Temperature 2 alarm high\",     DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, p2_set_hi,\n            \"alarm_low_2_C\",        \"Temperature 2 alarm low\",      DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, p2_set_lo,\n            \"flags\",                \"Status flags\",                 DATA_FORMAT,    \"%04x\",   DATA_INT,    flags,\n            \"mic\",                  \"Integrity\",                    DATA_STRING,    \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_1_C\",\n        \"alarm_high_1_C\",\n        \"alarm_low_1_C\",\n        \"temperature_2_C\",\n        \"alarm_high_2_C\",\n        \"alarm_low_2_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp28b = {\n        .name        = \"ThermoPro TP28b Super Long Range Wireless Meat Thermometer for Smoker BBQ Grill\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 105,\n        .long_width  = 105,\n        .reset_limit = 5500,\n        .decode_fn   = &thermopro_tp28b_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tp82xb.c",
    "content": "/** @file\n    ThermoPro TP82xB Meat Thermometer probes.\n\n    Copyright (C) 2024 Bruno OCTAU (\\@ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/*\nThermoPro TP82xB Meat Thermometer probes.\n\nDevices decoded:\n- TP828P, 2 Probes, current Temperature, BBQ LO and HI temperatures\n- TP829B, 4 Probes, simple Temperature\n- TP829, same as TP829B\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TP828P 2 Probes.\n\n- Current Temperature of probes, BBQ Target LO and HI temperatures\n- Issue #3082 open by Ryan Bray (\\@rbray89)\n- Product archive web page: http://web.archive.org/web/20240717222907/https://buythermopro.com/product/tp828w/\n- FCCID : https://fccid.io/2AATP-TP828B\n\nFlex decoder:\n\n    rtl_433 -X \"n=tp829b,m=FSK_PCM,s=102,l=102,r=5500,preamble=552dd4\" *.cu8 2>&1 | grep codes\n\n    codes: {164}772c2eceaa4f3eddeaa4d7b2d2d2d2d2d20000000\n\nData layout:\n\n    Byte Position              0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20\n                              II UF 11 11 11 11 12 22 22 22 22 CC TT TT TT TT TT TT TT TT T\n                                    PP PL LL HH HP PP LL LH HH\n    Sample        d2 55 2d d4 77 2c 2e ce aa 4f 3e dd ea a4 d7 b2 d2 d2 d2 d2 d2 00 00 00 0\n\n- II:      {8} Sensor ID,\n- U:       {4} Temp Unit Display, 0x0 for Celsius, 0x2 for Fahrenheit,\n- F:       {4} Unknown flags, always 0xC, to be confirmed as the battery low not identified,\n- 111/PPP:{12} Probe 1 Current Temp , °C, offset 500, scale 10,\n- 111/LLL:{12} Probe 1 Target LO Temp, °C, offset 500, scale 10,\n- 111/HHH:{12} Probe 1 Target HI Temp, °C, offset 500, scale 10,\n- 222/PPP:{12} Probe 2 Current Temp, °C, offset 500, scale 10,\n- 222/LLL:{12} Probe 2 Target LO Temp, °C, offset 500, scale 10,\n- 222/HHH:{12} Probe 2 Target HI Temp, °C, offset 500, scale 10,\n- CC: {8}  Checksum, Galois Bit Reflect Byte Reflect, gen 0x98, key 0x16, final XOR 0xac,\n- TT: Trailed bytes, not used (always d2 d2 ...... 00 00 ).\n\n*/\nstatic int thermopro_tp828b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = { // 0xd2, removed to increase success\n                                        0x55, 0x2d, 0xd4};\n    // Message len is 9 byte for tp829b and 12 byte for tp828b\n    uint8_t b[12];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n\n    if (msg_len > 280) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((msg_len - offset ) < 96 ) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 12 * 8);\n\n    uint8_t b_reflect[11];\n    for (int p = 10; p != -1; p += -1)\n        b_reflect[10 - p] = b[p];\n    int checksum = lfsr_digest8(b_reflect, 11, 0x98, 0x16) ^ 0xac ;\n    if (checksum != b[11]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error, calculated %x, expected %x\", checksum, b[11]);\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, 96, \"MSG\");\n\n    int id        = b[0];\n    int display_u = (b[1] & 0xF0) >> 4;\n    int flags     = b[1] & 0xF;\n    int p1_raw    = b[2] << 4 | (b[3] & 0xF0) >> 4;\n    int p1_lo_raw = (b[3] & 0x0F) << 8 | b[4];\n    int p1_hi_raw = b[5] << 4 | (b[6] & 0xF0) >> 4;\n    int p2_raw    = (b[6] & 0x0F) << 8 | b[7];\n    int p2_lo_raw = b[8] << 4 | (b[9] & 0xF0) >> 4;\n    int p2_hi_raw = (b[9] & 0x0F) << 8 | b[10];\n\n    float p1_temp = (p1_raw - 500)    * 0.1f;\n    float p1_t_lo = (p1_lo_raw - 500) * 0.1f;\n    float p1_t_hi = (p1_hi_raw - 500) * 0.1f;\n    float p2_temp = (p2_raw - 500)    * 0.1f;\n    float p2_t_lo = (p2_lo_raw - 500) * 0.1f;\n    float p2_t_hi = (p2_hi_raw - 500) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",                             DATA_STRING,    \"ThermoPro-TP828b\",\n            \"id\",                   \"\",                             DATA_FORMAT,    \"%02x\",         DATA_INT,    id,\n            \"display_u\",            \"Display Unit\",                 DATA_COND, display_u == 0x2,    DATA_STRING, \"Fahrenheit\",\n            \"display_u\",            \"Display Unit\",                 DATA_COND, display_u == 0x0,    DATA_STRING, \"Celsius\",\n            \"temperature_1_C\",      \"Temperature 1\",                DATA_COND, p1_raw != 0xedd ,    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p1_temp, // if 0xedd then no probe\n            \"temperature_1_LO_C\",   \"Temperature 1 LO\",             DATA_COND, p1_lo_raw != 0xeaa , DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p1_t_lo, // if 0xeaa then no LO\n            \"temperature_1_HI_C\",   \"Temperature 1 HI\",                                             DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p1_t_hi,\n            \"temperature_2_C\",      \"Temperature 2\",                DATA_COND, p2_raw != 0xedd ,    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p2_temp, // if 0xedd then no probe\n            \"temperature_2_LO_C\",   \"Temperature 2 LO\",             DATA_COND, p2_lo_raw != 0xeaa , DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p2_t_lo, // if 0xeaa then no LO\n            \"temperature_2_HI_C\",   \"Temperature 2 HI\",                                             DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p2_t_hi,\n            \"flags\",                \"Flags\",                        DATA_FORMAT,    \"%01x\",         DATA_INT,    flags,\n            \"mic\",                  \"Integrity\",                    DATA_STRING,    \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nThermoPro TP829B 4 Probes.\n\n- Current Temperature of probes only.\n- Issue #2961 open by \\@AryehGielchinsky\n- Product web page : https://buythermopro.com/product/tp829/\n\nFlex decoder:\n\n    rtl_433 -X \"n=tp829b,m=FSK_PCM,s=102,l=102,r=5500,preamble=552dd4\" *.cu8 2>&1 | grep codes\n\n    codes: {164}082f2efeddeddedde8d2d2d2d2d20000000000000\n\nData layout:\n\n    Byte Position              0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20\n                              II UF 11 12 22 33 34 44 CC TT TT TT TT TT TT TT TT TT TT TT T\n    Sample        d2 55 2d d4 08 2f 2e fe dd ed de dd e8 d2 d2 d2 d2 d2 00 00 00 00 00 00 0\n\n- II:  {8} Sensor ID,\n- U:   {4} Temp Unit Display, 0x0 for Celsius, 0x2 for Fahrenheit,\n- F:   {4} Unknown flags, always 0xF, to be confirmed as the battery low not identified,\n- 111:{12} Temp probe 1, °C, offset 500, scale 10,\n- 222:{12} Temp probe 2, °C, offset 500, scale 10,\n- 333:{12} Temp probe 3, °C, offset 500, scale 10,\n- 444:{12} Temp probe 4, °C, offset 500, scale 10,\n- CC:  {8} Checksum, Galois Bit Reflect Byte Reflect, gen 0x98, key 0x55, final XOR 0x00,\n- TT:      Trailed bytes, not used (always d2 d2 ...... 00 00 ).\n\n*/\nstatic int thermopro_tp829b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = { // 0xd2, removed to increase success\n                                        0x55, 0x2d, 0xd4};\n\n    uint8_t b[9];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n\n    if (msg_len > 260) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((msg_len - offset ) < 96 ) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 9 * 8);\n\n    // exclude conflict with ThermoPro TX-7B when byte position 5, 6 & 7 = 0xaa55aa and checksum = 0x00, issue #3306\n    if ( b[5] == 0xaa && b[6] == 0x55 && b[7] == 0xaa && b[8] == 0) {\n        decoder_log(decoder, 1, __func__, \"Not a TP829B sensor but TX7B\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    // checksum is a Galois bit reflect and byte reflect, gen 0x98, key 0x55, final XOR 0x00\n    uint8_t b_reflect[8];\n    for (int p = 7; p != -1; p += -1)\n        b_reflect[7 - p] = b[p];\n    int checksum = lfsr_digest8(b_reflect, 8, 0x98, 0x55);\n    if (checksum != b[8]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error, calculated %x, expected %x\", checksum, b[8]);\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, 72, \"MSG\");\n\n    int id        = b[0];\n    int display_u = (b[1] & 0xF0) >> 4;\n    int flags     = b[1] & 0xF;\n    int p1_raw    = b[2] << 4 | (b[3] & 0xF0) >> 4;\n    int p2_raw    = (b[3] & 0x0F) << 8 | b[4];\n    int p3_raw    = b[5] << 4 | (b[6] & 0xF0) >> 4;\n    int p4_raw    = (b[6] & 0x0F) << 8 | b[7];\n    float p1_temp = (p1_raw - 500) * 0.1f;\n    float p2_temp = (p2_raw - 500) * 0.1f;\n    float p3_temp = (p3_raw - 500) * 0.1f;\n    float p4_temp = (p4_raw - 500) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",                             DATA_STRING,    \"ThermoPro-TP829b\",\n            \"id\",                   \"\",                             DATA_FORMAT,    \"%02x\",      DATA_INT,    id,\n            \"display_u\",            \"Display Unit\",                 DATA_COND, display_u == 0x2, DATA_STRING, \"Fahrenheit\",\n            \"display_u\",            \"Display Unit\",                 DATA_COND, display_u == 0x0, DATA_STRING, \"Celsius\",\n            \"temperature_1_C\",      \"Temperature 1\",                DATA_COND, p1_raw != 0xedd , DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p1_temp, // if 0xedd then no probe\n            \"temperature_2_C\",      \"Temperature 2\",                DATA_COND, p2_raw != 0xedd , DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p2_temp, // if 0xedd then no probe\n            \"temperature_3_C\",      \"Temperature 3\",                DATA_COND, p3_raw != 0xedd , DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p3_temp, // if 0xedd then no probe\n            \"temperature_4_C\",      \"Temperature 4\",                DATA_COND, p4_raw != 0xedd , DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, p4_temp, // if 0xedd then no probe\n            \"flags\",                \"Flags\",                        DATA_FORMAT,    \"%01x\",      DATA_INT,    flags,\n            \"mic\",                  \"Integrity\",                    DATA_STRING,    \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const tp828b_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"display_u\",\n        \"temperature_1_C\",\n        \"temperature_1_LO_C\",\n        \"temperature_1_HI_C\",\n        \"temperature_2_C\",\n        \"temperature_2_LO_C\",\n        \"temperature_2_HI_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp828b = {\n        .name        = \"ThermoPro Meat Thermometers, TP828B 2 probes with Temp, BBQ Target LO and HI\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 102,\n        .long_width  = 102,\n        .reset_limit = 1500,\n        .decode_fn   = &thermopro_tp828b_decode,\n        .priority    = 10, // let decode first the tp829b as the message is shorter\n        .fields      = tp828b_output_fields,\n};\n\nstatic char const *const tp829b_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"display_u\",\n        \"temperature_1_C\",\n        \"temperature_2_C\",\n        \"temperature_3_C\",\n        \"temperature_4_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp829b = {\n        .name        = \"ThermoPro Meat Thermometers, TP829B 4 probes with temp only\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 102,\n        .long_width  = 102,\n        .reset_limit = 1500,\n        .decode_fn   = &thermopro_tp829b_decode,\n        .fields      = tp829b_output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tp862b.c",
    "content": "/** @file\n    ThermoPro TP862b TempSpike XR 1,000-ft Wireless Dual-Probe Meat Thermometer.\n\n    Copyright (C) 2026 n6ham <github.com/n6ham>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int thermopro_tp862b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nThermoPro TP862b TempSpike XR 1,000-ft Wireless Dual-Probe Meat Thermometer.\n\nExample data:\n\n    rtl_433 % rtl_433 -f 915M -F json -X 'n=name,m=FSK_PCM,s=104,l=104,r=2000,preamble=d2552dd4,bits=170' | jq --unbuffered -r '.codes[0]'\n    (spaces below added manually)\n\n    {74}36 8a 2a1 2a5 1f 3f c738 0 [internal: 17.3C, ambient: 17.7C]\n    {74}36 8a 2a1 2a5 1f 3f c738 0 [internal: 17.3C, ambient: 17.7C]\n    {74}c5 9a 2a4 2a9 19 3f fa05 0 [internal: 17.6C, ambient: 18.1C]\n    {74}c5 9a 2a5 2a9 19 3f 9d62 0 [internal: 17.7C, ambient: 18.1C]\n\nPayload format:\n- Preamble         {28} 0xd2552dd4\n- Id               {8} Probe id (seems like it's unique for a probe and doesn't change)\n- Probe            {8} Probe code (\n    Black: 0x8a or 0xca when docked\n    White: 0x9a or 0xda when docked\n- Internal         {12} Raw internal temperature value (raw = temp_c * 10 + 500). Example: 17.3 C -> 0x2a1\n- Ambient          {12} Raw ambient temperature value (raw = temp_c * 10 + 500). Example: 18.1 C -> 0x2a9\n- Flags            {8}  A battery state, or something else.\n- Separator        {8}  0x3f\n- Checksum         {16} [CRC-8][~CRC-8]\n\nExperimental data:\n- Color            (Probe & 0x10) >> 4 (0 for black, 1 for white)\n- Docked           (Probe & 0x40) >> 6 (0 for undocked, 1 for docked)\n\nData layout:\n    ID:8h PROBE:8h INTERNAL:12d AMBIENT:12d FLAGS:8h SEPARATOR:8h CHECKSUM:16h T:8b\n*/\nstatic int thermopro_tp862b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xd2, 0x55, 0x2d, 0xd4};\n\n    uint8_t b[9];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n    if (msg_len < 170) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n    if (msg_len > 170) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0,\n            preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 9 * 8);\n\n    // Validate Checksum format. Byte 7 must be equal to byte 8 inverted.\n    if (b[7] == ~b[8]) {\n        decoder_logf(decoder, 2, __func__, \"Checksum byte 7 is supposed to be equal to byte 8 inverted. Actual: %02x vs %02x (inverted %02x)\", b[7], b[8], ~b[8]);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Validate Checksum: CRC-8 (Poly 0x07, Init 0x00, Final XOR 0xDB. The checksum is stored as [CRC-8][~CRC-8] in bytes 7 and 8\n    uint8_t calc_crc = crc8(b, 7, 0x07, 0x00) ^ 0xdb;\n    if (calc_crc != b[7]) {\n        decoder_logf(decoder, 2, __func__, \"Integrity check failed %02x vs %02x\", b[7], calc_crc);\n        return DECODE_FAIL_MIC;\n    }\n\n    uint8_t id            = b[0];\n    uint8_t probe         = b[1];\n    uint16_t internal_raw = (b[2] << 4) | (b[3] >> 4);   // Internal: 12 bits starting at byte 2; Rounded to integer on the display\n    uint16_t ambient_raw  = ((b[3] & 0x0f) << 8) | b[4]; // Ambient: 12 bits starting at the middle of byte 3; Rounded to integer on the display\n    uint8_t flags         = b[5];\n\n    float internal_c = (internal_raw - 500) * 0.1f;\n    float ambient_c  = (ambient_raw - 500) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",                \"\",                 DATA_STRING,    \"ThermoPro-TP862b\",\n            \"id\",                   \"\",                 DATA_FORMAT,    \"%02x\",   DATA_INT,    id,\n            \"color\",                \"Color\",            DATA_STRING,    (probe & 0x10) ? \"white\" : \"black\",\n            \"is_docked\",            \"Docked\",           DATA_INT,       (probe & 0x40) >> 6,\n            \"temperature_int_C\",    \"Internal\",         DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, internal_c,\n            \"temperature_amb_C\",    \"Ambient\",          DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, ambient_c,\n            \"flags\",                \"Flags\",            DATA_FORMAT,    \"%02x\",   DATA_INT,    flags,\n            \"mic\",                  \"Integrity\",        DATA_STRING,    \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"color\",\n        \"is_docked\",\n        \"temperature_int_C\",\n        \"temperature_amb_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tp862b = {\n        .name        = \"ThermoPro TP862b TempSpike XR Wireless Dual-Probe Meat Thermometer\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 104,\n        .long_width  = 104,\n        .reset_limit = 2000,\n        .decode_fn   = &thermopro_tp862b_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tx2.c",
    "content": "/** @file\n    ThermoPro TX2 sensor protocol.\n\n    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TX2 sensor protocol.\n\nNote: this is the Prologue protocol with the battery low flag inverted.\nDisable Prologue and enable this to use, e.g. `-R -3 -R 162`.\n\nNote: this is a false positive for AlectoV1.\n\nThe sensor sends 36 bits 7 times, before the first packet there is a sync pulse.\nThe packets are ppm modulated (distance coding) with a pulse of ~500 us\nfollowed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a\n1 bit, the sync gap is ~9000 us.\n\nThe data is grouped in 9 nibbles\n\n    [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1]\n\n- type: 4 bit fixed 1001 (9) or 0110 (5)\n- id: 8 bit a random id that is generated when the sensor starts, could include battery status\n  the same batteries often generate the same id\n- flags(3): is 1 when the battery is low, otherwise 0 (ok)\n- flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor\n- flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X)\n- temp: 12 bit signed scaled by 10\n- humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available\n\n*/\nstatic int thermopro_tx2_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b;\n    data_t *data;\n\n    int type;\n    int id;\n    int battery;\n    int button;\n    int channel;\n    int temp_raw;\n    int humidity;\n\n    if (bitbuffer->bits_per_row[0] <= 8 && bitbuffer->bits_per_row[0] != 0)\n        return DECODE_ABORT_EARLY; // Alecto/Auriol-v2 has 8 sync bits, reduce false positive\n\n    int r = bitbuffer_find_repeated_row(bitbuffer, 4, 36); // only 3 repeats will give false positives for Alecto/Auriol-v2\n    if (r < 0)\n        return DECODE_ABORT_EARLY;\n\n    if (bitbuffer->bits_per_row[r] > 37) // we expect 36 bits but there might be a trailing 0 bit\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[r];\n\n    if ((b[0] & 0xF0) != 0x90 && (b[0] & 0xF0) != 0x50)\n        return DECODE_FAIL_SANITY;\n\n    // Prologue/ThermoPro-TX2 sensor\n    type     = b[0] >> 4;\n    id       = ((b[0] & 0x0F) << 4) | ((b[1] & 0xF0) >> 4);\n    battery  = b[1] & 0x08;\n    button   = (b[1] & 0x04) >> 2;\n    channel  = (b[1] & 0x03) + 1;\n    temp_raw = (int16_t)((b[2] << 8) | (b[3] & 0xF0)); // uses sign-extend\n    temp_raw = temp_raw >> 4;\n    humidity = ((b[3] & 0x0F) << 4) | (b[4] >> 4);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Thermopro-TX2\",\n            \"subtype\",       \"\",            DATA_INT, type,\n            \"id\",            \"\",            DATA_INT, id,\n            \"channel\",       \"Channel\",     DATA_INT, channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT, !battery,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp_raw * 0.1,\n            \"humidity\",      \"Humidity\",    DATA_COND, humidity != 0xcc, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"button\",        \"Button\",      DATA_INT, button,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"subtype\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        NULL,\n};\n\nr_device const thermopro_tx2 = {\n        .name        = \"ThermoPro-TX2 temperature sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .gap_limit   = 7000,\n        .reset_limit = 10000,\n        .decode_fn   = &thermopro_tx2_decode,\n        .disabled    = 1,\n        .priority    = 10, // Alecto collision, if Alecto checksum is correct it's not Prologue/ThermoPro-TX2\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermopro_tx2c.c",
    "content": "/** @file\n    ThermoPro TX-2C Outdoor Thermometer and humidity sensor.\n\n    Copyright (C) 2023 igor@pele.tech.\n    Copyright (C) 2023 maxime@werlen.fr.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TX-2C Outdoor Thermometer.\n\nExample data:\n\n    [00] { 7} 00\n    [01] {45} 95 00 ff e0 a0 00\n    [02] {45} 95 00 ff e0 a0 00\n    [03] {45} 95 00 ff e0 a0 00\n    [04] {45} 95 00 ff e0 a0 00\n    [05] {45} 95 00 ff e0 a0 00\n    [06] {45} 95 00 ff e0 a0 00\n    [07] {45} 95 00 ff e0 a0 00\n    [08] {36} 95 00 ff e0 a0\n\nData layout:\n\n    [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1] [zero] [zero] [zero]\n\n- type: 4 bit fixed 1001 (9) or 0110 (5)\n- id: 8 bit a random id that is generated when the sensor starts, could include battery status\n  the same batteries often generate the same id\n- flags(3): is 1 when the battery is low, otherwise 0 (ok)\n- flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor\n- flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X)\n- temp: 12 bit signed scaled by 10\n- humi: 8 bit always 00001010 (0x0A) if no humidity sensor is available\n- zero : a trailing 12 bit fixed 000000000000\n\n*/\n\nstatic int thermopro_tx2c_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Compare first four bytes of rows that have 45 or 36 bits.\n    int row = bitbuffer_find_repeated_row(bitbuffer, 4, 36);\n    if (row < 0)\n        return DECODE_ABORT_EARLY;\n    uint8_t *b = bitbuffer->bb[row];\n\n    if (bitbuffer->bits_per_row[row] > 45)\n        return DECODE_ABORT_LENGTH;\n\n    // No need to decode/extract values for simple test\n    if ((!b[0] && !b[1] && !b[2] && !b[3])\n            || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff && b[3] == 0xff)) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00 or 0xFF\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    // check existing 12 bit 0 trailer\n    if ((b[4] & 0x0F) != 0x00 || b[5] != 0x00)\n        return DECODE_FAIL_SANITY;\n\n    // int type     = b[0] >> 4;\n    int id       = (((b[0] & 0xF) << 4) | (b[1] >> 4));\n    int battery  = (b[1] & 0x08) >> 3;\n    int button   = (b[1] & 0x04) >> 2;\n    int channel  = (b[1] & 0x03) + 1;\n    int temp_raw = (int16_t)((b[2] << 8) | b[3]); // uses sign-extend\n    float temp_c = (temp_raw >> 4) * 0.1f;\n    int humidity = (((b[3] & 0xF) << 4) | (b[4] >> 4));\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Thermopro-TX2C\",\n            // \"subtype\",       \"\",            DATA_INT, type,\n            \"id\",            \"Id\",          DATA_INT, id,\n            \"channel\",       \"Channel\",     DATA_INT, channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT, !battery,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",      \"Humidity\",    DATA_COND, humidity != 0x0a, DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"button\",        \"Button\",      DATA_INT, button,\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        // \"subtype\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"button\",\n        NULL,\n};\n\nr_device const thermopro_tx2c = {\n        .name        = \"ThermoPro TX-2C Thermometer and Humidity sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1958,\n        .long_width  = 3825,\n        .gap_limit   = 3829,\n        .reset_limit = 8643,\n        .decode_fn   = &thermopro_tx2c_decode,\n        .fields      = output_fields,\n        .disabled    = 1, // default disabled because there is no checksum\n};\n"
  },
  {
    "path": "src/devices/thermopro_tx7b.c",
    "content": "/** @file\n    ThermoPro TX-7B Outdoor Thermometer Hygrometer.\n\n    Copyright (C) 2025 Bruno OCTAU (\\@ProfBoc75)\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermoPro TX-7B Outdoor Thermometer Hygrometer.\n\n- Outdor Sensor with Temperature and Hymidity\n- Compatible with ThermoPro TP260B/TP280B stations.\n- Issue #3306\n- Product web page : https://buythermopro.com/product/tx7b/\n- Very similar protocol and data layout with ThermoPro TP829b\n\nFlex decoder:\n\n    rtl_433 -X \"n=tx7b,m=FSK_PCM,s=108,l=108,r=1500,preamble=d2552dd4,bits>=160\" 2>&1 | grep codes\n\n    codes: {124}e800293017aa55aa83d2d2d2d2d200\n    codes: {124}25202ca00daa55aabbd2d2d2d2d200\n\nData layout:\n\n    Byte Position              0  1  2  3  4  5  6  7  8  9 10 11 12 13 14\n    Sample        d2 55 2d d4 e8 00 29 30 17 aa 55 aa 83 d2 d2 d2 d2 d2 00\n    Sample        d2 55 2d d4 25 20 2c a0 0d aa 55 aa bb d2 d2 d2 d2 d2 00\n                              II BF 11 12 22 aa 55 aa CC TT TT TT TT TT TT\n                                 X\n                                 C\n\n- II:  {8} Sensor ID,\n- B:   {1} Low Battery = 1, Normal Battery = 0\n- X:   {1} TX Button , 1 = pressed for immediate rf transmit.\n- C:   {2} Channel offset -1, 0x0 = CH 1, 0x1 = CH 2, 0x2 = CH 3 (3 sensors max are supported by station)\n- F:   {4} Unknown flags, always 0x0,\n- 111:{12} Temperature, °C, offset 400, scale 10,\n- 222:{12} Humidity, %,\n- aa55aa:{24} fixed value 0xaa55aa\n- CC:  {8} Checksum, Galois Bit Reflect Byte Reflect, gen 0x98, key 0x25, final XOR 0x00,\n- TT:      Trailed bytes, not used (always d2 d2 d2 d2 d2 00 ...).\n\n*/\nstatic int thermopro_tx7b_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = { // 0xd2, removed to increase success\n                                        0x55, 0x2d, 0xd4};\n\n    uint8_t b[9];\n\n    if (bitbuffer->num_rows > 1) {\n        decoder_logf(decoder, 1, __func__, \"Too many rows: %d\", bitbuffer->num_rows);\n        return DECODE_FAIL_SANITY;\n    }\n    int msg_len = bitbuffer->bits_per_row[0];\n\n    if (msg_len > 260) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int offset = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n\n    if (offset >= msg_len) {\n        decoder_log(decoder, 1, __func__, \"Sync word not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    if ((msg_len - offset ) < 96 ) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    offset += sizeof(preamble_pattern) * 8;\n    bitbuffer_extract_bytes(bitbuffer, 0, offset, b, 9 * 8);\n\n    // checksum is a Galois bit reflect and byte reflect, gen 0x98, key 0x25, final XOR 0x00\n    int checksum = lfsr_digest8_reverse(b, 8, 0x98, 0x25);\n\n    if (checksum != b[8]) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error, calculated %x, expected %x\", checksum, b[8]);\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, b, 72, \"MSG\");\n\n    int id        = b[0];\n    int channel   = ((b[1] & 0x30) >> 4) + 1;\n    int low_bat   = b[1] >> 7;\n    int tx_button = (b[1] & 0x40) >> 6;\n    int flags     = b[1] & 0xF;\n    int temp_raw  = b[2] << 4 | (b[3] & 0xF0) >> 4;\n    int humidity  = b[4];\n    float temp    = (temp_raw - 400) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",          \"\",             DATA_STRING, \"ThermoPro-TX7B\",\n            \"id\",             \"\",             DATA_FORMAT, \"%02x\",    DATA_INT,    id,\n            \"battery_ok\",     \"Battery\",      DATA_INT,    !low_bat,\n            \"button\",         \"Button\",       DATA_INT,    tx_button,\n            \"channel\",        \"Channel\",      DATA_INT,    channel,\n            \"flags\",          \"Flags\",        DATA_FORMAT, \"%04b\",    DATA_INT,    flags,\n            \"temperature_C\",  \"Temperature\",  DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temp,\n            \"humidity\",       \"Humidity\",     DATA_FORMAT, \"%d %%\",   DATA_INT, humidity,\n            \"mic\",            \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const tx7b_output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"button\",\n        \"channel\",\n        \"flags\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermopro_tx7b = {\n        .name        = \"ThermoPro TX-7B Outdoor Thermometer Hygrometer\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 108,\n        .long_width  = 108,\n        .reset_limit = 1500,\n        .decode_fn   = &thermopro_tx7b_decode,\n        .fields      = tx7b_output_fields,\n};\n"
  },
  {
    "path": "src/devices/thermor.c",
    "content": "/** @file\n    Thermor DG950 weather station.\n\n    Copyright (C) 2024 Nicolas Gagné, Bruno OCTAU (ProfBoc75)\n    Copyright (C) 2024 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nThermor DG950 weather station.\n\nThe weather station is composed of:\n- Display Receiver DG950R, FCC test reports are available at: https://fccid.io/S24DG950R\n- Thermometer-Transmitter Sensor DG950, FCC test reports are available at: https://fccid.io/S24DG950\n- Wind Sensor (speed and direction) DG950\n- Rain Gauge Sensor (cumulative rainfall) DG950\n\nThe manual is available at: https://fccid.io/S24DG950R/Users-Manual/USERS-MANUAL-522434\nReview of the station: https://www.home-weather-stations-guide.com/thermor-weather-station.html\n\n\nS.a #2879 open by Nicolas Gagné.\n\nRF raw Signal: 96 synchro pulses, 13 x [gap, start 1 bit 0, 8 bit]\n\n    {213}0000000000000000000000000c3adfe6b1f0f92eff258f4fe1f0f8\n\nFlex decoder:\n\n    rtl_433 -X 'n=thermor,m=OOK_PWM,s=750,l=2128,y=1438,g=3000,r=8000,get=byte:@1:{8}:%x'\n\n    {9}0c0, {9}758, {9}7f8, {9}358, {9}1f0, {9}1f0, {9}4b8, {9}7f8, {9}258, {9}1e8, {9}3f8, {9}0f8, {9}0f8\n\nSamples here from Nicolas Gagné:\n\nhttps://github.com/NicolasGagne/rtl_433_tests/tree/a20b49805ed7ba74db016ec43e2a34ccda8231a9/tests/Thermor%20DG950\n\nData Layout: (normal mode)\n\n    Byte position             0          1          2          3           4           5          6          7          8          9         10         11         12\n    bit position     0 12345678 0 12345678 0 12345678 0 12345678 0 1234 5678 0 1234 5678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678 0 12345678\n    Data             X IIIIIIII X TEMP_INT X Rain_mm  X TEMP_CHK X WDIR FLAG X WDIR FLAG X AAAA_LSB X AAAA_MSB X BBBBBBBB X CCCC_CHK X TEMP_DEC X ???????? X Rain_mm+7\n\nAll bytes are reflected/reverse8\n\n- II:{8}        ID\n- TEMP_INT:{8}  temperature interger part, offset +195, °C\n- TEMP_DEC:{8}  temperature decimal part, offset +245, scale 10\n- RTmm:{4}      rain rate in 0.1 mm\n- RCHK:{4}      rain rate in 0.1 mm offset +7 , used to check if same as RTmm\n- TEMP_CHK:{8}  Temp checksum\n- WDIR:{4}      wind direction, map table to be used\n- FLAG:{4}      if 1  = valid win dir, if 0 wind dir = unknown\n- A_L,  A_M,  B,  C_CHK :{8} values related to Wind Speed, C = A_L + A_M + B\n- ?:{8} or {4}  unknown values, fixed.\n- X:{1}         Bit start 0 is ignore\n\n\n*/\n\nstatic int thermor_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 13) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t b[13];\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        if (bitbuffer->bits_per_row[row] != 9) {\n            return DECODE_ABORT_EARLY;\n        }\n        // test is start bit = 0\n        if ((bitbuffer->bb[row][0] & 0x80) != 0) {\n            return DECODE_ABORT_EARLY;\n        }\n        // extract only the 8 bit, ignore the start bit 0\n        bitbuffer_extract_bytes(bitbuffer, row, 1, &b[row], 8);\n    }\n    // Test if Sync/pairing or normal signal\n    reflect_bytes(b,13);\n    decoder_log_bitrow(decoder, 1, __func__, b, sizeof(b) * 8, \"Reflected\");\n\n    int const wind_dir_degr[] = {157,45,135,67,180,22,112,90,225,337,247,315,202,0,270,292};\n\n    //sync pairing mode\n    if (b[0] == 0xff && b[1] == b[2] && b[1] == b[4] && b[1] == b[5] && b[1] == b[6] && b[1] == b[7] && b[1] == b[8] && b[1] == b[10]  ) {\n        int new_id = ~b[1] & 0xff;\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",          \"\",           DATA_STRING, \"Thermor-DG950\",\n                \"id\",         \"\",               DATA_FORMAT, \"%d\", DATA_INT, new_id,\n                \"pairing\",         \"Pairing?\",  DATA_INT, 1,\n                \"mic\",            \"Integrity\",  DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n\n    else {\n        decoder_logf(decoder, 2, __func__, \"Start decode ...\");\n        float wind_ratio = 0;\n        int have_rain = 0;\n        int have_wdir = 0;\n        int have_wspd = 0;\n        int rain_rate1 = 0;\n        int rain_rate2 = 0;\n        int wind_dir_d = 0;\n        int wind_coef  = 0;\n        float wind_speed_kmh = 0;\n\n        int id        = ~b[0] & 0xff;\n        decoder_logf(decoder, 1, __func__, \"ID %d\", id);\n\n        // Temp Checksum\n        int temp_chk = (b[1] + b[10]) & 0xff;\n        if ( (temp_chk + 1) != (b[3] & 0xff) ) {\n            decoder_logf(decoder, 2, __func__, \"Temp Check Sum failed %d %d\", temp_chk, (b[3] & 0xff));\n            return DECODE_ABORT_EARLY;\n        }\n\n        float temp_int   = b[1] - 195;\n        float temp_dec   = (b[10] - 245) * 0.1f;\n        float temp_C     = temp_int + temp_dec;\n        decoder_logf(decoder, 2, __func__, \"Temp %f\", temp_C);\n\n        // Test if valid rain\n        rain_rate1 = (~b[2] & 0xff);\n        rain_rate2 = (~b[12] & 0xff) -7;\n        // Rain check\n        if (rain_rate1 != rain_rate2) {\n            decoder_log(decoder, 2, __func__, \"Rain Check failed\");\n            return DECODE_ABORT_EARLY;\n        }\n        have_rain = 1;\n        decoder_logf(decoder, 1, __func__, \"Rain check passed ...\");\n\n        // Test if valid wind direction\n        if ( b[4] != 0xff && b[5] != 0xff) {\n            if (b[4] != b[5]) {\n                decoder_log(decoder, 2, __func__, \"Wind Direction Check failed\");\n                return DECODE_ABORT_EARLY;\n            }\n            wind_dir_d  = wind_dir_degr[(b[4] & 0x0f)];\n            have_wdir = 1;\n        }\n\n        // Wind speed check sum\n        int wind_chk = (~b[6] + ~b[7] + ~b[8]) & 0xff;\n        if (wind_chk != (~b[9] & 0xff)) {\n            decoder_logf(decoder, 2, __func__, \"Wind Check Sum failed %d %d\", wind_chk, (~b[9] & 0xff));\n            return DECODE_ABORT_EARLY;\n        }\n        decoder_logf(decoder, 2, __func__, \"Wind Speed check passed ...\");\n        if (b[8] != 0xff ) {\n            uint16_t wind_speed_raw = (~b[6] & 0xff) | ((~b[7] & 0xff) << 8) ;\n            wind_coef  = ~b[8] & 0xff;\n            if (wind_speed_raw < 256) {\n                wind_ratio = (wind_speed_raw * -0.0001746) + 0.155;\n            }\n            else {\n                wind_ratio = 0.11;\n            }\n            wind_speed_kmh = wind_ratio * (wind_speed_raw - wind_coef + 45);\n            if (wind_speed_kmh < 0 ) {\n                wind_speed_kmh = 0;\n            }\n            have_wspd = 1;\n            decoder_logf(decoder, 2, __func__, \"Wind Speed calc passed ...\");\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",          \"\",               DATA_STRING, \"Thermor-DG950\",\n                \"id\",             \"\",               DATA_FORMAT, \"%d\",    DATA_INT,    id,\n                \"temperature_C\",  \"Temperature\",    DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_C,\n                \"rain_rate_mm_h\", \"Rain Rate\",      DATA_COND, have_rain, DATA_FORMAT, \"%.1f mm/h\", DATA_DOUBLE, rain_rate1 * 0.1f,\n                \"wind_dir_deg\",   \"Wind Direction\", DATA_COND, have_wdir, DATA_INT,     wind_dir_d,\n                \"wind_avg_km_h\",  \"Wind avg speed\", DATA_COND, have_wspd, DATA_FORMAT, \"%.1f km/h\", DATA_DOUBLE, wind_speed_kmh,\n                //\"wind_coef\",      \"Wind Coef\",      DATA_COND, have_wspd, DATA_INT,     wind_coef,\n                //\"wind_ratio\",     \"Wind Ratio\",     DATA_COND, have_wspd, DATA_DOUBLE,  wind_ratio,\n                \"pairing\",         \"Pairing?\",      DATA_INT, 0,\n                \"mic\",            \"Integrity\",      DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"wind_avg_km_h\",\n        \"rain_rate_mm_h\",\n        \"wind_dir_deg\",\n        \"wind_ratio\",\n        \"wind_coef\",\n        \"pairing\",\n        \"mic\",\n        NULL,\n};\n\nr_device const thermor = {\n        .name        = \"Thermor DG950 weather station\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 680,\n        .long_width  = 2100,\n        .sync_width  = 1438,\n        .gap_limit   = 3000,\n        .reset_limit = 8000,\n        .decode_fn   = &thermor_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_abarth124.c",
    "content": "/**  @file\n    VDO Type TG1C FSK 9 byte Manchester encoded checksummed TPMS data.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\n(VDO Type TG1C via) Abarth 124 Spider TPMS decoded by TTigges\nProtocol similar (and based on) Jansite Solar TPMS by Andreas Spiess and Christian W. Zuckschwerdt\n\nOEM Sensor is said to be a VDO Type TG1C, available in different cars,\ne.g.: Abarth 124 Spider, some Fiat 124 Spider, some Mazda MX-5 ND (and NC?) and probably some other Mazdas.\nMazda reference/part no.: BHB637140A\nVDO reference/part no.: A2C1132410180\n\nCompatible with aftermarket sensors, e.g. Aligator sens.it RS3\n\n// Working Temperature: -50°C to 125°C\n// Working Frequency: 433.92MHz+-38KHz\n// Tire monitoring range value: 0kPa-350kPa+-7kPa (to be checked, VDO says 450/900kPa)\n\nData layout (nibbles):\n    II II II II ?? PP TT SS CC\n- I: 32 bit ID\n- ?: 4 bit unknown (seems to change with status)\n- ?: 4 bit unknown (seems static)\n- P: 8 bit Pressure (multiplied by 1.38 = kPa)\n- T: 8 bit Temperature (deg. C offset by 50)\n- S: Status? (first nibble seems static, second nibble seems to change with status)\n- C: 8 bit Checksum (Checksum8 XOR on bytes 0 to 8)\n- The preamble is 0xaa..aa9 (or 0x55..556 depending on polarity)\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_abarth124_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 72);\n\n    // make sure we decoded the expected number of bits\n    if (packet_bits.bits_per_row[0] < 72) {\n        // decoder_logf(decoder, 0, __func__, \"bitpos=%u start_pos=%u = %u\", bitpos, start_pos, (start_pos - bitpos));\n        return 0; // DECODE_FAIL_SANITY;\n    }\n\n    uint8_t *b = packet_bits.bb[0];\n\n    // check checksum (checksum8 xor)\n    int const checksum = xor_bytes(b, 9);\n    if (checksum != 0) {\n        return 0; // DECODE_FAIL_MIC;\n    }\n\n    int const pressure    = b[5];\n    int const temperature = b[6];\n    int const status      = b[7];\n    // int const checksum    = b[8];\n\n    char flags[1 * 2 + 1];\n    snprintf(flags, sizeof(flags), \"%02x\", b[4]);\n    char id_str[4 * 2 + 1];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x%02x\", b[0], b[1], b[2], b[3]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Abarth-124Spider\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"flags\",            \"\",             DATA_STRING, flags,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (float)pressure * 1.38,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (float)temperature - 50.0,\n            \"status\",           \"\",             DATA_INT, status,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_abarth124_decode() */\nstatic int tpms_abarth124_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble\n    uint8_t const preamble_pattern[3] = {0xaa, 0xaa, 0xa9}; // after invert\n\n    unsigned bitpos = 0;\n    int events      = 0;\n\n    bitbuffer_invert(bitbuffer);\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 24)) + 80 <=\n            bitbuffer->bits_per_row[0]) {\n        events += tpms_abarth124_decode(decoder, bitbuffer, 0, bitpos + 24);\n        bitpos += 2;\n    }\n\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"status\",\n        \"code\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_abarth124 = {\n        .name        = \"Abarth 124 Spider TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_abarth124_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_airpuxem.c",
    "content": "/** @file\n    Airpuxem TYH11_EU6_ZQ FSK 84 bits Manchester encoded TPMS data.\n\n    Copyright (C) 2019 Alexander Grau, Bruno Octau, Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nAirpuxem TPMS Model TYH11_EU6_ZQ FSK.\n- Working Temperature:-40 °C to 125 °C\n- Working Frequency: 433.92MHz+-30KHz\n- Tire monitoring range value: 100kPa-900kPa+-7kPa\n- Based on SENASIC SNP739D TPMS IC ( https://www.senasic.com/Public/Uploads/uploadfile2/files/20240206/DS0069SNP739D0XDatasheet.pdf )\n- Probably a 'white-labeled' Jansite TPMS ( http://www.jansite.cn/P_view.asp?pid=232 )\n\nData layout (nibbles):\n\n    F  II II II II   M N  PP  TT  BB  CC  CC\n\n- F: 4 bit Sync (5)\n- I: 32 bit ID\n- M: 1 bit Pressure MSB_ONE, 3 bit Flags\n- N: 1 bit Pressure MSB TWO, 3 bit Sensor position\n- P: 8 bit Pressure LSB (kPa)\n- T: 8 bit Temperature (deg. C)\n- B: 8 bit Battery level  (a good guess)\n- C: 8 bit Checksum\n- The preamble is 0xaa..aa9 (or 0x55..556 depending on polarity)\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_airpuxem_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t dec = {0};\n    uint8_t *b;\n\n    // Decode up to ~200 bits of Manchester into a temp buffer\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &dec, 354);\n    if (dec.bits_per_row[0] < 84) {  // need at least 4 (\"FIVE\") + 64 (CRC'ed data) + 8 (CRC) + 8 (CRC again) = 84 bits\n        return DECODE_FAIL_SANITY;\n    }\n\n    b = dec.bb[0];\n\n    unsigned nbits  = dec.bits_per_row[0];\n\n    // Basic structural checks\n    if ((b[0] >> 4) != 0x5) {\n        return DECODE_FAIL_SANITY; // header nibble mismatch\n    }\n\n    // Compute CRC over 84 bits starting right after the 4-bit constant header (FIVE)\n    uint8_t payload[16] = {0};\n    bitbuffer_extract_bytes(&dec, 0, 4, payload, 64);\n    uint8_t crc_calc = crc8(payload, 8, 0x2f, 0xaa);\n\n    // Extract two CRC bytes following the 4+64-bit payload\n    uint8_t crcs[2] = {0};\n    bitbuffer_extract_bytes(&dec, 0, 4 + 64, crcs, 16);\n    if (crcs[0] != crc_calc) {\n        decoder_logf(decoder, 2, __func__, \"CRC mismatch calc=%02x exp0=%02x exp1=%02x len=%u\", crc_calc, crcs[0], crcs[1], nbits);\n        return DECODE_FAIL_MIC;\n    }\n\n    // Extract bitstream starting at bit offset 4\n    uint8_t id_bytes[10] = {0};\n    bitbuffer_extract_bytes(&dec, 0, 4, id_bytes, 10 * 8);\n    unsigned id = ((unsigned)id_bytes[0] << 24) | (id_bytes[1] << 16) | (id_bytes[2] << 8) | id_bytes[3];\n\n\n    char id_str[8 + 1];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    int flags       = (id_bytes[4] >> 4) & 0x07;\n    int position    = id_bytes[4] & 0x07;\n    int pressure    = (id_bytes[5] | (((id_bytes[4] >> 7) & 1) << 8) | (((id_bytes[4] >> 3) & 1) << 9)) - 100;\n    int temperature  = (char) id_bytes[6];\n    int battery      =  id_bytes[7];\n\n    char code_str[11 * 2 + 1];\n    bitrow_snprint(b, 11 * 8, code_str, sizeof(code_str));\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Airpuxem-TYH11EU6ZQ\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"position\",         \"\",             DATA_INT, position,\n            \"flags\",            \"\",             DATA_INT, flags,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (double)pressure ,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temperature ,\n            \"battery_V\",        \"Battery\",      DATA_FORMAT, \"%.1f V\", DATA_DOUBLE, (double)battery * 0.02,\n            \"code\",             \"\",             DATA_STRING, code_str,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_airpuxem_decode() */\nstatic int tpms_airpuxem_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is (hex)\n    // 5555555555555555555555555555555555555555555556\n\n    // invert, search preamble on each row, then decode after it\n    uint8_t const preamble_pattern[3] = {0xaa, 0xaa, 0xa9}; // after invert\n\n    int ret    = 0;\n    int events = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        unsigned bitpos = 0;\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos, preamble_pattern, 24)) + 80 <=\n                bitbuffer->bits_per_row[row]) {\n                ret = tpms_airpuxem_decode(decoder, bitbuffer, row, bitpos + 24);\n            if (ret > 0)\n                events += ret;\n            bitpos += 2;\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"position\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"battery_V\",\n        \"code\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_airpuxem = {\n        .name        = \"Airpuxem TPMS TYH11_EU6_ZQ\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 150,\n        .decode_fn   = &tpms_airpuxem_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_ave.c",
    "content": "/** @file\n    AVE TPMS FSK 11 byte differential Manchester encoded CRC-8 TPMS data.\n\n    Copyright (C) 2021 Pascal Charest\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nAVE TPMS FSK 11 byte differential Manchester encoded CRC-8 TPMS data.\n\n\nPacket nibbles:\n\n    PRE    IIIIIIII PP TT FF  CC\n\n- PRE = preamble is 0xff 0xfe\n- I = sensor Id in hex\n- P = Pressure (4 conversion tables available)\n- T = Temperature (deg C offset by 50)\n- F = Flags\n--    mode: 2 bits, mode 0 and 1 are 2.35kPa per pressure bit, mode 2 and 3 are 5.5kPa\n--    battery: 3 bits, 7 is low, 6 not full and all other is full\n--    unknown: 3 bits, last bit seems to swap from time to time\n- C = CRC-8 with poly 0x31 init 0xff (alternatively, 0xd3 and 0x1e)\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_ave_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n\n    bitbuffer_differential_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 160);\n\n    if (packet_bits.bits_per_row[row] < 64) {\n        return DECODE_ABORT_LENGTH; // too short to be a whole packet\n    }\n    decoder_log_bitbuffer(decoder, 1, __func__, &packet_bits, \"\");\n\n    uint8_t *b = packet_bits.bb[row];\n\n    if (crc8(b, 8, 0x31, 0xff) != 0) {\n        return DECODE_FAIL_MIC; // bad checksum\n    }\n\n    unsigned id      = (unsigned)b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];\n    int pressure_raw = b[4];\n    int temperature  = b[5];\n    int mode         = b[6] >> 6 & 0x3;\n    int battery_raw  = b[6] >> 3 & 0x7;\n    int flags        = b[6] & 0x7;\n    // int crc          = b[7];\n\n    int battery_pct = 100; // any other value, i.e. less than 6\n    if (battery_raw == 6) {\n        battery_pct = 75; // not full\n    }\n    else if (battery_raw == 7) {\n        battery_pct = 25; // low\n    }\n\n    double ratio;\n    double offset;\n    switch (mode) {\n    case 0:\n        ratio  = 2.352f;\n        offset = 47.0f;\n        break;\n    case 1:\n    default:\n        ratio  = 2.352f;\n        offset = 0.0f;\n        break;\n    case 2:\n        ratio  = 5.491f;\n        offset = 18.2f;\n        break;\n    case 3:\n        ratio  = 5.491f;\n        offset = 0.0f;\n        break;\n    }\n    double pressure = ((double)pressure_raw - offset) * ratio;\n\n    char id_str[9 + 1];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"Model\",         DATA_STRING, \"AVE\",\n            \"type\",             \"Type\",          DATA_STRING, \"TPMS\",\n            \"id\",               \"Id\",            DATA_STRING, id_str,\n            \"mode\",             \"Mode\",          DATA_FORMAT, \"M%d\", DATA_INT, mode,\n            \"pressure_kPa\",     \"Pressure\",      DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, pressure,\n            \"temperature_C\",    \"Temperature\",   DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temperature - 50.0,\n            \"battery_ok\",       \"Battery\",       DATA_INT,    battery_raw != 7, // Value 7 means \"Low\"\n    \t\t\"battery_pct\",      \"Battery level\", DATA_INT,    battery_pct, // Note: this might change with #3103\n            \"flags\",            \"Flags\",         DATA_FORMAT, \"0x%x\", DATA_INT, flags,\n            \"mic\",              \"Integrity\",     DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nWrapper for the AVE tpms.\n@sa tpms_ave_decode()\n*/\nstatic int tpms_ave_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xcc, 0xcc, 0xcc, 0xcd}; // Raw pattern, before differential Manchester coding\n\n    int row;\n    unsigned bitpos;\n    int ret    = 0;\n    int events = 0;\n\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        bitpos = 0;\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 32)) + 132 <=\n                bitbuffer->bits_per_row[0]) {\n            ret = tpms_ave_decode(decoder, bitbuffer, row, bitpos + 32);\n            if (ret > 0) {\n                events += ret;\n                bitpos += 132;\n            }\n            bitpos += 31;\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"battery_ok\",\n        \"battery_pct\",\n        \"mode\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_ave = {\n        .name        = \"AVE TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100,\n        .long_width  = 100,\n        .reset_limit = 400,\n        .tolerance   = 15,\n        .decode_fn   = &tpms_ave_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_bmw.c",
    "content": "/** @file\n    BMW Gen4 Gen5 TPMS and Audi TPMS Pressure Alert sensor.\n\n    Copyright (C) 2024 Bruno OCTAU (ProfBoc75), \\@petrjac, \\@Gucioo, Christian W. Zuckschwerdt <christian@zuckschwerdt.org>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nBMW Gen4 Gen5 TPMS and Audi TPMS Pressure Alert sensor.\n\nissue #2821:\n- BMW Gen5 TPMS support open by \\@petrjac\n- BMW Gen4 TPMS supported\n\n#2821 issue comment 2043641606 \\@Gucioo\n- Audi TPMS based on the same protocol with shorter message in case of sudden increase or decrease in pressure\n\nSamples raw :\n\n    BMW\n    {207}555554b2aab4b2b552acb4d332accb32b552aaacd334d32ad334\n    {211}555554b2aab4b2b552acb4d332acb4cab54caaacd4cad32b4b55e\n\n    Audi Pressure Alert\n    {166}2aaaaa5955555955a5556a65666a56aa65a65999fc\n    {165}2aaaaa5955555955a5556a65666a56aa65a65999f8\n    {167}5555552caaaaacaad2aab532b3352b5532d32cccfe\n\n- Preamble {16} 0xaa59 before MC\n- MC Zero bit coded, 11 bytes or 8 bytes\n\nSamples after MC Inverted:\n\n    BMW\n     0  1  2  3  4  5  6  7  8  9 10\n    MM II II II II PP TT F1 F2 F3 CC\n    03 23 e1 36 a1 4a 3e 01 6b 68 6b\n    03 23 e1 36 a1 34 3d 01 74 68 cf\n\n    AUDI Pressure Alert\n     0  1  2  3  4  5  6  7\n    MM II II II II PP TT CC\n    00 20 c0 74 57 36 4c 23\n\n- MM : Brand BRAND ID, 0x00 = Audi Pressure Alert, 0x03 = HUF Gen 5/Beru, 0x23 = Schrader/Sensata, 0x80 = Continental, 0x88 Audi\n- II : Sensor ID\n- PP : Pressure * 2.45 kPa\n- TT : Temp - 52 C\n- F1 : BMW only, Warning Flags , battery, fast deflating ... not yet guess\n- F2 : BMW only, Sequence number, to be confirmed\n- F3 : BMW only, Target Nominal Pressure * 0.0245 for 0x03\n- CC : CRC 8 of previous bytes (7 bytes for Audi Pressure Alert, 10 bytes for BMW) , poli 0x2f, init 0xaa\n\nData layout after MC for HUF Gen 5:\n\n    BRAND = 8h | SENSOR_ID = 32h      | PRESS = 8d  | TEMP = 8d  | FLAGS1 = 8h | FLAGS2 = 8h | FLAGS3 = 8d  | CRC = 8h\n\n    BRAND = 03 | SENSOR_ID = 23e136a1 | PRESS = 074 | TEMP = 062 | FLAGS1 = 01 | FLAGS2 = 6b | FLAGS3 = 104 | CRC = 6b\n    BRAND = 03 | SENSOR_ID = 23e136a1 | PRESS = 052 | TEMP = 061 | FLAGS1 = 01 | FLAGS2 = 74 | FLAGS3 = 104 | CRC = cf\n\nContinental model:\n\n    F1, F2, F3 to guess\n\nSchrader/Sensata model:\n\n    F1, F2, F3 to guess\n\nAudi Pressure Alert:\n\n    BRAND = 8h | SENSOR_ID = 32h      | PRESS = 8d  | TEMP = 8d  | CRC = 8h\n\n    BRAND = 00 | SENSOR_ID = 20c07457 | PRESS = 054 | TEMP = 076 | CRC = 6b\n\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_bmw_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_t decoded = { 0 };\n    uint8_t *b;\n    // preamble is aa59\n    uint8_t const preamble_pattern[] = {0xaa, 0x59};\n    uint8_t len_msg = 11; // default for BMW = 11, if Audi-Alert len_msg = 8\n    int flags1      =  0;\n    int flags2      =  0;\n    int flags3      =  0;\n    char msg_str[23];\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 2, __func__, \"row error\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    int pos = 0;\n    pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n    if (pos >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 2, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_log_bitrow(decoder, 2, __func__, bitbuffer->bb[0], bitbuffer->bits_per_row[0], \"MSG\");\n\n    bitbuffer_manchester_decode(bitbuffer, 0, pos + sizeof(preamble_pattern) * 8, &decoded, len_msg * 8);\n\n    decoder_log_bitrow(decoder, 2, __func__, decoded.bb[0], decoded.bits_per_row[0], \"MC\");\n\n    if (decoded.bits_per_row[0] < 88) {\n        // Check if Audi\n        if (decoded.bits_per_row[0] >= 64 ) {\n            len_msg = 8;\n        }\n        else {\n            decoder_logf(decoder, 1, __func__, \"Too short\");\n            return DECODE_ABORT_LENGTH;\n        }\n    }\n\n    bitbuffer_invert(&decoded); // MC Zerobit\n    decoder_log_bitrow(decoder, 2, __func__, decoded.bb[0], decoded.bits_per_row[0], \"MC inverted\");\n    b = decoded.bb[0];\n    if (crc8(b, len_msg, 0x2f, 0xaa)) {\n        decoder_logf(decoder, 1, __func__, \"crc error, expected %02x, calculated %02x\", b[11], crc8(b, len_msg, 0x2f, 0xaa));\n        return DECODE_FAIL_MIC; // crc mismatch\n    }\n    decoder_log(decoder, 2, __func__, \"BMW or Audi found\");\n    int brand_id            = b[0]; // 0x00 = Audi-Alert, 0x03 = HUF/Beru, 0x23 = Schrader/Sensata, 0x80 = Continental, 0x88 = Audi\n    float pressure_kPa      = b[5] * 2.45;\n    int temperature_C       = b[6] - 52;\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x%02x\", b[1], b[2], b[3], b[4]);\n\n    if (len_msg == 11) {\n        flags1              = b[7]; // depends on brand_id, could be pressure or SEQ ID and other WARM flags Battery , fast deflating ...\n        flags2              = b[8]; // depends on brand_id, could be pressure and other WARM flags Battery , fast deflating ...\n        flags3              = b[9]; // Nominal Pressure for brand HUF 0x03, depends on brand_id, could be SEQ ID and other WARM flags Battery , fast deflating ...\n        snprintf(msg_str, sizeof(msg_str), \"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]);\n    }\n    else {\n        snprintf(msg_str, sizeof(msg_str), \"%02x%02x%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",               \"\",                DATA_COND, len_msg == 11, DATA_STRING, \"BMW-GEN5\",\n            \"model\",               \"\",                DATA_COND, len_msg == 8, DATA_STRING, \"Audi-PressureAlert\",\n            \"type\",                \"\",                DATA_STRING, \"TPMS\",\n            \"alert\",               \"Alert\",           DATA_COND, len_msg == 8, DATA_STRING, \"Alert Pressure increase/decrease !\",\n            \"brand\",               \"Brand\",           DATA_INT,    brand_id,\n            \"id\",                  \"\",                DATA_STRING, id_str,\n            \"pressure_kPa\",        \"Pressure\",        DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, (double)pressure_kPa,\n            \"temperature_C\",       \"Temperature\",     DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, (double)temperature_C,\n            \"flags1\",              \"\",                DATA_COND, len_msg == 11, DATA_INT,    flags1,\n            \"flags2\",              \"\",                DATA_COND, len_msg == 11, DATA_INT,    flags2,\n            \"flags3\",              \"\",                DATA_COND, len_msg == 11, DATA_INT,    flags3, // Nominal Pressure for brand HUF 0x03\n            \"msg\",                 \"msg\",             DATA_STRING, msg_str, // To remove after guess all tags\n            \"mic\",                 \"Integrity\",       DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"alert\",\n        \"id\",\n        \"brand\",\n        \"battery_ok\",\n        \"pressure_kPa\",\n        \"flags1\",\n        \"flags2\",\n        \"flags3\",\n        \"msg\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_bmw = {\n        .name        = \"BMW Gen4-Gen5 TPMS and Audi TPMS Pressure Alert, multi-brand HUF/Beru, Continental, Schrader/Sensata, Audi\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 25,\n        .long_width  = 25,\n        .reset_limit = 100,\n        .decode_fn   = &tpms_bmw_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_bmw_g3.c",
    "content": "/** @file\n    BMW Gen2 Gen3 TPMS sensor.\n\n    Copyright (C) 2024 Bruno OCTAU (ProfBoc75), \\@Billymazze\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nBMW Gen2 Gen3 TPMS sensor.\n\nissue #2893 BMW Gen3 TPMS support open by \\@Billymazze\n\nissue #3300 BMW gen 2/3 TPMS\n\nLast progress based on this:\n\n    rtl_433 -Y autolevel -Y minmax -X \"n=BMW_G3,m=FSK_PCM,s=52,l=52,r=1000,preamble=cccd,decode_dm,bits>=190\" *.cs8 2>&1 | grep \"\\{89\\}\"\n    codes : {89}1c50f1758545f8020373428\n\nRF signal:\n\n    FSK, PCM, s=l=52 µs, Differential Manchester\n\nData layout{89} 11 x 8 (Gen3):\n\n    Byte Position  0  1  2  3  4  5  6  7  8  9 10 11\n    Data Layout  [II II II II PP TT F1 F2 F3]CC CC 8\n    Sample        1c 50 f1 75 85 45 f8 02 03 73 42 8\n\nData layout {81} 10 x 8 (Gen2):\n\n                   0  1  2  3  4  5  6  7  8  9 10\n                 [78 34 a9 7e 90 3c 80 51]6a 6e 0 [CRC 16 0x1021 0x0000 OK]\n                 [1e 2a e7 fe 89 3a f8 51]71 0d 0 [CRC OK]\n                 [78 34 a9 7e 93 40 80 51]5c db 0 [CRC OK]\n\n- II:{32} ID, hexa 0x1c50f175 or decimal value 475066741\n- PP:{8}: Tire pressure, PSI = (PP - 43) * 0.363 or kPa = ( PP - 43 ) * 2.5\n- TT:{8}: Temperature in C offset 40\n- F1, F2 Flags that could contain battery information, flat tire, lost of pressure ...\n- F3, Flags only on Gen3 model.\n- CC: CRC-16 bits, poly 0x1021, init 0x0000 [from previous bytes].\n- 8 or 0: useless trailing bit after CRC.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_bmwg3_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    bitbuffer_t decoded = { 0 };\n    uint8_t *b;\n    // preamble = 0xcccd\n    uint8_t const preamble_pattern[] = {0xcc, 0xcd};\n\n    if (bitbuffer->num_rows != 1) {\n        decoder_logf(decoder, 2, __func__, \"row error\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    int pos = 0;\n    pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, 16);\n    if (pos >= bitbuffer->bits_per_row[0]) {\n        decoder_logf(decoder, 1, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    decoder_log_bitrow(decoder, 1, __func__, bitbuffer->bb[0], bitbuffer->bits_per_row[0], \"MSG\");\n\n    bitbuffer_differential_manchester_decode(bitbuffer, 0, pos + sizeof(preamble_pattern) * 8, &decoded, 88); // 11 * 8\n\n    decoder_log_bitrow(decoder, 2, __func__, decoded.bb[0], decoded.bits_per_row[0], \"DMC\");\n\n    // Based on the length decide if Gen2 (10 bytes, 81 bits) or Gen3 (11 bytes, 89 bits).\n    int msg_len = decoded.bits_per_row[0];\n    int is_gen2 = 0;\n\n    if (80 <= msg_len && msg_len < 88 ) { // could be Gen2\n        is_gen2 = 1;\n    }\n    if (msg_len < 80) {\n        decoder_logf(decoder, 2, __func__, \"Too short\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = decoded.bb[0];\n\n    if (crc16(b, 11 - is_gen2, 0x1021, 0x0000)) {\n        decoder_logf(decoder, 1, __func__, \"crc error, expected %02x%02x, calculated %04x\", b[9 - is_gen2], b[10 - is_gen2], crc16(b, 11 - is_gen2, 0x1021, 0x0000));\n        return DECODE_FAIL_MIC; // crc mismatch\n    }\n\n    if (is_gen2) {\n        decoder_log(decoder, 1, __func__, \"BMW Gen 2 found\");\n    } else {\n        decoder_log(decoder, 1, __func__, \"BMW Gen 3 found\");\n    }\n\n    float pressure_kPa      = (b[4] - 43) * 2.5f;\n    float temperature_C     = (b[5] - 40);\n    int flags1              = b[6]; // fixed value to 0xf8 could be Brand ID ?\n    int flags2              = b[7]; // Battery , pressure warning ?\n    int flags3              = b[8]; // fixed value to 0x03 could be Brand ID ?, useless with Gen2\n\n    uint32_t id             = ((uint32_t)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | (b[3]);\n\n    char id_str[23];\n    snprintf(id_str, sizeof(id_str), \"%u\", id);\n\n    char msg_str[23];\n    if (is_gen2) {\n        snprintf(msg_str, sizeof(msg_str), \"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9]);\n    }else{\n        snprintf(msg_str, sizeof(msg_str), \"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]);\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",               \"\",                DATA_COND, is_gen2, DATA_STRING, \"BMW-GEN2\",\n            \"model\",               \"\",                DATA_COND, !is_gen2, DATA_STRING, \"BMW-GEN3\",\n            \"type\",                \"\",                DATA_STRING, \"TPMS\",\n            \"id\",                  \"\",                DATA_FORMAT, \"%u\", DATA_INT, id,\n            \"uid\",                 \"\",                DATA_STRING,             id_str, // unsigned id\n            \"pressure_kPa\",        \"Pressure\",        DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, (double)pressure_kPa,\n            \"temperature_C\",       \"Temperature\",     DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, temperature_C,\n            \"flags1\",              \"\",                DATA_FORMAT, \"%08b\",     DATA_INT,    flags1,\n            \"flags2\",              \"\",                DATA_FORMAT, \"%08b\",     DATA_INT,    flags2,\n            \"flags3\",              \"\",                DATA_COND, !is_gen2,     DATA_FORMAT, \"%08b\",     DATA_INT,    flags3,\n            \"msg\",                 \"msg\",             DATA_STRING, msg_str, // To remove after guess all tags\n            \"mic\",                 \"Integrity\",       DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"uid\",\n        \"battery_ok\",\n        \"pressure_kPa\",\n        \"flags1\",\n        \"flags2\",\n        \"flags3\",\n        \"msg\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_bmwg3 = {\n        .name        = \"BMW Gen2 and Gen3 TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 160,\n        .decode_fn   = &tpms_bmwg3_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_citroen.c",
    "content": "/** @file\n    Citroen FSK 10 byte Manchester encoded checksummed TPMS data.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nCitroen FSK 10 byte Manchester encoded checksummed TPMS data\nalso Peugeot and likely Fiat, Mitsubishi, VDO-types.\n\n\nPacket nibbles:\n\n    UU  IIIIIIII FR  PP TT BB  CC\n\n- U = state, decoding unknown, not included in checksum\n- I = id\n- F = flags, (seen: 0: 69.4% 1: 0.8% 6: 0.4% 8: 1.1% b: 1.9% c: 25.8% e: 0.8%)\n- R = repeat counter (seen: 0,1,2,3)\n- P = Pressure (kPa in 1.364 steps, about fifth PSI?)\n- T = Temperature (deg C offset by 50)\n- B = Battery?\n- C = Checksum, XOR bytes 1 to 9 = 0\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_citroen_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    uint8_t *b;\n    int state;\n    unsigned id;\n    int flags;\n    int repeat;\n    int pressure;\n    int temperature;\n    int maybe_battery;\n    int crc;\n\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 88);\n\n    // decoder_logf(decoder, 3, __func__, \"bits %d\", packet_bits.bits_per_row[0]);\n    if (packet_bits.bits_per_row[0] < 80) {\n        return DECODE_FAIL_SANITY; // sanity check failed\n    }\n\n    b = packet_bits.bb[0];\n\n    if (b[6] == 0 || b[7] == 0) {\n        return DECODE_ABORT_EARLY; // sanity check failed\n    }\n\n    crc = b[1] ^ b[2] ^ b[3] ^ b[4] ^ b[5] ^ b[6] ^ b[7] ^ b[8] ^ b[9];\n    if (crc != 0) {\n        return DECODE_FAIL_MIC; // bad checksum\n    }\n\n    state         = b[0]; // not covered by CRC\n    id            = (unsigned)b[1] << 24 | b[2] << 16 | b[3] << 8 | b[4];\n    flags         = b[5] >> 4;\n    repeat        = b[5] & 0x0f;\n    pressure      = b[6];\n    temperature   = b[7];\n    maybe_battery = b[8];\n\n    char state_str[3];\n    snprintf(state_str, sizeof(state_str), \"%02x\", state);\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Citroen\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"state\",            \"\",             DATA_STRING, state_str,\n            \"flags\",            \"\",             DATA_INT,    flags,\n            \"repeat\",           \"\",             DATA_INT,    repeat,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (double)pressure * 1.364,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temperature - 50.0,\n            \"maybe_battery\",    \"\",             DATA_INT,    maybe_battery,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_citroen_decode() */\nstatic int tpms_citroen_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 55 55 55 56 (inverted: aa aa aa a9)\n    uint8_t const preamble_pattern[2] = {0xaa, 0xa9}; // 16 bits\n    // full trailer is 01111110\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 16)) + 178 <=\n            bitbuffer->bits_per_row[0]) {\n        ret = tpms_citroen_decode(decoder, bitbuffer, 0, bitpos + 16);\n        if (ret > 0)\n            events += ret;\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"state\",\n        \"flags\",\n        \"repeat\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"maybe_battery\",\n        \"code\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_citroen = {\n        .name        = \"Citroen TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_citroen_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_eezrv.c",
    "content": "/** @file\n    EezTire E618 TPMS and Carchet TPMS (same protocol).\n\n    Copyright (C) 2023 Bruno OCTAU (ProfBoc75), Gliebig, and Karen Suhm\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nEezTire E618 TPMS and Carchet TPMS (same protocol).\n\n- Eez RV supported TPMS sensor model E618 : https://eezrvproducts.com/shop/ols/products/tpms-system-e518-anti-theft-replacement-sensor-1-ea\n- Carchet TPMS: http://carchet.easyofficial.com/carchet-rv-trailer-car-solar-tpms-tire-pressure-monitoring-system-6-sensor-lcd-display-p6.html\n- TST (Truck Systems Technologies) 507 Series TPMS : https://github.com/cterwilliger/tst_tpms\n\nThe device uses OOK (ASK) encoding.\nThe device sends a transmission every 1 second when quick deflation is detected, every 13 - 23 sec when quick inflation is detected, and every 4 min 40 s under steady state pressure.\nA transmission starts with a preamble of 0x0000 and the packet is sent twice.\n\nS.a issue #2384, #2657, #2063, #2677, #2819\n\nData collection parameters on URH software were as follows:\n    Sensor frequency: 433.92 MHz\n    Sample rate: 2.0 MSps\n    Bandwidth: 2.0 Hz\n    Gain: 125\n\n    Modulation is ASK (OOK). Packets in URH arrive in the following format:\n\n    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa [Pause: 897679 samples]\n    aaaaaaaa5956a5a5a6555aaa65959999a5aaaaaa [Pause: 6030 samples]\n    aaaaaaaa5956a5a5a6555aaa65959999a5aaaaaa [Pause: 11176528 samples]\n\n    Decoding is Manchester I.  After decoding, the packets look like this:\n\n    00000000000000000000000000000000000000\n    0000de332fc0b7553000\n    0000de332fc0b7553000\n\n Using rtl_433 software, packets were detected using the following command line entry:\n rtl_433 -X \"n=Carchet,m=OOK_MC_ZEROBIT,s=50,l=50,r=1000,invert\" -s 1M\n\n Data layout:\n\n    PRE CC IIIIII PP TT FF FF\n\n- PRE : FFFF\n- C : 8 bit CheckSum, modulo 256 with overflow flag\n- I: 24 bit little-endian ID\n- P: 8 bit pressure  P * 2.5 = Pressure kPa\n- T: 8 bit temperature   T - 50 = Temperature C\n- F: 16 bit status flags: 0x8000 = low battery, 0x1000 = quick deflation, 0x3000 = quick inflation, 0x0000 = static/steady state\n\nRaw Data example :\n\n    ffff 8b 0d177e 8f 4a 10 00\n\nFormat string:\n\n    CHECKSUM:8h ID:24h KPA:8d TEMP:8d FLAG:8b 8b\n\nDecode example:\n\n    CHECKSUM:8b ID:0d177e KPA:8f TEMP:4a FLAG:10 00\n\n*/\n\nstatic int tpms_eezrv_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble is ffff\n    uint8_t const preamble_pattern[] = {0xff, 0xff};\n\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n    int pos = 0;\n    bitbuffer_invert(bitbuffer);\n    pos = bitbuffer_search(bitbuffer, 0, pos, preamble_pattern, sizeof(preamble_pattern) * 8);\n    if (pos >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 3, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n    if (pos + 8 * 8 > bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"Length check fail\");\n        return DECODE_ABORT_LENGTH;\n    }\n    uint8_t b[7]  = {0};\n    uint8_t cc[1] = {0};\n    bitbuffer_extract_bytes(bitbuffer, 0, pos + 16, cc, sizeof(cc) * 8);\n    bitbuffer_extract_bytes(bitbuffer, 0, pos + 24, b, sizeof(b) * 8);\n\n    // Verify checksum\n    // If the checksum is greater than 0xFF then the MSB is set.\n    // It occurs whether the bit is already set or not and was observed when checksum was in the 0x1FF and the 0x2FF range.\n    int computed_checksum = add_bytes(b, sizeof(b));\n    if (computed_checksum > 0xff) {\n        computed_checksum |= 0x80;\n    }\n\n    if ((computed_checksum & 0xff) != cc[0]) {\n        decoder_log(decoder, 2, __func__, \"Checksum fail\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int temperature_C      = b[4] - 50;\n    int flags1             = b[5];\n    int flags2             = b[6];\n    int fast_leak_detected = (flags1 & 0x10);      // fast leak - reports every second\n    int infl_detected      = (flags1 & 0x20) >> 5; // inflating - reports every 15 - 20 sec\n\n    int fast_leak      = fast_leak_detected && !infl_detected;\n    float pressure_kPa = (((flags2 & 0x01) << 8) + b[3]) * 2.5f;\n\n    // Low batt = 0x8000;\n    int low_batt = flags1 >> 7; // Low batt flag is MSB (activated at V < 3.15 V)(Device fails at V < 3.10 V)\n    // Mystery flag at (flags2 & 0x20) showed up during low batt testing\n\n    char id_str[7];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x\", b[0], b[1], b[2]);\n\n    char flags_str[5];\n    snprintf(flags_str, sizeof(flags_str), \"%02x%02x\", flags1, flags2);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"EezTire-E618\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"battery_ok\",       \"Battery_OK\",   DATA_INT,    !low_batt,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (double)pressure_kPa,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, (double)temperature_C,\n            \"flags\",            \"Flags\",        DATA_STRING, flags_str,\n            \"fast_leak\",        \"Fast Leak\",    DATA_INT,    fast_leak,\n            \"inflate\",          \"Inflate\",      DATA_INT,    infl_detected,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"battery_ok\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"flags\",\n        \"fast_leak\",\n        \"inflate\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_eezrv = {\n        .name        = \"EezTire E618, Carchet TPMS, TST-507 TPMS\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 50,\n        .long_width  = 50,\n        .reset_limit = 120,\n        .decode_fn   = &tpms_eezrv_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_elantra2012.c",
    "content": "/** @file\n    TPMS for Hyundai Elantra, Honda Civic.\n\n    Copyright (C) 2019 Kumar Vivek <kv2000in@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFSK 8 byte Manchester encoded TPMS with CRC8 checksum.\nSeen on Hyundai Elantra, Honda Civic.\n\n- TRW TPMS sensor FCC id GQ4-44T\n- Mode/Sensor status: shipping, test, parking, driving, first block mode\n- Battery voltage: Ok, low\n- Trigger information: LF initiate TM\n- Pressure: 1.4kPa\n- temperature: 27 deg C\n- acceleration: 0.5 g\n- Market: EU, America\n- Tire type: 450 kPa\n- Response time: 8.14 seconds\n- ID: 8 bytes\n\nPreamble is 111 0001 0101 0101 (0x7155).\n64 bits Manchester encoded data.\n\n    PPTT IDID IDID FFCC\n\n- P: Pressure in (8 bit), offset +60 = pressure in kPa\n- T: Temperature (8 bit), offset -50 = temp in C\n- I: ID (32 bit)\n- F: Flags (8 bit) = ???? ?SBT (Missing Acceleration, market - Europe/US/Asia, Tire type, Alert Mode, park mode, High Line vs Low LIne etc)\n  - S: Storage bit\n  - B: Battery low bit\n  - T: Triggered bit\n  - C0 =1100 0000 = Battery OK, Not Triggered\n  - C1 =1100 0001 = Battery OK, Triggered\n  - C2 =1100 0010 = Battery Low, Not Triggered\n  - C3 =1100 0011 = Battery Low, Triggered\n  - C5 =1100 0101 = Battery OK, Triggered, Storage Mode\n  - E1 =1110 0001 = Mx Sensor Clone for Elantra 2012 US market ? Low Line\n  - C1            = Mx Sensor Clone for Genesis Sedan 2012 US market ? High Line\n- C: CRC-8, poly 0x07, init 0x00\n\n*/\n\nstatic int tpms_elantra2012_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 64);\n    // require 64 data bits\n    if (packet_bits.bits_per_row[0] < 64) {\n        return DECODE_ABORT_LENGTH;\n    }\n    uint8_t *b = packet_bits.bb[0];\n\n    if (crc8(b, 8, 0x07, 0x00)) {\n        return DECODE_FAIL_MIC;\n    }\n\n    uint32_t id       = ((uint32_t)b[2] << 24) | (b[3] << 16) | (b[4] << 8) | (b[5]);\n    int flags         = b[6];\n    int pressure_kpa  = b[0] + 60;\n    int temperature_c = b[1] - 50;\n    int storage       = (b[6] & 0x04) >> 2;\n    int battery_low   = (b[6] & 0x02) >> 1;\n    int triggered     = (b[6] & 0x01) >> 0;\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n    char flags_str[3];\n    snprintf(flags_str, sizeof(flags_str), \"%x\", flags);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Elantra2012\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, (float)pressure_kpa,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (float)temperature_c,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"triggered\",        \"LF Triggered\", DATA_INT,    triggered,\n            \"storage\",          \"Storage mode\", DATA_INT,    storage,\n            \"flags\",            \"All Flags\",    DATA_STRING, flags_str,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_elantra2012_decode() */\nstatic int tpms_elantra2012_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Note that there is a (de)sync preamble of long/short, short/short, triple/triple,\n    // i.e. 104 44, 52 48, 144 148 us pulse/gap.\n    /* preamble = 111000101010101 0x71 0x55 */\n    uint8_t const preamble_pattern[] = {0x71, 0x55}; // 16 bits\n\n    int row;\n    unsigned bitpos;\n    int ret    = 0;\n    int events = 0;\n\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        bitpos = 0;\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos,\n                        preamble_pattern, 16)) + 128 <=\n                bitbuffer->bits_per_row[row]) {\n            ret = tpms_elantra2012_decode(decoder, bitbuffer, row, bitpos + 16);\n            if (ret > 0)\n                events += ret;\n            bitpos += 15;\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"battery_ok\",\n        \"triggered\",\n        \"storage\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_elantra2012 = {\n        .name        = \"Elantra2012 TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 49,  // 12-13 samples @250k\n        .long_width  = 49,  // FSK\n        .reset_limit = 200, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_elantra2012_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_ford.c",
    "content": "/** @file\n    FSK 8 byte Manchester encoded TPMS with simple checksum.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nFSK 8 byte Manchester encoded TPMS with simple checksum.\nSeen on Ford Fiesta, Focus, Kuga, Escape, Transit...\n\nSeen on 315.00 MHz (United States).\n\nSeen on 433.92 MHz.\nLikely VDO-Sensors, Type \"S180084730Z\", built by \"Continental Automotive GmbH\".\n\nTypically a transmission is sent 4 times.  Sometimes the T/P values\ndiffer (slightly) among those.\n\nSensor has 3 modes:\n  moving: while being driven\n  atrest: once after stopping, and every 6h thereafter (for months)\n  learn: 12 transmissions, caused by using learn tool\n\nPacket nibbles:\n\n    II II II II PP TT FF CC\n\n- I = ID\n- P = Pressure, as PSI * 4\n- T = Temperature, as C + 56, except:\n      When 0x80 is on, value is not temperature, meaning the full 8\n      bits is not temperature, and the lower 7 bits is also not\n      temperature.  Pattern of low 7 bits in this case seems more like\n      codepoints than a measurement.\n- F = Flags:\n      0x80 not seen\n      0x40 ON for vehicle moving\n        Is strongly correlated with 0x80 being set in TT\n      0x20: 9th bit of pressure.  Seen on Transit very high pressure, otherwise not.\n      0x10: not seen\n\n      0x08: ON for learn\n      0x04: ON for moving (0x08 and 0x04 both OFF for at rest)\n      0x02: ~always NOT 0x01 (meaning of 0x3 not understood, but MOVING\n            tends to have 0x02)\n      0x01: about 19% of samples\n- C = Checksum, SUM bytes 0 to 6 = byte 7\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_ford_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 160);\n\n    // require 64 data bits\n    if (packet_bits.bits_per_row[0] < 64) {\n        return 0;\n    }\n    uint8_t *b = packet_bits.bb[0];\n\n    if (((b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6]) & 0xff) != b[7]) {\n        return 0;\n    }\n\n    unsigned id = (unsigned)b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];\n\n    // Extract and log code to aid in debugging.\n    int code = b[4] << 16 | b[5] << 8 | b[6];\n\n    // Formula is a combination of regression and plausible, observed\n    // from roughly 31 to 36 psi.  (The bit at byte6-0x20 is shifted\n    // to 0x100.)\n    int psibits        = (((b[6] & 0x20) << 3) | b[4]);\n    float pressure_psi = psibits * 0.25f;\n\n    // Working theory is that temperature bits are temp + 56,\n    // encoding -56 to 71 C.  Validated as close around 15 C.\n    int temperature_valid = 0;\n    int temperature_c     = -1000.0;\n\n    if ((b[5] & 0x80) == 0) {\n        temperature_valid = 1;\n        temperature_c     = (b[5] & 0x7f) - 56;\n    }\n\n    // Set up syndrome of unexpected bits.  The point is to have a\n    // variable unknown which is zero if this packet matches the\n    // code's understanding, and to be non-zero if anything is unusual,\n    // to aid finding logged packets for manual study.\n\n    // Examine moving, learn and normal bits.\n    int learn  = 0;\n    int moving = 0;\n    int unknown = 0;\n    switch (b[6] & 0x4c) {\n    case 0x8:\n        // In response to learn tool\n        learn = 1;\n        break;\n\n    case 0x4:\n        // At rest.\n        break;\n\n    case 0x44:\n        // Moving.\n        moving = 1;\n        break;\n\n    default:\n        // These three bits taken together do not match a known\n        // pattern.  Therefore set all of them as the unknown\n        // syndrome.\n        unknown = (b[6] & 0x4c);\n        break;\n    }\n\n    // We've accounted for\n    // 0x40(moving) 0x20(temp) 0x8(learn) 04(normal)\n    // 0x3(separate, next)\n    // so that leaves 0x80 and 0x10, which are expected to be 0.\n    unknown |= (b[6] & 0x90);\n\n    /* Low-order 2 bits are variously 01, 10. */\n    int unknown_3 = b[6] & 0x3;\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n    char code_str[7];\n    snprintf(code_str, sizeof(code_str), \"%06x\", code);\n    char unknown_str[3];\n    snprintf(unknown_str, sizeof(unknown_str), \"%02x\", unknown);\n    char unknown_3_str[2];\n    snprintf(unknown_3_str, sizeof(unknown_3_str), \"%01x\", unknown_3);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Ford\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"pressure_PSI\",     \"Pressure\",     DATA_FORMAT, \"%.2f PSI\", DATA_DOUBLE, pressure_psi,\n            \"temperature_C\",    \"Temperature\",  DATA_COND, temperature_valid, DATA_FORMAT, \"%.1f C\",   DATA_DOUBLE, (float)temperature_c,\n            \"moving\",           \"Moving\",       DATA_INT,    moving,\n            \"learn\",            \"Learn\",        DATA_INT,    learn,\n            \"code\",             \"\",             DATA_STRING, code_str,\n            \"unknown\",          \"\",             DATA_STRING, unknown_str,\n            \"unknown_3\",        \"\",             DATA_STRING, unknown_3_str,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_ford_decode() */\nstatic int tpms_ford_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 55 55 55 56 (inverted: aa aa aa a9)\n    uint8_t const preamble_pattern[2] = {0xaa, 0xa9}; // 16 bits\n\n    int row;\n    unsigned bitpos;\n    int ret    = 0;\n    int events = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        bitpos = 0;\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos,\n                preamble_pattern, 16)) + 144 <=\n                bitbuffer->bits_per_row[row]) {\n            ret = tpms_ford_decode(decoder, bitbuffer, row, bitpos + 16);\n            if (ret > 0)\n                events += ret;\n            bitpos += 15;\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_PSI\",\n        \"temperature_C\",\n        \"moving\",\n        \"learn\",\n        \"code\",\n        \"unknown\",\n        \"unknown_3\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_ford = {\n        .name        = \"Ford TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_ford_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_gm.c",
    "content": "/** @file\n    General Motors Aftermarket TPMS.\n\n    Copyright (C) 2025 Eric Blevins\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nGeneral Motors Aftermarket TPMS.\n\nData was detected and initially captured using:\n\n    rtl_433 -X 'n=name,m=OOK_MC_ZEROBIT,s=120,l=0,r=15600'\n\n\nData layout, 130 bits:\n    AAAAAAAAAAAAFFFFDDDDIIIIIIPPTTCCX\n    0000000000004c90007849176600536d0\n\n\n- A: preamble 0x000000000000\n- F: Flags\n- D: Device type or prefix\n- I: Device uniquie identifier\n- P: Pressure\n- T: Temperature\n- C: CheckSum, modulo 256\n\nFormat string:\n\n    ID:10h FLAGS:4h KPA:2h TEMP:2h CHECKSUM:2h\n\nThe only status data detected is learn mode and low battery.\nBit 5 of status indicates low battery when set to 1.\nBits 0,1,8 are set to 0 to indicate learn mode and 1 for operational mode.\nThe sensors drop to learn mode when detecting a large pressure drop\nor when activated with the EL-50448 learning tool.\n\nIn learn mode with zero pressure they only transmit when activated by\nthe learning tool.\nOnce presurized they will transmit in learn mode and within a couple\nminutes switch to sending in operatioinal mode every two minutes.\n\n*/\n\nstatic int tpms_gm_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (bitbuffer->bits_per_row[0] != 130) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    static uint8_t const preamble_pattern[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};\n\n    int pos = bitbuffer_search(bitbuffer, 0, 0, preamble_pattern, sizeof(preamble_pattern) * 8);\n    if (pos < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    // Buffer for extracted bytes\n    uint8_t b[17] = {0};\n    bitbuffer_extract_bytes(bitbuffer, 0, 0, b, 130);\n\n    // Checksum skips preamble\n    uint8_t computed_checksum = 0;\n    for (int i = 6; i < 15; i++) {\n        computed_checksum += b[i];\n    }\n    if ((computed_checksum & 0xFF) != b[15]) {\n        return DECODE_FAIL_MIC;\n    }\n\n    // Convert ID to an integer\n    uint64_t sensor_id = ((uint64_t)b[8] << 32) | ((uint64_t)b[9] << 24) | (b[10] << 16) | (b[11] << 8) | b[12];\n    int flags     = (b[6] << 8) | b[7];\n\n    int pressure_raw    = b[13];\n    int temperature_raw = b[14];\n\n    // Adding 3.75 made my sensors accurate\n    // But I think it might be best to allow the user to\n    // to add their own offset when consuming the data\n    float pressure_kpa  = (pressure_raw * 2.75);\n    float temperature_c = temperature_raw - 60;\n\n    // Extract bits correctly based on little-endian order\n    int bit8 = (flags >> 8) & 1;\n    int bit1 = (flags >> 1) & 1;\n    int bit0 = (flags >> 0) & 1;\n\n    // Flags bits\n    int learn_mode = ((bit8 == 0) && (bit1 == 0) && (bit0 == 0));\n    int battery_ok = !((flags >> 5) & 1);\n\n    /* clang-format off */\n    data_t *data = data_make(\n        \"model\",           \"\",            DATA_STRING,  \"GM-Aftermarket\",\n        \"type\",            \"\",            DATA_STRING,  \"TPMS\",\n        \"id\",              \"\",            DATA_INT,     sensor_id,\n        \"flags\",           \"\",            DATA_INT,     flags,\n        \"learn_mode\",      \"\",            DATA_INT,     learn_mode,\n        \"battery_ok\",      \"\",            DATA_INT,     battery_ok,\n        \"pressure_kPa\",    \"\",            DATA_DOUBLE,  pressure_kpa,\n        \"temperature_C\",   \"\",            DATA_FORMAT, \"%.0f C\", DATA_DOUBLE,  temperature_c,\n        \"mic\",             \"Integrity\",   DATA_STRING,  \"CHECKSUM\",\n        NULL);\n\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** Output fields for rtl_433 */\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"learn_mode\",\n        \"battery_ok\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_gm = {\n        .name        = \"GM-Aftermarket TPMS\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 120,\n        .long_width  = 0,\n        .reset_limit = 15600,\n        .decode_fn   = &tpms_gm_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_hyundai_vdo.c",
    "content": "/** @file\n    Hyundai TPMS (VDO) FSK 10 byte Manchester encoded CRC-8 TPMS data.\n\n    Copyright (C) 2020 Todor Uzunov aka teou, TTiges, 2019 Andreas Spiess, 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nHyundai TPMS (VDO) FSK 10 byte Manchester encoded CRC-8 TPMS data.\n\nTested on a Hyundai i30 PDE. It uses sensors from Continental/VDO. VDO reference/part no.: A2C98607702, generation TG1C, FCC ID: KR5TIS-01\nSimilar sensors and probably protocol are used in models from BMW, Fiat-Chrysler-Alfa, Peugeot-Citroen, Hyundai-KIA, Mitsubishi, Mazda, etc.\nhttps://www.vdo.com/media/746526/2019-10_tpms-oe-sensors_application-list.pdf\nHence my compilation and fine-tuning the already present as of late 2020 source for Citroen, Abarth and Jansite.\n\n- Working Temperature: -50°C to 125°C (but according to some sources the chip can only handle -40°C)\n- Working Frequency: 433.92MHz+-38KHz\n- Tire monitoring range value: 0kPa-350kPa+-7kPa\n\nPacket nibbles:\n\n    PRE    UU  IIIIIIII FR  PP TT BB  CC\n\n- PRE = preamble is 55 55 55 56 (inverted: aa aa aa a9)\n- U = state, decoding unknown. In all tests has values 20,21,22,23 in hex.\n    Probably codes the information how was the sensor activated - external service tool or internal accelometer (first byte) and the speed of transmission?\n- I = sensor Id in hex\n- F = Flags, in my tests always 0. Most probably here are coded in separate bits the alert flags for low pressure (below 150kPa), temperature and low battery.\n    So it should be something like 00 (0) - all ok, 01 (1) - low pressure, 11 (3) - low pressure and low battery and so on, but more testing is necessary\n- R = packet Repetition in every burst (about 10-11 identical packets are transmitted in every burst approximately once per 64 seconds)\n- P = Pressure X/5=PSI or X(dec).1.375=kPa\n- T = Temperature (deg C offset by 50)\n- B = Battery and/or acceleration? Since decoding is currently unknown i will just leave it as a value\n- C = CRC-8 with poly 0x07 init 0xaa, including the first byte (UU), unlike in some other similar protocols\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_hyundai_vdo_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    uint8_t *b;\n    int state;\n    unsigned id;\n    int flags;\n    int repeat;\n    int pressure;\n    int temperature;\n    int maybe_battery;\n    int crc;\n\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 80);\n\n    if (packet_bits.bits_per_row[0] < 80) {\n        return DECODE_FAIL_SANITY; // too short to be a whole packet\n    }\n\n    b = packet_bits.bb[0];\n\n//  if (b[6] == 0 || b[7] == 0) {\n//      return DECODE_ABORT_EARLY; // pressure cannot really be 0, temperature is also probably not -50C\n//  }\n\n    crc = b[9];\n    if (crc8(b, 9, 0x07, 0xaa) != crc) {\n        return 0;\n    }\n\n    state         = b[0];\n    id            = (unsigned)b[1] << 24 | b[2] << 16 | b[3] << 8 | b[4];\n    flags         = b[5] >> 4;\n    repeat        = b[5] & 0x0f;\n    pressure      = b[6];\n    temperature   = b[7];\n    maybe_battery = b[8];\n\n    char id_str[9 + 1];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Hyundai-VDO\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"state\",            \"\",             DATA_INT,    state,\n            \"flags\",            \"\",             DATA_INT,    flags,\n            \"repeat\",           \"repetition\",   DATA_INT,    repeat,\n            \"pressure_kPa\",     \"pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (double)pressure * 1.375,\n            \"temperature_C\",    \"temp\",         DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temperature - 50.0,\n            \"maybe_battery\",    \"\",             DATA_INT,    maybe_battery,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nWrapper for the Hyundai-VDO tpms.\n@sa tpms_hyundai_vdo_decode()\n*/\nstatic int tpms_hyundai_vdo_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 55 55 55 56 (inverted: aa aa aa a9)\n    uint8_t const preamble_pattern[4] = {0xaa, 0xaa, 0xaa, 0xa9};\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 32)) + 80 <=\n            bitbuffer->bits_per_row[0]) {\n        ret = tpms_hyundai_vdo_decode(decoder, bitbuffer, 0, bitpos + 32);\n        if (ret > 0)\n            events += ret;\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"state\",\n        \"flags\",\n        \"repeat\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"maybe_battery\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_hyundai_vdo = {\n        .name        = \"Hyundai TPMS (VDO)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // in the FCC test protocol is actually 42us, but works with 52 also\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_hyundai_vdo_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_jansite.c",
    "content": "/** @file\n    Jansite FSK 7 byte Manchester encoded checksummed TPMS data.\n\n    Copyright (C) 2019 Andreas Spiess and Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nJansite Solar TPMS (Internal/External) Model TY02S.\n- Working Temperature:-40 °C to 125 °C\n- Working Frequency: 433.92MHz+-38KHz\n- Tire monitoring range value: 0kPa-350kPa+-7kPa\n\nData layout (nibbles):\n\n    II II II IS PP TT CC\n\n- I: 28 bit ID\n- S: 4 bit Status (deflation alarm, battery low etc)\n- P: 8 bit Pressure (best guess quarter PSI, i.e. ~0.58 kPa)\n- T: 8 bit Temperature (deg. C offset by 50)\n- C: 8 bit Checksum\n- The preamble is 0xaa..aa9 (or 0x55..556 depending on polarity)\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_jansite_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    uint8_t *b;\n    unsigned id;\n    int flags;\n    int pressure;\n    int temperature;\n\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 56);\n\n    if (packet_bits.bits_per_row[0] < 56) {\n        return DECODE_FAIL_SANITY;\n        // decoder_logf(decoder, 3, __func__, \"packet_bits.bits_per_row = %d\", packet_bits.bits_per_row[0]);\n    }\n    b = packet_bits.bb[0];\n\n    // TODO: validate checksum\n\n    id          = (unsigned)b[0] << 20 | b[1] << 12 | b[2] << 4 | b[3] >> 4;\n    flags       = b[3] & 0x0F;\n    pressure    = b[4];\n    temperature = b[5];\n    //crc         = b[6];\n\n    char id_str[7 + 1];\n    snprintf(id_str, sizeof(id_str), \"%07x\", id);\n    char code_str[7 * 2 + 1];\n    snprintf(code_str, sizeof(code_str), \"%02x%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5], b[6]); // figure out the checksum\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Jansite\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"flags\",            \"\",             DATA_INT, flags,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (double)pressure * 1.7,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temperature - 50.0,\n            \"code\",             \"\",             DATA_STRING, code_str,\n            //\"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_jansite_decode() */\nstatic int tpms_jansite_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is\n    // 0101 0101  0101 0101  0101 0101  0101 0110 = 55 55 55 56\n    uint8_t const preamble_pattern[3] = {0xaa, 0xaa, 0xa9}; // after invert\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 24)) + 80 <=\n            bitbuffer->bits_per_row[0]) {\n        ret = tpms_jansite_decode(decoder, bitbuffer, 0, bitpos + 24);\n        if (ret > 0)\n            events += ret;\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"code\",\n        //\"mic\",\n        NULL,\n};\n\nr_device const tpms_jansite = {\n        .name        = \"Jansite TPMS Model TY02S\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_jansite_callback,\n        .disabled    = 1, // Unknown checksum\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_jansite_solar.c",
    "content": "/** @file\n    Jansite FSK 11 byte Manchester encoded checksummed TPMS data.\n\n    Copyright (C) 2021 Benjamin Larsson\n\n    based on code\n    2019 Andreas Spiess and Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nJansite Solar TPMS Solar Model.\n\nhttp://www.jansite.cn/P_view.asp?pid=229\n\n- Frequency: 433.92 +/- 20.00 MHz\n- Pressure: +/- 0.1 bar from 0 bar to 6.6 bar\n- Temperature: +/- 3 C from -40 C to 75 C\n\nSignal is manchester encoded, and a 11 byte large message\n\nData layout (nibbles):\n\n    SS SS II II II 00 TT PP 00 CC CC\n\n- S: 16 bits sync word, 0xdd33\n- I: 24 bits ID\n- 0: 8 bits Unknown data 1\n- T: 8 bit Temperature (deg. C offset by 55)\n- P: 8 bit Pressure\n- 0: 8 bits Unknown data 2\n- C: 16 bit CRC (CRC-16/BUYPASS)\n- The preamble is 0xa6, 0xa6, 0x5a\n\nTODO: identify battery bits\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_jansite_solar_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    uint8_t *b;\n    unsigned id;\n    int flags;\n    int pressure;\n    int temperature;\n\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 88);\n    bitbuffer_invert(&packet_bits);\n\n    if (packet_bits.bits_per_row[0] < 88) {\n        return DECODE_FAIL_SANITY;\n    }\n    b = packet_bits.bb[0];\n\n    /* Check for sync */\n    if ((b[0] << 8 | b[1]) != 0xdd33) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* Check crc */\n    uint16_t crc_calc = crc16(&b[2], 7, 0x8005, 0x0000);\n    if (((b[9] << 8) | b[10]) != crc_calc) {\n        decoder_logf(decoder, 1, __func__, \"CRC mismatch %04x vs %02x %02x\", crc_calc, b[9], b[10]);\n        return DECODE_FAIL_MIC;\n    }\n\n    id          = (unsigned)b[2] << 16 | b[3] << 8 | b[4];\n    flags       = b[5];\n    temperature = b[6];\n    pressure    = b[7];\n\n    char id_str[7 + 1];\n    snprintf(id_str, sizeof(id_str), \"%06x\", id);\n    char code_str[9 * 2 + 1];\n    snprintf(code_str, sizeof(code_str), \"%02x%02x%02x%02x%02x%02x%02x%02x%02x\", b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Jansite-Solar\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"flags\",            \"\",             DATA_INT, flags,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.0f kPa\", DATA_DOUBLE, (float)pressure * 1.6,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (float)temperature - 55.0,\n            \"code\",             \"\",             DATA_STRING, code_str,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_jansite_solar_decode() */\nstatic int tpms_jansite_solar_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[3] = {0xa6, 0xa6, 0x5a};\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 24)) + 80 <=\n            bitbuffer->bits_per_row[0]) {\n\n        ret = tpms_jansite_solar_decode(decoder, bitbuffer, 0, bitpos);\n        if (ret > 0)\n            events += ret;\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"code\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_jansite_solar = {\n        .name        = \"Jansite TPMS Model Solar\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 51,\n        .long_width  = 51,\n        .reset_limit = 5000, // Large enough to merge the 3 duplicate messages\n        .decode_fn   = &tpms_jansite_solar_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_kia.c",
    "content": "/** @file\n    Kia Rio UB III (UB) 2011-2017 TPMS sensor and some Hyundai models too.\n\n    Copyright (C) 2022 Lasse Mikkel Reinhold, Todor Uzunov aka teou, TTiges, 2019 Andreas\n    Spiess, 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify it under the terms of\n    the GNU General Public License as published by the Free Software Foundation; either version\n    2 of the License, or (at your option) any later version.\n*/\n\n/**\nTPMS sensor for Kio Rio III (UB) 2011-2017 and some Hyundai models. Possibly other brands and\nmodels too.\n\nThe sensors have accelerometers that sense the centripetal force in a spinning wheel and begin\nto transmit data around 40 km/h. They usually keep transmitting for several minutes after\nstopping, but not always; on a few rare occasions they stop instantly. Each sensor sends a\nburst of 4-6 packets two times a minutes. The packets in a burst are often, but not always,\nidentical.\n\n154 bits in a packet. Bit layout (leftmost bit in a field is the most significant):\n    zzzzzzzzzzzzzzzz aaaa pppppppp tttttttt iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii dddddddd ccccc\n\nLegend:\n    z: 16-bit preamble = 0xed71. Must be omitted from Manchester-decoding\n    a: Unknown, but 0xf in all my own readings\n    p: 8-bit pressure given as PSI * 5\n    t: 8-bit temperature given as Celsius + 50\n    i: 32-bit Sensor ID\n    d: Unknown, with different value in each packet\n    c: First 5 bits of CRC. We need to append 000 to reach 8 bits. poly=0x07, init=0x76.\n\nNOTE: I often get pressure and temperature values that are outliers (like 200 C or 10 PSI) from\nall four sensors, even when CRC is OK. I don't know if all my sensors are defunct or if I have\nmissed something in the encoding. I have included a \"raw\" field to make it easier for other\nusers to investigate it.\n\nNOTE: You may need to use the \"-s 1000000\" option of rtl_433 in order to get a clear signal.\n */\n\n#include \"decoder.h\"\n\nstatic int tpms_kia_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    uint8_t *b;\n    unsigned id;\n    uint8_t unknown1;\n    uint8_t unknown2;\n    uint8_t pressure;\n\n    uint8_t temperature;\n    uint8_t crc;\n    unsigned int start_pos;\n    const unsigned int preamble_length = 16;\n\n    start_pos = bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 154 - preamble_length);\n    if (start_pos - bitpos < 154 - preamble_length) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = packet_bits.bb[0];\n\n    unknown1    = b[0] >> 4;\n    pressure    = b[0] << 4 | b[1] >> 4;\n    temperature = b[1] << 4 | b[2] >> 4;\n    id          = b[2] << 28 | b[3] << 20 | b[4] << 12 | b[5] << 4 | b[6] >> 4;\n    unknown2    = b[6] << 8 | b[7];\n\n    // The last 3 bits in b[8] are beyond the packet length of 154 bits. Make them 000.\n    crc       = b[8] & (~0x7);\n    uint8_t c = crc8(b, 8, 0x07, 0x76);\n    if (crc != c) {\n        return DECODE_FAIL_MIC;\n    }\n\n    char id_str[9 + 1];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n    char unknown1_str[2 + 1];\n    snprintf(unknown1_str, sizeof(unknown1_str), \"%02x\", unknown1);\n    char unknown2_str[3 + 1];\n    snprintf(unknown2_str, sizeof(unknown2_str), \"%03x\", unknown2);\n    char raw[9 * 2 + 1]; // 9 bytes in hex notation\n    snprintf(raw, sizeof(raw), \"%02x%02x%02x%02x%02x%02x%02x%02x%02x\", b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8]);\n\n    float pressure_float    = pressure / 5.0f;\n    float temperature_float = temperature - 50.0f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Kia\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"unknown1\",         \"\",             DATA_STRING, unknown1_str,\n            \"unknown2\",         \"\",             DATA_STRING, unknown2_str,\n            \"pressure_PSI\",     \"pressure\",     DATA_FORMAT, \"%.1f PSI\", DATA_DOUBLE, pressure_float,\n            \"temperature_C\",    \"temperature\",  DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, temperature_float,\n            \"raw\",              \"\",             DATA_STRING, raw,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nWrapper for the Kia tpms.\n@sa tpms_kia_decode()\n*/\nstatic int tpms_kia_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[2] = {0xed, 0x71};\n    const int preamble_length         = 16;\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, preamble_length)) + 154 <= bitbuffer->bits_per_row[0]) {\n        ret = tpms_kia_decode(decoder, bitbuffer, 0, bitpos + preamble_length);\n        if (ret > 0) {\n            events += ret;\n        }\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"unknown1\",\n        \"unknown2\",\n        \"pressure_PSI\",\n        \"temperature_C\",\n        \"raw\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_kia = {\n        .name        = \"Kia TPMS (-s 1000k)\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 50,\n        .long_width  = 50,\n        .reset_limit = 200,\n        .decode_fn   = &tpms_kia_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_nissan.c",
    "content": "/** @file\n    Nissan FSK 37 bit Manchester encoded checksummed TPMS data.\n    Reference issue: https://github.com/merbanan/rtl_433/issues/1024\n\n    Copyright (C) 2021 Alex Wilson <alex.david.wilson@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nNissan FSK 37 bit Manchester encoded checksummed TPMS data.\n\nData format:\n\n    MODE:3d TPMS_ID:24h (PSI+THREE)*FOUR=8d UNKNOWN:2b\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_nissan_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 113);\n    bitbuffer_invert(&packet_bits); // Manchester (G.E. Thomas) Decoded\n\n    // FIXME Debug stuff\n    // fprintf(stderr, \"packet_bits:\\n\");\n    // bitbuffer_print(&packet_bits);\n\n    // fprintf(stderr, \"%s : bits %d\\n\", __func__, packet_bits.bits_per_row[0]);\n    if (packet_bits.bits_per_row[0] < 37) {\n        return DECODE_FAIL_SANITY; // sanity check failed\n    }\n\n    uint8_t *b = packet_bits.bb[0];\n\n    // TODO Is there any parity or other checks we can perform to return\n    // DECODE_ABORT_EARLY or DECODE_FAIL_MIC\n\n    // MODE:3d\n    int mode = b[0] >> 5;\n\n    // TPMS_ID:24h\n    int id = (unsigned)((b[0] & 0x1F) << 19) | (b[1] << 11) | (b[2] << 3) | (b[3] >> 5);\n\n    // (PSI+THREE)*FOUR=8d\n    int pressure_raw   = ((b[3] & 0x1F) << 3) | (b[4] >> 5);\n    float pressure_psi = (float)pressure_raw / 4.0f;\n\n    // UNKNOWN:2b\n    int unknown = (b[4] & 0x1F) >> 3;\n\n    char id_str[7];\n    snprintf(id_str, sizeof(id_str), \"%06x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Nissan\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"mode\",             \"\",             DATA_INT,    mode,\n            \"pressure_PSI\",     \"Pressure\",     DATA_FORMAT, \"%.1f PSI\", DATA_DOUBLE, pressure_psi,\n            \"unknown\",          \"\",             DATA_INT,    unknown,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_nissan_decode() */\nstatic int tpms_nissan_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble is f5 55 55 55 e\n    uint8_t const preamble_pattern[5] = {0xf5, 0x55, 0x55, 0x55, 0xe0}; // 36 bits\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 36)) + 77 <=\n            bitbuffer->bits_per_row[0]) {\n        ret = tpms_nissan_decode(decoder, bitbuffer, 0, bitpos + 36);\n        if (ret > 0)\n            events += ret;\n        bitpos += 1;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"mode\",\n        \"pressure_PSI\",\n        \"unknown\",\n        NULL,\n};\n\nr_device const tpms_nissan = {\n        .name        = \"Nissan TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 120, // TODO The preamble plus pre-MC data is 113, what should this be?\n        .long_width  = 120, // FSK\n        .reset_limit = 250, // Maximum gap size before End Of Message [us]. TODO What should this be?\n        .decode_fn   = &tpms_nissan_callback,\n        .disabled    = 1, // no MIC, disabled by default\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_pmv107j.c",
    "content": "/** @file\n    FSK 8-byte Differential Manchester encoded TPMS data with CRC-8.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nFSK 8-byte Differential Manchester encoded TPMS data with CRC-8.\nPacific PMV-107J TPMS (315MHz) sensors used by Toyota\nbased on work by Werner Johansson.\n\n66 bits Differential Manchester encoded TPMS data with CRC-8.\n\n    II II II I F* PP NN TT CC\n\n- I: ID (28 bit)\n- F*: Flags, 6 bits (BCCURF, battery_low, repeat_counter, Unknown, rapid_change, failed)\n    battery_low 1 is low, 0 is okay, inverted for battery_ok\n    repeat_counter 2 bits, normally each message is 1, 2, 3, then repeat\n    Unknown \"Must be zero for the packet to be recognized by the car\" https://github.com/xnk/pacific-tpms\n    rapid_change 1 when there is a rapid pressure change, the sensor will\n        not wait for the normal 60 second time slot, and will repeat.\n    failed (self test?) 1 if failed, 0 if okay\n- P: Tire pressure (PSI/0.363 + 40 or kPa/2.48 + 40)\n- N: Inverted tire pressure\n- T: Tire temperature (Celsius +40, range from -40 to +215 C)\n- C: CRC over bits 0 - 57, poly 0x13, init 0\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_pmv107j_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    uint8_t b[9];\n\n    unsigned start_pos = bitbuffer_differential_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 70); // 67 bits expected\n    if (start_pos - bitpos < 67 * 2) {\n        return 0;\n    }\n    decoder_log_bitbuffer(decoder, 2, __func__, &packet_bits, \"\");\n\n    // realign the buffer, prepending 6 bits of 0.\n    b[0] = packet_bits.bb[0][0] >> 6;\n    bitbuffer_extract_bytes(&packet_bits, 0, 2, b + 1, 64);\n    decoder_log_bitrow(decoder, 2, __func__, b, 72, \"Realigned\");\n\n    int crc = b[8];\n    if (crc8(b, 8, 0x13, 0x00) != crc) {\n        return 0;\n    }\n\n    unsigned id            = b[0] << 26 | b[1] << 18 | b[2] << 10 | b[3] << 2 | b[4] >> 6; // realigned bits 6 - 34\n    unsigned status        = b[4] & 0x3f;                                                  // status bits and 0 filler\n    unsigned battery_low   = (b[4] & 0x20) >> 5;\n    unsigned counter       = (b[4] & 0x18) >> 3;\n    // unknown bit         = (b[4] & 0x4) >> 2;\n    unsigned rapid_change  = (b[4] & 0x2) >> 1;\n    unsigned failed        = b[4] & 0x01;\n    unsigned pressure1     = b[5];\n    unsigned pressure2     = b[6] ^ 0xff;\n    unsigned temp          = b[7];\n    float pressure_kpa  = (pressure1 - 40.0f) * 2.48f;\n    float temperature_c = temp - 40.0f;\n\n    if (pressure1 != pressure2) {\n        decoder_logf(decoder, 1, __func__, \"Toyota TPMS pressure check error: %02x vs %02x\", pressure1, pressure2);\n        return 0;\n    }\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING,    \"PMV-107J\",\n            \"type\",             \"\",             DATA_STRING,    \"TPMS\",\n            \"id\",               \"\",             DATA_STRING,    id_str,\n            \"status\",           \"\",             DATA_INT,       status,\n            \"battery_ok\",       \"\",             DATA_INT,       !battery_low,\n            \"counter\",          \"\",             DATA_INT,       counter,\n            \"rapid_change\",     \"\",             DATA_INT,       rapid_change,\n            \"failed\",           \"\",             DATA_STRING,    failed ? \"FAIL\" : \"OK\",\n            \"pressure_kPa\",     \"\",             DATA_DOUBLE,    pressure_kpa,\n            \"temperature_C\",    \"\",             DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, temperature_c,\n            \"mic\",              \"Integrity\",    DATA_STRING,    \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_pmv107j_decode() */\nstatic int tpms_pmv107j_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is (7 bits) 11111 10\n    uint8_t const preamble_pattern[1] = {0xf8}; // 6 bits\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 6)) + 67 * 2 <=\n            bitbuffer->bits_per_row[0]) {\n        ret = tpms_pmv107j_decode(decoder, bitbuffer, 0, bitpos + 6);\n        if (ret > 0)\n            events += ret;\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"status\",\n        \"battery_ok\",\n        \"counter\",\n        \"rapid_change\",\n        \"failed\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_pmv107j = {\n        .name        = \"PMV-107J (Toyota) TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 100, // 25 samples @250k\n        .long_width  = 100, // FSK\n        .reset_limit = 250, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_pmv107j_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_porsche.c",
    "content": "/**  @file\n    Porsche Boxster/Cayman TPMS.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nPorsche Boxster/Cayman TPMS.\nSeen on Porsche second generation (Typ 987) Boxster/Cayman.\n\nFull preamble is {30}ccccccca (33333332).\nThe data is Differential Manchester Coded (DMC).\n\nExample data:\n\n    {193}333333354ab32d334b2d4ab2cab54cb34cb2aab4cd552ccd8\n    {193}333333354ab32d334b2d4ab2caaab34cb34d554aacd2b2cd8\n\n    23d7 ad23 623b bb02 f05f\n    23d7 ad23 603b bb02 1d37\n\nData layout (nibbles):\n\n    II II II II PP TT SS SS CC\n\n- I: 32 bit ID\n- P: 8 bit Pressure (scale 2.5 offset 100, minimum seen 41 = 0 kPa)\n- T: 8 bit Temperature (deg. C offset by 40)\n- S: Status?\n- C: 16 bit Checksum, CRC-16 poly 0x1021 init 0xffff\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_porsche_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_differential_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 80);\n\n    // make sure we decoded the expected number of bits\n    if (packet_bits.bits_per_row[0] < 80) {\n        // decoder_logf(decoder, 0, __func__, \"bitpos=%u start_pos=%u = %u\", bitpos, start_pos, (start_pos - bitpos));\n        return 0; // DECODE_FAIL_SANITY;\n    }\n\n    uint8_t *b = packet_bits.bb[0];\n\n    // Checksum is CRC-16 poly 0x1021 init 0xffff over 8 bytes\n    int checksum = crc16(b, 10, 0x1021, 0xffff);\n    if (checksum != 0) {\n        return 0; // DECODE_FAIL_MIC;\n    }\n\n    int id          = (unsigned)b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];\n    int pressure    = b[4];\n    int temperature = b[5];\n    int flags       = b[6] << 8 | b[7];\n\n    int pressure_kpa  = pressure * 5 / 2 - 100;\n    int temperature_c = temperature - 40;\n\n    char id_str[4 * 2 + 1];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Porsche\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"pressure_kPa\",     \"Pressure\",     DATA_FORMAT, \"%.1f kPa\",    DATA_DOUBLE, (float)pressure_kpa,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\",      DATA_DOUBLE, (float)temperature_c,\n            \"flags\",            \"\",             DATA_FORMAT, \"%04x\",        DATA_INT,    flags,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_porsche_decode() */\nstatic int tpms_porsche_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Full preamble is {30}ccccccca (33333332).\n    uint8_t const preamble_pattern[] = {0x33, 0x33, 0x20}; // 20 bit\n\n    int events = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    unsigned bitpos = 0;\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 20)) + 100 <=\n            bitbuffer->bits_per_row[0]) {\n        events += tpms_porsche_decode(decoder, bitbuffer, 0, bitpos + 20);\n        bitpos += 2;\n    }\n\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"pressure\",\n        \"temperature_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_porsche = {\n        .name        = \"Porsche Boxster/Cayman TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_porsche_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_renault.c",
    "content": "/** @file\n    FSK 9 byte Manchester encoded TPMS with CRC.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nFSK 9 byte Manchester encoded TPMS with CRC.\nSeen on Renault Clio, Renault Captur, Renault Zoe and maybe Dacia Sandero.\n\nPacket nibbles:\n\n    F F/P PP TT II II II ?? ?? CC\n\n- F = flags, (seen: c0: 22% c8: 14% d0: 31% d8: 33%) maybe 110??T\n- P = Pressure, 10 bit 0.75 kPa\n- I = id, 24-bit little-endian\n- T = Temperature in C, offset -30\n- ? = Unknown, mostly 0xffff\n- C = Checksum, CRC-8 truncated poly 0x07 init 0x00\n\nNotes from benppp:\nThe last bit in flags maybe indicates test/startup/reset condition,\nit will be set for 2 minutes after power-up.\nAt least for a Zoe2 (Zoe2 original TPMS sensor: 407004CB0B) the 16 unknown bits are\n9409 for stable pressure, 8C09 for pressure decrease and 9449 for something else.\nCould be that the 4th/5th bit encode a pressure alert and the 10th bit indicates some other state.\n*/\nstatic int tpms_renault_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 160);\n    // require 72 data bits\n    if (packet_bits.bits_per_row[0] < 72) {\n        return 0; // DECODE_ABORT_LENGTH\n    }\n    uint8_t *b = packet_bits.bb[0];\n\n    // 0x83; 0x107 FOP-8; ATM-8; CRC-8P\n    if (crc8(b, 8, 0x07, 0x00) != b[8]) {\n        return 0; // DECODE_FAIL_MIC\n    }\n\n    int flags           = b[0] >> 2;\n    unsigned id         = b[5] << 16 | b[4] << 8 | b[3]; // little-endian\n    int pressure_raw    = (b[0] & 0x03) << 8 | b[1];\n    double pressure_kpa = pressure_raw * 0.75;\n    int temp_c          = b[2] - 30;\n    int unknown         = b[7] << 8 | b[6]; // little-endian, fixed 0xffff?\n\n    char flags_str[3];\n    snprintf(flags_str, sizeof(flags_str), \"%02x\", flags);\n    char id_str[7];\n    snprintf(id_str, sizeof(id_str), \"%06x\", id);\n    char code_str[5];\n    snprintf(code_str, sizeof(code_str), \"%04x\", unknown);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Renault\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"id\",               \"\",             DATA_STRING, id_str,\n            \"flags\",            \"\",             DATA_STRING, flags_str,\n            \"pressure_kPa\",     \"\",             DATA_FORMAT, \"%.1f kPa\", DATA_DOUBLE, (double)pressure_kpa,\n            \"temperature_C\",    \"\",             DATA_FORMAT, \"%.0f C\", DATA_DOUBLE, (double)temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_renault_decode() */\nstatic int tpms_renault_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 55 55 55 56 (inverted: aa aa aa a9)\n    uint8_t const preamble_pattern[2] = {0xaa, 0xa9}; // 16 bits\n\n    int row;\n    unsigned bitpos;\n    int ret    = 0;\n    int events = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    for (row = 0; row < bitbuffer->num_rows; ++row) {\n        bitpos = 0;\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos,\n                preamble_pattern, 16)) + 160 <=\n                bitbuffer->bits_per_row[row]) {\n            ret = tpms_renault_decode(decoder, bitbuffer, row, bitpos + 16);\n            if (ret > 0)\n                events += ret;\n            bitpos += 15;\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_renault = {\n        .name        = \"Renault TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_renault_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_renault_0435r.c",
    "content": "/** @file\n    FSK 9 byte Manchester encoded TPMS with xor checksum, 0435R.\n\n    Copyright (C) 2021 Tomas Ebenlendr <ebik@ucw.cz>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nFSK 9 byte Manchester encoded TPMS with xor checksum, Renault 0435R.\n\nPart no:\n- Renault 40700 0435R\n- VDO     S180052064Z\n\nList of compatible Renault vehicles (from: https://www.vdo.com/media/190553/vdo-2017-tpms_2017-05-03.pdf)\n- FLUENCE (L30_)\n- LAGUNA III (BT0/1)\n- LAGUNA III Grandtour (KT0/1)\n- LATITUDE (L70_)\n- MEGANE III Coupe (DZ0/1_)\n- MEGANE III Grandtour (KZ0/1)\n- MEGANE III Hatchback(BZ0_)\n- SCÉNIC III (JZ0/1_)\n- ZOE (BFM_)\n\nPacket nibbles:\n\n    II II II fx PP TT AA CC tt\n\n- P = Pressure, 4/3 kPa\n- I = id, 24-bit little-endian\n- T = Temperature C, offset -50\n- A = centrifugal acceleration, 5 m/s² (or maybe 0.5G), value of 255 means overflow\n- C = Checksum, 8bit xor\n- f = flags, (seen only c)\n- x = flags (seen only 0), or maybe upper bits or pressure, if 340kPa is exceeded\n- tt = 0x80 + measurement count (first == 0, up to 29), after 30th measuremet set to 0x00\n\n\nNote: Pressure unit of 4/3 kPa is a guess, one of possible alternatives\n is 10Torr, which matches 4/3 kPa up to 0.1%. That is probably below\n the precision of the sensor anyways.\n\nNote: Centrifugal acceleration unit guessed by following calculation:\n- I have tires 195/65R15:\n- tire height over wheel:       s  = 195mm * 65% = 0.12675m\n- radius of wheel without tire: r₀ = 15''/2 = 0.1905m\n- radius of tire:               r  = r₀ + s = 0.31725m\n- cirumference of tire:         c  = 2πr = 0.31725m * 6.283186 = 1.99334m\n\nCentrifugal acceleration at circumference of a tire (a) is related to the centrifugal\nacceleration at sensor (a₀) by ratio of radius of tire and position of sensor,\nwhich we guess is located exactly at the edge between the wheel and the tire.\n    a₀ = a * r₀/r\nRadial acceleration at circumference of tire can be calculated by formula\n    a = v²/r where v is speed of the vehicle\nThus centrifugal acceleration at the sensor should be:\n    a₀ = v² * r₀/r² = (KPH² / 3.6²) * (r₀/r²)\n\nI get a₀ = KPH² * 0.146 for my tires. I plugged in speed obtained by OBD interface\n(which matches GPS speed with less than 1% accuracy (yes it is lower than speed\ndisplayed to driver)), and got values exactly five times greater than values\nreported by the sensor for speeds under 93 kph. The sensor sends 255 when value does\nnot fit into 8 bits (that is for speeds above 93 kph on my tires).\n\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_renault_0435r_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 160);\n    // require 72 data bits\n    if (packet_bits.bits_per_row[0] < 72) {\n        return DECODE_ABORT_EARLY;\n    }\n    uint8_t *b = packet_bits.bb[0];\n\n    // check checksum (checksum8 xor)\n    int chk = xor_bytes(b, 9);\n    if (chk != 0) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int tick     = b[8] & 0x7f;\n    int has_tick = b[8] >> 7;\n\n    // Sensor begins with has_tick = 1, and tick = 0. It sends data every 4.5s\n    // and increments tick. Value tick >= 30 is never send, sensor instead\n    // drops flag has_tick, and sets tick = 0 for rest of measurement session.\n    // Tick counter is reset by several minutes of inactivity (vehicle stopped).\n    if (b[8] && (!has_tick || tick > 30)) {\n        return DECODE_FAIL_SANITY;\n    }\n\n    int flags = b[3];\n    // observed always 0xc0 - FIXME: find possible combinations and reject message with impossible combination\n    // to avoid confusion with other FSK manchester 9-byte sensors with 8bit xor checksum.\n\n    int pressure_raw    = b[4];\n    double pressure_kpa = pressure_raw / 0.75;\n    int temp_c          = (int)b[5] - 50;\n    int rad_acc         = (int)b[6] * 5;\n\n    char id_str[7];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x\", b[0], b[1], b[2]);\n\n    char flags_str[3];\n    snprintf(flags_str, sizeof(flags_str), \"%02x\", flags);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",           \"\",                         DATA_STRING, \"Renault-0435R\",\n            \"type\",            \"\",                         DATA_STRING, \"TPMS\",\n            \"id\",              \"\",                         DATA_STRING, id_str,\n            \"flags\",           \"\",                         DATA_STRING, flags_str,\n            \"pressure_kPa\",    \"Pressure\",                 DATA_FORMAT, \"%.1f kPa\",  DATA_DOUBLE, (double)pressure_kpa,\n            \"temperature_C\",   \"Temperature\",              DATA_FORMAT, \"%.0f C\",    DATA_DOUBLE, (double)temp_c,\n            \"centrifugal_acc\", \"Centrifugal Acceleration\", DATA_FORMAT, \"%.0f m/s2\", DATA_DOUBLE, (double)rad_acc,\n            \"mic\",             \"\",                         DATA_STRING, \"CRC\",\n            \"has_tick\",        \"\",                         DATA_INT,    has_tick,\n            \"tick\",            \"\",                         DATA_INT,    tick - 0x80*(1-has_tick), //set to negative value when has_tick == 0 (invert bit 7)\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_renault_0435r_decode() */\nstatic int tpms_renault_0435r_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 55 55 55 56 (inverted: aa aa aa a9)\n    uint8_t const preamble_pattern[2] = {0xaa, 0xa9}; // 16 bits\n\n    int ret    = 0;\n    int events = 0;\n\n    bitbuffer_invert(bitbuffer);\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        unsigned bitpos = 0;\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos,\n                        preamble_pattern, 16)) +\n                        160 <=\n                bitbuffer->bits_per_row[row]) {\n            ret = tpms_renault_0435r_decode(decoder, bitbuffer, row, bitpos + 16);\n            if (ret > 0)\n                events += ret;\n            bitpos += 15;\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"centrifugal_acc\",\n        \"mic\",\n        \"has_tick\",\n        \"tick\",\n        NULL,\n};\n\nr_device const tpms_renault_0435r = {\n        .name        = \"Renault 0435R TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_renault_0435r_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_toyota.c",
    "content": "/** @file\n    FSK 9-byte Differential Manchester encoded TPMS data with CRC-8.\n\n    Copyright (C) 2017 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nFSK 9-byte Differential Manchester encoded TPMS data with CRC-8.\nPacific Industries Co.Ltd. PMV-C210\nSeen on a Toyota Auris(Corolla). The manufacturers of the Toyota TPMS are\nPacific Industrial Corp and sometimes TRW Automotive and might also be used\nin other car brands. Contact me with your observations!\n\nThere are 14 bits sync followed by 72 bits manchester encoded data and\n3 bits trailer.\nE.g. 01010101001111 00110011 [...64 manchester bits] 00101010111\n\nThe first 4 bytes are the ID. Followed by 1-bit state,\n8-bit values of pressure, temperature, 7-bit state, 8-bit inverted pressure\nand then the a CRC-8 with 0x07 truncated poly and init 0x80.\nThe temperature is offset by 40 deg C.\nThe pressure seems to be 1/4 PSI offset by -7 PSI (i.e. 28 raw = 0 PSI).\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_toyota_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    unsigned int start_pos;\n    bitbuffer_t packet_bits = {0};\n    uint8_t *b;\n    unsigned id;\n    unsigned status, pressure1, pressure2, temp;\n    int crc;\n\n    // skip the first 1 bit, i.e. raw \"01\" to get 72 bits\n    start_pos = bitbuffer_differential_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 80);\n    if (start_pos - bitpos < 144) {\n        return 0;\n    }\n    b = packet_bits.bb[0];\n\n    crc = b[8];\n    if (crc8(b, 8, 0x07, 0x80) != crc) {\n        return 0;\n    }\n\n    id        = (unsigned)b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];\n    status    = (b[4] & 0x80) | (b[6] & 0x7f); // status bit and 0 filler\n    pressure1 = (b[4] & 0x7f) << 1 | b[5] >> 7;\n    temp      = (b[5] & 0x7f) << 1 | b[6] >> 7;\n    pressure2 = b[7] ^ 0xff;\n\n    if (pressure1 != pressure2) {\n        decoder_logf(decoder, 1, __func__, \"Toyota TPMS pressure check error: %02x vs %02x\", pressure1, pressure2);\n        return 0;\n    }\n\n    char id_str[9];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING,    \"Toyota\",\n            \"type\",             \"\",             DATA_STRING,    \"TPMS\",\n            \"id\",               \"\",             DATA_STRING,    id_str,\n            \"status\",           \"\",             DATA_INT,       status,\n            \"pressure_PSI\",     \"\",             DATA_DOUBLE,    pressure1*0.25-7.0,\n            \"temperature_C\",    \"\",             DATA_FORMAT,    \"%.0f C\", DATA_DOUBLE, temp - 40.0,\n            \"mic\",              \"Integrity\",    DATA_STRING,    \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_toyota_decode() */\nstatic int tpms_toyota_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // full preamble is 0101 0101 0011 11 = 55 3c\n    // could be shorter   11 0101 0011 11\n    uint8_t const preamble_pattern[2] = {0xa9, 0xe0}; // 12 bits (but pass last bit to decode)\n\n    unsigned bitpos = 0;\n    int ret         = 0;\n    int events      = 0;\n\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 12)) + 156 <=\n            bitbuffer->bits_per_row[0]) {\n        ret = tpms_toyota_decode(decoder, bitbuffer, 0, bitpos + 11);\n        if (ret > 0)\n            events += ret;\n        bitpos += 2;\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"status\",\n        \"pressure_PSI\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_toyota = {\n        .name        = \"Toyota TPMS\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,  // 12-13 samples @250k\n        .long_width  = 52,  // FSK\n        .reset_limit = 150, // Maximum gap size before End Of Message [us].\n        .decode_fn   = &tpms_toyota_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_truck.c",
    "content": "/**  @file\n    Unbranded SolarTPMS for trucks.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nUnbranded SolarTPMS for trucks, with wheel counter, set of 6.\n\nS.a. #1893\n\nThe preamble is 232 bit 0x55..5556.\nThe data packet is Manchester coded.\n\nSpecification:\n- Monitoring temperature range: -127 C to 127 C\n- Monitoring air pressure range: 0.1 bar to 12.0 bar\n- 433 MHz FSK\n\nData layout (nibbles):\n\n    U II II II II WW F PPP TT CC ?\n\n- U: 4 bit state, decoding unknown, not included in checksum, could be sync\n- I: 32 bit ID\n- W: 8 bit wheel position\n- F: 4 bit status flags: 0x8 = unknown. 0x4 = pressure alert. 0x2|0x1 = battery status (where 00 = low battery).\n- P: 12 bit Pressure (kPa)\n- T: 8 bit Temperature (deg. C, signed)\n- C: 8 bit Checksum (XOR on bytes 0 to 7)\n- ?: 4 bit unknown (seems static, probably a decoding artifact)\n\nExample data:\n\n    ID:32h POS:8h FLAGS?4h KPA:12d TEMP:8d CHK:8h TRAILER?8h\n\n    {401} 555555555555555555555555555555555555555555555555555555 5556 99 55 66 95 56 9a 59 95 56 55 59 5a 55 55 55 56 a9 96 9a aa  ff 80\n    {401} 555555555555555555555555555555555555555555555555555555 5556 99 a5 66 96 65 99 a9 56 65 55 66 5a 55 55 55 56 a5 a5 59 aa  ff 80\n\n    00000000000000000000000000001 a 0581b281 02 3 000 1e 9b f\n    00000000000000000000000000001 a c594ae14 05 3 000 1c c2 f\n\n*/\n\n#include \"decoder.h\"\n\nstatic int tpms_truck_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    bitbuffer_t packet_bits = {0};\n    bitbuffer_manchester_decode(bitbuffer, row, bitpos, &packet_bits, 76);\n\n    if (packet_bits.bits_per_row[row] < 76) {\n        return 0; // DECODE_FAIL_SANITY;\n    }\n\n    uint8_t b[9] = {0};\n    bitbuffer_extract_bytes(&packet_bits, 0, 4, b, 72);\n\n    int chk = xor_bytes(b, 9);\n    if (chk != 0) {\n        return 0; // DECODE_FAIL_MIC;\n    }\n\n    unsigned id        = (unsigned)(b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];\n    int wheel          = b[4];\n    int flags          = (b[5] >> 4);\n    int pressure       = ((b[5] & 0x0f) << 8) | b[6];\n    int temperature    = b[7];\n    int pressure_alert = (flags & 0x4) == 0x4;\n    int battery_ok     = (flags & 0x3) == 0x3; // 0x3 = battery ok, 0x0 = battery low, else unknown.\n\n    char id_str[4 * 2 + 1];\n    snprintf(id_str, sizeof(id_str), \"%08x\", id);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",          \"\",               DATA_STRING, \"Truck\",\n            \"type\",           \"\",               DATA_STRING, \"TPMS\",\n            \"id\",             \"\",               DATA_STRING, id_str,\n            \"wheel\",          \"\",               DATA_INT,    wheel,\n            \"pressure_kPa\",   \"Pressure\",       DATA_FORMAT, \"%.0f kPa\",     DATA_DOUBLE, (float)pressure,\n            \"temperature_C\",  \"Temperature\",    DATA_FORMAT, \"%.0f C\",       DATA_DOUBLE, (float)temperature,\n            \"pressure_alert\", \"Pressure Alert\", DATA_COND,   pressure_alert, DATA_INT,    pressure_alert,\n            \"battery_ok\",     \"Battery Ok\",     DATA_INT,    battery_ok,\n            \"flags\",          \"Flag?\",          DATA_FORMAT, \"%x\",           DATA_INT,    flags,\n            \"mic\",            \"Integrity\",      DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_truck_decode() */\nstatic int tpms_truck_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble\n    uint8_t const preamble_pattern[3] = {0xaa, 0xaa, 0xa9}; // after invert\n\n    unsigned bitpos = 0;\n    int events      = 0;\n\n    bitbuffer_invert(bitbuffer);\n    // Find a preamble with enough bits after it that it could be a complete packet\n    while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 24)) + 160 <=\n            bitbuffer->bits_per_row[0]) {\n        events += tpms_truck_decode(decoder, bitbuffer, 0, bitpos + 24);\n        bitpos += 2;\n    }\n\n    return events;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n        \"wheel\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"state\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_truck = {\n        .name        = \"Unbranded SolarTPMS for trucks\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 150,\n        .decode_fn   = &tpms_truck_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_trw.c",
    "content": "/** @file\n    TRW TPMS Sensor.\n\n    Copyright (C) 2025 Bruno OCTAU \\@ProfBoc75\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int tpms_trw_decode(r_device *decoder, bitbuffer_t *bitbuffer, int type)\nTRW TPMS Sensor.\n\nFCC-ID: GQ4-70T\n\n- OEM and Clone OEM models\n- Used into Chrysler car from 2014 until 2022 : car models https://www.chryslertpms.com/chrysler-tpms-types-fitment\n\n- OOK then FSK rf signals, mode detail here : https://fcc.report/FCC-ID/GQ4-70T/2201535\n\nS.a issue #3256\n\n Data layout:\n\n    Byte Position xx xx 0  1  2  3  4  5  6  7  8  9  10\n    Sample OOK    00 01 5c 3e 52 85 2e 61 53 4b 0e 52 4\n    Sample FSK    7f ff 5c 3e 52 85 2e 62 53 4b 0e 68 4\n                   PRE  MM II II II II FN PP TT SS CC X\n\n- PRE : 7FFF (FSK) or 0001 (OOK)\n\n- M:{8}  Mode/Model : 0x5c, 0x5d or 0x5e (Always 0x5c for Clone OEM model)\n- I:{32} Sensor ID\n- F:{4}  Flag status : 0x6 = pressure drop (OEM) , 0x9 pressure increase (OEM) or drop (Clone OEM), 0xb or 0xc alternatly in Motion\n- N:{4}  Seq number : 0,1,2,3,0,1,2,3... for OEM, 1,2,3,4,1,2,3,4... for Clone OEM\n- P:{8}  Pressure PSI, scale 2.5\n- T:{8}  Temperature C, offset 50\n- S:{8}  Motion status, 0x0e parked, other value in motion, not yet guesses\n- C:{8}  CRC-8/SMBUS, poly 0x07, init 0x00, final XOR 0x00\n- X:{4}  Trailing bit but 0x4 OEM, 0x0 Clone OEM\n\n*/\n\nstatic unsigned char const preamble_ook[] = { 0x00, 0x01 };\nstatic unsigned char const preamble_fsk[] = { 0x7f, 0xff };\n\nstatic int tpms_trw_decode(r_device *decoder, bitbuffer_t *bitbuffer, int type)\n{\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int msg_len = bitbuffer->bits_per_row[0];\n\n    if (msg_len > 98) {\n        decoder_logf(decoder, 1, __func__, \"Packet too long: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    int pos = 0;\n\n    if (type == 0) {\n        pos = bitbuffer_search(bitbuffer, 0, pos, preamble_ook, sizeof(preamble_ook) * 8);\n    }\n    if (type == 1) {\n        pos = bitbuffer_search(bitbuffer, 0, pos, preamble_fsk, sizeof(preamble_fsk) * 8);\n    }\n\n    if (pos >= bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"Preamble not found\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (pos + 8 * 11 > bitbuffer->bits_per_row[0]) {\n        decoder_log(decoder, 2, __func__, \"Length check fail\");\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t b[11]  = {0};\n    pos += 16;\n\n    if ((msg_len - pos) < 81 ) {\n        decoder_logf(decoder, 1, __func__, \"Packet too short: %d bits\", msg_len);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    bitbuffer_extract_bytes(bitbuffer, 0, pos, b, sizeof(b) * 8);\n\n    if (crc8(b,10,0x07,0x00)) {\n        decoder_logf(decoder, 1, __func__, \"CRC Error, expected: %02x\", crc8(b, 9, 0x07, 0x00));\n        return DECODE_FAIL_MIC;\n    }\n\n    decoder_log_bitrow(decoder, 0, __func__, b, 88, \"MSG\");\n\n    int mode               = b[0];\n    int id                 = b[1] << 24 | b[2] << 16 | b[3] << 8 | b[4];\n    int flags              = (b[5] & 0xF0) >> 4;\n    int seq_num            = b[5] & 0x0F;\n    int pressure_raw       = b[6];\n    float pressure_psi     = pressure_raw * 0.4f;\n    int temp_raw           = b[7];\n    int temperature_C      = temp_raw - 50;\n    int motion_flags       = b[8];\n    int oem_model          = (b[10] & 0xf0) >> 4;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"TRW\",\n            \"type\",             \"\",             DATA_STRING, \"TPMS\",\n            \"mode\",             \"\",             DATA_FORMAT, \"%02x\",     DATA_INT, mode,\n            \"id\",               \"\",             DATA_FORMAT, \"%08x\",     DATA_INT, id,\n            //\"battery_ok\",       \"Battery_OK\",   DATA_INT,    !low_batt,\n            \"flags\",            \"Flags\",        DATA_FORMAT, \"%01x\",     DATA_INT, flags,\n            \"alert\",            \"Alert\",        DATA_COND, flags == 0x6 || flags == 0x9, DATA_STRING, \"Pressure increase/decrease !\",\n            \"seq_num\",          \"Seq Num\",                               DATA_INT, seq_num,\n            \"pressure_PSI\",     \"Pressure\",     DATA_FORMAT, \"%.1f PSI\", DATA_DOUBLE, pressure_psi,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.0f C\",   DATA_DOUBLE, (double)temperature_C,\n            \"motion_flags\",     \"Motion flags\", DATA_FORMAT, \"%02x\",     DATA_INT, motion_flags,\n            \"motion_status\",    \"Motion\",       DATA_STRING, motion_flags == 0x0e ? \"Parked\" : \"Moving\",\n            \"oem_model\",        \"OEM Model\",    DATA_COND,   oem_model == 0x4, DATA_STRING, \"OEM\",\n            \"oem_model\",        \"OEM Model\",    DATA_COND,   oem_model == 0x0, DATA_STRING, \"Clone\",\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nTRW TPMS Sensor.\n@sa tpms_trw_decode()\n*/\nstatic int tpms_trw_callback_ook(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    return tpms_trw_decode(decoder, bitbuffer, 0);\n}\n\n/**\nTRW TPMS Sensor.\n@sa tpms_trw_decode()\n*/\nstatic int tpms_trw_callback_fsk(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    return tpms_trw_decode(decoder, bitbuffer, 1);\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"mode\",\n        \"id\",\n        //\"battery_ok\",\n        \"flags\",\n        \"alert\",\n        \"seq_num\",\n        \"pressure_PSI\",\n        \"temperature_C\",\n        \"motion_flags\",\n        \"motion_status\",\n        //\"fast_leak\",\n        //\"inflate\",\n        \"oem_model\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_trw_ook = {\n        .name        = \"TRW TPMS OOK OEM and Clone models\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 150,\n        .decode_fn   = &tpms_trw_callback_ook,\n        .fields      = output_fields,\n};\n\nr_device const tpms_trw_fsk = {\n        .name        = \"TRW TPMS FSK OEM and Clone models\",\n        .modulation  = FSK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 52,\n        .long_width  = 52,\n        .reset_limit = 150,\n        .decode_fn   = &tpms_trw_callback_fsk,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/tpms_tyreguard400.c",
    "content": "/** @file\n    TPMS TyreGuard 400 from Davies Craig.\n\n    Copyright (C) 2022 R ALVERGNAT\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nTPMS TyreGuard 400 from Davies Craig.\n\n- Type:            TPMS\n- Freq:            434.1 MHz\n- Modulation:      ASK -> OOK_MC_ZEROBIT (Manchester Code with fixed leading zero bit)\n- Symbol duration: 100us (same for l or 0)\n- Length:          22 bytes long\n\nPacket layout:\n\n    bytes : 1    2    3    4    5    6    7    8   9   10  11  12  13  14  15  16  17   18   19   20   21  22\n    coded : S/P  S/P  S/P  S/P  S/P  S/P  S/P  ID  ID  ID  ID  ID  ID  ID  Pr  Pr  Temp Temp Flg  Flg  CRC CRC\n\n- S/P   : preamble/sync \"0xfd5fd5f\" << always fixed\n- ID    : 6 bytes long start with 0x6b????? ex 0x6b20d21\n- Pr    : Last 2 bytes of pressure in psi ex : 0xe8 means XX232 psi (for XX see flags bytes)\n- Temp  : Temperature in °C offset by +40 ex : 0x2f means (47-40)=+7°C\n- Flg   : Flags bytes => should be read in binary format :\n  - Bit 73 : Unknown ; maybe the 20th MSB pressure bit? The sensor is not capable to reach this so high pressure\n  - Bit 74 : add 1024 psi (19th MSB pressure bit)\n  - Bit 75 : add  512 psi (18th MSB pressure bit)\n  - Bit 76 : add  256 psi (17th MSB pressure bit)\n  - Bit 77 : Acknoldge pressure leaking 1=Ack 0=No_ack (nothing to report)\n  - Bit 78 : Unknown\n  - Bit 79 : Leaking pressure detected 1=Leak 0=No leak (nothing to report)\n  - Bit 80 : Leaking pressure detected 1=Leak 0=No leak (nothing to report)\n- CRC   : CRC poly 0x31 start value 0xdd final 0x00 from 1st bit 80th bits\n\nTo peer a new sensor to the unit, bit 79 and 80 has to be both to 1.\n\nNOTE: In the datasheet, it is said that the sensor can report low batterie. During my tests/research i'm not able to see this behavior. I have fuzzed all bits nothing was reported to the reader.\n\nFlex decoder:\n\n    -X \"n=TPMS,m=OOK_MC_ZEROBIT,s=100,l=100,r=500,preamble=fd5fd5f\"\n\n    decoder {\n        name        = TPMS-TYREGUARD400,\n        modulation  = OOK_MC_ZEROBIT,\n        short       = 100,\n        long        = 100,\n        gap         = 0,\n        reset       = 500,\n        preamble    = fd5fd5f,\n        get         = id:@0:{28},\n        get         = pression:@57:{8},\n        get         = temp:@65:{8},\n        get         = flags:@73:{8},\n        get         = add_psi:@74:{3}:[0:no 1:256 2:512 3:768 4:1024 5:1280 6:1536 7:1792],\n        get         = AckLeaking:@77:{1}:[1: yes 0:no],\n        get         = Leaking_detected:@79:{2}:[0:no 1:yes 2:yes],\n        get         = CRC:@81:{8},\n    }\n\n*/\n\n#include \"decoder.h\"\n\n#define TPMS_TYREGUARD400_MESSAGE_BITLEN     88\n\nstatic int tpms_tyreguard400_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    uint8_t b[(TPMS_TYREGUARD400_MESSAGE_BITLEN + 7) / 8];\n\n    // Extract the message\n    bitbuffer_extract_bytes(bitbuffer, row, bitpos, b, TPMS_TYREGUARD400_MESSAGE_BITLEN);\n\n    //CRC poly 0x31 start value 0xdd final 0x00 from 1st bit to 80bits\n    if (crc8(b, 11, 0x31, 0xdd) != 0) {\n        decoder_log_bitrow(decoder, 2, __func__, b, TPMS_TYREGUARD400_MESSAGE_BITLEN, \"CRC error\");\n        return DECODE_FAIL_MIC;\n    }\n\n    uint8_t flags = b[9];\n    //int bat_low = flags & 0x1; // TBC ?!?\n    int peering_request = flags & 0x3; // bytes = 0b00000011\n    int ack_leaking = flags & 0x8; // byte = 0b00001000 :: NOTA ack_leaking = 1 means ack ;; ack_leaking=0 nothing to do\n\n    // test if bits 1st or 2nd is set to 1 of 0b000000XX\n    int leaking = flags & 0x3;\n\n    //int add256  = (flags & 0x10) >> 4;  // bytes = 0b000X0000\n    //int add512  = (flags & 0x20) >> 5;  // bytes = 0b00X00000\n    //int add1024 = (flags & 0x40) >> 6; // bytes = 0b0X000000\n\n    char id_str[8];\n    snprintf(id_str, sizeof(id_str), \"%07x\", (uint32_t)(((b[3] & 0xf)<<24)) | (b[4]<<16) | (b[5]<<8) |  b[6]); // 28 bits ID\n    char flags_str[3];\n    snprintf(flags_str, sizeof(flags_str), \"%02x\", flags);\n\n    //id = (b[4] << 8)\n    int pressure_kpa = b[7] | ((flags & 0x70) << 4);\n    int temp_c = b[8] - 40;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",           \"Model\",                 DATA_STRING, \"TyreGuard400\",\n            \"type\",            \"Type\",                  DATA_STRING, \"TPMS\",\n            \"id\",              \"ID\",                    DATA_STRING, id_str,\n//            \"flags\",           \"Flags\",                 DATA_STRING, flags_str,\n            \"pressure_kPa\",    \"Pressure\",              DATA_FORMAT, \"%.1f kPa\",  DATA_DOUBLE, (double)pressure_kpa,\n            \"temperature_C\",   \"Temperature\",           DATA_FORMAT, \"%.0f C\",    DATA_DOUBLE, (double)temp_c,\n            \"peering_request\", \"Peering req\",           DATA_INT,    peering_request,\n            \"leaking\",         \"Leaking detected\",      DATA_INT,    leaking,\n            \"ack_leaking\",     \"Ack leaking\",           DATA_INT,    ack_leaking,\n//            \"add256\",          \"\",                      DATA_INT,    add256,\n//            \"add512\",          \"\",                      DATA_INT,    add512,\n//            \"add1024\",          \"\",                     DATA_INT,    add1024,\n//            \"battery_ok\",    \"Batt OK\",                 DATA_INT,    !bat_low,\n            \"mic\",             \"Integrity\",             DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/** @sa tpms_tyreguard400_decode() */\nstatic int tpms_tyreguard400_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    //uint8_t const tyreguard_frame_sync[] = {0xf, 0xd5, 0xfd, 0x5f}\n    uint8_t const tyreguard_frame_sync[] = {0xfd, 0x5f, 0xd5, 0xf0}; // needs to shift sync to align bytes 28x bits useful\n\n    int ret    = 0;\n    int events = 0;\n\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        if (bitbuffer->bits_per_row[row] < TPMS_TYREGUARD400_MESSAGE_BITLEN) {\n            // bail out of this \"too short\" row early\n            // Output the bad row, only for message level debug / deciphering.\n            decoder_logf_bitrow(decoder, 2, __func__, bitbuffer->bb[row], bitbuffer->bits_per_row[row],\n                    \"Bad message in row %d need %d bits got %d\",\n                    row, TPMS_TYREGUARD400_MESSAGE_BITLEN, bitbuffer->bits_per_row[row]);\n            continue; // DECODE_ABORT_LENGTH\n        }\n\n        unsigned bitpos = 0;\n\n        // Find a preamble with enough bits after it that it could be a complete packet\n        while ((bitpos = bitbuffer_search(bitbuffer, row, bitpos, tyreguard_frame_sync, 28)) + TPMS_TYREGUARD400_MESSAGE_BITLEN <=\n                bitbuffer->bits_per_row[row]) {\n\n            decoder_logf_bitrow(decoder, 2, __func__, bitbuffer->bb[row], bitbuffer->bits_per_row[row],\n                    \"Find bitpos with preamble row %d at %u\", row, bitpos);\n\n            ret = tpms_tyreguard400_decode(decoder, bitbuffer, row, bitpos);\n            if (ret > 0)\n                events += ret;\n\n            bitpos += TPMS_TYREGUARD400_MESSAGE_BITLEN;\n        }\n    }\n    // (Only) for future regression tests.\n    if (events == 0) {\n        decoder_logf(decoder, 3, __func__, \"Bad transmission\");\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"type\",\n        \"id\",\n//        \"flags\",\n        \"pressure_kPa\",\n        \"temperature_C\",\n        \"peering_request\",\n        \"leaking\",\n        \"ack_leaking\",\n//        \"add256\",\n//        \"add512\",\n//        \"add1024\",\n//        \"battery_ok\",\n        \"mic\",\n        NULL,\n};\n\nr_device const tpms_tyreguard400 = {\n        .name        = \"TyreGuard 400 TPMS\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 100,\n        .long_width  = 100,\n        .gap_limit   = 0,\n        .reset_limit = 500,\n        .decode_fn   = &tpms_tyreguard400_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ts_ft002.c",
    "content": "/** @file\n    TS-FT002 Tank Liquid Level decoder.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nTS-FT002 Wireless Ultrasonic Tank Liquid Level Meter With Temperature Sensor.\n\nPPM with 500 us pulse, 464 us short gap (0), 948 us long gap (1), 1876 us packet gap, two packets per transmission.\n\nE.g. rtl_433 -R 0 -X 'n=TS-FT002T,m=OOK_PPM,s=500,l=1000,g=1200,r=2000,bits>=70'\n\nBits are sent LSB first, full packet is 9 bytes (1 byte preamble + 8 bytes payload)\n\nData layout:\n\n    SS II MM DD BD VT TT CC\n\n(Nibble number after reflecting bytes)\n| Nibble   | Description\n| 0,1      | Sync 0xfa (0x5f before reverse)\n| 2,3      | ID\n| 4,5      | Message type (fixed 0x11)\n| 6,7,9    | Depth H,M,L (in Centimeter, 0x5DC if invalid, range 0-15M)\n| 8        | Transmit Interval (bit 7=0: 180S, bit 7 =1: 30S, bit 4-6=1: 5S)\n| 10       | Battery indicator?\n| 12,13,11 | Temp H, M, L (scale 10, offset 400), 0x3E8 if invalid\n| 14,15    | Rain H, L (Value 0-256), not used\n| 16,17    | XOR checksum (include the preamble)\n*/\n\n#include \"decoder.h\"\n\nstatic int ts_ft002_decoder(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t b[9];\n    int id, type, depth, transmit, temp_raw, batt_low;\n    float temp_c;\n\n    if (bitbuffer->bits_per_row[0] == 72) {\n        bitbuffer_extract_bytes(bitbuffer, 0, 0, b, 72);\n    }\n    else if (bitbuffer->bits_per_row[0] == 71) {\n        bitbuffer_extract_bytes(bitbuffer, 0, 7, b + 1, 64);\n        b[0] = bitbuffer->bb[0][0] >> 1;\n    }\n    else if (bitbuffer->bits_per_row[0] == 70) {\n        bitbuffer_extract_bytes(bitbuffer, 0, 6, b + 1, 64);\n        b[0] = (bitbuffer->bb[0][0] >> 2) | 0x80;\n    }\n    else\n        return DECODE_ABORT_LENGTH;\n\n    int chk = xor_bytes(b, 9);\n    if (chk)\n        return DECODE_FAIL_MIC;\n\n    // reflect bits (also reverses nibbles)\n    reflect_bytes(b, 8);\n\n    id       = b[1];\n    type     = b[2];\n    depth    = (b[3] << 4) | (b[4] & 0x0f);\n    batt_low = b[4] >> 4;\n    transmit = b[5] >> 4;\n    temp_raw = (b[6] << 4) | (b[5] & 0x0f);\n    //rain     = b[7];\n    //chk      = b[8];\n    temp_c   = (temp_raw - 400) * 0.1f;\n\n    if ((transmit & 0x07) == 0x07)\n        transmit = 5;\n    else if ((transmit & 0x08) == 0x08)\n        transmit = 30;\n    else if (transmit == 0)\n        transmit = 180;\n    else // invalid\n        transmit = 0;\n\n    if (type != 0x11)\n        return DECODE_FAIL_SANITY;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                     DATA_STRING, \"TS-FT002\",\n            \"id\",               \"Id\",                   DATA_INT,    id,\n            \"depth_cm\",         \"Depth\",                DATA_INT,    depth,\n            \"temperature_C\",    \"Temperature\",          DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"transmit_s\",       \"Transmit Interval\",    DATA_INT,    transmit,\n            //\"battery_ok\",       \"Battery\",              DATA_INT,    batt_low,\n            \"flags\",            \"Battery Flag?\",        DATA_INT,    batt_low,\n            \"mic\",              \"Integrity\",            DATA_STRING, \"CHECKSUM\",\n            NULL);\n    decoder_output_data(decoder, data);\n    /* clang-format on */\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"depth_cm\",\n        \"temperature_C\",\n        \"transmit_s\",\n        //\"battery_ok\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ts_ft002 = {\n        .name        = \"TS-FT002 Wireless Ultrasonic Tank Liquid Level Meter With Temperature Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 464,\n        .long_width  = 948,\n        .gap_limit   = 1200,\n        .reset_limit = 2000,\n        .decode_fn   = &ts_ft002_decoder,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ttx201.c",
    "content": "/** @file\n    Emos TTX201 Thermo Remote Sensor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nEmos TTX201 Thermo Remote Sensor.\n\nManufacturer: Ewig Industries Macao\nMaybe same as Ewig TTX201M (FCC ID: N9ZTTX201M)\n\nIROX ETS69 temperature sensor with DCF77 receiver for EBR606C weather station (Ewig WSA101)\nuses the same protocol. It transmits temperature the same way as TTX201 (except for different M bits).\nIf its internal clock is synchronized to DCF77, it transmits the date/time every hour (:00) instead of\nthe temperature. The date/time is also transmitted after clock is synced at startup.\n\nTransmit Interval: every ~61 s\nFrequency: 433.92 MHz\nManchester Encoding, pulse width: 500 us, interpacket gap width 1500 us.\n\nA complete message is 445 bits:\n\n       PPPPPPPP PPPPPPPP P\n    LL LLKKKKKK IIIIIIII S???BCCC ?XXXTTTT TTTTTTTT MMMMMMMM JJJJ  (repeated 7 times)\n    LL LLKKKKKK IIIIIIII S???BCCC ?XXXTTTT TTTTTTTT MMMMMMMM       (last packet without J)\n\n17-bit initial preamble, always 0\n\n    PPPPPPPP PPPPPPPP P = 0x00 0x00 0\n\n54-bit data packet format\n\n    0    1   2    3   4    5   6    7   8    9   10   11  12   13  (nibbles #, aligned to 8-bit values)\n    ..LL LLKKKKKK IIIIIIII StttBCCC 0XXXTTTT TTTTTTTT MMMMMMMM JJJJ (temperature)\nor  ..LL LLKKKKKK zyyyyyyy 0tttmmmm dddddHHH HHMMMMMM 0SSSSSS? JJJJ (date/time)\n\n- L = 4-bit start of packet, always 0\n- K = 6-bit checksum, sum of nibbles 3-12\n- I = 8-bit sensor ID\n- S = startup (0 = normal operation, 1 = reset or battery changed)\n- t = data type (000 = temperature, 101 = date/time)\n- 0 = unknown, always 0\n- B = battery status (0 = OK, 1 = low)\n- C = 3-bit channel, 0-4\n- X = 3-bit packet index, 0-7\n- T = 12-bit signed temperature * 10 in Celsius\n- M = 8-bit postmark (sensor model?), always 0x14 for TTX201, 0x00 for ETS69\n- J = 4-bit packet separator, always 0xF\n\ndate/time bit definitions:\n- z = time zone/summer time (0 = CET, 1 = CEST)\n- y = year\n- m = month\n- d = day\n- H = hour\n- M = minute\n- S = second\n- ? = purpose unknown, always 0 or 1, changes only after reset (battery change)\n\nSample received raw data package:\n\n    bitbuffer:: Number of rows: 10\n    [00] {17} 00 00 00             : 00000000 00000000 0\n    [01] {54} 07 30 80 00 42 05 3c\n    [02] {54} 07 70 80 04 42 05 3c\n    [03] {54} 07 b0 80 08 42 05 3c\n    [04] {54} 07 f0 80 0c 42 05 3c\n    [05] {54} 08 30 80 10 42 05 3c\n    [06] {54} 08 70 80 14 42 05 3c\n    [07] {54} 08 b0 80 18 42 05 3c\n    [08] {50} 08 f0 80 1c 42 05 00 : 00001000 11110000 10000000 00011100 01000010 00000101 00\n    [09] { 1} 00                   : 0\n\nData decoded:\n\n    r  cs    K   ID    S   B  C  X    T    M     J\n    1  28    28  194  0x0  0  0  0   264  0x14  0xf\n    2  29    29  194  0x0  0  0  1   264  0x14  0xf\n    3  30    30  194  0x0  0  0  2   264  0x14  0xf\n    4  31    31  194  0x0  0  0  3   264  0x14  0xf\n    5  32    32  194  0x0  0  0  4   264  0x14  0xf\n    6  33    33  194  0x0  0  0  5   264  0x14  0xf\n    7  34    34  194  0x0  0  0  6   264  0x14  0xf\n    8  35    35  194  0x0  0  0  7   264  0x14\n */\n\n#include \"decoder.h\"\n\n#define MSG_PREAMBLE_BITS    17\n#define MSG_PACKET_MIN_BITS  50\n#define MSG_PACKET_BITS      54\n#define MSG_PACKET_POSTMARK  0x14\n#define MSG_MIN_ROWS         2\n#define MSG_MAX_ROWS         10\n\n#define MSG_PAD_BITS         ((((MSG_PACKET_BITS / 8) + 1) * 8) - MSG_PACKET_BITS)\n#define MSG_PACKET_LEN       ((MSG_PACKET_BITS + MSG_PAD_BITS) / 8)\n\n#define DATA_TYPE_TEMP       0x00\n#define DATA_TYPE_DATETIME   0x05\n\nstatic int ttx201_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)\n{\n    int rowlen = bitbuffer->bits_per_row[row];\n    if (rowlen != MSG_PACKET_MIN_BITS && rowlen != MSG_PACKET_BITS) {\n        if (row == 0) {\n            if (rowlen < MSG_PREAMBLE_BITS) {\n                decoder_logf(decoder, 2, __func__, \"Short preamble: %d bits (expected %d)\",\n                        rowlen, MSG_PREAMBLE_BITS);\n            }\n        } else if (row != (unsigned)bitbuffer->num_rows - 1 && rowlen == 1) {\n            decoder_logf(decoder, 2, __func__, \"Wrong packet #%u length: %d bits (expected %d)\",\n                    row, rowlen, MSG_PACKET_BITS);\n        }\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t b[MSG_PACKET_LEN];\n    bitbuffer_extract_bytes(bitbuffer, row, bitpos + MSG_PAD_BITS, b, MSG_PACKET_BITS + MSG_PAD_BITS);\n\n    /* Aligned data: LLKKKKKK IIIIIIII S???BCCC ?XXXTTTT TTTTTTTT MMMMMMMM JJJJ */\n    int chk = b[0] & 0x3f;\n    int sum = add_nibbles(&b[1], 5);\n    int data_type = (b[2] & 0x70) >> 4;\n    int postmark = b[5];\n\n    if (decoder_verbose(decoder) > 1) {\n        decoder_log(decoder, 0, __func__, \"TTX201 received raw data\");\n        decoder_log_bitbuffer(decoder, 0, __func__, bitbuffer, \"\");\n        decoder_logf(decoder, 0, __func__, \"Data decoded:\" \\\n                \" r  cs    K   ID    S   B  C  X    T    M     J\\n\");\n        decoder_logf(decoder, 0, __func__, \"%2u  %2d    %2d  %3d  0x%01x  %1d  %1d  %1d  %4d  0x%02x\",\n                row,\n                sum,\n                chk,\n                b[1],                                       // Device Id\n                (b[2] & 0xf0) >> 4,                         // Unknown 1\n                (b[2] & 0x08) >> 3,                         // Battery\n                b[2] & 0x07,                                // Channel\n                b[3] >> 4,                                  // Packet index\n                ((int8_t)((b[3] & 0x0f) << 4) << 4) | b[4], // Temperature\n                postmark);\n        if (rowlen == MSG_PACKET_BITS) {\n            decoder_logf(decoder, 0, __func__, \"  0x%01x\", b[6] >> 4);         // Packet separator\n        }\n        decoder_log(decoder, 0, __func__, \"\");\n    }\n\n    // reduce false positives\n    if (sum == 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    if (chk != (sum & 0x3f)) {\n        decoder_logf(decoder, 2, __func__, \"Packet #%u checksum error.\", row);\n        return DECODE_FAIL_MIC;\n    }\n\n    data_t *data;\n    if (data_type == DATA_TYPE_DATETIME) {\n        int cest = b[1] & 0x80;\n        int year = b[1] & 0x7f;\n        int month = b[2] & 0x0f;\n        int day = (b[3] & 0xf8) >> 3;\n        int hour = (b[3] & 0x07) << 2 | (b[4] & 0xc0) >> 6;\n        int minute = b[4] & 0x3f;\n        int second = (b[5] & 0x7e) >> 1;\n        char clock_str[25];\n        snprintf(clock_str, sizeof(clock_str), \"%04d-%02d-%02dT%02d:%02d:%02d %s\", year + 2000, month, day, hour, minute, second, cest ? \"CEST\" : \"CET\");\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Emos-TTX201\",\n                \"radio_clock\",      \"Radio Clock\",  DATA_STRING, clock_str,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n    } else { // temperature\n        int device_id       = b[1];\n        int battery_low     = (b[2] & 0x08) != 0; // if not zero, battery is low\n        int channel         = (b[2] & 0x07) + 1;\n        int temperature     = (int16_t)(((b[3] & 0x0f) << 12) | (b[4] << 4)); // uses sign extend\n        float temperature_c = (temperature >> 4) * 0.1f;\n\n        /* clang-format off */\n        data = data_make(\n                \"model\",            \"\",             DATA_STRING, \"Emos-TTX201\",\n                \"id\",               \"House Code\",   DATA_INT,    device_id,\n                \"channel\",          \"Channel\",      DATA_INT,    channel,\n                \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temperature_c,\n                \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n    }\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\n/**\nEmos TTX201 Thermo Remote Sensor.\n@sa ttx201_decode()\n*/\nstatic int ttx201_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row;\n    int ret    = 0;\n    int events = 0;\n\n    if (MSG_MIN_ROWS <= bitbuffer->num_rows && bitbuffer->num_rows <= MSG_MAX_ROWS) {\n        for (row = 0; row < bitbuffer->num_rows; ++row) {\n            ret = ttx201_decode(decoder, bitbuffer, row, 0);\n            if (ret > 0)\n                events += ret;\n            if (events && !decoder_verbose(decoder))\n                return events; // for now, break after first successful message\n        }\n    }\n\n    return events > 0 ? events : ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"mic\",\n        \"radio_clock\",\n        NULL,\n};\n\nr_device const ttx201 = {\n        .name        = \"Emos TTX201 Temperature Sensor\",\n        .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n        .short_width = 510,\n        .long_width  = 0, // not used\n        .reset_limit = 1700,\n        .tolerance   = 250,\n        .decode_fn   = &ttx201_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/universalfanctrl.c",
    "content": "/** @file\n    Decoder for 'Universal reversable Fan controller 24V'.\n\n    Copyright (C) 2025 Marcel Verpaalen <marcel@verpaalen.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n#include \"decoder.h\"\n\n/**\nDecoder for 'Universal (Reverseable) 24V Fan Controller'.\n\nThe device uses PWM encoding,\n- 0 is encoded as 756 us pulse and 252 us gap,\n- 1 is encoded as 256 us pulse and 756 us gap.\n\nA transmission starts with a pulse of 3616 us,\nthere a 7 repeated packets, each with a 8200 us gap.\n\nData layout:\n    AAAAAAAAAAAAAAAAAAAABBBBBRRRRCCCC1\n\n- A: 20 bit Address / id\n- B: 5-bit buttoncode\n- R: 3 bit rolling counter\n- C: 4 bit Checksum, init 0x0A\n- 1: Always 1\n\nExample:\n    (without checksum check using flex decode)\n     $ rtl_433 -X 'n=fan,m=OOK_PWM,s=252,l=756,r=8288,g=0,t=0,y=3620,bits=33,\n        get=address:@0:{20};%x,get=address:@20:{5},get=msgcount:@25:{3}:%d,get=crc:@28:{4}:%02x,unique'\n*/\n\nstatic int universalfan_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row = bitbuffer_find_repeated_row(bitbuffer, 3, 33);\n    if (row < 0) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t *b  = bitbuffer->bb[row];\n    // int chk_msg = (b[3] & 0x0F);\n    int sum = xor_bytes(b, 4); // xor message bytes, last byte also has the checksum\n    sum     = (sum >> 4) ^ (sum & 0xf); // fold nibbles\n    if (sum != 0xa) {\n        decoder_log(decoder, 1, __func__, \"Checksum error.\");\n        return DECODE_FAIL_MIC;\n    }\n\n    int address = (b[0] << 12) + (b[1] << 4) + (b[2] >> 4);    // @0 {20};\n    int button  = ((b[2] & 0x0F) << 1) + ((b[3] & 0x80) >> 7); // @20 {5}\n    int counter = (b[3] & 0x7F) >> 4;                          // @25 {3}\n    // int rc      = (b[3] & 0x0F);                               // @28 {4}\n    char const *button_str;\n\n    switch (button) {\n    case 0x19:\n        button_str = \"All Off\";\n        break;\n    case 0x17:\n        button_str = \"Light On/Off\";\n        break;\n    case 0x1b:\n        button_str = \"Forward\";\n        break;\n    case 0x0a:\n        button_str = \"Fan\";\n        break;\n    case 0x0e:\n        button_str = \"Reverse\";\n        break;\n    case 0x09:\n        button_str = \"Fan Off\";\n        break;\n    case 0x0f:\n        button_str = \"Speed 1\";\n        break;\n    case 0x0d:\n        button_str = \"Speed 2\";\n        break;\n    case 0x03:\n        button_str = \"Speed 3\";\n        break;\n    case 0x15:\n        button_str = \"Speed 4\";\n        break;\n    case 0x10:\n        button_str = \"Speed 5\";\n        break;\n    case 0x13:\n        button_str = \"speed 6\";\n        break;\n    case 0x1d:\n        button_str = \"1H\";\n        break;\n    case 0x16:\n        button_str = \"2H\";\n        break;\n    case 0x06:\n        button_str = \"3H\";\n        break;\n    default:\n        button_str = \"Unknown\";\n        break;\n    }\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",                 DATA_STRING, \"UniFan-24V\",\n            \"id\",           \"Transmitter ID\",   DATA_INT,    address,\n            \"button\",       \"Button\",           DATA_STRING, button_str,\n            \"button_code\",  \"Button Code\",      DATA_INT,    button,\n            \"counter\",      \"Rolling Counter\",  DATA_INT,    counter,\n            \"mic\",          \"\",                 DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"button\",\n        \"button_code\",\n        \"counter\",\n        \"mic\",\n        NULL,\n};\n\nr_device const universalfanctrl = {\n        .name        = \"Universal (Reverseable) 24V Fan Controller\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 256,\n        .long_width  = 756,\n        .gap_limit   = 8000,\n        .sync_width  = 3616,\n        .reset_limit = 8800,\n        .decode_fn   = &universalfan_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/vaillant_vrt340f.c",
    "content": "/** @file\n    Vaillant VRT 340f (calorMatic 340f) central heating control.\n\n    Copyright (C) 2017 Reinhold Kainhofer <reinhold@kainhofer.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nVaillant VRT 340f (calorMatic 340f) central heating control.\n\n    http://wiki.kainhofer.com/hardware/vaillantvrt340f\n\nThe data is sent differential Manchester encoded\nwith bit-stuffing (after five 1 bits an extra 0 bit is inserted)\n\nAll bytes are sent with least significant bit FIRST (1000 0111 = 0xE1)\n\n    0x00 00 7E | 6D F6 | 00 20 00 | 00 | 80 | B4 | 00 | FD 49 | FF 00\n      SYNC+HD. | DevID | CONST?   |Rep.|Wtr.|Htg.|Btr.|Checksm| EPILOGUE\n\n- CONST? ... Unknown, but constant in all observed signals\n- Rep.   ... Repeat indicator: 0x00=original signal, 0x01=first repeat\n- Wtr.   ... pre-heated Water: 0x80=ON, 0x88=OFF (bit 8 is always set)\n- Htg.   ... Heating: 0x00=OFF, 0xB4=ON (2-point), 0x01-0x7F=target heating water temp\n             (bit 8 indicates 2-point heating mode, bits 1-7 the heating water temp)\n- Btr.   ... Battery: 0x00=OK, 0x01=LOW\n- Checksm... Checksum (2-byte signed int): = -sum(bytes 4-12)\n\n*/\n\n#include \"decoder.h\"\n\nstatic int validate_checksum(r_device *decoder, uint8_t *b, int from, int to, int cs_from, int cs_to)\n{\n    // Fields cs_from and cs_to hold the 2-byte checksum as signed int\n    int expected   = (b[cs_from] << 8) | b[cs_to];\n    int calculated = add_bytes(&b[from], to - from + 1);\n    int chk        = (calculated + expected) & 0xffff;\n\n    if (chk) {\n        decoder_logf(decoder, 1, __func__, \"Checksum error in Vaillant VRT340f.  Expected: %04x  Calculated: %04x\", expected, calculated);\n        decoder_logf_bitrow(decoder, 1, __func__, &b[from], (to - from + 1) * 8, \"Message (data content of bytes %d-%d)\", from, to);\n    }\n    return !chk;\n}\n\nstatic int vaillant_vrt340_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b = bitbuffer->bb[0];\n\n    // TODO: Use repeat signal for error checking / correction!\n\n    // each row needs to have at least 128 bits (plus a few more due to bit stuffing)\n    if (bitbuffer->bits_per_row[0] < 128)\n        return DECODE_ABORT_LENGTH;\n\n    // The protocol uses bit-stuffing => remove 0 bit after five consecutive 1 bits\n    // Also, each byte is represented with least significant bit first -> swap them!\n    bitbuffer_t bits = {0};\n    int ones = 0;\n    for (uint16_t k = 0; k < bitbuffer->bits_per_row[0]; k++) {\n        int bit = bitrow_get_bit(b, k);\n        if (bit == 1) {\n            bitbuffer_add_bit(&bits, 1);\n            ones++;\n        } else {\n            if (ones != 5) { // Ignore a 0 bit after five consecutive 1 bits:\n                bitbuffer_add_bit(&bits, 0);\n            }\n            ones = 0;\n        }\n    }\n\n    b = bits.bb[0];\n    uint16_t bitcount = bits.bits_per_row[0];\n\n    // Change to least-significant-bit last (protocol uses least-significant-bit first)\n    reflect_bytes(b, (bitcount - 1) / 8);\n\n    // A correct message has 128 bits plus potentially two extra bits for clock sync at the end\n    if (!(128 <= bitcount && bitcount <= 131) && !(168 <= bitcount && bitcount <= 171))\n        return DECODE_ABORT_LENGTH;\n\n    // \"Normal package\":\n    if ((b[0] == 0x00) && (b[1] == 0x00) && (b[2] == 0x7e) && (128 <= bitcount && bitcount <= 131)) {\n\n        if (!validate_checksum(decoder, b, /* Data from-to: */3, 11, /*Checksum from-to:*/12, 13)) {\n            return DECODE_FAIL_MIC;\n        }\n\n        // Device ID starts at byte 4:\n        int device_id          = (b[3] << 8) | b[4];\n        int heating_mode       = (b[10] >> 7);    // highest bit indicates automatic (2-point) / analogue mode\n        int target_temperature = (b[10] & 0x7f);  // highest bit indicates auto(2-point) / analogue mode\n        int water_preheated    = (b[9] & 8) == 0; // bit 4 indicates water: 1=Pre-heat, 0=no pre-heated water\n        int battery_low        = b[11] != 0;      // if not zero, battery is low\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"\",                     DATA_STRING, \"Vaillant-VRT340f\",\n                \"id\",           \"Device ID\",            DATA_FORMAT, \"0x%04X\", DATA_INT, device_id,\n                \"heating\",      \"Heating Mode\",         DATA_STRING, (heating_mode == 0 && target_temperature == 0) ? \"OFF\" : heating_mode ? \"ON (2-point)\" : \"ON (analogue)\",\n                \"heating_temp\", \"Heating Water Temp.\",  DATA_FORMAT, \"%d\", DATA_INT, target_temperature,\n                \"water\",        \"Pre-heated Water\",     DATA_STRING, water_preheated ? \"ON\" : \"off\",\n                \"battery_ok\",   \"Battery\",              DATA_INT,    !battery_low,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n\n        return 1;\n    }\n\n    // \"RF detection package\":\n    if ((b[0] == 0x00) && (b[1] == 0x00) && (b[2] == 0x7E) && (168 <= bitcount && bitcount <= 171)) {\n\n        if (!validate_checksum(decoder, b, /* Data from-to: */ 3, 16, /*Checksum from-to:*/ 17, 18)) {\n            return DECODE_FAIL_MIC;\n        }\n\n        // Device ID starts at byte 12:\n        int device_id = (b[11] << 8) | b[12];\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"\",                     DATA_STRING, \"Vaillant-VRT340f\",\n                \"id\",           \"Device ID\",            DATA_INT,    device_id,\n                NULL);\n        /* clang-format on */\n        decoder_output_data(decoder, data);\n\n        return 1;\n    }\n\n    return DECODE_FAIL_SANITY;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"heating\",\n        \"heating_temp\",\n        \"water\",\n        \"battery_ok\",\n        NULL,\n};\n\nr_device const vaillant_vrt340f = {\n        .name        = \"Vaillant calorMatic VRT340f Central Heating Control\",\n        .modulation  = OOK_PULSE_DMC,\n        .short_width = 836,  // half-bit width 836 us\n        .long_width  = 1648, // bit width 1648 us\n        .reset_limit = 4000,\n        .tolerance   = 120, // us\n        .decode_fn   = &vaillant_vrt340_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/vauno_en8822c.c",
    "content": "/** @file\n    Vauno EN8822C sensor on 433.92MHz.\n\n    Copyright (C) 2022 Jamie Barron <gumbald@gmail.com>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nVauno EN8822C sensor on 433.92MHz.\n\nLargely the same as Esperanza EWS, s3318p.\n@sa esperanza_ews.c s3318p.c\n\nList of known supported devices:\n- Vauno EN8822C-1\n- FUZHOU ESUN ELECTRONIC outdoor T21 sensor\n\nFrame structure (42 bits):\n\n    Byte:      0        1        2        3        4\n    Nibble:    1   2    3   4    5   6    7   8    9   10   11\n    Type:      IIIIIIII B?CCTTTT TTTTTTTT HHHHHHHF FFFBXXXX XX\n\n- I: Random device ID\n- C: Channel (1-3)\n- T: Temperature (Little-endian)\n- H: Humidity (Little-endian)\n- F: Flags (unknown)\n- B: Battery (1=low voltage ~<2.5V)\n- X: Checksum (6 bit nibble sum)\n\nSample Data:\n\n    [00] {42} af 0f a2 7c 01 c0 : 10101111 00001111 10100010 01111100 00000001 11\n\n- Sensor ID = 175 = 0xaf\n- Channel = 0\n- temp = -93 = 0x111110100010\n- TemperatureC = -9.3\n- hum = 62% = 0x0111110\n\n*/\n\n#include \"decoder.h\"\n\nstatic int vauno_en8822c_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    int row = bitbuffer_find_repeated_prefix(bitbuffer, 4, 42);\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    uint8_t *b = bitbuffer->bb[row];\n\n    // checksum is addition\n    int chk = ((b[4] & 0x0f) << 2) | (b[5] >> 6);\n    int sum = add_nibbles(b, 4) + (b[4] >> 4);\n    if (sum == 0) {\n        return DECODE_ABORT_EARLY; // reject all-zeros\n    }\n    if ((sum & 0x3f) != chk) {\n        return DECODE_FAIL_MIC;\n    }\n\n    int device_id = b[0];\n    int channel   = ((b[1] & 0x30) >> 4) + 1;\n    int battery_low = (b[4] & 0x10) >> 4;\n    int temp_raw = (int16_t)(((b[1] & 0x0f) << 12) | ((b[2] & 0xff) << 4));\n    float temp_c  = (temp_raw >> 4) * 0.1f;\n    int humidity  = (b[3] >> 1);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"Vauno-EN8822C\",\n            \"id\",               \"ID\",           DATA_INT, device_id,\n            \"channel\",          \"Channel\",      DATA_INT, channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT, !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"mic\",\n        NULL,\n};\n\nr_device const vauno_en8822c = {\n        .name        = \"Vauno EN8822C\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 2000,\n        .long_width  = 4000,\n        .tolerance   = 500,\n        .gap_limit   = 5000,\n        .reset_limit = 9500,\n        .decode_fn   = &vauno_en8822c_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/vevor_7in1.c",
    "content": "/** @file\n    Vevor Wireless Weather Station 7-in-1.\n\n    Copyright (C) 2024 Bruno OCTAU (ProfBoc75)\n\n    Based on Emax protocol\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nVevor Wireless Weather Station 7-in-1.\n\nManufacturer : Fujian Youtong Industries Co., Ltd. rebrand under Vevor name.\nReference:\n\n- YT60231, Vevor Weather Station 7-in-1\n- R53 / R56 Fujian Youtong Industries , FCC ID : https://fccid.io/2AQBD-R53, https://fccid.io/2AQBD-R56\n\nS.a. issue #3020\n\nData Layout:\n\n- Preamble/Syncword  .... AA AA AA CA CA 54\n\n    Byte Position   0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34\n    Sample         AA 00 f8 f7 9d 02 e3 32 01 0e 03 02 0b 01 38 02 39 7a 86 e0 87 21 85 6a d0 08 da fa ab 2f 64 4a e3 00 00\n                   AA KC II II BF TT TT HH 0W WW GG 0D DD RR RR UU LL LL xx SS yy ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??\n\n- K:  {4} Type of sensor, = 0x0\n- C:  {4} Channel, = 0x0\n- I: {16} Sensor ID\n- BF: {8} Battery Flag 0x9d = battery low, 0x1d = normal battery, may be pairing button to be confirmed ?\n- T: {12} temperature in C, offset 500, scale 10\n- H:  {8} humidity %\n- W: {16} Wind speed, scale 10, offset 257 (0x0101)\n- G:  {8} Wind Gust m/s scale 1.5\n- D: {12} Wind Direction, offset 257\n- R: {16} Total Rain mm/m2, 0.4 mm/m²/tips , offset 257\n- U:  {5} UV index from 0 to 16, offset 1\n- L: {1 + 15 bit} Lux value, if first bit = 1 , then x 10 the 15 bit (offset 257).\n- ?: unknown, fixed values\n- A:  {4} fixed values of 0xA\n- 0:  {4} fixed values of 0x0\n- xx: {8} incremental value each tx\n- S:  {8} checksum\n- yy: {8} incremental value each tx yy = xx + 1\n\n*/\n\n#define VEVOR_MESSAGE_BITLEN     264\n\nstatic int vevor_7in1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // preamble is ....aaaaaaaaaacaca54\n    uint8_t const preamble_pattern[] = {0xaa, 0xaa, 0xca, 0xca, 0x54};\n\n    // Because of a gap false positive if LUX at max for weather station, only single row to be analyzed with expected 2 repeats inside the data.\n    if (bitbuffer->num_rows != 1) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    int ret = 0;\n    int pos = 0;\n    while ((pos = bitbuffer_search(bitbuffer, 0, pos, preamble_pattern, sizeof(preamble_pattern) * 8)) + VEVOR_MESSAGE_BITLEN <= bitbuffer->bits_per_row[0]) {\n\n        if (pos >= bitbuffer->bits_per_row[0]) {\n            decoder_log(decoder, 2, __func__, \"Preamble not found\");\n            ret = DECODE_ABORT_EARLY;\n            continue;\n        }\n        decoder_logf(decoder, 2, __func__, \"Found Vevor preamble pos: %d\", pos);\n\n        pos += sizeof(preamble_pattern) * 8;\n        // we expect at least 21 bytes\n        if (pos + 21 * 8 > bitbuffer->bits_per_row[0]) {\n            decoder_log(decoder, 2, __func__, \"Length check fail\");\n            ret = DECODE_ABORT_LENGTH;\n            continue;\n        }\n        uint8_t b[21] = {0};\n        bitbuffer_extract_bytes(bitbuffer, 0, pos, b, sizeof(b) * 8);\n\n        // verify checksum\n        if ((add_bytes(b, 19) & 0xff) != b[19]) {\n            decoder_log(decoder, 2, __func__, \"Checksum fail\");\n            ret = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        //int kind        = ((b[1] & 0xf0) >> 4);\n        int channel     = (b[1] & 0x0f);\n        int id          = (b[2] << 8) | b[3];\n        int battery_low = (b[4] & 0x80) >> 7;\n\n        if (b[0] == 0xAA && b[1] == 0) {\n\n            int temp_raw      = (b[5] << 8) | b[6];\n            float temp_c      = (temp_raw - 500) * 0.1f;\n            int humidity      = b[7];\n            int wind_raw      = ((b[8] << 8) | b[9]) - 257; // need to remove 0x0101.\n            float speed_kmh   = wind_raw / 10.0f  ; // wind_raw / 36.0f for m/s\n            int gust_raw      = b[10];\n            float gust_kmh    = gust_raw / 1.5f ; // gust_raw / 1.5f / 3.6f m/s, + 0.1f offset from the weather display\n            int direction_deg = (((b[11] & 0x0f) << 8) | b[12]) - 257; // need to remove 0x101.\n            int rain_raw      = ((b[13] << 8) | b[14]) - 257; // need to remove 0x101.\n            float rain_mm     = rain_raw * 0.233f; // calculation is 0.43f but display is 0.5f\n            int uv_index      = (b[15] & 0x1f) - 1;\n            int light_lux     = ((b[16] << 8) | b[17]) - 257; // need to remove 0x0101.\n            int lux_multi     = (light_lux & 0x8000) >> 15;\n\n            if (lux_multi == 1) {\n                light_lux = (light_lux & 0x7fff) * 10;\n            }\n\n            /* clang-format off */\n            data_t *data = data_make(\n                    \"model\",            \"\",                 DATA_STRING, \"Vevor-7in1\",\n                    \"id\",               \"\",                 DATA_FORMAT, \"%04x\", DATA_INT,    id,\n                    \"channel\",          \"Channel\",          DATA_INT,    channel,\n                    \"battery_ok\",       \"Battery_OK\",       DATA_INT,    !battery_low,\n                    \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n                    \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n                    \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, speed_kmh,\n                    \"wind_max_km_h\",    \"Wind max speed\",   DATA_FORMAT, \"%.1f km/h\",  DATA_DOUBLE, gust_kmh,\n                    \"wind_dir_deg\",     \"Wind Direction\",   DATA_INT,    direction_deg,\n                    \"rain_mm\",          \"Total rainfall\",   DATA_FORMAT, \"%.1f mm\",  DATA_DOUBLE, rain_mm,\n                    \"uvi\",              \"UV Index\",         DATA_FORMAT, \"%.0f\", DATA_DOUBLE, (double)uv_index,\n                    \"light_lux\",        \"Lux\",              DATA_FORMAT, \"%u\", DATA_INT, light_lux,\n                    \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                    NULL);\n            /* clang-format on */\n\n            decoder_output_data(decoder, data);\n            return 1;\n        }\n        pos += VEVOR_MESSAGE_BITLEN;\n    }\n    return ret;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_avg_km_h\",\n        \"wind_max_km_h\",\n        \"rain_mm\",\n        \"wind_dir_deg\",\n        \"uvi\",\n        \"light_lux\",\n        \"mic\",\n        NULL,\n};\n\nr_device const vevor_7in1 = {\n        .name        = \"Vevor Wireless Weather Station 7-in-1\",\n        .modulation  = FSK_PULSE_PCM,\n        .short_width = 87,\n        .long_width  = 87,\n        .reset_limit = 9000, // keep message is one row because of a possible gap in the message if LUX values are zeros\n        .decode_fn   = &vevor_7in1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/visonic_powercode.c",
    "content": "/** @file\n    Decoder for Visonic Powercode devices. Tested with an MCT-302.\n\n    Copyright (C) 2020 Maxwell Lock\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/**\nThe device uses OOK PWM encoding, short pulse 400us long pulse 800us, and repeats 6 times\nYou can use a flex decoder -X 'n=visonic_powercode,m=OOK_PWM,s=400,l=800,r=5000,g=900,t=160,y=0'\n\nPowercode packet structure is 37 bits. 4 examples follow\n\n              s addr                       data     cksm\n              1 01101111 01000111 01110000 10001100 1001 - magnet near, case open\n              1 01101111 01000111 01110000 11001100 1101 - magnet away, case open\n              1 01101111 01000111 01110000 00001100 0001 - magnet near, case closed\n              1 01101111 01000111 01110000 01001100 0101 - magnet away, case closed\n              | |                        | |||||||| |  |\n     StartBit_/ /                        / |||||||| \\__\\_checksum, XOR of preceding nibbles\n     DeviceID__/________________________/  ||||||||\n                                           ||||||||\n                                    Tamper_/||||||\\_Repeater\n                                      Alarm_/||||\\_Spidernet\n                                     Battery_/||\\_Supervise\n                                         Else_/\\_Restore\n\n1 bit start bit\n3 byte(24 bit) device ID\n1 byte data\n1 nibble (4 bit) checksum\n\nChecksum is a londitudinal redundancy check of the 4 bytes containing the device ID and data.\nBytes are split into nibbles. 1st bit of each nibble is XORed and result is 1st bit of checksum,\nthen the same for the 2nd, 3rd and 4th bits.\n\nProtocol cribbed from:\n * Visonic MCR-300 UART Manual http://www.el-sys.com.ua/wp-content/uploads/MCR-300_UART_DE3140U0.pdf\n * https://metacpan.org/release/Device-RFXCOM/source/lib/Device/RFXCOM/Decoder/Visonic.pm\n * https://forum.arduino.cc/index.php?topic=289554.0\n*/\n\n#include \"decoder.h\"\n\nstatic int visonic_powercode_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t msg[32];\n    uint8_t lrc;\n\n    // 37 bits expected, 6 packet repetitions, accept 2\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 37);\n\n    // exit if anything other than one row returned (-1 if failed)\n    if (row == -1)\n        return DECODE_ABORT_LENGTH;\n\n    // exit if incorrect number of bits in row or none\n    if (bitbuffer->bits_per_row[row] != 37)\n        return DECODE_ABORT_LENGTH;\n\n    // extract message, drop leading start bit, include trailing LRC nibble\n    bitbuffer_extract_bytes(bitbuffer, row, 1, msg, 36);\n\n    // No need to decode/extract values for simple test\n    if (!msg[0] && !msg[1] && !msg[2] && !msg[3] && !msg[4]) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    lrc = xor_bytes(msg, 5);\n    if (((lrc >> 4) ^ (lrc & 0xf)) != 0)\n        return DECODE_FAIL_MIC;\n\n    // debug\n    decoder_logf(decoder, 2, __func__, \"data byte is %02x\", msg[3]);\n\n    // format device id\n    char id_str[7];\n    snprintf(id_str, sizeof(id_str), \"%02x%02x%02x\", msg[0], msg[1], msg[2]);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"Model\",        DATA_STRING, \"Visonic-Powercode\",\n            \"id\",           \"ID\",           DATA_STRING, id_str,\n            \"tamper\",       \"Tamper\",       DATA_INT,    ((0x80 & msg[3]) == 0x80) ? 1 : 0,\n            \"alarm\",        \"Alarm\",        DATA_INT,    ((0x40 & msg[3]) == 0x40) ? 1 : 0,\n            \"battery_ok\",   \"Battery\",      DATA_INT,    ((0x20 & msg[3]) == 0x20) ? 0 : 1,\n            \"else\",         \"Else\",         DATA_INT,    ((0x10 & msg[3]) == 0x10) ? 1 : 0,\n            \"restore\",      \"Restore\",      DATA_INT,    ((0x08 & msg[3]) == 0x08) ? 1 : 0,\n            \"supervised\",   \"Supervised\",   DATA_INT,    ((0x04 & msg[3]) == 0x04) ? 1 : 0,\n            \"spidernet\",    \"Spidernet\",    DATA_INT,    ((0x02 & msg[3]) == 0x02) ? 1 : 0,\n            \"repeater\",     \"Repeater\",     DATA_INT,    ((0x01 & msg[3]) == 0x01) ? 1 : 0,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    // return data\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"tamper\",\n        \"alarm\",\n        \"battery_ok\",\n        \"else\",\n        \"restore\",\n        \"supervised\",\n        \"spidernet\",\n        \"repeater\",\n        \"mic\",\n        NULL,\n};\n\nr_device const visonic_powercode = {\n        .name        = \"Visonic powercode\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 400,\n        .long_width  = 800,\n        .gap_limit   = 900,\n        .reset_limit = 5000,\n        .decode_fn   = &visonic_powercode_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/wallarge_cltx001.c",
    "content": "/** @file\n    WallarGe CLTX001 Outdoor Temperature Sensor.\n\n    Copyright (C) 2026 Dennis Kehrig\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int wallarge_cltx001_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nWallarGe CLTX001 Outdoor Temperature Sensor.\n\nFCC ID: 2AYIQ-TX100 (https://fcc.report/FCC-ID/2AYIQ-TX100)\n\nCan be purchased individually (https://www.amazon.com/dp/B0CB17H77R/) or\nbundled with WallarGe clocks like the CL6007 (https://www.amazon.com/dp/B0D9BNSQCS)\nand CL7001 (https://www.amazon.com/dp/B0BYNJW532, http://www.us-wallarge.com/item/3015.html).\n\n## Modulation\n\nHIGH/LOW periods are multiples of 250 µs long.\nThe following uses `-` for HIGHs and `_` for LOWs lasting 250 µs, respectively.\n\n### 1) Preamble: `-___` or `--___` (single pulse followed by a 750 µs gap)\n\nSometimes, the initial pulse is too short and gets ignored by rtl_433.\nWhen it does get registered by rtl_433, the following gap exceeds the\nconfigured gap limit of 650 µs, resulting in a row with a single bit.\nThis prevents the bit from becoming part of the following row,\nmaking that row easier to decode.\n\n### 2) Payload with 0 = `-__` and 1 = `--_` (750 µs per symbol)\n\nrtl_433 interprets this as 1 = `-__` and 0 = `--_`, so we have to invert the data.\n\nSometimes, the transmitter seems to skip ahead by 250 µs and/or flip a bit.\nIn such cases, rtl_433 may drop some bits and split the row instead.\nThese partial rows are currently ignored.\n\nFor some reason these issues mainly affect periodic transmissions as opposed to\ntransmissions that occur immediately after changing the transmitter's channel.\n\n### 3) Separator: `_---___---___` (between each repeated payload)\n\nWhen the payload ends with `--_`, rtl_433 will see two gaps exceeding the limit:\n\n    ...--__---___---___\n              ^^^   ^^^\n\nWhen the payload ends with `-__`, rtl_433 will see three gaps exceeding the limit:\n\n    ...-___---___---___\n        ^^^   ^^^   ^^^\n\nFor each of the gaps exceeding the configured limit it will close the current row,\nresulting in either one or two empty rows occurring between rows with data.\n\nThe payload gets sent five times per transmission.\n\n## Payload encoding: 56 bits / 7 bytes\n\n1. `IIIIIIII` Bits 1 to  8 of a uint16_t sensor ID\n2. `IIIIIIII` Bits 9 to 16 of a uint16_t sensor ID\n3. `00000000` Always zero, unknown purpose, ignored by clock\n4. `BMCCTTTT` Battery status (0 = okay, 1 = low),\n              test mode (0 = off, 1 = on),\n              2-bit channel ID (0 = A, 1 = B, 2 = C),\n              bits 1 to  4 of an \"int12_t\" temperature reading\n5. `TTTTTTTT` Bits 5 to 12 of an \"int12_t\" temperature reading\n6. `PPPPP000` Parity data - even number of set bits in byte N => bit N = 1, else 0.\n              The remaining 3 bits are always zero, the clock rejects the signal otherwise.\n7. `SSSSSSSS` Checksum, sum of bytes 1-5 (indexes 0-4) modulo 256\n\n### Battery mode\n\nOnly reported when the battery is low.\n\n### Test mode\n\nOnly reported when turned on (not used by the CLTX001).\n\nThe sensor transmits immediately after plugging in a battery or selecting a different\nchannel, and every 31/33/35 seconds thereafter for channels A/B/C, respectively.\n\nNormally the clock will turn its receiver off after receiving a signal until the\nsensor is expected to transmit its next signal.\nThe test mode overrides this behavior and the clock will keep the receiver on.\n\n### Channel ID\n\nThe CLTX001 transmitter and matching clocks only support three channels, but\nvalue 3 = D may also occur while changing the transmitter's channel.\n\n### Temperature reading\n\n12 bit signed integer (two's complement) representing 0.1°C increments in temperature.\nRange: -204.8 to 204.7°C.\nThe clock will show HH.H above 70°C (158°F) and LL.L below -40°C (-40°F).\n\n*/\n\n#define BITS_PER_ROW  56\n#define BYTES_PER_ROW 7\n#define DATA_BYTES    5\n\nstatic int wallarge_cltx001_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // This value will be changed to reflect the best row.\n    // If it doesn't get changed, we didn't see any row with exactly 56 bits.\n    int return_value = DECODE_ABORT_LENGTH;\n\n    // Consider each row in order of appearance\n    for (int row_index = 0; row_index < bitbuffer->num_rows; row_index++) {\n        // 1) Ignore rows that don't have 56 bits\n\n        if (bitbuffer->bits_per_row[row_index] != BITS_PER_ROW) {\n            continue;\n        }\n\n        // 2) Invert the data\n\n        // Pointer through which we will access the selected row's bytes\n        uint8_t *b = bitbuffer->bb[row_index];\n\n        // Invert just the bits in this row\n        for (int i = 0; i < BYTES_PER_ROW; i++) {\n            b[i] = ~b[i];\n        }\n\n        // 3) Ignore rows with an invalid checksum\n\n        // Sum up the first five bytes\n        int checksum = add_bytes(b, DATA_BYTES) & 0xFF;\n\n        if (b[6] != checksum) {\n            return_value = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        // 4) Ignore rows with invalid parity data\n\n        uint8_t parity_byte = b[5];\n        int parity_valid    = 1;\n\n        // The last three bits must be 0\n        if (parity_byte & 0x07) { // 0x07 = 0b00000111\n            parity_valid = 0;\n        }\n        else {\n            // parity_byte's Nth bit should be 1 if the Nth byte has an even number of 1s\n            for (int byte_index = 0; byte_index < DATA_BYTES; byte_index++) {\n                // parity8() returns 0/1 for even/odd parity, the protocol uses the inverse, so fail if equal\n                if (parity8(b[byte_index]) == ((parity_byte >> (7 - byte_index)) & 1)) {\n                    parity_valid = 0;\n                    break;\n                }\n            }\n        }\n\n        if (!parity_valid) {\n            return_value = DECODE_FAIL_MIC;\n            continue;\n        }\n\n        // 5) Extract the actual data and output it\n\n        // Combine the first two bytes into a single sensor ID\n        int sensor_id = ((int)b[0] << 8) | b[1];\n\n        // Extract the low battery and test mode bits\n        int battery_low = (b[3] & 0x80) >> 7; // 0x80 = 0b10000000\n        int test_mode   = (b[3] & 0x40) >> 6; // 0x40 = 0b01000000\n\n        // Extract the channel ID (0-3 => A-D)\n        int channel = ((b[3] & 0x30) >> 4); // 0x30 = 0b00110000\n\n        // The temperature is essentially an int12_t starting half-way in b[3].\n        // Sign-extend it by shifting it left an extra 4 bits and casting it as an int16_t.\n        int temp_raw = (int16_t)(((b[3] & 0x0F) << 12) | (b[4] << 4)); // 0x0F = 0b00001111\n        // The temperature is stored as multiples of 0.1°C. Undo the extra shifting by 4 bits and scale it.\n        double temp_c = (temp_raw >> 4) * 0.1f;\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"Model\",        DATA_STRING,    \"WallarGe-CLTX001\",\n                \"id\",               \"Sensor ID\",    DATA_INT,       sensor_id,\n                \"channel\",          \"Channel\",      DATA_INT,       channel + 1,\n                \"battery_ok\",       \"Battery\",      DATA_COND,      battery_low, DATA_INT, !battery_low,\n                \"temperature_C\",    \"Temperature\",  DATA_FORMAT,    \"%.1f C\", DATA_DOUBLE, temp_c,\n                \"test\",             \"Test?\",        DATA_COND,      test_mode,   DATA_INT, test_mode,\n                \"mic\",              \"Integrity\",    DATA_STRING,    \"CHECKSUM\", // Technically CHECKSUM+PARITY\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n\n        // We've found a valid row and will ignore any remaining rows, so 1 is the number of packets successfully decoded\n        return_value = 1;\n        break;\n    }\n\n    return return_value;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"test\",\n        \"mic\",\n        NULL,\n};\n\nr_device const wallarge_cltx001 = {\n        .name        = \"WallarGe CLTX001 Outdoor Temperature Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .tolerance   = 75,\n        .short_width = 250,\n        .long_width  = 500,\n        .gap_limit   = 650, // Gaps that deliniate rows are ~700-750 µs long and tolerance does not apply to the gap limit\n        .reset_limit = 1250,\n        .decode_fn   = &wallarge_cltx001_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/watts_thermostat.c",
    "content": "/** @file\n    Watts WFHT-RF Thermostat.\n\n    Copyright (C) 2022 Ådne Hovda <aadne@hovda.no>\n    based on protocol decoding by Christian W. Zuckschwerdt <zany@triq.net>\n    and Ådne Hovda <aadne@hovda.no>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/** @fn int watts_thermostat_decode(r_device *decoder, bitbuffer_t *bitbuffer)\nWatts WFHT-RF Thermostat.\n\nThis code is based on a slightly older OEM system created by ADEV in France which\nlater merged with Watts. The closest thing currently available seems to be\nhttps://wattswater.eu/catalog/regulation-and-control/radio-wfht-thermostats/electronic-room-thermostat-with-rf-control-wfht-rf-basic/,\nbut it is not known whether they are protocol compatible.\n\nModulation is PWM with preceeding gap. There is a very long lead-in pulse.\nSymbols are ~260 us gap + ~600 us pulse and ~600 us gap + ~260 us pulse.\nBits are inverted and reflected.\n\nExample Data:\n\n    10100101   1011010001110110   1000   100100001   000011000   10101011\n    preamble   id                 flags  temp         setpoint   chksum\n\nData Layout:\n\n    PP II II F .TT .SS XX\n\n- P: (8-bit reflected) Preamble\n- I: (16-bit reflected) ID\n- F: (4-bit reflected) Flags\n- T: (9-bit reflected) Temperature\n- S: (9-bit reflected) Set-Point\n- X: (8-bit reflected) Checksum (8-bit sum)\n\n    The only flag found is PAIRING (0b0001). Chksum is calculated by summing all\n    high and low bytes the for ID, Flags, Temperature and Set-Point.\n\n    Temperature and Set-Point values are in 0.1°C steps with an observed Set-Point\n    range of ~4°C to ~30°C.\n\nRaw data:\n\n    {54}5ab24971f79994\n    {54}5ab24971f79994\n    {54}5ab249f1f79b94\n    {54}5ab249f1f79b94\n    {54}5ab249f9f79854\n    {54}5ab249f5f79a54\n    {54}5ab249f68f998c\n    {54}5ab249f98f9a4c\n    {54}5ab249f58b9a4c\n    {54}5ab249fb8f9acc\n\n    https://tinyurl.com/wattsthermobitbench\n\n    Format string:\n    PRE:^8h ID:^16d FLAGS:^4b TEMP:^9d SETP:^9d CHK:^8d\n\nDecoded example:\n\n    PRE:a5 ID:28082 FLAGS:0001 TEMP:271 SETP:304 CHK:097\n    PRE:a5 ID:28252 FLAGS:0000 TEMP:019 SETP:303 CHK:013\n\n*/\n\n#define WATTSTHERMO_BITLEN             54\n#define WATTSTHERMO_PREAMBLE_BITLEN    8\n#define WATTSTHERMO_ID_BITLEN          16\n#define WATTSTHERMO_FLAGS_BITLEN       4\n#define WATTSTHERMO_TEMPERATURE_BITLEN 9\n#define WATTSTHERMO_SETPOINT_BITLEN    9\n#define WATTSTHERMO_CHKSUM_BITLEN      8\n\nenum WATTSTHERMO_FLAGS {\n    WF_NONE     = 0,\n    WF_PAIRING  = 1,\n    WF_UNKNOWN1 = 2,\n    WF_UNKNOWN2 = 4,\n    WF_UNKNOWN3 = 8,\n};\n\nstatic int watts_thermostat_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble_pattern[] = {0xa5}; // inverted, raw value is 0x5a\n\n    bitbuffer_invert(bitbuffer);\n\n    // We're expecting a single row\n    for (uint16_t row = 0; row < bitbuffer->num_rows; ++row) {\n        uint16_t row_len = bitbuffer->bits_per_row[row];\n\n        unsigned bitpos = bitbuffer_search(bitbuffer, row, 0, preamble_pattern, WATTSTHERMO_PREAMBLE_BITLEN);\n        if (bitpos >= row_len) {\n            decoder_log(decoder, 2, __func__, \"Preamble not found\");\n            return DECODE_ABORT_EARLY;\n        }\n\n        if (bitpos + WATTSTHERMO_BITLEN > row_len) {\n            decoder_log(decoder, 2, __func__, \"Message too short\");\n            return DECODE_ABORT_LENGTH;\n        }\n        bitpos += WATTSTHERMO_PREAMBLE_BITLEN;\n\n        uint8_t id_raw[2];\n        bitbuffer_extract_bytes(bitbuffer, row, bitpos, id_raw, WATTSTHERMO_ID_BITLEN);\n        reflect_bytes(id_raw, 2);\n        int id = (id_raw[1] << 8) | id_raw[0];\n        bitpos += WATTSTHERMO_ID_BITLEN;\n\n        uint8_t flags[1];\n        bitbuffer_extract_bytes(bitbuffer, row, bitpos, flags, WATTSTHERMO_FLAGS_BITLEN);\n        reflect_bytes(flags, 1);\n        int pairing = flags[0] & WF_PAIRING;\n        bitpos += WATTSTHERMO_FLAGS_BITLEN;\n\n        uint8_t temp_raw[2];\n        bitbuffer_extract_bytes(bitbuffer, row, bitpos, temp_raw, WATTSTHERMO_TEMPERATURE_BITLEN);\n        reflect_bytes(temp_raw, 2);\n        int temp = (temp_raw[1] << 8) | temp_raw[0];\n        bitpos += WATTSTHERMO_TEMPERATURE_BITLEN;\n\n        uint8_t setp_raw[2];\n        bitbuffer_extract_bytes(bitbuffer, row, bitpos, setp_raw, WATTSTHERMO_SETPOINT_BITLEN);\n        reflect_bytes(setp_raw, 2);\n        int setp = (setp_raw[1] << 8) | setp_raw[0];\n        bitpos += WATTSTHERMO_SETPOINT_BITLEN;\n\n        uint8_t chksum = add_bytes(id_raw, 2)\n                + add_bytes(flags, 1)\n                + add_bytes(temp_raw, 2)\n                + add_bytes(setp_raw, 2);\n\n        uint8_t chk[1];\n        bitbuffer_extract_bytes(bitbuffer, row, bitpos, chk, WATTSTHERMO_CHKSUM_BITLEN);\n        reflect_bytes(chk, 1);\n        if (chk[0] != chksum) {\n            decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"Checksum fail\");\n            return DECODE_FAIL_MIC;\n        }\n\n        if (id == 0 && flags[0] == 0 && temp == 0 && setp == 0 && chk[0] == 0) {\n            decoder_log(decoder, 2, __func__, \"Rejecting false positive\");\n            return DECODE_ABORT_EARLY;\n        }\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",            \"Model\",            DATA_STRING, \"Watts-WFHTRF\",\n                \"id\",               \"ID\",               DATA_INT,    id,\n                \"pairing\",          \"Pairing\",          DATA_INT,    pairing,\n                \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",      DATA_DOUBLE,  temp * 0.1f,\n                \"setpoint_C\",       \"Setpoint\",         DATA_FORMAT, \"%.1f C\",      DATA_DOUBLE,  setp * 0.1f,\n                \"flags\",            \"Flags\",            DATA_INT,    flags[0],\n                \"mic\",              \"Integrity\",        DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"pairing\",\n        \"temperature_C\",\n        \"setpoint_C\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const watts_thermostat = {\n        .name        = \"Watts WFHT-RF Thermostat\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 260,\n        .long_width  = 600,\n        .sync_width  = 6000,\n        .reset_limit = 900,\n        .decode_fn   = &watts_thermostat_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/waveman.c",
    "content": "/** @file\n    Example of a generic remote using PT2260/PT2262 SC2260/SC2262 EV1527 protocol.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n\n/**\nExample of a generic remote using PT2260/PT2262 SC2260/SC2262 EV1527 protocol.\n\nfixed bit width of 1445 us\nshort pulse is 357 us (1/4th)\nlong pulse is 1064 (3/4th)\na packet is 15 pulses, the last pulse (short) is sync pulse\npacket gap is 11.5 ms\n\nnote that this decoder uses:\nshort-short (1 1 by the demod) as 0 (per protocol),\nshort-long (1 0 by the demod) as 1 (F per protocol),\nlong-long (0 0 by the demod) not used (1 per protocol).\n*/\n\n#include \"decoder.h\"\n\nstatic int waveman_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b    = bitbuffer->bb[0];\n    uint8_t nb[3] = {0}; // maps a pair of bits to two states, 1 0 -> 1 and 1 1 -> 0\n    char id_str[2];\n    int i;\n\n    /* TODO: iterate through all rows */\n\n    /* Reject codes of wrong length */\n    if (25 != bitbuffer->bits_per_row[0])\n        return DECODE_ABORT_LENGTH;\n\n    /*\n     * Catch the case triggering false positive for other transmitters.\n     * example: Brennstuhl RCS 2044SN\n     * TODO: is this message valid at all??? if not then put more validation below\n     *       instead of this special case\n     */\n    if (0xFF == b[0] &&\n            0xFF == b[1] &&\n            0xFF == b[2])\n        return DECODE_ABORT_EARLY;\n\n    /* Test if the bit stream has every even bit set to one */\n    if (((b[0] & 0xaa) != 0xaa) || ((b[1] & 0xaa) != 0xaa) || ((b[2] & 0xaa) != 0xaa))\n        return DECODE_FAIL_SANITY;\n\n    /* Extract data from the bit stream */\n    for (i = 0; i < 3; ++i) {\n        nb[i] = ((b[i] & 0x40) ? 0x00 : 0x01)\n                | ((b[i] & 0x10) ? 0x00 : 0x02)\n                | ((b[i] & 0x04) ? 0x00 : 0x04)\n                | ((b[i] & 0x01) ? 0x00 : 0x08);\n    }\n\n    id_str[0] = 'A' + nb[0];\n    id_str[1] = '\\0';\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",    \"\",     DATA_STRING,    \"Waveman-Switch\",\n            \"id\",       \"\",     DATA_STRING,    id_str,\n            \"channel\",  \"\",     DATA_INT,       (nb[1] >> 2) + 1,\n            \"button\",   \"\",     DATA_INT,       (nb[1] & 3) + 1,\n            \"state\",    \"\",     DATA_STRING,    (nb[2] == 0xe) ? \"ON\" : \"OFF\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"button\",\n        \"state\",\n        NULL,\n};\n\nr_device const waveman = {\n        .name        = \"Waveman Switch Transmitter\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 357,\n        .long_width  = 1064,\n        .gap_limit   = 1400,\n        .reset_limit = 12000,\n        .sync_width  = 0,   // No sync bit used\n        .tolerance   = 200, // us\n        .decode_fn   = &waveman_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/wec2103.c",
    "content": "/** @file\n    WEC-2103 temperature/humidity sensor.\n\n    Copyright (C) 2022 Tobias Thurnreiter\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n#include \"decoder.h\"\n\n/**\nWEC-2103 temperature/humidity sensor.\n\nCircuit board model numbers: TX07Y-THC V1, TX07K-THC V4\n\nSimilar to prologue, kedsum, esperanza_ews, s3318p\nOnly available information for this device: https://fcc.report/FCC-ID/WEC-2103\n\nData:\n\n    Byte:      0        1        2        3        4        5\n    Nibble:    1   2    3   4    5   6    7   8    9   10   11\n    Type:      IIIIIIII XXXXFFFF TTTTTTTT TTTTHHHH HHHHCCCC SS\n\n- I: random device ID, changes on powercycle\n- X: Checksum: mangled CRC-4, poly 3, init 0.\n- F: Flags: tx-button pressed|batt-low|?|?\n- T: Temperature\n- H: Humidity\n- S: Stop bit(s): 0b10\n\nExample datagram:\n\n     f2 90             6b5         96       1       8\n    |ID|Checksum+Flags|Temperature|Humidity|Channel|Stop bits\n\n- Temperature in Fahrenheit*100+900->hex\n- Example: 82.4F->824->1724->0x6bc\n*/\n\nstatic int wec2103_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    if (bitbuffer->num_rows != 6 || bitbuffer->bits_per_row[2] != 42) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    uint8_t b[5];\n    bitbuffer_extract_bytes(bitbuffer, 3, 0, b, 40);\n\n    int crc_received = b[1] >> 4;\n    b[1] = (b[1] & 0x0F) | ((b[4] & 0x0f) << 4);\n    int crc_calculated = crc4(b, sizeof(b) - 1, 3, 0) ^ (b[4] >> 4);\n    if (crc_calculated != crc_received) {\n        decoder_logf(decoder, 0, __func__, \"CRC check failed (0x%X != 0x%X)\", crc_calculated, crc_received);\n        return DECODE_FAIL_MIC;\n    }\n\n    int temp_raw    = (b[2] << 4) | ((b[3] & 0xf0) >> 4);\n    int device_id   = b[0];\n    int channel     = b[4] & 0x0f;\n    int flags       = b[1] & 0xf;\n    float temp_f    = (temp_raw - 900) * 0.1f;\n    int humidity    = ((b[3] & 0x0f) * 10) + ((b[4] & 0xf0) >> 4);\n    int button      = (b[1] & 0x08) >> 3;\n    int battery_low = (b[1] & 0x04) >> 3;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"WEC-2103\",\n            \"id\",               \"ID\",           DATA_INT,    device_id,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"button\",           \"Button\",       DATA_INT,    button,\n            \"temperature_F\",    \"Temperature\",  DATA_FORMAT, \"%.2f F\", DATA_DOUBLE, temp_f,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"flags\",            \"Flags\",        DATA_INT,    flags,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"button\",\n        \"temperature_F\",\n        \"humidity\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const wec2103 = {\n        .name           = \"WEC-2103 temperature/humidity sensor\",\n        .modulation     = OOK_PULSE_PPM,\n        .short_width    = 1900,\n        .long_width     = 3800,\n        .gap_limit      = 4400,\n        .reset_limit    = 9400,\n        .decode_fn      = &wec2103_decode,\n        .fields         = output_fields,\n};\n"
  },
  {
    "path": "src/devices/wg_pb12v1.c",
    "content": "/** @file\n    WG-PB12V1 Temperature Sensor.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Modifications Copyright (C) 2017 Ciarán Mooney\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nWG-PB12V1 Temperature Sensor.\n\nDevice method to decode a generic wireless temperature probe. Probe marked\nwith WG-PB12V1-2016/11.\n\nFormat of Packets:\n\nThe packet format appears to be similar those the Lacrosse format.\n(http://fredboboss.free.fr/articles/tx29.php)\n\n    AAAAAAAA MMMMTTTT TTTTTTTT ???IIIII HHHHHHHH CCCCCCCC\n\n- A: Preamble - 11111111\n- M: Message type?, fixed 0x3, e.g. Fine Offset WH2 has 0x4 here\n- T: Temperature, scale 10, offset 40\n- I: ID of probe is set randomly each time the device is powered off-on,\n     Note, base station has and unused \"123\" symbol, but ID values can be\n     higher than this.\n- H: Humidity - not used, is always 11111111\n- C: Checksum - CRC8, polynomial 0x31, initial value 0x0, final value 0x0\n\nTemperature:\n\nTemperature value is \"deci-celsius\", ie 10 dC = 1C, offset by -40 C.\n\n    0010 01011101 = 605 dC => 60.5 C\n    Remove offset => 60.5 C - 40 C = 20.5 C\n\nUnknown:\n\nPossible uses could be weak battery, or new battery.\n\nAt the moment it this device cannot distinguish between a Fine Offset\ndevice, see fineoffset.c.\n*/\n\n#include \"decoder.h\"\n\nstatic int wg_pb12v1_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Validate package\n    uint8_t *b = bitbuffer->bb[0];\n    if (bitbuffer->bits_per_row[0] < 48)\n        return DECODE_ABORT_LENGTH;\n    if (b[0] != 0xFF) // Preamble\n        return DECODE_ABORT_EARLY;\n    if ((b[1] & 0xf0) != 0x30) // Message type, TODO: is this always a fixed value?\n        return DECODE_ABORT_EARLY;\n    if (b[5] != crc8(&b[1], 4, 0x31, 0)) // CRC (excluding preamble)\n        return DECODE_FAIL_MIC;\n    if (b[4] != 0xFF) // Humidity set to 11111111\n        return DECODE_FAIL_OTHER;\n\n    // Nibble 7,8 contains id\n    int id = b[3] & 0x1F;\n\n    // Nibble 5,6,7 contains 12 bits of temperature\n    // Temperature, scaled by 10, offset by -40 C.\n    int temp_raw = ((b[1] & 0x0F) << 8) | b[2];\n    float temp_c = (temp_raw - 400) * 0.1f;\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"WG-PB12V1\",\n            \"id\",               \"ID\",           DATA_INT,    id,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const wg_pb12v1 = {\n        .name        = \"WG-PB12V1 Temperature Sensor\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 564,  // Short pulse 564µs, long pulse 1476µs, fixed gap 960µs\n        .long_width  = 1476, // Maximum pulse period (long pulse + fixed gap)\n        .reset_limit = 2500, // We just want 1 package\n        .decode_fn   = &wg_pb12v1_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/ws2032.c",
    "content": "/** @file\n    WS2032 weather station.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n/**\nWS2032 weather station.\n\n- Outdoor temperature range: -40F to 140F (-40C to 60C)\n- Temperature accuracy: +- 1.0 C\n- Humidity range: 20% to 90%\n- Humidity accuracy: +-5%\n- Wind direction: E,S,W,N,SE,NE,SW,NW\n- Wind direction accuracy: +- 10 deg\n- Wind speed: 0 to 50m/s, Accuracy: 0.1 m/s\n\nData format:\n\n    1x PRE:8h ID:16h FLAGS:8h DIR:4h TEMP:12d HUM:8d AVG:8d GUST:8d RAIN:24h SUM:8h CHK:8h TRAIL:3b\n\nOOK with PWM. Long = 1000 us, short = 532 us, gap = 484 us.\nThe overlong and very short pulses are sync, see the Pulseview.\n\nTemp, not 2's complement but a dedicated sign-bit, i.e. 1 bit sign, 11 bit temp.\n\n*/\n\n#include \"decoder.h\"\n\nstatic int fineoffset_ws2032_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t const preamble[] = {0x0a}; // 8 bits, 0xf5 inverted\n\n    data_t *data;\n    uint8_t b[14];\n\n    // find a proper row\n    int row = bitbuffer_find_repeated_row(bitbuffer, 2, 14 * 8); // expected: 3 rows of 113 bits\n    if (row < 0) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    unsigned offset = bitbuffer_search(bitbuffer, row, 0, preamble, 8);\n    if (offset + 14 * 8 > bitbuffer->bits_per_row[row]) {\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // invert and align the row\n    bitbuffer_invert(bitbuffer);\n    bitbuffer_extract_bytes(bitbuffer, row, offset, b, 14 * 8);\n\n    // verify the checksums\n    int sum = add_bytes(b, 12);\n    if (sum == 0) {\n        return DECODE_FAIL_SANITY; // discard all zeros\n    }\n    if ((sum & 0xff) != b[12]) {\n        return DECODE_FAIL_MIC; // sum mismatch\n    }\n    if (crc8(b, 14, 0x31, 0x00)) {\n        return DECODE_FAIL_MIC; // crc mismatch\n    }\n\n    // get weather sensor data\n    // 1x PRE:8h ID:16h ?8h DIR:4h TEMP:12d HUM:8d AVG?8d GUST?8d 24h SUM8h CHK8h TRAIL:3b\n    int device_id     = (b[1] << 8) | (b[2]);\n    int flags         = (b[3] & 0xfe);\n    int battery_low   = (b[3] & 0x01);\n    float dir         = (b[4] >> 4) * 22.5f;\n    int temp_sign     = (b[4] & 0x08) ? -1 : 1;\n    int temp_raw      = ((b[4] & 0x07) << 8) | b[5];\n    float temperature = temp_sign * temp_raw * 0.1f;\n    int humidity      = (b[6]);\n    float speed       = (b[7] * 0.43f) * 3.6f; // m/s -> km/h\n    float gust        = (b[8] * 0.43f) * 3.6f; // m/s -> km/h\n    int rain_raw      = (b[9] << 16) | (b[10] << 8) | b[11]; // raw tip count\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",                 DATA_STRING, \"WS2032\",\n            \"id\",               \"Station ID\",        DATA_FORMAT, \"%04X\",    DATA_INT,    device_id,\n            \"battery_ok\",       \"Battery\",                                  DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",      DATA_FORMAT, \"%.1f C\",  DATA_DOUBLE, temperature,\n            \"humidity\",         \"Humidity\",         DATA_FORMAT, \"%u %%\",   DATA_INT,    humidity,\n            \"wind_dir_deg\",     \"Wind Direction\",   DATA_FORMAT, \"%.1f\",    DATA_DOUBLE, dir,\n            \"wind_avg_km_h\",    \"Wind avg speed\",   DATA_FORMAT, \"%.1f km/h\",    DATA_DOUBLE, speed,\n            \"wind_max_km_h\",    \"Wind gust\",        DATA_FORMAT, \"%.1f km/h\",    DATA_DOUBLE, gust,\n            \"rain\",             \"Rain tips\",                                DATA_INT,    rain_raw,\n            \"flags\",            \"Flags\",            DATA_FORMAT, \"%02x\",    DATA_INT,    flags,\n            \"mic\",              \"Integrity\",                                DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"wind_dir_deg\",\n        \"wind_avg_km_h\",\n        \"wind_max_km_h\",\n        \"rain\",\n        \"flags\",\n        \"mic\",\n        NULL,\n};\n\nr_device const ws2032 = {\n        .name        = \"WS2032 weather station\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 500,\n        .long_width  = 1000,\n        .gap_limit   = 750,\n        .reset_limit = 4000,\n        .decode_fn   = &fineoffset_ws2032_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/wssensor.c",
    "content": "/** @file\n    Hyundai WS SENZOR Remote Temperature Sensor.\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n/**\nHyundai WS SENZOR Remote Temperature Sensor.\n\n- Transmit Interval: every ~33s\n- Frequency 433.92 MHz\n- Distance coding: Pulse length 224 us\n- Short distance: 1032 us, long distance: 1992 us, packet distance: 4016 us\n\n24-bit data packet format, repeated 23 times\n\n    TTTTTTTT TTTTBSCC IIIIIIII\n\n- T = signed temperature * 10 in Celsius\n- B = battery status (0 = low, 1 = OK)\n- S = startup (0 = normal operation, 1 = battery inserted or TX button pressed)\n- C = channel (0-2)\n- I = sensor ID\n*/\n\n#include \"decoder.h\"\n\n#define WS_PACKETLEN 24\n#define WS_MINREPEATS 4\n#define WS_REPEATS 23\n\nstatic int wssensor_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b;\n    data_t *data;\n\n    // the signal should have 23 repeats\n    // require at least 4 received repeats\n    int r = bitbuffer_find_repeated_row(bitbuffer, WS_MINREPEATS, WS_REPEATS);\n    if (r < 0 || bitbuffer->bits_per_row[r] != WS_PACKETLEN)\n        return DECODE_ABORT_LENGTH;\n\n    b = bitbuffer->bb[r];\n\n    // No need to decode/extract values for simple test\n    if ((!b[0] && !b[1] && !b[2])\n       || (b[0] == 0xff && b[1] == 0xff && b[2] == 0xff)) {\n        decoder_log(decoder, 2, __func__, \"DECODE_FAIL_SANITY data all 0x00 or 0xFF\");\n        return DECODE_FAIL_SANITY;\n    }\n\n    int temperature;\n    int battery_status;\n    int startup;\n    int channel;\n    int sensor_id;\n    float temperature_c;\n\n    /* TTTTTTTT TTTTBSCC IIIIIIII  */\n    temperature = (int16_t)((b[0] << 8) | (b[1] & 0xf0)); // uses sign extend\n    battery_status = (b[1] & 0x08) >> 3;\n    startup = (b[1] & 0x04) >> 2;\n    channel = (b[1] & 0x03) + 1;\n    sensor_id = b[2];\n\n    temperature_c = (temperature >> 4) * 0.1f;\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",         \"\",            DATA_STRING, \"Hyundai-WS\",\n            \"id\",            \"House Code\",  DATA_INT, sensor_id,\n            \"channel\",       \"Channel\",     DATA_INT, channel,\n            \"battery_ok\",    \"Battery\",     DATA_INT,    !!battery_status,\n            \"temperature_C\", \"Temperature\", DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temperature_c,\n            \"button\",           \"Button\",       DATA_INT, startup,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"button\",\n        NULL,\n};\n\nr_device const wssensor = {\n        .name        = \"Hyundai WS SENZOR Remote Temperature Sensor\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 1000,\n        .long_width  = 2000,\n        .gap_limit   = 2400,\n        .reset_limit = 4400,\n        .decode_fn   = &wssensor_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/wt0124.c",
    "content": "/** @file\n    WT0124 Pool Thermometer decoder.\n\n    Copyright (C) 2018 Benjamin Larsson\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n/**\nWT0124 Pool Thermometer decoder.\n\n    5e       ba       9a       9f       e1       34       1\n    01011110 10111010 10011010 10011111 11100001 00110100 1\n    5555RRRR RRRRTTTT TTTTTTTT UUCCFFFF XXXXXXXX SSSSSSSS 1\n\n- 5 = constant 5\n- R = random power on id\n- T = 12 bits of temperature with 0x990 bias and scaled by 10\n- U = unk, maybe battery indicator (display is missing one though)\n- C = channel\n- F = constant F\n- X = xor checksum\n- S = sum checksum\n- 1 = constant 1\n*/\n\n#include \"decoder.h\"\n\nstatic int wt1024_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b; // bits of a row\n    uint16_t sum;\n    uint16_t sensor_rid;\n    float temp_c;\n    uint8_t channel;\n\n    if (bitbuffer->bits_per_row[1] != 49)\n        return DECODE_ABORT_LENGTH;\n\n    /* select row after preamble */\n    b = bitbuffer->bb[1];\n\n    /* Validate constant */\n    if (b[0] >> 4 != 0x5) {\n        return DECODE_ABORT_EARLY;\n    }\n\n    /* Validate xor checksum */\n    if (xor_bytes(b, 4) != b[4])\n        return DECODE_FAIL_MIC;\n\n    /* Validate sum checksum */\n    sum = add_bytes(b, 4);\n    /* Carry bits are added to the sum .. */\n    sum += sum >> 8;\n    /* .. but no carry bit is added to the sum from the last addition */\n    sum += b[4];\n    sum &= 0xFF;\n    if (sum != b[5])\n        return DECODE_FAIL_MIC;\n\n    /* Get rid */\n    sensor_rid = (b[0] & 0x0F) << 4 | (b[1] & 0x0F);\n\n    /* Get temperature */\n    temp_c = ((((b[1] & 0xF) << 8) | b[2]) - 0x990) * 0.1f;\n\n    /* Get channel */\n    channel = (b[3] >> 4) & 0x3;\n\n    decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"\");\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"WT0124-Pool\",\n            \"id\",               \"Random ID\",    DATA_INT,    sensor_rid,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.1f C\", DATA_DOUBLE, temp_c,\n            \"mic\",              \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    // Return 1 if message successfully decoded\n    return 1;\n}\n\n/*\n * List of fields that may appear in the output\n *\n * Used to determine what fields will be output in what\n * order for this device when using -F csv.\n *\n */\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"temperature_C\",\n        \"mic\",\n        NULL,\n};\n\nr_device const wt1024 = {\n        .name        = \"WT0124 Pool Thermometer\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 680,\n        .long_width  = 1850,\n        .reset_limit = 30000,\n        .gap_limit   = 4000,\n        .sync_width  = 10000,\n        .decode_fn   = &wt1024_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/wt450.c",
    "content": "/** @file\n    WT450 wireless weather sensors protocol.\n\n    Tested devices:\n    WT260H\n    WT405H\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2015 Ladislav Foldyna\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n */\n\n/**\nsource from:\nhttp://ala-paavola.fi/jaakko/doku.php?id=wt450h\n\n- The signal is FM encoded with clock cycle around 2000 µs\n- No level shift within the clock cycle translates to a logic 0\n- One level shift within the clock cycle translates to a logic 1\n- Each clock cycle begins with a level shift\n- My timing constants defined below are those observed by my program\n\n    +---+   +---+   +-------+       +  high\n    |   |   |   |   |       |       |\n    |   |   |   |   |       |       |\n    +   +---+   +---+       +-------+  low\n    ^       ^       ^       ^       ^  clock cycle\n    |   1   |   1   |   0   |   0   |  translates as\n\nEach transmission is 36 bits long (i.e. 72 ms).\n\nData is transmitted in pure binary values, NOT BCD-coded.\n\nOutdoor sensor transmits data temperature, humidity.\nTransmissions also include channel code and house code. The sensor transmits\nevery 60 seconds 3 packets.\n\n    1100 0001 | 0011 0011 | 1000 0011 | 1011 0011 | 0001\n    xxxx ssss | ccxx bhhh | hhhh tttt | tttt tttt | sseo\n\n- x: constant\n- s: House code\n- c: Channel\n- b: battery low indicator (0=>OK, 1=>LOW)\n- h: Humidity\n- t: Temperature, 12 bit, offset 50, scale 16\n- s: sequence number of message repeat\n- e: parity of all even bits\n- o: parity of all odd bits\n*/\n\n#include \"decoder.h\"\n\nstatic int wt450_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b = bitbuffer->bb[0];\n    uint8_t humidity;\n    uint8_t temp_whole;\n    uint8_t temp_fraction;\n    uint8_t house_code;\n    uint8_t channel;\n    uint8_t battery_low;\n    int seq;\n    float temp;\n    uint8_t parity;\n    data_t *data;\n\n    if (bitbuffer->bits_per_row[0] != 36) {\n        decoder_logf(decoder, 1, __func__, \"wrong size of bit per row %d\",\n                    bitbuffer->bits_per_row[0]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    if (b[0] >> 4 != 0xC) {\n        decoder_log_bitbuffer(decoder, 1, __func__, bitbuffer, \"wrong preamble\");\n        return DECODE_ABORT_EARLY;\n    }\n\n    parity = xor_bytes(b, 5);\n    parity ^= (parity >> 4);\n    parity ^= (parity >> 2);\n    parity &= 0x3;\n\n    if (parity) {\n        decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"wrong parity (%x)\", parity);\n        return DECODE_FAIL_MIC;\n    }\n\n    house_code    = b[0] & 0xF;\n    channel       = (b[1] >> 6) + 1;\n    battery_low   = b[1] & 0x8;\n    humidity      = ((b[1] & 0x7) << 4) | (b[2] >> 4);\n    temp_whole    = (b[2] << 4) | (b[3] >> 4);\n    temp_fraction = (b[3] & 0xF);\n    temp          = (temp_whole - 50.0f) + (temp_fraction / 16.0f);\n    seq           = (b[4] >> 6);\n\n    /* clang-format off */\n    data = data_make(\n            \"model\",            \"\",             DATA_STRING, \"WT450-TH\",\n            \"id\",               \"House Code\",   DATA_INT,    house_code,\n            \"channel\",          \"Channel\",      DATA_INT,    channel,\n            \"battery_ok\",       \"Battery\",      DATA_INT,    !battery_low,\n            \"temperature_C\",    \"Temperature\",  DATA_FORMAT, \"%.2f C\", DATA_DOUBLE, temp,\n            \"humidity\",         \"Humidity\",     DATA_FORMAT, \"%u %%\", DATA_INT, humidity,\n            \"seq\",              \"Sequence\",     DATA_INT,    seq,\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"channel\",\n        \"battery_ok\",\n        \"temperature_C\",\n        \"humidity\",\n        \"seq\",\n        NULL,\n};\n\nr_device const wt450 = {\n        .name        = \"WT450, WT260H, WT405H\",\n        .modulation  = OOK_PULSE_DMC,\n        .short_width = 976,  // half-bit width 976 us\n        .long_width  = 1952, // bit width 1952 us\n        .reset_limit = 18000,\n        .tolerance   = 100, // us\n        .decode_fn   = &wt450_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/x10_rf.c",
    "content": "/** @file\n    X10 sensor (Non-security devices).\n\n    Copyright (C) 2015 Tommy Vestermark\n    Mods. by Dave Fleck 2021\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nX10  sensor decoder.\n\nEach packet starts with a sync pulse of 9000 us (16x a bit time)\nand a 4500 us gap.\nThe message is OOK PPM encoded with 562.5 us pulse and long gap (0 bit)\nof 1687.5 us or short gap (1 bit) of 562.5 us.\n\nThere are 32bits. The message is repeated 5 times with\na packet gap of 40000 us.\n\nThe protocol has a lot of similarities to the NEC IR protocol\n\nThe second byte is the inverse of the first.\nThe fourth byte is the inverse of the third.\n\nBased on protocol informtation found at:\nhttp://www.wgldesigns.com/protocols/w800rf32_protocol.txt\n\nTested with American sensors operating at 310 MHz\ne.g., rtl_433 -f 310M -R 22\n\nSeems to work best with 2 MHz sample rate:\nrtl_433 -f 310M -R 22 -s 2M\n\nTested with HR12A, RMS18, HD23A, MS14A, PMS03, MS12A,\nRMS18, Radio Shack 61-2675-T\n\n*/\n\n#include \"decoder.h\"\n\nstatic int x10_rf_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    uint8_t *b = bitbuffer->bb[1];\n\n    uint8_t const arrbKnownConstBitMask[4]  = {0x0B, 0x0B, 0x07, 0x07};\n    uint8_t const arrbKnownConstBitValue[4] = {0x00, 0x0B, 0x00, 0x07};\n\n    // Row [0] is sync pulse\n    // Validate length\n    if (bitbuffer->bits_per_row[1] != 32) { // Don't waste time on a wrong length package\n        if (bitbuffer->bits_per_row[1] != 0)\n            decoder_logf(decoder, 1, __func__, \"DECODE_ABORT_LENGTH, Received message length=%d\", bitbuffer->bits_per_row[1]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    // Validate complement values\n    if ((b[0] ^ b[1]) != 0xff || (b[2] ^ b[3]) != 0xff) {\n        decoder_logf(decoder, 1, __func__, \"DECODE_FAIL_SANITY, b0=%02x b1=%02x b2=%02x b3=%02x\", b[0], b[1], b[2], b[3]);\n        return DECODE_FAIL_SANITY;\n    }\n\n    // Some bits are constant.\n    for (int8_t bIdx = 0; bIdx < 4; bIdx++) {\n        uint8_t bTest = arrbKnownConstBitMask[bIdx] & b[bIdx];  // Mask the appropriate bits\n\n        if (bTest != arrbKnownConstBitValue[bIdx]) {  // If resulting bits are incorrectly set\n            decoder_logf(decoder, 1, __func__, \"DECODE_FAIL_SANITY, b0=%02x b1=%02x b2=%02x b3=%02x\", b[0], b[1], b[2], b[3]);\n            return DECODE_FAIL_SANITY;\n        }\n    }\n\n    // We have received a valid message, decode it\n\n    unsigned code = (unsigned)b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3];\n\n    uint8_t bHouseCode  = 0;\n    uint8_t bDeviceCode = 0;\n    uint8_t arrbHouseBits[4] = {0, 0, 0, 0};\n\n    // Extract House bits\n    arrbHouseBits[0] = (b[0] & 0x80) >> 7;\n    arrbHouseBits[1] = (b[0] & 0x40) >> 6;\n    arrbHouseBits[2] = (b[0] & 0x20) >> 5;\n    arrbHouseBits[3] = (b[0] & 0x10) >> 4;\n\n    // Convert bits into integer\n    bHouseCode   = (~(arrbHouseBits[0] ^ arrbHouseBits[1])  & 0x01) << 3;\n    bHouseCode  |= ( ~arrbHouseBits[1]                      & 0x01) << 2;\n    bHouseCode  |= ( (arrbHouseBits[1] ^ arrbHouseBits[2])  & 0x01) << 1;\n    bHouseCode  |=    arrbHouseBits[3]                      & 0x01;\n\n    // Extract and convert Unit bits to integer\n    bDeviceCode  = (b[0] & 0x04) << 1;\n    bDeviceCode |= (b[2] & 0x40) >> 4;\n    bDeviceCode |= (b[2] & 0x08) >> 2;\n    bDeviceCode |= (b[2] & 0x10) >> 4;\n    bDeviceCode += 1;\n\n    char housecode[2] = {0};\n    *housecode = bHouseCode + 'A';\n\n    int state = (b[2] & 0x20) == 0x00;\n\n    char const *event_str = \"UNKNOWN\";         // human-readable event\n\n    if ((b[2] & 0x80) == 0x80) {         // Special event bit\n        bDeviceCode = 0;                 // No device for special events\n\n        switch (b[2]) {\n        case 0x98:\n            event_str = \"DIM\";\n            break;\n        case 0x88:\n            event_str = \"BRI\";\n            break;\n        case 0x90:\n            event_str = \"ALL LTS ON\";\n            break;\n        case 0x80:\n            event_str = \"ALL OFF\";\n            break;\n        }\n    }\n    else {\n        event_str = state ? \"ON\" : \"OFF\";\n    }\n\n    // debug output\n    decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"id=%s%d event_str=%s\", housecode, bDeviceCode, event_str);\n\n    /* clang-format off */\n    data_t *data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"X10-RF\",\n            \"id\",           \"\",             DATA_INT,    bDeviceCode,\n            \"channel\",      \"\",             DATA_STRING, housecode,\n            \"state\",        \"State\",        DATA_STRING, event_str,\n            \"data\",         \"Data\",         DATA_FORMAT, \"%08x\", DATA_INT, code,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"PARITY\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"channel\",\n        \"id\",\n        \"state\",\n        \"data\",\n        \"mic\",\n        NULL,\n};\n\nr_device const X10_RF = {\n        .name        = \"X10 RF\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 562,  // Short gap 562.5 µs\n        .long_width  = 1687, // Long gap 1687.5 µs\n        .gap_limit   = 2200, // Gap after sync is 4.5ms (1125)\n        .reset_limit = 6000, // Gap seen between messages is ~40ms so let's get them individually\n        .decode_fn   = &x10_rf_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/x10_sec.c",
    "content": "/** @file\n    X10 Security sensor decoder.\n\n    Copyright (C) 2018 Anthony Kava\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n*/\n/**\nX10 Security sensor decoder.\n\nEach packet starts with a sync pulse of 9000 us and 4500 us gap.\nThe message is OOK PPM encoded with 562 us pulse and long gap (0 bit)\nof 1687 us or short gap (1 bit) of 562 us. There are 41 bits, the\nmessage is repeated 5 times with a packet gap of 40000 us.\n\nThe protocol has a lot of similarities to the NEC IR protocol\n\nBits 0-7 are first part of the device ID\nBits 8-11 should be identical to bits 0-3\nBits 12-15 should be the XOR function of bits 4-7\nBits 16-23 are the code/message sent\nBits 24-31 should be the XOR function of bits 16-23\nBits 32-39 are the second part of the device ID\nBit 40 is CRC checksum (even parity)\n\nTested with American sensors operating at 310 MHz\ne.g., rtl_433 -f 310.558M\n\nTested with European/International sensors, DS18, KR18 and MS18 operating at 433 MHz\ne.g., rtl_433\n\nAmerican sensor names ends with an 'A', like DS18A, while European/International\nsensor names ends with an 'E', like MS18E\n\nThe byte value decoding is based on limited observations, and it is likely\nthat there are missing pieces.\n\nDS10 & DS18 door/window sensor bitmask : CTUUUDUB\n     C = Door/window closed flag.\n     T = Tamper alarm. Set to 1 if lid is open. (Not supported on DS10)\n     U = Unknown. Cleared in all samples.\n     D = Delay setting. Min=1. Max=0.\n     B = Battery low flag.\n\nDS18 has both a magnetic (reed) relay and an external input. The two inputs\nare reported using two different ID's as if they were two separate sensors.\n\nMS10 does not support tamper alarm, while MS18 does\n\nBased on code provided by Willi 'wherzig' in issue #30 (2014-04-21)\n\n*/\n\n#include \"decoder.h\"\n\nstatic int x10_sec_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    data_t *data;\n    uint8_t *b;                       /* bits of a row            */\n    char const *event_str = \"UNKNOWN\"; /* human-readable event     */\n    int battery_low       = 0;         /* battery indicator        */\n    int delay             = 0;         /* delay setting            */\n    uint8_t tamper        = 0;         /* tamper alarm indicator   */\n    uint8_t parity        = 0;         /* for CRC calculation      */\n\n    if (bitbuffer->num_rows != 2)\n        return DECODE_ABORT_EARLY;\n\n    /* First row should be sync, second row should be 41 bit message */\n    if (bitbuffer->bits_per_row[1] < 41) {\n        if (bitbuffer->bits_per_row[1] != 0)\n            decoder_logf(decoder, 1, __func__, \"DECODE_ABORT_LENGTH, Received message length=%d\", bitbuffer->bits_per_row[1]);\n        return DECODE_ABORT_LENGTH;\n    }\n\n    b = bitbuffer->bb[1];\n\n    /* validate what we received */\n    if ((b[0] ^ b[1]) != 0x0f || (b[2] ^ b[3]) != 0xff) {\n        decoder_logf(decoder, 1, __func__, \"DECODE_FAIL_SANITY, b0=%02x b1=%02x b2=%02x b3=%02x\", b[0], b[1], b[2], b[3]);\n        return DECODE_FAIL_SANITY;\n    }\n\n    /* Check CRC */\n    parity = b[0] ^ b[1] ^ b[2] ^ b[3] ^ b[4] ^ (b[5] & 0x80); // parity as byte\n    parity = (parity >> 4) ^ (parity & 0xf);                   // fold to nibble\n    parity = (parity >> 2) ^ (parity & 0x3);                   // fold to 2 bits\n    parity = (parity >> 1) ^ (parity & 0x1);                   // fold to 1 bit\n    if (parity) {                                              // even parity - parity should be zero\n        decoder_logf(decoder, 1, __func__, \"DECODE_FAIL_MIC CRC Fail, b0=%02x b1=%02x b2=%02x b3=%02x b4=%02x b5-CRC-bit=%02x\", b[0], b[1], b[2], b[3], b[4], (b[5] & 0x80));\n        return DECODE_FAIL_MIC;\n    }\n\n    /* We have received a valid message, decode it */\n\n    battery_low = b[2] & 0x01;\n\n    /* set event_str based on code received */\n    switch (b[2] & 0xfe) {\n    case 0x00: // OPEN\n    case 0x04: // OPEN & Delay\n    case 0x40: // OPEN & Tamper Alarm\n    case 0x44: // OPEN & Tamper Alarm & Delay\n        event_str = \"DOOR/WINDOW OPEN\";\n        delay     = !(b[2] & 0x04);\n        tamper    = (b[2] & 0x40) >> 6;\n        break;\n    case 0x80: // CLOSED\n    case 0x84: // CLOSED & Delay\n    case 0xc0: // CLOSED & Tamper Alarm\n    case 0xc4: // CLOSED & Tamper Alarm & Delay\n        event_str = \"DOOR/WINDOW CLOSED\";\n        delay     = !(b[2] & 0x04);\n        tamper    = (b[2] & 0x40) >> 6;\n        break;\n    case 0x06:\n        event_str = \"KEY-FOB ARM\";\n        break;\n    case 0x0c: // MOTION TRIPPED\n    case 0x4c: // MOTION TRIPPED & Tamper Alarm\n        event_str = \"MOTION TRIPPED\";\n        tamper    = (b[2] & 0x40) >> 6;\n        break;\n    case 0x26:\n        event_str = \"KR18 PANIC\";\n        break;\n    case 0x42:\n        event_str = \"KEY-FOB LIGHTS A ON\"; // KR18\n        break;\n    case 0x46:\n        event_str = \"KEY-FOB LIGHTS B ON\"; // KR15 and KR18\n        break;\n    case 0x82:\n        event_str = \"SH624 SEC-REMOTE DISARM\";\n        break;\n    case 0x86:\n        event_str = \"KEY-FOB DISARM\";\n        break;\n    case 0x88:\n        event_str = \"KR15 PANIC\";\n        break;\n    case 0x8c: // MOTION READY\n    case 0xcc: // MOTION READY & Tamper Alarm\n        event_str = \"MOTION READY\";\n        tamper    = (b[2] & 0x40) >> 6;\n        break;\n    case 0x98:\n        event_str = \"KR15 PANIC-3SECOND\";\n        break;\n    case 0xc2:\n        event_str = \"KEY-FOB LIGHTS A OFF\"; // KR18\n        break;\n    case 0xc6:\n        event_str = \"KEY-FOB LIGHTS B OFF\"; // KR15 and KR18\n        break;\n    }\n\n    /* get x10_id_str, x10_code_str ready for output */\n    char x10_id_str[12];\n    snprintf(x10_id_str, sizeof(x10_id_str), \"%02x%02x\", b[0], b[4]);\n    char x10_code_str[5];\n    snprintf(x10_code_str, sizeof(x10_code_str), \"%02x\", b[2]);\n\n    /* debug output */\n    decoder_logf_bitbuffer(decoder, 1, __func__, bitbuffer, \"id=%02x%02x code=%02x event_str=%s\", b[0], b[4], b[2], event_str);\n\n    /* build and handle data set for normal output */\n    /* clang-format off */\n    data = data_make(\n            \"model\",        \"\",             DATA_STRING, \"X10-Security\",\n            \"id\",           \"Device ID\",    DATA_STRING, x10_id_str,\n            \"code\",         \"Code\",         DATA_STRING, x10_code_str,\n            \"event\",        \"Event\",        DATA_STRING, event_str,\n            \"delay\",        \"Delay\",        DATA_COND,   delay,         DATA_INT, delay,\n            \"battery_ok\",   \"Battery\",      DATA_COND,   battery_low,   DATA_INT, !battery_low,\n            \"tamper\",       \"Tamper\",       DATA_COND,   tamper,        DATA_INT, tamper,\n            \"mic\",          \"Integrity\",    DATA_STRING, \"CRC\",\n            NULL);\n    /* clang-format on */\n\n    decoder_output_data(decoder, data);\n    return 1;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"code\",\n        \"event\",\n        \"delay\",\n        \"battery_ok\",\n        \"tamper\",\n        \"mic\",\n        NULL,\n};\n\n/* r_device definition */\nr_device const x10_sec = {\n        .name        = \"X10 Security\",\n        .modulation  = OOK_PULSE_PPM,\n        .short_width = 562,  // Short gap 562us\n        .long_width  = 1687, // Long gap 1687us\n        .gap_limit   = 2200, // Gap after sync is 4.5ms (1125)\n        .reset_limit = 6000,\n        .decode_fn   = &x10_sec_callback,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/devices/yale_hsa.c",
    "content": "/** @file\n    Yale HSA (Home Security Alarm) protocol.\n\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"decoder.h\"\n\n/**\nYale HSA (Home Security Alarm) protocol.\n\nYale HSA Alarms, YES-Alarmkit:\n- Yale HSA6010 Door/Window Contact\n- Yale HSA6080 Keypad\n- Yale HSA6020 Motion PIR\n- Yale HSA6060 Remote Keyfob\n\nA message is made up of 6 packets and then repeats.\nPackets are 13 bits, start with 0x5 and a end-of-message flag, then 8 bit data.\n\nActually data should be in the gaps, which are tighter timings of 368 / 978 us.\n\nThe 6 packets combined decode as\n\nData Layout:\n\n    ID:16h TYPE:8h STATE:8b EVENT:8h CHK:8h\n\nOr perhaps?\n\n    ID:16h TYPE:12h STATE:8b EVENT:4h CHK:8h\n\nThe checksum is just remainder of adding the 5 messages bytes, i.e. adding 6 bytes checks to zero.\n\nGuessed data so far:\n- Sensor types: ac1, ad1: window sensor, 153: PIR\n- Events 1: trigger, 3: binding, 4: tamper\n- State: Could be battery?\n\nData table:\n- W/D: Contact opened:              stype: ac state: 1 0 event: 01\n- W/D: Tamper button closed/off:    stype: ac state: 1 0 event: 04\n- W/D: Tamper button released/on:   stype: ac state: 1 2 event: 04\n- W/D: Binding button pressed:      stype: ac state: 1 2 event: 03\n- W/D: Low battery:                 stype: ac state: 1 8 event: 04\n- PIR:  Binding Button:             stype: 15 state: 3 0 event: 03\n- PIR:  Tamper button closed/off:   stype: 15 state: 3 0 event: 04\n- PIR:  Tamper button released/on:  stype: 15 state: 3 2 event: 04\n- PIR:  Movement trigger:           stype: 15 state: 3 0 event: 01\n- PIR:  Low battery:                stype: 15 state: 3 2 event: 01\n\nGet Raw data with:\n\n    rtl_433 -R 0 -X 'n=name,m=OOK_PWM,s=850,l=1460,y=5380,r=1500' ~/Desktop/Yale-6010.ook\n\n*/\n\nstatic int yale_hsa_decode(r_device *decoder, bitbuffer_t *bitbuffer)\n{\n    // Require at least 6 rows\n    if (bitbuffer->num_rows < 6)\n        return DECODE_ABORT_EARLY;\n\n    uint8_t msg[6] = {0};\n    for (int row = 0; row < bitbuffer->num_rows; ++row) {\n        // Find one full message\n        int ok = 0;\n        for (int i = 0; i < 6; ++i, ++row) {\n            if (bitbuffer->bits_per_row[row] != 13)\n                break; // wrong length\n            uint8_t *b = bitbuffer->bb[row];\n            if ((b[0] & 0xf0) != 0x50)\n                break; // wrong sync\n            int eom = (b[0] & 0x08);\n            if ((i < 5 && eom) || (i == 5 && !eom))\n                break; // wrong end-of-message\n            bitbuffer_extract_bytes(bitbuffer, row, 5, &msg[i], 8);\n            if (i == 5)\n                ok = 1;\n        }\n        // Skip to end-of-message on error\n        if (!ok) {\n            for (; row < bitbuffer->num_rows; ++row) {\n                uint8_t *b = bitbuffer->bb[row];\n                int eom    = (b[0] & 0x08);\n                if (eom)\n                    break; // end-of-message\n            }\n            continue;\n        }\n        // Message found\n        int chk = add_bytes(msg, 6);\n        if (chk & 0xff)\n            continue; // bad checksum\n\n        // Get the data\n        int id    = (msg[0] << 8) | (msg[1]);\n        int stype = (msg[2]);\n        int state = (msg[3]);\n        int event = (msg[4]);\n\n        /* clang-format off */\n        data_t *data = data_make(\n                \"model\",        \"\",             DATA_STRING, \"Yale-HSA\",\n                \"id\",           \"\",             DATA_FORMAT, \"%04x\", DATA_INT, id,\n                \"stype\",        \"Sensor type\",  DATA_FORMAT, \"%02x\", DATA_INT, stype,\n                \"state\",        \"State\",        DATA_FORMAT, \"%02x\", DATA_INT, state,\n                \"event\",        \"Event\",        DATA_FORMAT, \"%02x\", DATA_INT, event,\n                \"mic\",          \"Integrity\",    DATA_STRING, \"CHECKSUM\",\n                NULL);\n        /* clang-format on */\n\n        decoder_output_data(decoder, data);\n        return 1;\n    }\n    return 0;\n}\n\nstatic char const *const output_fields[] = {\n        \"model\",\n        \"id\",\n        \"stype\",\n        \"state\",\n        \"event\",\n        \"mic\",\n        NULL,\n};\n\nr_device const yale_hsa = {\n        .name        = \"Yale HSA (Home Security Alarm), YES-Alarmkit\",\n        .modulation  = OOK_PULSE_PWM,\n        .short_width = 850,\n        .long_width  = 1460,\n        .sync_width  = 5380,\n        .reset_limit = 1500,\n        .decode_fn   = &yale_hsa_decode,\n        .fields      = output_fields,\n};\n"
  },
  {
    "path": "src/fileformat.c",
    "content": "/** @file\n    Various utility functions handling file formats.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <string.h>\n#include <stdlib.h>\n#ifdef _MSC_VER\n#ifndef strncasecmp // Microsoft Visual Studio\n#define strncasecmp  _strnicmp\n#endif\n#else\n#include <strings.h>\n#endif\n//#include \"optparse.h\"\n#include \"fileformat.h\"\n\n#ifdef _WIN32\n#define PATH_SEPARATOR '\\\\'\n#else\n#define PATH_SEPARATOR '/'\n#endif\n\nchar const *file_basename(char const *path)\n{\n    char const *p = strrchr(path, PATH_SEPARATOR);\n    if (p)\n        return p + 1;\n    else\n        return path;\n}\n\nvoid file_info_clear(file_info_t *info)\n{\n    if (info) {\n        *info = (file_info_t const){0};\n    }\n}\n\nvoid file_info_check_read(file_info_t *info)\n{\n    if (info->format != CU8_IQ\n            && info->format != CS8_IQ\n            && info->format != CS16_IQ\n            && info->format != CF32_IQ\n            && info->format != S16_AM\n            && info->format != PULSE_OOK) {\n        fprintf(stderr, \"File type not supported as input (%s).\\n\", info->spec);\n        exit(1);\n    }\n}\n\nvoid file_info_check_write(file_info_t *info)\n{\n    if (info->format != CU8_IQ\n            && info->format != CS8_IQ\n            && info->format != S16_AM\n            && info->format != S16_FM\n            && info->format != CS16_IQ\n            && info->format != CF32_IQ\n            && info->format != F32_AM\n            && info->format != F32_FM\n            && info->format != F32_I\n            && info->format != F32_Q\n            && info->format != U8_LOGIC\n            && info->format != VCD_LOGIC) {\n        fprintf(stderr, \"File type not supported as output (%s).\\n\", info->spec);\n        exit(1);\n    }\n}\n\nchar const *file_info_string(file_info_t *info)\n{\n    switch (info->format) {\n    case CU8_IQ:    return \"CU8 IQ (2ch uint8)\";\n    case S16_AM:    return \"S16 AM (1ch int16)\";\n    case S16_FM:    return \"S16 FM (1ch int16)\";\n    case CF32_IQ:   return \"CF32 IQ (2ch float32)\";\n    case CS16_IQ:   return \"CS16 IQ (2ch int16)\";\n    case F32_AM:    return \"F32 AM (1ch float32)\";\n    case F32_FM:    return \"F32 FM (1ch float32)\";\n    case F32_I:     return \"F32 I (1ch float32)\";\n    case F32_Q:     return \"F32 Q (1ch float32)\";\n    case VCD_LOGIC: return \"VCD logic (text)\";\n    case U8_LOGIC:  return \"U8 logic (1ch uint8)\";\n    case PULSE_OOK: return \"OOK pulse data (text)\";\n    default:        return \"Unknown\";\n    }\n}\n\nstatic void file_type_set_format(uint32_t *type, uint32_t val)\n{\n    *type = (*type & 0xffff0000) | val;\n}\n\nstatic void file_type_set_content(uint32_t *type, uint32_t val)\n{\n    *type = (*type & 0x0000ffff) | val;\n}\n\nstatic uint32_t file_type_guess_auto_format(uint32_t type)\n{\n    if (type == 0) return CU8_IQ;\n    else if (type == F_IQ) return CU8_IQ;\n    else if (type == F_AM) return S16_AM;\n    else if (type == F_FM) return S16_FM;\n    else if (type == F_I) return F32_I;\n    else if (type == F_Q) return F32_Q;\n    else if (type == F_LOGIC) return U8_LOGIC;\n\n    else if (type == F_CU8) return CU8_IQ;\n    else if (type == F_CS8) return CS8_IQ;\n    else if (type == F_S16) return S16_AM;\n    else if (type == F_U8) return U8_LOGIC;\n    else if (type == F_VCD) return VCD_LOGIC;\n    else if (type == F_OOK) return PULSE_OOK;\n    else if (type == F_CS16) return CS16_IQ;\n    else if (type == F_CF32) return CF32_IQ;\n    else return type;\n}\n\nstatic void file_type(char const *filename, file_info_t *info)\n{\n    if (!filename || !*filename) {\n        return;\n    }\n\n    char const *p = filename;\n    while (*p) {\n        if (*p >= '0' && *p <= '9') {\n            char const *n = p; // number starts here\n            while (*p >= '0' && *p <= '9')\n                ++p;\n            if (*p == '.') {\n                ++p;\n                // if not [0-9] after '.' abort\n                if (*p < '0' || *p > '9')\n                    continue;\n                while (*p >= '0' && *p <= '9')\n                    ++p;\n            }\n            char const *s = p; // number ends and unit starts here\n            while ((*p >= 'A' && *p <= 'Z')\n                    || (*p >= 'a' && *p <= 'z'))\n                ++p;\n            double num = atof(n); // atouint32_metric() ?\n            size_t len = p - s;\n            double scale = 1.0;\n            switch (*s) {\n            case 'k':\n            case 'K':\n                scale *= 1e3;\n                break;\n            case 'M':\n            case 'm':\n                scale *= 1e6;\n                break;\n            case 'G':\n            case 'g':\n                scale *= 1e9;\n                break;\n            }\n            if (len == 1 && !strncasecmp(\"M\", s, 1)) info->center_frequency = num * 1e6;\n            else if (len == 1 && !strncasecmp(\"k\", s, 1)) info->sample_rate = num * 1e3;\n            else if (len == 2 && !strncasecmp(\"Hz\", s, 2)) info->center_frequency = num;\n            else if (len == 3 && !strncasecmp(\"sps\", s, 3)) info->sample_rate = num;\n            else if (len == 3 && !strncasecmp(\"Hz\", s+1, 2) && scale > 1.0) info->center_frequency = num * scale;\n            else if (len == 4 && !strncasecmp(\"sps\", s+1, 3) && scale > 1.0) info->sample_rate = num * scale;\n            //fprintf(stderr, \"Got number %g, f is %u, s is %u\\n\", num, info->center_frequency, info->sample_rate);\n        } else if ((*p >= 'A' && *p <= 'Z')\n                || (*p >= 'a' && *p <= 'z')) {\n            char const *t = p; // type starts here\n            while ((*p >= '0' && *p <= '9')\n                    || (*p >= 'A' && *p <= 'Z')\n                    || (*p >= 'a' && *p <= 'z'))\n                ++p;\n            size_t len = p - t;\n            if (len == 1 && !strncasecmp(\"i\", t, 1)) file_type_set_content(&info->format, F_I);\n            else if (len == 1 && !strncasecmp(\"q\", t, 1)) file_type_set_content(&info->format, F_Q);\n            else if (len == 2 && !strncasecmp(\"iq\", t, 2)) file_type_set_content(&info->format, F_IQ);\n            else if (len == 2 && !strncasecmp(\"am\", t, 2)) file_type_set_content(&info->format, F_AM);\n            else if (len == 2 && !strncasecmp(\"fm\", t, 2)) file_type_set_content(&info->format, F_FM);\n            else if (len == 2 && !strncasecmp(\"u8\", t, 2)) file_type_set_format(&info->format, F_U8);\n            else if (len == 2 && !strncasecmp(\"s8\", t, 2)) file_type_set_format(&info->format, F_S8);\n            else if (len == 3 && !strncasecmp(\"cu8\", t, 3)) file_type_set_format(&info->format, F_CU8);\n            else if (len == 4 && !strncasecmp(\"data\", t, 4)) file_type_set_format(&info->format, F_CU8); // compat\n            else if (len == 3 && !strncasecmp(\"cs8\", t, 3)) file_type_set_format(&info->format, F_CS8);\n            else if (len == 3 && !strncasecmp(\"u16\", t, 3)) file_type_set_format(&info->format, F_U16);\n            else if (len == 3 && !strncasecmp(\"s16\", t, 3)) file_type_set_format(&info->format, F_S16);\n            else if (len == 3 && !strncasecmp(\"u32\", t, 3)) file_type_set_format(&info->format, F_U32);\n            else if (len == 3 && !strncasecmp(\"s32\", t, 3)) file_type_set_format(&info->format, F_S32);\n            else if (len == 3 && !strncasecmp(\"f32\", t, 3)) file_type_set_format(&info->format, F_F32);\n            else if (len == 3 && !strncasecmp(\"vcd\", t, 3)) file_type_set_content(&info->format, F_VCD);\n            else if (len == 3 && !strncasecmp(\"ook\", t, 3)) file_type_set_content(&info->format, F_OOK);\n            else if (len == 4 && !strncasecmp(\"cs16\", t, 4)) file_type_set_format(&info->format, F_CS16);\n            else if (len == 4 && !strncasecmp(\"cs32\", t, 4)) file_type_set_format(&info->format, F_CS32);\n            else if (len == 4 && !strncasecmp(\"cf32\", t, 4)) file_type_set_format(&info->format, F_CF32);\n            else if (len == 5 && !strncasecmp(\"cfile\", t, 5)) file_type_set_format(&info->format, F_CF32); // compat\n            else if (len == 5 && !strncasecmp(\"logic\", t, 5)) file_type_set_content(&info->format, F_LOGIC);\n            else if (len == 3 && !strncasecmp(\"complex16u\", t, 10)) file_type_set_format(&info->format, F_CU8); // compat\n            else if (len == 3 && !strncasecmp(\"complex16s\", t, 10)) file_type_set_format(&info->format, F_CS8); // compat\n            else if (len == 4 && !strncasecmp(\"complex\", t, 7)) file_type_set_format(&info->format, F_CF32); // compat\n            //else fprintf(stderr, \"Skipping type (len %ld) %s\\n\", len, t);\n        } else {\n            p++; // skip non-alphanum char otherwise\n        }\n    }\n}\n\n// return the last colon not followed by a backslash, otherwise NULL\nstatic char const *last_plain_colon(char const *p)\n{\n    char const *found = NULL;\n    char const *next = strchr(p, ':');\n    while (next && next[1] != '\\\\') {\n        found = next;\n        next = strchr(next+1, ':');\n    }\n    return found;\n}\n\n/**\nThis will detect file info and overrides.\n\nParse \"[0-9]+(\\.[0-9]+)?[A-Za-z]\"\n as frequency (suffix \"M\" or \"[kMG]?Hz\")\n or sample rate (suffix \"k\" or \"[kMG]?sps\")\n\nParse \"[A-Za-z][0-9A-Za-z]+\" as format or content specifier:\n\n2ch formats: \"cu8\", \"cs8\", \"cs16\", \"cs32\", \"cf32\"\n1ch formats: \"u8\", \"s8\", \"s16\", \"u16\", \"s32\", \"u32\", \"f32\"\ntext formats: \"vcd\", \"ook\"\ncontent types: \"iq\", \"i\", \"q\", \"am\", \"fm\", \"logic\"\n\nParses left to right, with the exception of a prefix up to the last colon \":\"\nThis prefix is the forced override, parsed last and removed from the filename.\n\nAll matches are case-insensitive.\n\ndefault detection, e.g.: path/filename.am.s16\noverrides, e.g.: am:s16:path/filename.ext\nother styles are detected but discouraged, e.g.:\n  am-s16:path/filename.ext, am.s16:path/filename.ext, path/filename.am_s16\n*/\nint file_info_parse_filename(file_info_t *info, char const *filename)\n{\n    if (!filename) {\n        return 0;\n    }\n\n    info->spec = filename;\n\n    char const *p = last_plain_colon(filename);\n    if (p && p - filename < 64) {\n        size_t len = p - filename;\n        char forced[64];\n        memcpy(forced, filename, len);\n        forced[len] = '\\0';\n        p++;\n        file_type(p, info);\n        file_type(forced, info);\n        info->path = p;\n    } else {\n        file_type(filename, info);\n        info->path = filename;\n    }\n    info->raw_format = info->format;\n    info->format = file_type_guess_auto_format(info->format);\n    return info->format;\n}\n\n// Unit testing\n#ifdef _TEST\nstatic void assert_file_type(int check, char const *spec)\n{\n    file_info_t info = {0};\n    int ret = file_info_parse_filename(&info, spec);\n    if (check != ret) {\n        fprintf(stderr, \"\\nTEST failed: determine_file_type(\\\"%s\\\", &foo) = %8x == %8x\\n\", spec, ret, check);\n    } else {\n        fprintf(stderr, \".\");\n    }\n}\n\nstatic void assert_str_equal(char const *a, char const *b)\n{\n    if (a != b && (!a || !b || strcmp(a, b))) {\n        fprintf(stderr, \"\\nTEST failed: \\\"%s\\\" == \\\"%s\\\"\\n\", a, b);\n    } else {\n        fprintf(stderr, \".\");\n    }\n}\n\nint main(void)\n{\n    fprintf(stderr, \"Testing:\\n\");\n\n    assert_str_equal(last_plain_colon(\"foo:bar:baz\"), \":baz\");\n    assert_str_equal(last_plain_colon(\"foo\"), NULL);\n    assert_str_equal(last_plain_colon(\":foo\"), \":foo\");\n    assert_str_equal(last_plain_colon(\"foo:\"), \":\");\n    assert_str_equal(last_plain_colon(\"foo:bar:C:\\\\path.txt\"), \":C:\\\\path.txt\");\n    assert_str_equal(last_plain_colon(\"foo:bar:C:\\\\path.txt:baz\"), \":C:\\\\path.txt:baz\");\n\n    assert_file_type(CU8_IQ, \"cu8:\");\n    assert_file_type(CS16_IQ, \"cs16:\");\n    assert_file_type(CF32_IQ, \"cf32:\");\n    assert_file_type(S16_AM, \"am:\");\n    assert_file_type(S16_AM, \"am.s16:\");\n    assert_file_type(S16_AM, \"am-s16:\");\n    assert_file_type(S16_AM, \"am_s16:\");\n    assert_file_type(S16_AM, \"s16.am:\");\n    assert_file_type(S16_AM, \"s16-am:\");\n    assert_file_type(S16_AM, \"s16_am:\");\n    assert_file_type(S16_AM, \"am-s16.am:\");\n    assert_file_type(S16_FM, \"fm:\");\n    assert_file_type(S16_FM, \"fm.s16:\");\n    assert_file_type(S16_FM, \"fm-s16:\");\n    assert_file_type(S16_FM, \"fm_s16:\");\n    assert_file_type(S16_FM, \"s16.fm:\");\n    assert_file_type(S16_FM, \"s16-fm:\");\n    assert_file_type(S16_FM, \"s16_fm:\");\n    assert_file_type(S16_FM, \"fm+s16:\");\n    assert_file_type(S16_FM, \"s16,fm:\");\n\n    assert_file_type(CU8_IQ, \".cu8\");\n    assert_file_type(CS16_IQ, \".cs16\");\n    assert_file_type(CF32_IQ, \".cf32\");\n    assert_file_type(S16_AM, \".am\");\n    assert_file_type(S16_AM, \".am.s16\");\n    assert_file_type(S16_AM, \".am-s16\");\n    assert_file_type(S16_AM, \".am_s16\");\n    assert_file_type(S16_AM, \".s16+am\");\n    assert_file_type(S16_AM, \".s16.am\");\n    assert_file_type(S16_AM, \".s16-am\");\n    assert_file_type(S16_AM, \".am-s16.am\");\n    assert_file_type(S16_FM, \".fm\");\n    assert_file_type(S16_FM, \".fm.s16\");\n    assert_file_type(S16_FM, \".fm-s16\");\n    assert_file_type(S16_FM, \".fm_s16\");\n    assert_file_type(S16_FM, \".fm+s16\");\n    assert_file_type(S16_FM, \".s16.fm\");\n    assert_file_type(S16_FM, \".s16-fm\");\n    assert_file_type(S16_FM, \".s16_fm\");\n    assert_file_type(S16_FM, \".s16,fm\");\n\n    fprintf(stderr, \"\\nDone!\\n\");\n}\n#endif /* _TEST */\n"
  },
  {
    "path": "src/getopt/getopt.c",
    "content": "/* Getopt for GNU.\n   NOTE: getopt is now part of the C library, so if you don't know what\n   \"Keep this file name-space clean\" means, talk to drepper@gnu.org\n   before changing it!\n   Copyright (C) 1987,88,89,90,91,92,93,94,95,96,98,99,2000,2001\n   Free Software Foundation, Inc.\n   This file is part of the GNU C Library.\n\n   The GNU C Library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Lesser General Public\n   License as published by the Free Software Foundation; either\n   version 2.1 of the License, or (at your option) any later version.\n\n   The GNU C Library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Lesser General Public License for more details.\n\n   You should have received a copy of the GNU Lesser General Public\n   License along with the GNU C Library; if not, write to the Free\n   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA\n   02111-1307 USA.  */\n/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.\n   Ditto for AIX 3.2 and <stdlib.h>.  */\n#ifndef _NO_PROTO\n# define _NO_PROTO\n#endif\n\n#ifdef HAVE_CONFIG_H\n# include <config.h>\n#endif\n\n#if !defined __STDC__ || !__STDC__\n/* This is a separate conditional since some stdc systems\n   reject `defined (const)'.  */\n# ifndef const\n#  define const\n# endif\n#endif\n\n#include <stdio.h>\n\n/* Comment out all this code if we are using the GNU C Library, and are not\n   actually compiling the library itself.  This code is part of the GNU C\n   Library, but also included in many other GNU distributions.  Compiling\n   and linking in this code is a waste when using the GNU C library\n   (especially if it is a shared library).  Rather than having every GNU\n   program understand `configure --with-gnu-libc' and omit the object files,\n   it is simpler to just do this in the source for each such file.  */\n\n#define GETOPT_INTERFACE_VERSION 2\n#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2\n# include <gnu-versions.h>\n# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION\n#  define ELIDE_CODE\n# endif\n#endif\n\n#ifndef ELIDE_CODE\n\n\n/* This needs to come after some library #include\n   to get __GNU_LIBRARY__ defined.  */\n#ifdef\t__GNU_LIBRARY__\n/* Don't include stdlib.h for non-GNU C libraries because some of them\n   contain conflicting prototypes for getopt.  */\n# include <stdlib.h>\n# include <unistd.h>\n#endif\t/* GNU C library.  */\n\n#ifdef VMS\n# include <unixlib.h>\n# if HAVE_STRING_H - 0\n#  include <string.h>\n# endif\n#endif\n\n#ifndef _\n/* This is for other GNU distributions with internationalized messages.  */\n# if (HAVE_LIBINTL_H && ENABLE_NLS) || defined _LIBC\n#  include <libintl.h>\n#  ifndef _\n#   define _(msgid)\tgettext (msgid)\n#  endif\n# else\n#  define _(msgid)\t(msgid)\n# endif\n#endif\n\n/* This version of `getopt' appears to the caller like standard Unix `getopt'\n   but it behaves differently for the user, since it allows the user\n   to intersperse the options with the other arguments.\n\n   As `getopt' works, it permutes the elements of ARGV so that,\n   when it is done, all the options precede everything else.  Thus\n   all application programs are extended to handle flexible argument order.\n\n   Setting the environment variable POSIXLY_CORRECT disables permutation.\n   Then the behavior is completely standard.\n\n   GNU application programs can use a third alternative mode in which\n   they can distinguish the relative order of options and other arguments.  */\n\n#include \"getopt.h\"\n\n/* For communication from `getopt' to the caller.\n   When `getopt' finds an option that takes an argument,\n   the argument value is returned here.\n   Also, when `ordering' is RETURN_IN_ORDER,\n   each non-option ARGV-element is returned here.  */\n\nchar *optarg;\n\n/* Index in ARGV of the next element to be scanned.\n   This is used for communication to and from the caller\n   and for communication between successive calls to `getopt'.\n\n   On entry to `getopt', zero means this is the first call; initialize.\n\n   When `getopt' returns -1, this is the index of the first of the\n   non-option elements that the caller should itself scan.\n\n   Otherwise, `optind' communicates from one call to the next\n   how much of ARGV has been scanned so far.  */\n\n/* 1003.2 says this must be 1 before any call.  */\nint optind = 1;\n\n/* Formerly, initialization of getopt depended on optind==0, which\n   causes problems with re-calling getopt as programs generally don't\n   know that. */\n\nint __getopt_initialized;\n\n/* The next char to be scanned in the option-element\n   in which the last option character we returned was found.\n   This allows us to pick up the scan where we left off.\n\n   If this is zero, or a null string, it means resume the scan\n   by advancing to the next ARGV-element.  */\n\nstatic char *nextchar;\n\n/* Callers store zero here to inhibit the error message\n   for unrecognized options.  */\n\nint opterr = 1;\n\n/* Set to an option character which was unrecognized.\n   This must be initialized on some systems to avoid linking in the\n   system's own getopt implementation.  */\n\nint optopt = '?';\n\n/* Describe how to deal with options that follow non-option ARGV-elements.\n\n   If the caller did not specify anything,\n   the default is REQUIRE_ORDER if the environment variable\n   POSIXLY_CORRECT is defined, PERMUTE otherwise.\n\n   REQUIRE_ORDER means don't recognize them as options;\n   stop option processing when the first non-option is seen.\n   This is what Unix does.\n   This mode of operation is selected by either setting the environment\n   variable POSIXLY_CORRECT, or using `+' as the first character\n   of the list of option characters.\n\n   PERMUTE is the default.  We permute the contents of ARGV as we scan,\n   so that eventually all the non-options are at the end.  This allows options\n   to be given in any order, even with programs that were not written to\n   expect this.\n\n   RETURN_IN_ORDER is an option available to programs that were written\n   to expect options and other ARGV-elements in any order and that care about\n   the ordering of the two.  We describe each non-option ARGV-element\n   as if it were the argument of an option with character code 1.\n   Using `-' as the first character of the list of option characters\n   selects this mode of operation.\n\n   The special argument `--' forces an end of option-scanning regardless\n   of the value of `ordering'.  In the case of RETURN_IN_ORDER, only\n   `--' can cause `getopt' to return -1 with `optind' != ARGC.  */\n\nstatic enum\n{\n  REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER\n} ordering;\n\n/* Value of POSIXLY_CORRECT environment variable.  */\nstatic char *posixly_correct;\n\n#ifdef\t__GNU_LIBRARY__\n/* We want to avoid inclusion of string.h with non-GNU libraries\n   because there are many ways it can cause trouble.\n   On some systems, it contains special magic macros that don't work\n   in GCC.  */\n# include <string.h>\n# define my_index\tstrchr\n#else\n\n# if 1 //HAVE_STRING_H\n#  include <string.h>\n# else\n#  include <strings.h>\n# endif\n\n/* Avoid depending on library functions or files\n   whose names are inconsistent.  */\n\n#ifndef getenv\n#ifdef _MSC_VER\n// DDK will complain if you don't use the stdlib defined getenv\n#include <stdlib.h>\n#else\nextern char *getenv ();\n#endif\n#endif\n\nstatic char *\nmy_index (str, chr)\n     const char *str;\n     int chr;\n{\n  while (*str)\n    {\n      if (*str == chr)\n\treturn (char *) str;\n      str++;\n    }\n  return 0;\n}\n\n/* If using GCC, we can safely declare strlen this way.\n   If not using GCC, it is ok not to declare it.  */\n#ifdef __GNUC__\n/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.\n   That was relevant to code that was here before.  */\n# if (!defined __STDC__ || !__STDC__) && !defined strlen\n/* gcc with -traditional declares the built-in strlen to return int,\n   and has done so at least since version 2.4.5. -- rms.  */\nextern int strlen (const char *);\n# endif /* not __STDC__ */\n#endif /* __GNUC__ */\n\n#endif /* not __GNU_LIBRARY__ */\n\n/* Handle permutation of arguments.  */\n\n/* Describe the part of ARGV that contains non-options that have\n   been skipped.  `first_nonopt' is the index in ARGV of the first of them;\n   `last_nonopt' is the index after the last of them.  */\n\nstatic int first_nonopt;\nstatic int last_nonopt;\n\n#ifdef _LIBC\n/* Stored original parameters.\n   XXX This is no good solution.  We should rather copy the args so\n   that we can compare them later.  But we must not use malloc(3).  */\nextern int __libc_argc;\nextern char **__libc_argv;\n\n/* Bash 2.0 gives us an environment variable containing flags\n   indicating ARGV elements that should not be considered arguments.  */\n\n# ifdef USE_NONOPTION_FLAGS\n/* Defined in getopt_init.c  */\nextern char *__getopt_nonoption_flags;\n\nstatic int nonoption_flags_max_len;\nstatic int nonoption_flags_len;\n# endif\n\n# ifdef USE_NONOPTION_FLAGS\n#  define SWAP_FLAGS(ch1, ch2) \\\n  if (nonoption_flags_len > 0)\t\t\t\t\t\t      \\\n    {\t\t\t\t\t\t\t\t\t      \\\n      char __tmp = __getopt_nonoption_flags[ch1];\t\t\t      \\\n      __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2];\t      \\\n      __getopt_nonoption_flags[ch2] = __tmp;\t\t\t\t      \\\n    }\n# else\n#  define SWAP_FLAGS(ch1, ch2)\n# endif\n#else\t/* !_LIBC */\n# define SWAP_FLAGS(ch1, ch2)\n#endif\t/* _LIBC */\n\n/* Exchange two adjacent subsequences of ARGV.\n   One subsequence is elements [first_nonopt,last_nonopt)\n   which contains all the non-options that have been skipped so far.\n   The other is elements [last_nonopt,optind), which contains all\n   the options processed since those non-options were skipped.\n\n   `first_nonopt' and `last_nonopt' are relocated so that they describe\n   the new indices of the non-options in ARGV after they are moved.  */\n\n#if defined __STDC__ && __STDC__\nstatic void exchange (char **);\n#endif\n\nstatic void\nexchange (argv)\n     char **argv;\n{\n  int bottom = first_nonopt;\n  int middle = last_nonopt;\n  int top = optind;\n  char *tem;\n\n  /* Exchange the shorter segment with the far end of the longer segment.\n     That puts the shorter segment into the right place.\n     It leaves the longer segment in the right place overall,\n     but it consists of two parts that need to be swapped next.  */\n\n#if defined _LIBC && defined USE_NONOPTION_FLAGS\n  /* First make sure the handling of the `__getopt_nonoption_flags'\n     string can work normally.  Our top argument must be in the range\n     of the string.  */\n  if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len)\n    {\n      /* We must extend the array.  The user plays games with us and\n\t presents new arguments.  */\n      char *new_str = malloc (top + 1);\n      if (new_str == NULL)\n\tnonoption_flags_len = nonoption_flags_max_len = 0;\n      else\n\t{\n\t  memset (__mempcpy (new_str, __getopt_nonoption_flags,\n\t\t\t     nonoption_flags_max_len),\n\t\t  '\\0', top + 1 - nonoption_flags_max_len);\n\t  nonoption_flags_max_len = top + 1;\n\t  __getopt_nonoption_flags = new_str;\n\t}\n    }\n#endif\n\n  while (top > middle && middle > bottom)\n    {\n      if (top - middle > middle - bottom)\n\t{\n\t  /* Bottom segment is the short one.  */\n\t  int len = middle - bottom;\n\t  register int i;\n\n\t  /* Swap it with the top part of the top segment.  */\n\t  for (i = 0; i < len; i++)\n\t    {\n\t      tem = argv[bottom + i];\n\t      argv[bottom + i] = argv[top - (middle - bottom) + i];\n\t      argv[top - (middle - bottom) + i] = tem;\n\t      SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);\n\t    }\n\t  /* Exclude the moved bottom segment from further swapping.  */\n\t  top -= len;\n\t}\n      else\n\t{\n\t  /* Top segment is the short one.  */\n\t  int len = top - middle;\n\t  register int i;\n\n\t  /* Swap it with the bottom part of the bottom segment.  */\n\t  for (i = 0; i < len; i++)\n\t    {\n\t      tem = argv[bottom + i];\n\t      argv[bottom + i] = argv[middle + i];\n\t      argv[middle + i] = tem;\n\t      SWAP_FLAGS (bottom + i, middle + i);\n\t    }\n\t  /* Exclude the moved top segment from further swapping.  */\n\t  bottom += len;\n\t}\n    }\n\n  /* Update records for the slots the non-options now occupy.  */\n\n  first_nonopt += (optind - last_nonopt);\n  last_nonopt = optind;\n}\n\n/* Initialize the internal data when the first call is made.  */\n\n#if defined __STDC__ && __STDC__\nstatic const char *_getopt_initialize (int, char *const *, const char *);\n#endif\nstatic const char *\n_getopt_initialize (argc, argv, optstring)\n     int argc;\n     char *const *argv;\n     const char *optstring;\n{\n  /* Start processing options with ARGV-element 1 (since ARGV-element 0\n     is the program name); the sequence of previously skipped\n     non-option ARGV-elements is empty.  */\n\n  first_nonopt = last_nonopt = optind;\n\n  nextchar = NULL;\n\n  posixly_correct = getenv (\"POSIXLY_CORRECT\");\n\n  /* Determine how to handle the ordering of options and nonoptions.  */\n\n  if (optstring[0] == '-')\n    {\n      ordering = RETURN_IN_ORDER;\n      ++optstring;\n    }\n  else if (optstring[0] == '+')\n    {\n      ordering = REQUIRE_ORDER;\n      ++optstring;\n    }\n  else if (posixly_correct != NULL)\n    ordering = REQUIRE_ORDER;\n  else\n    ordering = PERMUTE;\n\n#if defined _LIBC && defined USE_NONOPTION_FLAGS\n  if (posixly_correct == NULL\n      && argc == __libc_argc && argv == __libc_argv)\n    {\n      if (nonoption_flags_max_len == 0)\n\t{\n\t  if (__getopt_nonoption_flags == NULL\n\t      || __getopt_nonoption_flags[0] == '\\0')\n\t    nonoption_flags_max_len = -1;\n\t  else\n\t    {\n\t      const char *orig_str = __getopt_nonoption_flags;\n\t      int len = nonoption_flags_max_len = strlen (orig_str);\n\t      if (nonoption_flags_max_len < argc)\n\t\tnonoption_flags_max_len = argc;\n\t      __getopt_nonoption_flags =\n\t\t(char *) malloc (nonoption_flags_max_len);\n\t      if (__getopt_nonoption_flags == NULL)\n\t\tnonoption_flags_max_len = -1;\n\t      else\n\t\tmemset (__mempcpy (__getopt_nonoption_flags, orig_str, len),\n\t\t\t'\\0', nonoption_flags_max_len - len);\n\t    }\n\t}\n      nonoption_flags_len = nonoption_flags_max_len;\n    }\n  else\n    nonoption_flags_len = 0;\n#endif\n\n  return optstring;\n}\n\n/* Scan elements of ARGV (whose length is ARGC) for option characters\n   given in OPTSTRING.\n\n   If an element of ARGV starts with '-', and is not exactly \"-\" or \"--\",\n   then it is an option element.  The characters of this element\n   (aside from the initial '-') are option characters.  If `getopt'\n   is called repeatedly, it returns successively each of the option characters\n   from each of the option elements.\n\n   If `getopt' finds another option character, it returns that character,\n   updating `optind' and `nextchar' so that the next call to `getopt' can\n   resume the scan with the following option character or ARGV-element.\n\n   If there are no more option characters, `getopt' returns -1.\n   Then `optind' is the index in ARGV of the first ARGV-element\n   that is not an option.  (The ARGV-elements have been permuted\n   so that those that are not options now come last.)\n\n   OPTSTRING is a string containing the legitimate option characters.\n   If an option character is seen that is not listed in OPTSTRING,\n   return '?' after printing an error message.  If you set `opterr' to\n   zero, the error message is suppressed but we still return '?'.\n\n   If a char in OPTSTRING is followed by a colon, that means it wants an arg,\n   so the following text in the same ARGV-element, or the text of the following\n   ARGV-element, is returned in `optarg'.  Two colons mean an option that\n   wants an optional arg; if there is text in the current ARGV-element,\n   it is returned in `optarg', otherwise `optarg' is set to zero.\n\n   If OPTSTRING starts with `-' or `+', it requests different methods of\n   handling the non-option ARGV-elements.\n   See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.\n\n   Long-named options begin with `--' instead of `-'.\n   Their names may be abbreviated as long as the abbreviation is unique\n   or is an exact match for some defined option.  If they have an\n   argument, it follows the option name in the same ARGV-element, separated\n   from the option name by a `=', or else the in next ARGV-element.\n   When `getopt' finds a long-named option, it returns 0 if that option's\n   `flag' field is nonzero, the value of the option's `val' field\n   if the `flag' field is zero.\n\n   The elements of ARGV aren't really const, because we permute them.\n   But we pretend they're const in the prototype to be compatible\n   with other systems.\n\n   LONGOPTS is a vector of `struct option' terminated by an\n   element containing a name which is zero.\n\n   LONGIND returns the index in LONGOPT of the long-named option found.\n   It is only valid when a long-named option has been found by the most\n   recent call.\n\n   If LONG_ONLY is nonzero, '-' as well as '--' can introduce\n   long-named options.  */\n\nint\n_getopt_internal (argc, argv, optstring, longopts, longind, long_only)\n     int argc;\n     char *const *argv;\n     const char *optstring;\n     const struct option *longopts;\n     int *longind;\n     int long_only;\n{\n  int print_errors = opterr;\n  if (optstring[0] == ':')\n    print_errors = 0;\n\n  if (argc < 1)\n    return -1;\n\n  optarg = NULL;\n\n  if (optind == 0 || !__getopt_initialized)\n    {\n      if (optind == 0)\n\toptind = 1;\t/* Don't scan ARGV[0], the program name.  */\n      optstring = _getopt_initialize (argc, argv, optstring);\n      __getopt_initialized = 1;\n    }\n\n  /* Test whether ARGV[optind] points to a non-option argument.\n     Either it does not have option syntax, or there is an environment flag\n     from the shell indicating it is not an option.  The later information\n     is only used when the used in the GNU libc.  */\n#if defined _LIBC && defined USE_NONOPTION_FLAGS\n# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\\0'\t      \\\n\t\t      || (optind < nonoption_flags_len\t\t\t      \\\n\t\t\t  && __getopt_nonoption_flags[optind] == '1'))\n#else\n# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\\0')\n#endif\n\n  if (nextchar == NULL || *nextchar == '\\0')\n    {\n      /* Advance to the next ARGV-element.  */\n\n      /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been\n\t moved back by the user (who may also have changed the arguments).  */\n      if (last_nonopt > optind)\n\tlast_nonopt = optind;\n      if (first_nonopt > optind)\n\tfirst_nonopt = optind;\n\n      if (ordering == PERMUTE)\n\t{\n\t  /* If we have just processed some options following some non-options,\n\t     exchange them so that the options come first.  */\n\n\t  if (first_nonopt != last_nonopt && last_nonopt != optind)\n\t    exchange ((char **) argv);\n\t  else if (last_nonopt != optind)\n\t    first_nonopt = optind;\n\n\t  /* Skip any additional non-options\n\t     and extend the range of non-options previously skipped.  */\n\n\t  while (optind < argc && NONOPTION_P)\n\t    optind++;\n\t  last_nonopt = optind;\n\t}\n\n      /* The special ARGV-element `--' means premature end of options.\n\t Skip it like a null option,\n\t then exchange with previous non-options as if it were an option,\n\t then skip everything else like a non-option.  */\n\n      if (optind != argc && !strcmp (argv[optind], \"--\"))\n\t{\n\t  optind++;\n\n\t  if (first_nonopt != last_nonopt && last_nonopt != optind)\n\t    exchange ((char **) argv);\n\t  else if (first_nonopt == last_nonopt)\n\t    first_nonopt = optind;\n\t  last_nonopt = argc;\n\n\t  optind = argc;\n\t}\n\n      /* If we have done all the ARGV-elements, stop the scan\n\t and back over any non-options that we skipped and permuted.  */\n\n      if (optind == argc)\n\t{\n\t  /* Set the next-arg-index to point at the non-options\n\t     that we previously skipped, so the caller will digest them.  */\n\t  if (first_nonopt != last_nonopt)\n\t    optind = first_nonopt;\n\t  return -1;\n\t}\n\n      /* If we have come to a non-option and did not permute it,\n\t either stop the scan or describe it to the caller and pass it by.  */\n\n      if (NONOPTION_P)\n\t{\n\t  if (ordering == REQUIRE_ORDER)\n\t    return -1;\n\t  optarg = argv[optind++];\n\t  return 1;\n\t}\n\n      /* We have found another option-ARGV-element.\n\t Skip the initial punctuation.  */\n\n      nextchar = (argv[optind] + 1\n\t\t  + (longopts != NULL && argv[optind][1] == '-'));\n    }\n\n  /* Decode the current option-ARGV-element.  */\n\n  /* Check whether the ARGV-element is a long option.\n\n     If long_only and the ARGV-element has the form \"-f\", where f is\n     a valid short option, don't consider it an abbreviated form of\n     a long option that starts with f.  Otherwise there would be no\n     way to give the -f short option.\n\n     On the other hand, if there's a long option \"fubar\" and\n     the ARGV-element is \"-fu\", do consider that an abbreviation of\n     the long option, just like \"--fu\", and not \"-f\" with arg \"u\".\n\n     This distinction seems to be the most useful approach.  */\n\n  if (longopts != NULL\n      && (argv[optind][1] == '-'\n\t  || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1])))))\n    {\n      char *nameend;\n      const struct option *p;\n      const struct option *pfound = NULL;\n      int exact = 0;\n      int ambig = 0;\n      int indfound = -1;\n      int option_index;\n\n      for (nameend = nextchar; *nameend && *nameend != '='; nameend++)\n\t/* Do nothing.  */ ;\n\n      /* Test all long options for either exact match\n\t or abbreviated matches.  */\n      for (p = longopts, option_index = 0; p->name; p++, option_index++)\n\tif (!strncmp (p->name, nextchar, nameend - nextchar))\n\t  {\n\t    if ((unsigned int) (nameend - nextchar)\n\t\t== (unsigned int) strlen (p->name))\n\t      {\n\t\t/* Exact match found.  */\n\t\tpfound = p;\n\t\tindfound = option_index;\n\t\texact = 1;\n\t\tbreak;\n\t      }\n\t    else if (pfound == NULL)\n\t      {\n\t\t/* First nonexact match found.  */\n\t\tpfound = p;\n\t\tindfound = option_index;\n\t      }\n\t    else if (long_only\n\t\t     || pfound->has_arg != p->has_arg\n\t\t     || pfound->flag != p->flag\n\t\t     || pfound->val != p->val)\n\t      /* Second or later nonexact match found.  */\n\t      ambig = 1;\n\t  }\n\n      if (ambig && !exact)\n\t{\n\t  if (print_errors)\n\t    fprintf (stderr, _(\"%s: option `%s' is ambiguous\\n\"),\n\t\t     argv[0], argv[optind]);\n\t  nextchar += strlen (nextchar);\n\t  optind++;\n\t  optopt = 0;\n\t  return '?';\n\t}\n\n      if (pfound != NULL)\n\t{\n\t  option_index = indfound;\n\t  optind++;\n\t  if (*nameend)\n\t    {\n\t      /* Don't test has_arg with >, because some C compilers don't\n\t\t allow it to be used on enums.  */\n\t      if (pfound->has_arg)\n\t\toptarg = nameend + 1;\n\t      else\n\t\t{\n\t\t  if (print_errors)\n\t\t    {\n\t\t      if (argv[optind - 1][1] == '-')\n\t\t\t/* --option */\n\t\t\tfprintf (stderr,\n\t\t\t\t _(\"%s: option `--%s' doesn't allow an argument\\n\"),\n\t\t\t\t argv[0], pfound->name);\n\t\t      else\n\t\t\t/* +option or -option */\n\t\t\tfprintf (stderr,\n\t\t\t\t _(\"%s: option `%c%s' doesn't allow an argument\\n\"),\n\t\t\t\t argv[0], argv[optind - 1][0], pfound->name);\n\t\t    }\n\n\t\t  nextchar += strlen (nextchar);\n\n\t\t  optopt = pfound->val;\n\t\t  return '?';\n\t\t}\n\t    }\n\t  else if (pfound->has_arg == 1)\n\t    {\n\t      if (optind < argc)\n\t\toptarg = argv[optind++];\n\t      else\n\t\t{\n\t\t  if (print_errors)\n\t\t    fprintf (stderr,\n\t\t\t   _(\"%s: option `%s' requires an argument\\n\"),\n\t\t\t   argv[0], argv[optind - 1]);\n\t\t  nextchar += strlen (nextchar);\n\t\t  optopt = pfound->val;\n\t\t  return optstring[0] == ':' ? ':' : '?';\n\t\t}\n\t    }\n\t  nextchar += strlen (nextchar);\n\t  if (longind != NULL)\n\t    *longind = option_index;\n\t  if (pfound->flag)\n\t    {\n\t      *(pfound->flag) = pfound->val;\n\t      return 0;\n\t    }\n\t  return pfound->val;\n\t}\n\n      /* Can't find it as a long option.  If this is not getopt_long_only,\n\t or the option starts with '--' or is not a valid short\n\t option, then it's an error.\n\t Otherwise interpret it as a short option.  */\n      if (!long_only || argv[optind][1] == '-'\n\t  || my_index (optstring, *nextchar) == NULL)\n\t{\n\t  if (print_errors)\n\t    {\n\t      if (argv[optind][1] == '-')\n\t\t/* --option */\n\t\tfprintf (stderr, _(\"%s: unrecognized option `--%s'\\n\"),\n\t\t\t argv[0], nextchar);\n\t      else\n\t\t/* +option or -option */\n\t\tfprintf (stderr, _(\"%s: unrecognized option `%c%s'\\n\"),\n\t\t\t argv[0], argv[optind][0], nextchar);\n\t    }\n\t  nextchar = (char *) \"\";\n\t  optind++;\n\t  optopt = 0;\n\t  return '?';\n\t}\n    }\n\n  /* Look at and handle the next short option-character.  */\n\n  {\n    char c = *nextchar++;\n    char *temp = my_index (optstring, c);\n\n    /* Increment `optind' when we start to process its last character.  */\n    if (*nextchar == '\\0')\n      ++optind;\n\n    if (temp == NULL || c == ':')\n      {\n\tif (print_errors)\n\t  {\n\t    if (posixly_correct)\n\t      /* 1003.2 specifies the format of this message.  */\n\t      fprintf (stderr, _(\"%s: illegal option -- %c\\n\"),\n\t\t       argv[0], c);\n\t    else\n\t      fprintf (stderr, _(\"%s: invalid option -- %c\\n\"),\n\t\t       argv[0], c);\n\t  }\n\toptopt = c;\n\treturn '?';\n      }\n    /* Convenience. Treat POSIX -W foo same as long option --foo */\n    if (temp[0] == 'W' && temp[1] == ';')\n      {\n\tchar *nameend;\n\tconst struct option *p;\n\tconst struct option *pfound = NULL;\n\tint exact = 0;\n\tint ambig = 0;\n\tint indfound = 0;\n\tint option_index;\n\n\t/* This is an option that requires an argument.  */\n\tif (*nextchar != '\\0')\n\t  {\n\t    optarg = nextchar;\n\t    /* If we end this ARGV-element by taking the rest as an arg,\n\t       we must advance to the next element now.  */\n\t    optind++;\n\t  }\n\telse if (optind == argc)\n\t  {\n\t    if (print_errors)\n\t      {\n\t\t/* 1003.2 specifies the format of this message.  */\n\t\tfprintf (stderr, _(\"%s: option requires an argument -- %c\\n\"),\n\t\t\t argv[0], c);\n\t      }\n\t    optopt = c;\n\t    if (optstring[0] == ':')\n\t      c = ':';\n\t    else\n\t      c = '?';\n\t    return c;\n\t  }\n\telse\n\t  /* We already incremented `optind' once;\n\t     increment it again when taking next ARGV-elt as argument.  */\n\t  optarg = argv[optind++];\n\n\t/* optarg is now the argument, see if it's in the\n\t   table of longopts.  */\n\n\tfor (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++)\n\t  /* Do nothing.  */ ;\n\n\t/* Test all long options for either exact match\n\t   or abbreviated matches.  */\n\tfor (p = longopts, option_index = 0; p->name; p++, option_index++)\n\t  if (!strncmp (p->name, nextchar, nameend - nextchar))\n\t    {\n\t      if ((unsigned int) (nameend - nextchar) == strlen (p->name))\n\t\t{\n\t\t  /* Exact match found.  */\n\t\t  pfound = p;\n\t\t  indfound = option_index;\n\t\t  exact = 1;\n\t\t  break;\n\t\t}\n\t      else if (pfound == NULL)\n\t\t{\n\t\t  /* First nonexact match found.  */\n\t\t  pfound = p;\n\t\t  indfound = option_index;\n\t\t}\n\t      else\n\t\t/* Second or later nonexact match found.  */\n\t\tambig = 1;\n\t    }\n\tif (ambig && !exact)\n\t  {\n\t    if (print_errors)\n\t      fprintf (stderr, _(\"%s: option `-W %s' is ambiguous\\n\"),\n\t\t       argv[0], argv[optind]);\n\t    nextchar += strlen (nextchar);\n\t    optind++;\n\t    return '?';\n\t  }\n\tif (pfound != NULL)\n\t  {\n\t    option_index = indfound;\n\t    if (*nameend)\n\t      {\n\t\t/* Don't test has_arg with >, because some C compilers don't\n\t\t   allow it to be used on enums.  */\n\t\tif (pfound->has_arg)\n\t\t  optarg = nameend + 1;\n\t\telse\n\t\t  {\n\t\t    if (print_errors)\n\t\t      fprintf (stderr, _(\"\\\n\t\t\t%s: option `-W %s' doesn't allow an argument\\n\"),\n\t\t\t       argv[0], pfound->name);\n\n\t\t    nextchar += strlen (nextchar);\n\t\t    return '?';\n\t\t  }\n\t      }\n\t    else if (pfound->has_arg == 1)\n\t      {\n\t\tif (optind < argc)\n\t\t  optarg = argv[optind++];\n\t\telse\n\t\t  {\n\t\t    if (print_errors)\n\t\t      fprintf (stderr,\n\t\t\t       _(\"%s: option `%s' requires an argument\\n\"),\n\t\t\t       argv[0], argv[optind - 1]);\n\t\t    nextchar += strlen (nextchar);\n\t\t    return optstring[0] == ':' ? ':' : '?';\n\t\t  }\n\t      }\n\t    nextchar += strlen (nextchar);\n\t    if (longind != NULL)\n\t      *longind = option_index;\n\t    if (pfound->flag)\n\t      {\n\t\t*(pfound->flag) = pfound->val;\n\t\treturn 0;\n\t      }\n\t    return pfound->val;\n\t  }\n\t  nextchar = NULL;\n\t  return 'W';\t/* Let the application handle it.   */\n      }\n    if (temp[1] == ':')\n      {\n\tif (temp[2] == ':')\n\t  {\n\t    /* This is an option that accepts an argument optionally.  */\n\t    if (*nextchar != '\\0')\n\t      {\n\t\toptarg = nextchar;\n\t\toptind++;\n\t      }\n\t    else\n\t      optarg = NULL;\n\t    nextchar = NULL;\n\t  }\n\telse\n\t  {\n\t    /* This is an option that requires an argument.  */\n\t    if (*nextchar != '\\0')\n\t      {\n\t\toptarg = nextchar;\n\t\t/* If we end this ARGV-element by taking the rest as an arg,\n\t\t   we must advance to the next element now.  */\n\t\toptind++;\n\t      }\n\t    else if (optind == argc)\n\t      {\n\t\tif (print_errors)\n\t\t  {\n\t\t    /* 1003.2 specifies the format of this message.  */\n\t\t    fprintf (stderr,\n\t\t\t     _(\"%s: option requires an argument -- %c\\n\"),\n\t\t\t     argv[0], c);\n\t\t  }\n\t\toptopt = c;\n\t\tif (optstring[0] == ':')\n\t\t  c = ':';\n\t\telse\n\t\t  c = '?';\n\t      }\n\t    else\n\t      /* We already incremented `optind' once;\n\t\t increment it again when taking next ARGV-elt as argument.  */\n\t      optarg = argv[optind++];\n\t    nextchar = NULL;\n\t  }\n      }\n    return c;\n  }\n}\n\nint\ngetopt (argc, argv, optstring)\n     int argc;\n     char *const *argv;\n     const char *optstring;\n{\n  return _getopt_internal (argc, argv, optstring,\n\t\t\t   (const struct option *) 0,\n\t\t\t   (int *) 0,\n\t\t\t   0);\n}\n\n#endif\t/* Not ELIDE_CODE.  */\n\n#ifdef TEST\n\n/* Compile with -DTEST to make an executable for use in testing\n   the above definition of `getopt'.  */\n\nint\nmain (argc, argv)\n     int argc;\n     char **argv;\n{\n  int c;\n  int digit_optind = 0;\n\n  while (1)\n    {\n      int this_option_optind = optind ? optind : 1;\n\n      c = getopt (argc, argv, \"abc:d:0123456789\");\n      if (c == -1)\n\tbreak;\n\n      switch (c)\n\t{\n\tcase '0':\n\tcase '1':\n\tcase '2':\n\tcase '3':\n\tcase '4':\n\tcase '5':\n\tcase '6':\n\tcase '7':\n\tcase '8':\n\tcase '9':\n\t  if (digit_optind != 0 && digit_optind != this_option_optind)\n\t    printf (\"digits occur in two different argv-elements.\\n\");\n\t  digit_optind = this_option_optind;\n\t  printf (\"option %c\\n\", c);\n\t  break;\n\n\tcase 'a':\n\t  printf (\"option a\\n\");\n\t  break;\n\n\tcase 'b':\n\t  printf (\"option b\\n\");\n\t  break;\n\n\tcase 'c':\n\t  printf (\"option c with value `%s'\\n\", optarg);\n\t  break;\n\n\tcase '?':\n\t  break;\n\n\tdefault:\n\t  printf (\"?? getopt returned character code 0%o ??\\n\", c);\n\t}\n    }\n\n  if (optind < argc)\n    {\n      printf (\"non-option ARGV-elements: \");\n      while (optind < argc)\n\tprintf (\"%s \", argv[optind++]);\n      printf (\"\\n\");\n    }\n\n  exit (0);\n}\n\n#endif /* TEST */\n"
  },
  {
    "path": "src/getopt/getopt.h",
    "content": "/* Declarations for getopt.\n   Copyright (C) 1989-1994, 1996-1999, 2001 Free Software Foundation, Inc.\n   This file is part of the GNU C Library.\n\n   The GNU C Library is free software; you can redistribute it and/or\n   modify it under the terms of the GNU Lesser General Public\n   License as published by the Free Software Foundation; either\n   version 2.1 of the License, or (at your option) any later version.\n\n   The GNU C Library is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n   Lesser General Public License for more details.\n\n   You should have received a copy of the GNU Lesser General Public\n   License along with the GNU C Library; if not, write to the Free\n   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA\n   02111-1307 USA.  */\n\n#ifndef _GETOPT_H\n\n#ifndef __need_getopt\n# define _GETOPT_H 1\n#endif\n\n/* If __GNU_LIBRARY__ is not already defined, either we are being used\n   standalone, or this is the first header included in the source file.\n   If we are being used with glibc, we need to include <features.h>, but\n   that does not exist if we are standalone.  So: if __GNU_LIBRARY__ is\n   not defined, include <ctype.h>, which will pull in <features.h> for us\n   if it's from glibc.  (Why ctype.h?  It's guaranteed to exist and it\n   doesn't flood the namespace with stuff the way some other headers do.)  */\n#if !defined __GNU_LIBRARY__\n# include <ctype.h>\n#endif\n\n#ifdef\t__cplusplus\nextern \"C\" {\n#endif\n\n/* For communication from `getopt' to the caller.\n   When `getopt' finds an option that takes an argument,\n   the argument value is returned here.\n   Also, when `ordering' is RETURN_IN_ORDER,\n   each non-option ARGV-element is returned here.  */\n\nextern char *optarg;\n\n/* Index in ARGV of the next element to be scanned.\n   This is used for communication to and from the caller\n   and for communication between successive calls to `getopt'.\n\n   On entry to `getopt', zero means this is the first call; initialize.\n\n   When `getopt' returns -1, this is the index of the first of the\n   non-option elements that the caller should itself scan.\n\n   Otherwise, `optind' communicates from one call to the next\n   how much of ARGV has been scanned so far.  */\n\nextern int optind;\n\n/* Callers store zero here to inhibit the error message `getopt' prints\n   for unrecognized options.  */\n\nextern int opterr;\n\n/* Set to an option character which was unrecognized.  */\n\nextern int optopt;\n\n#ifndef __need_getopt\n/* Describe the long-named options requested by the application.\n   The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector\n   of `struct option' terminated by an element containing a name which is\n   zero.\n\n   The field `has_arg' is:\n   no_argument\t\t(or 0) if the option does not take an argument,\n   required_argument\t(or 1) if the option requires an argument,\n   optional_argument \t(or 2) if the option takes an optional argument.\n\n   If the field `flag' is not NULL, it points to a variable that is set\n   to the value given in the field `val' when the option is found, but\n   left unchanged if the option is not found.\n\n   To have a long-named option do something other than set an `int' to\n   a compiled-in constant, such as set a value from `optarg', set the\n   option's `flag' field to zero and its `val' field to a nonzero\n   value (the equivalent single-letter option character, if there is\n   one).  For long options that have a zero `flag' field, `getopt'\n   returns the contents of the `val' field.  */\n\nstruct option\n{\n# if (defined __STDC__ && __STDC__) || defined __cplusplus\n  const char *name;\n# else\n  char *name;\n# endif\n  /* has_arg can't be an enum because some compilers complain about\n     type mismatches in all the code that assumes it is an int.  */\n  int has_arg;\n  int *flag;\n  int val;\n};\n\n/* Names for the values of the `has_arg' field of `struct option'.  */\n\n# define no_argument\t\t0\n# define required_argument\t1\n# define optional_argument\t2\n#endif\t/* need getopt */\n\n\n/* Get definitions and prototypes for functions to process the\n   arguments in ARGV (ARGC of them, minus the program name) for\n   options given in OPTS.\n\n   Return the option character from OPTS just read.  Return -1 when\n   there are no more options.  For unrecognized options, or options\n   missing arguments, `optopt' is set to the option letter, and '?' is\n   returned.\n\n   The OPTS string is a list of characters which are recognized option\n   letters, optionally followed by colons, specifying that that letter\n   takes an argument, to be placed in `optarg'.\n\n   If a letter in OPTS is followed by two colons, its argument is\n   optional.  This behavior is specific to the GNU `getopt'.\n\n   The argument `--' causes premature termination of argument\n   scanning, explicitly telling `getopt' that there are no more\n   options.\n\n   If OPTS begins with `--', then non-option arguments are treated as\n   arguments to the option '\\0'.  This behavior is specific to the GNU\n   `getopt'.  */\n\n#if (defined __STDC__ && __STDC__) || defined __cplusplus\n# ifdef __GNU_LIBRARY__\n/* Many other libraries have conflicting prototypes for getopt, with\n   differences in the consts, in stdlib.h.  To avoid compilation\n   errors, only prototype getopt for the GNU C library.  */\nextern int getopt (int __argc, char *const *__argv, const char *__shortopts);\n# else /* not __GNU_LIBRARY__ */\nextern int getopt ();\n# endif /* __GNU_LIBRARY__ */\n\n# ifndef __need_getopt\nextern int getopt_long (int __argc, char *const *__argv, const char *__shortopts,\n\t\t        const struct option *__longopts, int *__longind);\nextern int getopt_long_only (int __argc, char *const *__argv,\n\t\t\t     const char *__shortopts,\n\t\t             const struct option *__longopts, int *__longind);\n\n/* Internal only.  Users should not call this directly.  */\nextern int _getopt_internal (int __argc, char *const *__argv,\n\t\t\t     const char *__shortopts,\n\t\t             const struct option *__longopts, int *__longind,\n\t\t\t     int __long_only);\n# endif\n#else /* not __STDC__ */\nextern int getopt ();\n# ifndef __need_getopt\nextern int getopt_long ();\nextern int getopt_long_only ();\n\nextern int _getopt_internal ();\n# endif\n#endif /* __STDC__ */\n\n#ifdef\t__cplusplus\n}\n#endif\n\n/* Make sure we later can get all the definitions and declarations.  */\n#undef __need_getopt\n\n#endif /* getopt.h */\n"
  },
  {
    "path": "src/http_server.c",
    "content": "/**\n * HTTP control interface\n *\n * Copyright (C) 2018 Christian Zuckschwerdt\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n */\n\n/*\n# HTTP control interface\n\nA choice of endpoints are available:\n- \"/\": serves a user interface (currently redirected to hosted app)\n    (also serves \"/favicon.ico\", \"/app.css\", \"/app.js\", \"/vendor.css\", \"/vendor.js\")\n- \"/jsonrpc\": JSON-RPC API\n- \"/cmd\": simple JSON command API\n- \"/events\": HTTP (chunked) streaming API, streams JSON events\n- \"/stream\": HTTP (plain) streaming API, streams JSON events\n- \"/api\": RESTful API (not implemented)\n- \"ws:\": Websocket API (similar to cmd/events API)\n\n## JSON-RPC API\n\nS.a. https://www.jsonrpc.org/specification\n\nExamples:\n\n    {\"jsonrpc\": \"2.0\", \"method\": \"sample_rate\", \"params\": [1024000], \"id\": 0}\n    {\"jsonrpc\": \"2.0\", \"result\": \"Ok\", \"id\": 0}\n    {\"jsonrpc\": \"2.0\", \"error\": {\"code\": -32600, \"message\": \"Invalid Request\"}, \"id\": null}\n\n## JSON command / Websocket API\n\nSimplified JSON command and query.\n\nExamples:\n\n    {\"cmd\": \"sample_rate\", \"val\": 1024000}\n    {\"result\": \"Ok\"}\n    {\"error\": \"Invalid Request\"}}\n\n## HTTP events / streaming / Websocket API\n\nYou will receive JSON events, one per line terminated with CRLF.\nOn Events and Stream endpoints a keep-alive of CRLF will be send every 60 seconds.\nUse e.g. httpie with `http --stream --timeout=70 :8433/events`\nor `(echo \"GET /stream HTTP/1.0\\n\"; sleep 600) | socat - tcp:127.0.0.1:8433`\n\n## Queries\n\n- \"registered_protocols\"\n- \"enabled_protocols\"\n- \"protocol_info\"\n    .name\n    .modulation\n    .short_width\n    .long_width\n    .sync_width\n    .tolerance\n    .gap_limit\n    .reset_limit\n    .fields\n\n- \"device_info\"\n    device  0:  Realtek, RTL2838UHIDIR, SN: 00000001\n    Found Rafael Micro R820T tuner\n    Using device 0: Generic RTL2832U OEM\n\n- \"settings\"\n    \"device\":           0\n    \"gain\":             0\n    \"center_frequency\": 433920000\n    \"hop_interval\":     600\n    \"ppm_error\":        0\n    \"sample_rate\":      250000\n    \"report_meta\":      [\"time\", \"reltime\", \"notime\", \"hires\", \"utc\", \"protocol\", \"level\"]\n    \"convert\":          \"native\"|\"si\"|\"customary\"\n\n## Commands\n\n- \"device\":           0\n- \"gain\":             0\n- \"center_frequency\": 433920000\n- \"hop_interval\":     600\n- \"ppm_error\":        0\n- \"sample_rate\":      250000\n- \"report_meta\":      \"time\"|\"reltime\"|\"notime\"|\"hires\"|\"utc\"|\"protocol\"|\"level\"\n- \"convert\":          \"native\"|\"si\"|\"customary\"\n- \"protocol\":         1\n\n*/\n\n#include \"http_server.h\"\n#include \"data.h\"\n#include \"rtl_433.h\"\n#include \"r_api.h\"\n#include \"r_device.h\" // used for protocols\n#include \"r_private.h\" // used for protocols\n#include \"r_util.h\"\n#include \"optparse.h\"\n#include \"abuf.h\"\n#include \"list.h\" // used for protocols\n#include \"jsmn.h\"\n#include \"mongoose.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include <stdbool.h>\n\n// embed index.html so browsers allow access as local\n#define INDEX_HTML \\\n    \"<!DOCTYPE html>\" \\\n    \"<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1\\\">\" \\\n    \"<meta http-equiv=\\\"Content-Type\\\" content=\\\"text/html; charset=UTF-8\\\">\" \\\n    \"<link rel=\\\"icon\\\" href=\\\"https://triq.org/rxui/favicon.ico\\\">\" \\\n    \"<title>rxui</title>\" \\\n    \"<link href=\\\"https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons\\\" rel=\\\"stylesheet\\\">\" \\\n    \"<link href=\\\"https://triq.org/rxui/css/app.css\\\" rel=\\\"preload\\\" as=\\\"style\\\">\" \\\n    \"<link href=\\\"https://triq.org/rxui/css/chunk-vendors.css\\\" rel=\\\"preload\\\" as=\\\"style\\\">\" \\\n    \"<link href=\\\"https://triq.org/rxui/js/app.js\\\" rel=\\\"preload\\\" as=\\\"script\\\">\" \\\n    \"<link href=\\\"https://triq.org/rxui/js/chunk-vendors.js\\\" rel=\\\"preload\\\" as=\\\"script\\\">\" \\\n    \"<link href=\\\"https://triq.org/rxui/css/chunk-vendors.css\\\" rel=\\\"stylesheet\\\">\" \\\n    \"<link href=\\\"https://triq.org/rxui/css/app.css\\\" rel=\\\"stylesheet\\\">\" \\\n    \"<div id=\\\"app\\\"></div>\" \\\n    \"<noscript><strong>We're sorry but rxui doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript>\" \\\n    \"<script src=\\\"https://triq.org/rxui/js/chunk-vendors.js\\\"></script>\" \\\n    \"<script src=\\\"https://triq.org/rxui/js/app.js\\\"></script>\"\n\n// generic ring list\n\n#define DEFAULT_HISTORY_SIZE 100\n\ntypedef struct {\n    unsigned size;\n    void **data;\n    void **head;\n    void **tail;\n} ring_list_t;\n\nstatic ring_list_t *ring_list_new(unsigned size)\n{\n    ring_list_t *ring = calloc(1, sizeof(ring_list_t));\n    if (!ring) {\n        WARN_CALLOC(\"ring_list_new()\");\n        return NULL;\n    }\n\n    ring->data = calloc(size, sizeof(void *));\n    if (!ring->data) {\n        WARN_CALLOC(\"ring_list_new()\");\n        free(ring);\n        return NULL;\n    }\n\n    ring->size = size;\n    ring->tail = ring->data;\n\n    return ring;\n}\n\n// the ring needs to be empty before calling this\nstatic void ring_list_free(ring_list_t *ring)\n{\n    if (ring) {\n        if (ring->data)\n            free(ring->data);\n        free(ring);\n    }\n}\n\n// free the data returned\nstatic void *ring_list_shift(ring_list_t *ring)\n{\n    if (!ring->head)\n        return NULL;\n\n    void *ret = *ring->head;\n\n    ++ring->head;\n    if (ring->head >= ring->data + ring->size)\n        ring->head -= ring->size;\n    if (ring->head == ring->tail)\n        ring->head = NULL;\n\n    return ret;\n}\n\n// retain data before passing in and free the data returned.\nstatic void *ring_list_push(ring_list_t *ring, void *data)\n{\n    *ring->tail = data;\n\n    if (!ring->head)\n        ring->head = ring->tail;\n\n    ++ring->tail;\n    if (ring->tail >= ring->data + ring->size)\n        ring->tail -= ring->size;\n\n    if (ring->tail == ring->head)\n        return ring_list_shift(ring);\n\n    return NULL;\n}\n\nstatic void **ring_list_iter(ring_list_t *ring)\n{\n    return ring->head;\n}\n\nstatic void **ring_list_next(ring_list_t *ring, void **iter)\n{\n    if (!iter)\n        return NULL;\n\n    ++iter;\n    if (iter >= ring->data + ring->size)\n        iter -= ring->size;\n    if (iter == ring->tail)\n        iter = NULL;\n\n    return iter;\n}\n\n// data helpers that could go into r_api\n\nstatic data_t *meta_data(r_cfg_t *cfg)\n{\n    return data_make(\n            \"frequencies\", \"\", DATA_ARRAY, data_array(cfg->frequencies, DATA_INT, cfg->frequency),\n            \"hop_times\", \"\", DATA_ARRAY, data_array(cfg->hop_times, DATA_INT, cfg->hop_time),\n            \"center_frequency\", \"\", DATA_INT, cfg->center_frequency,\n            \"duration\", \"\", DATA_INT, cfg->duration,\n            \"samp_rate\", \"\", DATA_INT, cfg->samp_rate,\n            \"conversion_mode\", \"\", DATA_INT, cfg->conversion_mode,\n            \"fsk_pulse_detect_mode\", \"\", DATA_INT, cfg->fsk_pulse_detect_mode,\n            \"after_successful_events_flag\", \"\", DATA_INT, cfg->after_successful_events_flag,\n            \"report_meta\", \"\", DATA_INT, cfg->report_meta,\n            \"report_protocol\", \"\", DATA_INT, cfg->report_protocol,\n            \"report_time\", \"\", DATA_INT, cfg->report_time,\n            \"report_time_hires\", \"\", DATA_INT, cfg->report_time_hires,\n            \"report_time_tz\", \"\", DATA_INT, cfg->report_time_tz,\n            \"report_time_utc\", \"\", DATA_INT, cfg->report_time_utc,\n            \"report_description\", \"\", DATA_INT, cfg->report_description,\n            \"report_stats\", \"\", DATA_INT, cfg->report_stats,\n            \"stats_interval\", \"\", DATA_INT, cfg->stats_interval,\n            NULL);\n}\n\nstatic data_t *protocols_data(r_cfg_t *cfg)\n{\n    list_t devs = {0};\n    list_ensure_size(&devs, cfg->num_r_devices);\n\n    // list regular protocols\n    for (int i = 0; i < cfg->num_r_devices; ++i) {\n        r_device *dev = &cfg->devices[i];\n\n        int enabled = 0;\n        for (void **iter = cfg->demod->r_devs.elems; iter && *iter; ++iter) {\n            r_device *r_dev = *iter;\n            if (r_dev->protocol_num == dev->protocol_num) {\n                enabled = 1;\n                break;\n            }\n        }\n        int fields_len = 0;\n        for (char const *const *iter = dev->fields; iter && *iter; ++iter) {\n            fields_len++;\n        }\n        data_t *data = data_make(\n                \"num\", \"\", DATA_INT, dev->protocol_num,\n                \"name\", \"\", DATA_STRING, dev->name,\n                \"mod\", \"\", DATA_INT, dev->modulation,\n                \"short\", \"\", DATA_DOUBLE, dev->short_width,\n                \"long\", \"\", DATA_DOUBLE, dev->long_width,\n                \"reset\", \"\", DATA_DOUBLE, dev->reset_limit,\n                \"gap\", \"\", DATA_DOUBLE, dev->gap_limit,\n                \"sync\", \"\", DATA_DOUBLE, dev->sync_width,\n                \"tolerance\", \"\", DATA_DOUBLE, dev->tolerance,\n                \"fields\", \"\", DATA_ARRAY, data_array(fields_len, DATA_STRING, dev->fields),\n                \"def\", \"\", DATA_INT, dev->disabled == 0,\n                \"en\", \"\", DATA_INT, enabled,\n                \"verbose\", \"\", DATA_INT, dev->verbose,\n                \"verbose_bits\", \"\", DATA_INT, dev->verbose_bits,\n                NULL);\n        list_push(&devs, data);\n    }\n\n    // list dynamic protocols (flex decoders and create instances)\n    for (void **iter = cfg->demod->r_devs.elems; iter && *iter; ++iter) {\n        r_device *dev = *iter;\n        if (dev->protocol_num > 0) {\n                continue;\n        }\n        int fields_len = 0;\n        for (char const *const *iter2 = dev->fields; iter2 && *iter2; ++iter2) {\n            fields_len++;\n        }\n        data_t *data = data_make(\n                \"name\", \"\", DATA_STRING, dev->name,\n                \"mod\", \"\", DATA_INT, dev->modulation,\n                \"short\", \"\", DATA_DOUBLE, dev->short_width,\n                \"long\", \"\", DATA_DOUBLE, dev->long_width,\n                \"reset\", \"\", DATA_DOUBLE, dev->reset_limit,\n                \"gap\", \"\", DATA_DOUBLE, dev->gap_limit,\n                \"sync\", \"\", DATA_DOUBLE, dev->sync_width,\n                \"tolerance\", \"\", DATA_DOUBLE, dev->tolerance,\n                \"fields\", \"\", DATA_ARRAY, data_array(fields_len, DATA_STRING, dev->fields),\n                \"en\", \"\", DATA_INT, 1,\n                \"verbose\", \"\", DATA_INT, dev->verbose,\n                \"verbose_bits\", \"\", DATA_INT, dev->verbose_bits,\n                NULL);\n        list_push(&devs, data);\n    }\n\n    data_t *data = data_make(\n            \"protocols\", \"\", DATA_ARRAY, data_array(devs.len, DATA_DATA, devs.elems),\n            NULL);\n    list_free_elems(&devs, NULL);\n    return data;\n}\n\n// very narrowly tailored JSON parsing\n\ntypedef struct rpc rpc_t;\n\ntypedef void (*rpc_response_fn)(rpc_t *rpc, int error_code, char const *message, int is_json);\n\nstruct rpc {\n    struct mg_connection *nc;\n    rpc_response_fn response;\n    int ver;\n    char *method;\n    char *arg;\n    uint32_t val;\n    //list_t params;\n    char *id;\n};\n\nstatic int jsoneq(const char *json, jsmntok_t *tok, const char *s)\n{\n    if (tok->type == JSMN_STRING && (int)strlen(s) == tok->end - tok->start &&\n            strncmp(json + tok->start, s, tok->end - tok->start) == 0) {\n        return 0;\n    }\n    return -1;\n}\n\nstatic char *jsondup(const char *json, jsmntok_t *tok)\n{\n    int len = tok->end - tok->start;\n    char *p = malloc(len + 1);\n    if (!p) {\n        WARN_MALLOC(\"jsondup()\");\n        return NULL;\n    }\n    p[len] = '\\0';\n    return memcpy(p, json + tok->start, len);\n}\n\nstatic char *jsondupq(const char *json, jsmntok_t *tok)\n{\n    int len = tok->end - tok->start + 2;\n    char *p = malloc(len + 1);\n    if (!p) {\n        WARN_MALLOC(\"jsondupq()\");\n        return NULL;\n    }\n    p[len] = '\\0';\n    return memcpy(p, json + tok->start - 1, len);\n}\n\n// {\"cmd\": \"report_meta\", \"arg\": \"utc\", \"val\": 1}\nstatic int json_parse(rpc_t *rpc, struct mg_str const *json)\n{\n    int i;\n    int r;\n    jsmn_parser p;\n    jsmntok_t t[16]; /* We expect no more than 7 tokens */\n\n    char *cmd    = NULL;\n    char *arg    = NULL;\n    uint32_t val = 0;\n\n    jsmn_init(&p);\n    r = jsmn_parse(&p, json->p, json->len, t, sizeof(t) / sizeof(t[0]));\n    if (r < 0) {\n        print_logf(LOG_WARNING, __func__, \"Failed to parse JSON: %d\", r);\n        return -1;\n    }\n\n    /* Assume the top-level element is an object */\n    if (r < 1 || t[0].type != JSMN_OBJECT) {\n        print_log(LOG_WARNING, __func__, \"Object expected\");\n        return -1;\n    }\n\n    /* Loop over all keys of the root object */\n    for (i = 1; i < r; i++) {\n        if (jsoneq(json->p, &t[i], \"cmd\") == 0) {\n            i++;\n            free(cmd);\n            cmd = jsondup(json->p, &t[i]);\n        }\n        else if (jsoneq(json->p, &t[i], \"arg\") == 0) {\n            i++;\n            free(arg);\n            arg = jsondup(json->p, &t[i]);\n        }\n        else if (jsoneq(json->p, &t[i], \"val\") == 0) {\n            i++;\n            char *endptr = NULL;\n            val = strtol(json->p + t[i].start, &endptr, 10);\n            // compare endptr to t[i].end\n        }\n        else {\n            print_logf(LOG_WARNING, __func__, \"Unexpected key: %.*s\", t[i].end - t[i].start, json->p + t[i].start);\n        }\n    }\n\n    if (!cmd) {\n        free(arg);\n        return -1;\n    }\n    rpc->method     = cmd;\n    rpc->arg        = arg;\n    rpc->val        = val;\n    return 0;\n}\n\n// {\"jsonrpc\": \"2.0\", \"method\": \"report_meta\", \"params\": [\"utc\", 1], \"id\": 0}\nstatic int jsonrpc_parse(rpc_t *rpc, struct mg_str const *json)\n{\n    int r;\n    jsmn_parser p;\n    jsmntok_t t[16]; /* We expect no more than 11 tokens */\n\n    char *cmd    = NULL;\n    char *id     = NULL;\n    char *arg    = NULL;\n    uint32_t val = 0;\n\n    jsmn_init(&p);\n    r = jsmn_parse(&p, json->p, json->len, t, sizeof(t) / sizeof(t[0]));\n    if (r < 0) {\n        print_logf(LOG_WARNING, __func__, \"Failed to parse JSON: %d\", r);\n        return -1;\n    }\n\n    /* Assume the top-level element is an object */\n    if (r < 1 || t[0].type != JSMN_OBJECT) {\n        print_log(LOG_WARNING, __func__, \"Object expected\");\n        return -1;\n    }\n\n    /* Loop over all keys of the root object */\n    for (int i = 1; i < r; i++) {\n        if (jsoneq(json->p, &t[i], \"jsonrpc\") == 0) {\n            i++;\n            // (jsoneq(json->p, &t[i], \"2.0\") == 0);\n        }\n        else if (jsoneq(json->p, &t[i], \"method\") == 0) {\n            i++;\n            free(cmd);\n            cmd = jsondup(json->p, &t[i]);\n        }\n        else if (jsoneq(json->p, &t[i], \"id\") == 0) {\n            i++;\n            if (t[i].type == JSMN_STRING) {\n                free(id);\n                id = jsondupq(json->p, &t[i]);\n            }\n            else if (t[i].type == JSMN_PRIMITIVE) {\n                free(id);\n                id = jsondup(json->p, &t[i]);\n            }\n        }\n        else if (jsoneq(json->p, &t[i], \"params\") == 0) {\n            //printf(\"- Params:\\n\");\n            if (t[i + 1].type != JSMN_ARRAY) {\n                continue; /* We expect groups to be an array of strings */\n            }\n            for (int j = 0; j < t[i + 1].size; j++) {\n                jsmntok_t *g = &t[i + j + 2];\n                if (g->type == JSMN_STRING) {\n                    free(arg);\n                    arg = jsondup(json->p, g);\n                }\n                else if (g->type == JSMN_PRIMITIVE) {\n                    // Number, null/true/false not supported\n                    char *endptr = NULL;\n                    val          = strtol(json->p + g->start, &endptr, 10);\n                }\n                //printf(\"  * %.*s\\n\", g->end - g->start, json + g->start);\n            }\n            i += t[i + 1].size + 1;\n        }\n        else {\n            print_logf(LOG_WARNING, __func__, \"Unexpected key: %.*s\", t[i].end - t[i].start, json->p + t[i].start);\n        }\n    }\n\n    if (!cmd) {\n        free(id);\n        free(arg);\n        return -1;\n    }\n    rpc->method  = cmd;\n    rpc->arg     = arg;\n    rpc->val     = val;\n    rpc->id      = id;\n    return 0;\n}\n\nstatic void rpc_exec(rpc_t *rpc, r_cfg_t *cfg)\n{\n    if (!rpc || !rpc->method || !*rpc->method) {\n        rpc->response(rpc, -1, \"Method invalid\", 0);\n    }\n    // Getter\n    else if (!strcmp(rpc->method, \"get_dev_query\")) {\n        rpc->response(rpc, 0, cfg->dev_query, 0);\n    }\n    else if (!strcmp(rpc->method, \"get_dev_info\")) {\n        rpc->response(rpc, 1, cfg->dev_info, 0);\n    }\n    else if (!strcmp(rpc->method, \"get_gain\")) {\n        rpc->response(rpc, 0, cfg->gain_str, 0);\n    }\n\n    else if (!strcmp(rpc->method, \"get_ppm_error\")) {\n        rpc->response(rpc, 2, NULL, cfg->ppm_error);\n    }\n    else if (!strcmp(rpc->method, \"get_hop_interval\")) {\n        rpc->response(rpc, 2, NULL, cfg->hop_time[0]);\n    }\n    else if (!strcmp(rpc->method, \"get_center_frequency\")) {\n        rpc->response(rpc, 3, NULL, cfg->center_frequency); // unsigned\n    }\n    else if (!strcmp(rpc->method, \"get_sample_rate\")) {\n        rpc->response(rpc, 3, NULL, cfg->samp_rate); // unsigned\n    }\n    else if (!strcmp(rpc->method, \"get_grab_mode\")) {\n        rpc->response(rpc, 2, NULL, cfg->grab_mode);\n    }\n    else if (!strcmp(rpc->method, \"get_raw_mode\")) {\n        rpc->response(rpc, 2, NULL, cfg->raw_mode);\n    }\n    else if (!strcmp(rpc->method, \"get_verbosity\")) {\n        rpc->response(rpc, 2, NULL, cfg->verbosity);\n    }\n    else if (!strcmp(rpc->method, \"get_verbose_bits\")) {\n        rpc->response(rpc, 2, NULL, cfg->verbose_bits);\n    }\n    else if (!strcmp(rpc->method, \"get_conversion_mode\")) {\n        rpc->response(rpc, 2, NULL, cfg->conversion_mode);\n    }\n    else if (!strcmp(rpc->method, \"get_stats\")) {\n        char buf[20480]; // we expect the stats string to be around 15k bytes.\n        data_t *data = create_report_data(cfg, 2/*report active devices*/);\n        // flush_report_data(cfg); // snapshot, do not flush\n        data_print_jsons(data, buf, sizeof(buf));\n        rpc->response(rpc, 1, buf, 0);\n        data_free(data);\n    }\n    else if (!strcmp(rpc->method, \"get_meta\")) {\n        char buf[2048]; // we expect the meta string to be around 500 bytes.\n        data_t *data = meta_data(cfg);\n        data_print_jsons(data, buf, sizeof(buf));\n        rpc->response(rpc, 1, buf, 0);\n        data_free(data);\n    }\n    else if (!strcmp(rpc->method, \"get_protocols\")) {\n        char buf[102400]; // we expect the protocol string to be around 80k bytes.\n        data_t *data = protocols_data(cfg);\n        data_print_jsons(data, buf, sizeof(buf));\n        rpc->response(rpc, 1, buf, 0);\n        data_free(data);\n    }\n\n    // Setter\n    else if (!strcmp(rpc->method, \"hop_interval\")) {\n        cfg->hop_time[0] = rpc->val;\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"report_meta\")) {\n        if (!rpc->arg)\n            rpc->response(rpc, -1, \"Missing arg\", 0);\n        else if (!strcasecmp(rpc->arg, \"time\"))\n            cfg->report_time = REPORT_TIME_DATE;\n        else if (!strcasecmp(rpc->arg, \"reltime\"))\n            cfg->report_time = REPORT_TIME_SAMPLES;\n        else if (!strcasecmp(rpc->arg, \"notime\"))\n            cfg->report_time = REPORT_TIME_OFF;\n        else if (!strcasecmp(rpc->arg, \"hires\"))\n            cfg->report_time_hires = rpc->val;\n        else if (!strcasecmp(rpc->arg, \"utc\"))\n            cfg->report_time_utc = rpc->val;\n        else if (!strcasecmp(rpc->arg, \"protocol\"))\n            cfg->report_protocol = rpc->val;\n        else if (!strcasecmp(rpc->arg, \"level\"))\n            cfg->report_meta = rpc->val;\n        else if (!strcasecmp(rpc->arg, \"bits\"))\n            cfg->verbose_bits = rpc->val;\n        else if (!strcasecmp(rpc->arg, \"description\"))\n            cfg->report_description = rpc->val;\n        else\n            cfg->report_meta = rpc->val;\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"convert\")) {\n        cfg->conversion_mode = rpc->val;\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"raw_mode\")) {\n        cfg->raw_mode = rpc->val;\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"verbosity\")) {\n        cfg->verbosity = rpc->val;\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"verbose_bits\")) {\n        cfg->verbose_bits = rpc->val;\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"protocol\")) {\n        // set_protocol(rpc->val);\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n\n    // Apply\n    else if (!strcmp(rpc->method, \"device\")) {\n        if (!rpc->arg)\n            rpc->response(rpc, -1, \"Missing arg\", 0);\n        /*\n        if (cfg->set_dev_query)\n            rpc->response(rpc, -1, \"Try again later\", 0);\n        cfg->set_dev_query = strdup(rpc->arg);\n        if (!cfg->set_dev_query) {\n            WARN_STRDUP(\"rpc_exec()\");\n        }\n        */\n        rpc->response(rpc, -1, \"Not implemented\", 0);\n    }\n    else if (!strcmp(rpc->method, \"gain\")) {\n        if (!rpc->arg)\n            rpc->response(rpc, -1, \"Missing arg\", 0);\n        set_gain_str(cfg, rpc->arg);\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"center_frequency\")) {\n        set_center_freq(cfg, rpc->val);\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"ppm_error\")) {\n        set_freq_correction(cfg, rpc->val);\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n    else if (!strcmp(rpc->method, \"sample_rate\")) {\n        set_sample_rate(cfg, rpc->val);\n        rpc->response(rpc, 0, \"Ok\", 0);\n    }\n\n    // Invalid\n    else {\n        rpc->response(rpc, -1, \"Unknown method\", 0);\n    }\n}\n\n// http server\n\n#define KEEP_ALIVE 60 /* seconds */\n\nstruct http_server_context {\n    struct mg_connection *conn;\n    struct mg_serve_http_opts server_opts;\n    r_cfg_t *cfg;\n    struct data_output *output;\n    ring_list_t *history;\n};\n\nstruct nc_context {\n    int is_chunked;\n};\n\nstatic void handle_options(struct mg_connection *nc, struct http_message *hm)\n{\n    UNUSED(hm);\n    mg_printf(nc,\n            \"HTTP/1.1 204 No Content\\r\\n\"\n            \"Content-Length: 0\\r\\n\"\n            \"Cache-Control: max-age=0, private, must-revalidate\\r\\n\"\n            \"Access-Control-Allow-Origin: *\\r\\n\"\n            \"Access-Control-Expose-Headers:\\r\\n\"\n            \"Access-Control-Allow-Credentials: true\\r\\n\"\n            \"Access-Control-Max-Age: 1728000\\r\\n\"\n            \"Access-Control-Allow-Headers: Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-CSRF-Token\\r\\n\"\n            \"Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,OPTIONS\\r\\n\"\n            \"\\r\\n\");\n}\n\nstatic void handle_get(struct mg_connection *nc, struct http_message *hm, char const *buf, unsigned int len)\n{\n    UNUSED(hm);\n    //mg_send_head(nc, 200, -1, NULL);\n    mg_printf(nc,\n            \"HTTP/1.1 200 OK\\r\\n\"\n            \"Content-Length: %u\\r\\n\"\n            \"\\r\\n\", len);\n    mg_send(nc, buf, (size_t)len);\n}\n\nstatic void handle_redirect(struct mg_connection *nc, struct http_message *hm)\n{\n    // get the host header\n    struct mg_str host = {0};\n    for (int i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {\n        // struct mg_str hn = hm->header_names[i];\n        // struct mg_str hv = hm->header_values[i];\n        // fprintf(stderr, \"Header: %.*s: %.*s\\n\", (int)hn.len, hn.p, (int)hv.len, hv.p);\n        if (mg_vcasecmp(&hm->header_names[i], \"Host\") == 0) {\n            host = hm->header_values[i];\n            break;\n        }\n    }\n\n    mg_printf(nc, \"%s%s%.*s%s\\r\\n\",\n            \"HTTP/1.1 307 Temporary Redirect\\r\\n\",\n            \"Location: http://triq.org/rxui/#\",\n            (int)host.len, host.p,\n            \"\\r\\n\\r\\n\");\n}\n\nstatic void handle_openmetrics(struct mg_connection *nc, struct http_message *hm)\n{\n    if (mg_vcmp(&hm->method, \"GET\") != 0) {\n        mg_http_send_error(nc, 405, NULL); // 405 Method Not Allowed\n        return;\n    }\n\n    struct http_server_context *ctx = nc->user_data;\n    r_cfg_t *cfg = ctx->cfg;\n\n    time_t now;\n    time(&now);\n\n    char buf[2000];\n    int len = snprintf(buf, sizeof(buf),\n            \"# TYPE uptime_seconds counter\\n\"\n            \"# UNIT uptime_seconds seconds\\n\"\n            \"# HELP uptime_seconds Program uptime.\\n\"\n            \"uptime_seconds_total %.1f\\n\"\n            \"uptime_seconds_created %.1f\\n\"\n            \"# TYPE decoder_enabled gauge\\n\"\n            \"# HELP decoder_enabled Number of enabled decoders.\\n\"\n            \"decoder_enabled %u\\n\"\n            \"# TYPE input_uptime_seconds counter\\n\"\n            \"# UNIT input_uptime_seconds seconds\\n\"\n            \"# HELP input_uptime_seconds SDR Receiver uptime.\\n\"\n            \"input_uptime_seconds_total %.1f\\n\"\n            \"input_uptime_seconds_created %.1f\\n\"\n            \"# TYPE input_count_frames counter\\n\"\n            \"# UNIT input_count_frames frames\\n\"\n            \"# HELP input_count_frames Number of SDR frames received.\\n\"\n            \"input_count_frames_total %u\\n\"\n            \"# TYPE input_squelch_frames counter\\n\"\n            \"# UNIT input_squelch_frames frames\\n\"\n            \"# HELP input_squelch_frames Number of SDR frames skipped by squelch.\\n\"\n            \"input_squelch_frames_total %u\\n\"\n            \"# TYPE input_ook_frames counter\\n\"\n            \"# UNIT input_ook_frames frames\\n\"\n            \"# HELP input_ook_frames Number of SDR frames with OOK demodulation.\\n\"\n            \"input_ook_frames_total %u\\n\"\n            \"# TYPE input_fsk_frames counter\\n\"\n            \"# UNIT input_fsk_frames frames\\n\"\n            \"# HELP input_fsk_frames Number of SDR frames with FSK demodulation.\\n\"\n            \"input_fsk_frames_total %u\\n\"\n            \"# TYPE input_event_frames counter\\n\"\n            \"# UNIT input_event_frames frames\\n\"\n            \"# HELP input_event_frames Number of SDR frames with decode events.\\n\"\n            \"input_event_frames_total %u\\n\"\n            \"# EOF\\n\",\n            (float)(now - cfg->running_since), // uptime_seconds_total,\n            (float)cfg->running_since,         // uptime_seconds_created,\n            (unsigned)cfg->demod->r_devs.len,  // decoder_enabled,\n            (float)(now - cfg->sdr_since),     // input_uptime_seconds_total,\n            (float)cfg->sdr_since,             // input_uptime_seconds_created,\n            cfg->total_frames_count,           // input_count_frames_total,\n            cfg->total_frames_squelch,         // input_squelch_frames_total,\n            cfg->total_frames_ook,             // input_ook_frames_total,\n            cfg->total_frames_fsk,             // input_fsk_frames_total,\n            cfg->total_frames_events);         // input_event_frames_total,\n\n    mg_printf(nc,\n            \"HTTP/1.1 200 OK\\r\\n\"\n            \"Content-Length: %u\\r\\n\"\n            \"Content-Type: text/plain; version=0.0.4; charset=utf-8\\r\\n\"\n            \"\\r\\n\",\n            len);\n    mg_send(nc, buf, (size_t)len);\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n}\n\n// reply to ws command\nstatic void rpc_response_ws(rpc_t *rpc, int ret_code, char const *message, int arg)\n{\n    if (ret_code < 0) {\n        mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,\n                \"{\\\"error\\\": {\\\"code\\\": %d, \\\"message\\\": \\\"%s\\\"}}\",\n                ret_code, message);\n    }\n    else if (ret_code == 0 && message) {\n        mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,\n                \"{\\\"result\\\": \\\"%s\\\"}\",\n                message);\n    }\n    else if (ret_code == 0) {\n        mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,\n                \"{\\\"result\\\": null}\");\n    }\n    else if (ret_code == 1) {\n        mg_send_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT, message, strlen(message));\n    }\n    else if (ret_code == 2) {\n        mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,\n                \"{\\\"result\\\": %d}\",\n                arg);\n    }\n    else /* if (ret_code == 3) */ {\n        mg_printf_websocket_frame(rpc->nc, WEBSOCKET_OP_TEXT,\n                \"{\\\"result\\\": %u}\",\n                (unsigned)arg);\n    }\n}\n\n// reply to jsonrpc command\nstatic void rpc_response_jsonrpc(rpc_t *rpc, int ret_code, char const *message, int arg)\n{\n    char const *id = rpc->id ? rpc->id : \"null\";\n    if (ret_code < 0) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"jsonrpc\\\": \\\"2.0\\\", \\\"error\\\": {\\\"code\\\": %d, \\\"message\\\": \\\"%s\\\"}, \\\"id\\\": %s}\",\n                ret_code, message, id);\n    }\n    else if (ret_code == 0 && message) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"jsonrpc\\\": \\\"2.0\\\", \\\"result\\\": \\\"%s\\\", \\\"id\\\": %s}\",\n                message, id);\n    }\n    else if (ret_code == 0) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"jsonrpc\\\": \\\"2.0\\\", \\\"result\\\": null, \\\"id\\\": %s}\",\n                id);\n    }\n    else if (ret_code == 1) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"jsonrpc\\\": \\\"2.0\\\", \\\"result\\\": %s, \\\"id\\\": %s}\",\n                message, id);\n    }\n    else if (ret_code == 2) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"jsonrpc\\\": \\\"2.0\\\", \\\"result\\\": %d, \\\"id\\\": %s}\",\n                arg, id);\n    }\n    else /* if (ret_code == 3) */ {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"jsonrpc\\\": \\\"2.0\\\", \\\"result\\\": %u, \\\"id\\\": %s}\",\n                (unsigned)arg, id);\n    }\n    mg_send_http_chunk(rpc->nc, \"\", 0); /* Send empty chunk, the end of response */\n}\n\n// reply to json command\nstatic void rpc_response_jsoncmd(rpc_t *rpc, int ret_code, char const *message, int arg)\n{\n    if (ret_code < 0) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"error\\\": {\\\"code\\\": %d, \\\"message\\\": \\\"%s\\\"}}\",\n                ret_code, message);\n    }\n    else if (ret_code == 0 &&message) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"result\\\": \\\"%s\\\"}\",\n                message);\n    }\n    else if (ret_code == 0) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"result\\\": null}\");\n    }\n    else if (ret_code == 1) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"result\\\": %s}\",\n                message);\n    }\n    else if (ret_code == 2) {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"result\\\": %d}\",\n                arg);\n    }\n    else /* if (ret_code == 3) */ {\n        mg_printf_http_chunk(rpc->nc,\n                \"{\\\"result\\\": %u}\",\n                (unsigned)arg);\n    }\n    mg_send_http_chunk(rpc->nc, \"\", 0); /* Send empty chunk, the end of response */\n}\n\n// {\"cmd\":\"sample_rate\",\"val\":1024000}\n// http --stream --timeout=70 :8433/events\n//s.a. https://developer.twitter.com/en/docs/tutorials/consuming-streaming-data.html\nstatic void handle_json_events(struct mg_connection *nc, struct http_message *hm)\n{\n    UNUSED(hm);\n    /* Send headers */\n    mg_printf(nc, \"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\");\n\n    /* Mark connection */\n    struct nc_context *ctx = calloc(1, sizeof(*ctx));\n    if (!ctx) {\n        WARN_CALLOC(\"handle_json_events()\");\n        return;\n    }\n    ctx->is_chunked = 1;\n    nc->user_data   = ctx;\n\n    mg_set_timer(nc, mg_time() + KEEP_ALIVE); // set keep alive timer\n}\n\n// (echo \"GET /stream HTTP/1.0\\n\"; sleep 600) | socat - tcp:127.0.0.1:8433\nstatic void handle_json_stream(struct mg_connection *nc, struct http_message *hm)\n{\n    UNUSED(hm);\n    /* Send headers */\n    mg_printf(nc, \"HTTP/1.1 200 OK\\r\\n\\r\\n\");\n\n    /* Mark connection */\n    struct nc_context *ctx = calloc(1, sizeof(*ctx));\n    if (!ctx) {\n        WARN_CALLOC(\"handle_json_stream()\");\n        return;\n    }\n    ctx->is_chunked = 0;\n    nc->user_data   = ctx;\n\n    mg_set_timer(nc, mg_time() + KEEP_ALIVE); // set keep alive timer\n}\n\n// Handles GET with query string and POST with form-encoded body\n// curl -D - 'http://127.0.0.1:8433/cmd?cmd=report_meta&arg=level'\n// curl -D - -d \"cmd=report_meta&arg=level\" -X POST 'http://127.0.0.1:8433/cmd'\n// http :8433/cmd cmd==center_frequency val==868000000'\n// http --form POST :8433/cmd cmd=report_meta arg=level val=1\n// xh :8433/cmd cmd==center_frequency val==433920123\n// xh :8433/cmd cmd==sample_rate val==250000\n// xh :8433/cmd cmd==gain arg==10\nstatic void handle_cmd_rpc(struct mg_connection *nc, struct http_message *hm)\n{\n    struct http_server_context *ctx = nc->user_data;\n    char cmd[100], arg[100], val[100];\n    rpc_t rpc = {\n            .nc = nc,\n            .response = rpc_response_jsoncmd,\n            .method = cmd,\n            .arg = arg,\n    };\n\n    /* Send headers */\n    mg_printf(nc, \"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\");\n\n    /* Get URL variables */\n    if (mg_vcmp(&hm->method, \"GET\") == 0) {\n        mg_get_http_var(&hm->query_string, \"cmd\", cmd, sizeof(cmd));\n        mg_get_http_var(&hm->query_string, \"arg\", arg, sizeof(arg));\n        mg_get_http_var(&hm->query_string, \"val\", val, sizeof(val));\n    }\n    /* Get form variables */\n    else {\n        mg_get_http_var(&hm->body, \"cmd\", cmd, sizeof(cmd));\n        mg_get_http_var(&hm->body, \"arg\", arg, sizeof(arg));\n        mg_get_http_var(&hm->body, \"val\", val, sizeof(val));\n    }\n    char *endptr = NULL;\n    rpc.val = strtol(val, &endptr, 10);\n    fprintf(stderr, \"POST Got %s, arg %s, val %s (%u)\\n\", cmd, arg, val, rpc.val);\n\n    rpc_exec(&rpc, ctx->cfg);\n}\n\n// Handles POST with JSONRPC command\n// http POST :8433/jsonrpc jsonrpc=2.0 method=sample_rate params:='[1024000]'\nstatic void handle_json_rpc(struct mg_connection *nc, struct http_message *hm)\n{\n    struct http_server_context *ctx = nc->user_data;\n\n    rpc_t rpc = {\n            .nc       = nc,\n            .response = rpc_response_jsonrpc,\n    };\n\n    /* Send headers */\n    mg_printf(nc, \"HTTP/1.1 200 OK\\r\\nTransfer-Encoding: chunked\\r\\n\\r\\n\");\n\n    /* Parse JSON */\n    int ret = jsonrpc_parse(&rpc, &hm->body);\n    if (!ret) {\n        rpc_exec(&rpc, ctx->cfg);\n    }\n    else {\n        char *error = \"{\\\"error\\\":\\\"Invalid command\\\"}\";\n        mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, error, strlen(error));\n    }\n\n    free(rpc.method);\n    free(rpc.id);\n    free(rpc.arg);\n}\n\n// Handles WS with JSON command\nstatic void handle_ws_rpc(struct mg_connection *nc, struct websocket_message *wm)\n{\n    struct http_server_context *ctx = nc->user_data;\n\n    rpc_t rpc = {\n            .nc       = nc,\n            .response = rpc_response_ws,\n    };\n\n    struct mg_str d = {(char *)wm->data, wm->size};\n\n    /* Parse JSON */\n    int ret = json_parse(&rpc, &d);\n    if (!ret) {\n        rpc_exec(&rpc, ctx->cfg);\n    }\n    else {\n        char *error = \"{\\\"error\\\":\\\"Invalid command\\\"}\";\n        mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, error, strlen(error));\n    }\n\n    free(rpc.method);\n    free(rpc.id);\n    free(rpc.arg);\n}\n\nstatic void ev_handler(struct mg_connection *nc, int ev, void *ev_data);\n\nstatic void send_keep_alive(struct mg_connection *nc)\n{\n    if (nc->handler != ev_handler)\n        return; // this should not happen\n\n    struct nc_context *ctx = nc->user_data;\n    if (!ctx)\n        return; // this should not happen\n\n    if (ctx->is_chunked) {\n        mg_send_http_chunk(nc, \"\\r\\n\", 2);\n    }\n    else {\n        mg_send(nc, \"\\r\\n\", 2);\n    }\n    mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer\n}\n\nstatic void ev_handler(struct mg_connection *nc, int ev, void *ev_data)\n{\n    switch (ev) {\n    case MG_EV_TIMER:\n        send_keep_alive(nc);\n        break;\n    case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {\n        struct http_server_context *ctx = nc->user_data;\n        /* New websocket connection. Send meta. */\n        data_t *meta = meta_data(ctx->cfg);\n        data_output_print(ctx->output, meta);\n        data_free(meta);\n        /* Send history */\n        for (void **iter = ring_list_iter(ctx->history); iter; iter = ring_list_next(ctx->history, iter))\n            mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, (char *)*iter, strlen((char *)*iter));\n        break;\n    }\n    case MG_EV_WEBSOCKET_FRAME: {\n        struct websocket_message *wm = (struct websocket_message *)ev_data;\n\n        handle_ws_rpc(nc, wm);\n        break;\n    }\n    case MG_EV_HTTP_REQUEST: {\n        struct http_message *hm = (struct http_message *)ev_data;\n\n        if (mg_vcmp(&hm->method, \"OPTIONS\") == 0) {\n            handle_options(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/\") == 0) {\n            handle_get(nc, hm, INDEX_HTML, sizeof(INDEX_HTML));\n            handle_redirect(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/ui\") == 0) {\n            handle_redirect(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/jsonrpc\") == 0) {\n            handle_json_rpc(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/cmd\") == 0) {\n            handle_cmd_rpc(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/events\") == 0) {\n            handle_json_events(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/stream\") == 0) {\n            handle_json_stream(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/metrics\") == 0) {\n            handle_openmetrics(nc, hm);\n        }\n        else if (mg_vcmp(&hm->uri, \"/api\") == 0) {\n            //handle_api_query(nc, hm);\n        }\n#ifdef SERVE_STATIC\n        else {\n            struct http_server_context *ctx = nc->user_data;\n            mg_serve_http(nc, hm, ctx->server_opts); /* Serve static content */\n        }\n#endif\n        break;\n    }\n    case MG_EV_CLOSE:\n        //fprintf(stderr, \"MG_EV_CLOSE %p %p %p\\n\", ev_data, nc, nc->user_data);\n        break;\n    default:\n        break;\n    }\n}\n\nstatic int is_websocket(const struct mg_connection *nc)\n{\n    return nc->flags & MG_F_IS_WEBSOCKET;\n}\n\n// event handler to broadcast to all our sockets\nstatic void http_broadcast_send(struct http_server_context *ctx, char const *msg, size_t len)\n{\n    struct mg_connection *nc;\n    struct mg_mgr *mgr = ctx->conn->mgr;\n\n    char *dup = strdup(msg);\n    if (!dup) {\n        WARN_STRDUP(\"http_broadcast_send()\");\n    }\n    else {\n        free(ring_list_push(ctx->history, dup));\n    }\n\n    for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {\n        if (nc->handler != ev_handler)\n            continue;\n\n        struct nc_context *cctx = nc->user_data; // might not be valid\n        if (is_websocket(nc)) {\n            mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, msg, len);\n        }\n        else if (cctx && cctx->is_chunked) {\n            mg_send_http_chunk(nc, msg, len);\n            mg_send_http_chunk(nc, \"\\r\\n\", 2);\n            mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer\n        }\n        else if (cctx && !cctx->is_chunked) {\n            mg_send(nc, msg, len);\n            mg_send(nc, \"\\r\\n\", 2);\n            mg_set_timer(nc, mg_time() + KEEP_ALIVE); // reset keep alive timer\n        }\n    }\n}\n\nstatic struct http_server_context *http_server_start(struct mg_mgr *mgr, char const *host, char const *port, r_cfg_t *cfg, struct data_output *output)\n{\n    struct mg_bind_opts bind_opts;\n    const char *err_str;\n\n    //struct http_server_context\n    struct http_server_context *ctx = calloc(1, sizeof(struct http_server_context));\n    if (!ctx) {\n        WARN_CALLOC(\"http_server_start()\");\n        return NULL;\n    }\n\n    ctx->cfg     = cfg;\n    ctx->output  = output;\n    ctx->history = ring_list_new(DEFAULT_HISTORY_SIZE);\n\n    char address[253 + 6 + 1]; // dns max + port\n    // if the host is an IPv6 address it needs quoting\n    if (strchr(host, ':'))\n        snprintf(address, sizeof(address), \"[%s]:%s\", host, port);\n    else\n        snprintf(address, sizeof(address), \"%s:%s\", host, port);\n\n    /* Set HTTP server options */\n    memset(&bind_opts, 0, sizeof(bind_opts));\n    bind_opts.user_data = ctx;\n    bind_opts.error_string = &err_str;\n\n    ctx->conn = mg_bind_opt(mgr, address, ev_handler, bind_opts);\n    if (ctx->conn == NULL) {\n        print_logf(LOG_ERROR, __func__, \"Error starting server on address %s: %s\", address,\n                *bind_opts.error_string);\n        ring_list_free(ctx->history);\n        free(ctx);\n        return NULL;\n    }\n\n    mg_set_protocol_http_websocket(ctx->conn);\n    ctx->server_opts.document_root            = \".\"; // Serve current directory\n    ctx->server_opts.enable_directory_listing = \"yes\";\n\n    print_logf(LOG_NOTICE, \"HTTP server\", \"Serving HTTP-API on address %s, serving %s\", address,\n            ctx->server_opts.document_root);\n\n    return ctx;\n}\n\n#define SHUTDOWN_JSON \"{\\\"shutdown\\\":\\\"goodbye\\\"}\"\n\nstatic int http_server_stop(struct http_server_context *ctx)\n{\n    if (!ctx)\n        return 0;\n\n    // close the server\n    ctx->conn->user_data = NULL;\n    ctx->conn->flags |= MG_F_CLOSE_IMMEDIATELY;\n\n    // close connections with a goodbye\n    struct mg_mgr *mgr = ctx->conn->mgr;\n    for (struct mg_connection *nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {\n        if (nc->handler != ev_handler)\n            continue;\n\n        struct nc_context *cctx = nc->user_data; // might not be valid\n        if (is_websocket(nc)) {\n            mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);\n        }\n        else if (cctx && cctx->is_chunked) {\n            mg_send_http_chunk(nc, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);\n            mg_send_http_chunk(nc, \"\\r\\n\", 2);\n            mg_send_http_chunk(nc, \"\", 0);            /* Send empty chunk, the end of response */\n        }\n        else if (cctx && !cctx->is_chunked) {\n            mg_send(nc, SHUTDOWN_JSON, sizeof(SHUTDOWN_JSON) - 1);\n            mg_send(nc, \"\\r\\n\", 2);\n        }\n    }\n\n    for (void **iter = ring_list_iter(ctx->history); iter; iter = ring_list_next(ctx->history, iter))\n        free((data_t *)*iter);\n    ring_list_free(ctx->history);\n\n    free(ctx);\n\n    return 0;\n}\n\n/* HTTP data output */\n\ntypedef struct {\n    struct data_output output;\n    struct http_server_context *server;\n} data_output_http_t;\n\nstatic void R_API_CALLCONV print_http_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_output_http_t *http = (data_output_http_t *)output;\n\n    // collect well-known top level keys\n    data_t *data_model = NULL;\n    for (data_t *d = data; d; d = d->next) {\n        if (!strcmp(d->key, \"model\"))\n            data_model = d;\n    }\n\n    if (data_model) {\n        // \"events\"\n        char buf[2048]; // we expect the biggest strings to be around 500 bytes.\n        size_t len = data_print_jsons(data, buf, sizeof(buf));\n        http_broadcast_send(http->server, buf, len);\n    }\n    else {\n        // \"states\"\n        size_t buf_size = 20000; // state message need a large buffer\n        char *buf       = malloc(buf_size);\n        if (!buf) {\n            WARN_MALLOC(\"print_http_data()\");\n            return; // NOTE: skip output on alloc failure.\n        }\n        size_t len = data_print_jsons(data, buf, buf_size);\n        http_broadcast_send(http->server, buf, len);\n        free(buf);\n    }\n}\n\nstatic void R_API_CALLCONV data_output_http_free(data_output_t *output)\n{\n    data_output_http_t *http = (data_output_http_t *)output;\n\n    if (!http)\n        return;\n\n    http_server_stop(http->server);\n\n    free(http);\n}\n\nstruct data_output *data_output_http_create(struct mg_mgr *mgr, char const *host, char const *port, r_cfg_t *cfg)\n{\n    data_output_http_t *http = calloc(1, sizeof(data_output_http_t));\n    if (!http) {\n        WARN_CALLOC(\"data_output_http_create()\");\n        return NULL;\n    }\n\n    http->output.log_level    = LOG_TRACE; // sensible default, not parsed from args\n    http->output.print_data   = print_http_data;\n    http->output.output_free  = data_output_http_free;\n\n    http->server = http_server_start(mgr, host, port, cfg, &http->output);\n    if (!http->server) {\n        exit(1);\n    }\n\n    return (struct data_output *)http;\n}\n"
  },
  {
    "path": "src/jsmn.c",
    "content": "#include \"jsmn.h\"\n\n/**\n * Allocates a fresh unused token from the token pool.\n */\nstatic jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,\n\t\tjsmntok_t *tokens, size_t num_tokens) {\n\tjsmntok_t *tok;\n\tif (parser->toknext >= num_tokens) {\n\t\treturn NULL;\n\t}\n\ttok = &tokens[parser->toknext++];\n\ttok->start = tok->end = -1;\n\ttok->size = 0;\n#ifdef JSMN_PARENT_LINKS\n\ttok->parent = -1;\n#endif\n\treturn tok;\n}\n\n/**\n * Fills token type and boundaries.\n */\nstatic void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,\n                            int start, int end) {\n\ttoken->type = type;\n\ttoken->start = start;\n\ttoken->end = end;\n\ttoken->size = 0;\n}\n\n/**\n * Fills next available token with JSON primitive.\n */\nstatic int jsmn_parse_primitive(jsmn_parser *parser, const char *js,\n\t\tsize_t len, jsmntok_t *tokens, size_t num_tokens) {\n\tjsmntok_t *token;\n\tint start;\n\n\tstart = parser->pos;\n\n\tfor (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {\n\t\tswitch (js[parser->pos]) {\n#ifndef JSMN_STRICT\n\t\t\t/* In strict mode primitive must be followed by \",\" or \"}\" or \"]\" */\n\t\t\tcase ':':\n#endif\n\t\t\tcase '\\t' : case '\\r' : case '\\n' : case ' ' :\n\t\t\tcase ','  : case ']'  : case '}' :\n\t\t\t\tgoto found;\n\t\t}\n\t\tif (js[parser->pos] < 32 || js[parser->pos] >= 127) {\n\t\t\tparser->pos = start;\n\t\t\treturn JSMN_ERROR_INVAL;\n\t\t}\n\t}\n#ifdef JSMN_STRICT\n\t/* In strict mode primitive must be followed by a comma/object/array */\n\tparser->pos = start;\n\treturn JSMN_ERROR_PART;\n#endif\n\nfound:\n\tif (tokens == NULL) {\n\t\tparser->pos--;\n\t\treturn 0;\n\t}\n\ttoken = jsmn_alloc_token(parser, tokens, num_tokens);\n\tif (token == NULL) {\n\t\tparser->pos = start;\n\t\treturn JSMN_ERROR_NOMEM;\n\t}\n\tjsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);\n#ifdef JSMN_PARENT_LINKS\n\ttoken->parent = parser->toksuper;\n#endif\n\tparser->pos--;\n\treturn 0;\n}\n\n/**\n * Fills next token with JSON string.\n */\nstatic int jsmn_parse_string(jsmn_parser *parser, const char *js,\n\t\tsize_t len, jsmntok_t *tokens, size_t num_tokens) {\n\tjsmntok_t *token;\n\n\tint start = parser->pos;\n\n\tparser->pos++;\n\n\t/* Skip starting quote */\n\tfor (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {\n\t\tchar c = js[parser->pos];\n\n\t\t/* Quote: end of string */\n\t\tif (c == '\\\"') {\n\t\t\tif (tokens == NULL) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\ttoken = jsmn_alloc_token(parser, tokens, num_tokens);\n\t\t\tif (token == NULL) {\n\t\t\t\tparser->pos = start;\n\t\t\t\treturn JSMN_ERROR_NOMEM;\n\t\t\t}\n\t\t\tjsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);\n#ifdef JSMN_PARENT_LINKS\n\t\t\ttoken->parent = parser->toksuper;\n#endif\n\t\t\treturn 0;\n\t\t}\n\n\t\t/* Backslash: Quoted symbol expected */\n\t\tif (c == '\\\\' && parser->pos + 1 < len) {\n\t\t\tint i;\n\t\t\tparser->pos++;\n\t\t\tswitch (js[parser->pos]) {\n\t\t\t\t/* Allowed escaped symbols */\n\t\t\t\tcase '\\\"': case '/' : case '\\\\' : case 'b' :\n\t\t\t\tcase 'f' : case 'r' : case 'n'  : case 't' :\n\t\t\t\t\tbreak;\n\t\t\t\t/* Allows escaped symbol \\uXXXX */\n\t\t\t\tcase 'u':\n\t\t\t\t\tparser->pos++;\n\t\t\t\t\tfor(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\\0'; i++) {\n\t\t\t\t\t\t/* If it isn't a hex character we have an error */\n\t\t\t\t\t\tif(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */\n\t\t\t\t\t\t\t\t\t(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */\n\t\t\t\t\t\t\t\t\t(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */\n\t\t\t\t\t\t\tparser->pos = start;\n\t\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparser->pos++;\n\t\t\t\t\t}\n\t\t\t\t\tparser->pos--;\n\t\t\t\t\tbreak;\n\t\t\t\t/* Unexpected symbol */\n\t\t\t\tdefault:\n\t\t\t\t\tparser->pos = start;\n\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t}\n\t\t}\n\t}\n\tparser->pos = start;\n\treturn JSMN_ERROR_PART;\n}\n\n/**\n * Parse JSON string and fill tokens.\n */\nint jsmn_parse(jsmn_parser *parser, const char *js, size_t len,\n\t\tjsmntok_t *tokens, unsigned int num_tokens) {\n\tint r;\n\tint i;\n\tjsmntok_t *token;\n\tint count = parser->toknext;\n\n\tfor (; parser->pos < len && js[parser->pos] != '\\0'; parser->pos++) {\n\t\tchar c;\n\t\tjsmntype_t type;\n\n\t\tc = js[parser->pos];\n\t\tswitch (c) {\n\t\t\tcase '{': case '[':\n\t\t\t\tcount++;\n\t\t\t\tif (tokens == NULL) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ttoken = jsmn_alloc_token(parser, tokens, num_tokens);\n\t\t\t\tif (token == NULL)\n\t\t\t\t\treturn JSMN_ERROR_NOMEM;\n\t\t\t\tif (parser->toksuper != -1) {\n\t\t\t\t\ttokens[parser->toksuper].size++;\n#ifdef JSMN_PARENT_LINKS\n\t\t\t\t\ttoken->parent = parser->toksuper;\n#endif\n\t\t\t\t}\n\t\t\t\ttoken->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);\n\t\t\t\ttoken->start = parser->pos;\n\t\t\t\tparser->toksuper = parser->toknext - 1;\n\t\t\t\tbreak;\n\t\t\tcase '}': case ']':\n\t\t\t\tif (tokens == NULL)\n\t\t\t\t\tbreak;\n\t\t\t\ttype = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);\n#ifdef JSMN_PARENT_LINKS\n\t\t\t\tif (parser->toknext < 1) {\n\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t}\n\t\t\t\ttoken = &tokens[parser->toknext - 1];\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (token->start != -1 && token->end == -1) {\n\t\t\t\t\t\tif (token->type != type) {\n\t\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttoken->end = parser->pos + 1;\n\t\t\t\t\t\tparser->toksuper = token->parent;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (token->parent == -1) {\n\t\t\t\t\t\tif(token->type != type || parser->toksuper == -1) {\n\t\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\ttoken = &tokens[token->parent];\n\t\t\t\t}\n#else\n\t\t\t\tfor (i = parser->toknext - 1; i >= 0; i--) {\n\t\t\t\t\ttoken = &tokens[i];\n\t\t\t\t\tif (token->start != -1 && token->end == -1) {\n\t\t\t\t\t\tif (token->type != type) {\n\t\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tparser->toksuper = -1;\n\t\t\t\t\t\ttoken->end = parser->pos + 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t/* Error if unmatched closing bracket */\n\t\t\t\tif (i == -1) return JSMN_ERROR_INVAL;\n\t\t\t\tfor (; i >= 0; i--) {\n\t\t\t\t\ttoken = &tokens[i];\n\t\t\t\t\tif (token->start != -1 && token->end == -1) {\n\t\t\t\t\t\tparser->toksuper = i;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n#endif\n\t\t\t\tbreak;\n\t\t\tcase '\\\"':\n\t\t\t\tr = jsmn_parse_string(parser, js, len, tokens, num_tokens);\n\t\t\t\tif (r < 0) return r;\n\t\t\t\tcount++;\n\t\t\t\tif (parser->toksuper != -1 && tokens != NULL)\n\t\t\t\t\ttokens[parser->toksuper].size++;\n\t\t\t\tbreak;\n\t\t\tcase '\\t' : case '\\r' : case '\\n' : case ' ':\n\t\t\t\tbreak;\n\t\t\tcase ':':\n\t\t\t\tparser->toksuper = parser->toknext - 1;\n\t\t\t\tbreak;\n\t\t\tcase ',':\n\t\t\t\tif (tokens != NULL && parser->toksuper != -1 &&\n\t\t\t\t\t\ttokens[parser->toksuper].type != JSMN_ARRAY &&\n\t\t\t\t\t\ttokens[parser->toksuper].type != JSMN_OBJECT) {\n#ifdef JSMN_PARENT_LINKS\n\t\t\t\t\tparser->toksuper = tokens[parser->toksuper].parent;\n#else\n\t\t\t\t\tfor (i = parser->toknext - 1; i >= 0; i--) {\n\t\t\t\t\t\tif (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {\n\t\t\t\t\t\t\tif (tokens[i].start != -1 && tokens[i].end == -1) {\n\t\t\t\t\t\t\t\tparser->toksuper = i;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n#endif\n\t\t\t\t}\n\t\t\t\tbreak;\n#ifdef JSMN_STRICT\n\t\t\t/* In strict mode primitives are: numbers and booleans */\n\t\t\tcase '-': case '0': case '1' : case '2': case '3' : case '4':\n\t\t\tcase '5': case '6': case '7' : case '8': case '9':\n\t\t\tcase 't': case 'f': case 'n' :\n\t\t\t\t/* And they must not be keys of the object */\n\t\t\t\tif (tokens != NULL && parser->toksuper != -1) {\n\t\t\t\t\tjsmntok_t *t = &tokens[parser->toksuper];\n\t\t\t\t\tif (t->type == JSMN_OBJECT ||\n\t\t\t\t\t\t\t(t->type == JSMN_STRING && t->size != 0)) {\n\t\t\t\t\t\treturn JSMN_ERROR_INVAL;\n\t\t\t\t\t}\n\t\t\t\t}\n#else\n\t\t\t/* In non-strict mode every unquoted value is a primitive */\n\t\t\tdefault:\n#endif\n\t\t\t\tr = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);\n\t\t\t\tif (r < 0) return r;\n\t\t\t\tcount++;\n\t\t\t\tif (parser->toksuper != -1 && tokens != NULL)\n\t\t\t\t\ttokens[parser->toksuper].size++;\n\t\t\t\tbreak;\n\n#ifdef JSMN_STRICT\n\t\t\t/* Unexpected char in strict mode */\n\t\t\tdefault:\n\t\t\t\treturn JSMN_ERROR_INVAL;\n#endif\n\t\t}\n\t}\n\n\tif (tokens != NULL) {\n\t\tfor (i = parser->toknext - 1; i >= 0; i--) {\n\t\t\t/* Unmatched opened object or array */\n\t\t\tif (tokens[i].start != -1 && tokens[i].end == -1) {\n\t\t\t\treturn JSMN_ERROR_PART;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn count;\n}\n\n/**\n * Creates a new parser based over a given  buffer with an array of tokens\n * available.\n */\nvoid jsmn_init(jsmn_parser *parser) {\n\tparser->pos = 0;\n\tparser->toknext = 0;\n\tparser->toksuper = -1;\n}\n"
  },
  {
    "path": "src/list.c",
    "content": "/** @file\n    Generic list.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"list.h\"\n#include \"fatal.h\"\n#include <stdlib.h>\n#include <stdio.h>\n\nvoid list_ensure_size(list_t *list, size_t min_size)\n{\n    if (!list->elems || list->size < min_size) {\n        // the input pointer is still valid if reallocation fails\n        void *elems_realloc = realloc(list->elems, min_size * sizeof(*list->elems));\n        if (!elems_realloc) {\n            FATAL_REALLOC(\"list_ensure_size()\");\n        }\n        list->elems = elems_realloc;\n        list->size  = min_size;\n\n        list->elems[list->len] = NULL; // ensure a terminating NULL\n    }\n}\n\nvoid list_push(list_t *list, void *p)\n{\n    if (list->len + 1 >= list->size) // account for terminating NULL\n        list_ensure_size(list, list->size < 8 ? 8 : list->size + list->size / 2);\n\n    list->elems[list->len++] = p;\n\n    list->elems[list->len] = NULL; // ensure a terminating NULL\n}\n\nvoid list_push_all(list_t *list, void **p)\n{\n    for (void **iter = p; iter && *iter; ++iter)\n        list_push(list, *iter);\n}\n\nvoid list_remove(list_t *list, size_t idx, list_elem_free_fn elem_free)\n{\n    if (idx >= list->len) {\n        return; // report error?\n    }\n    if (elem_free) {\n        elem_free(list->elems[idx]);\n    }\n    for (size_t i = idx; i < list->len; ++i) { // list might contain NULLs\n        list->elems[i] = list->elems[i + 1]; // ensures a terminating NULL\n    }\n    list->len--;\n}\n\n#if defined(__clang__)\n    // ignore \"call to function _free through pointer to incorrect function type\"\n    __attribute__((no_sanitize(\"undefined\")))\n#endif\nvoid list_clear(list_t *list, list_elem_free_fn elem_free)\n{\n    if (elem_free) {\n        for (size_t i = 0; i < list->len; ++i) { // list might contain NULLs\n            elem_free(list->elems[i]);\n        }\n    }\n    list->len = 0;\n    if (list->elems) {\n        list->elems[0] = NULL; // ensure a terminating NULL\n    }\n}\n\nvoid list_free_elems(list_t *list, list_elem_free_fn elem_free)\n{\n    list_clear(list, elem_free);\n    free(list->elems);\n    list->elems = NULL;\n    list->size  = 0;\n}\n"
  },
  {
    "path": "src/logger.c",
    "content": "/** @file\n    Basic logging.\n\n    Copyright (C) 2021 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n#include <logger.h>\n\nstatic r_logger_handler logger_handler = NULL;\nstatic void *logger_handler_userdata   = NULL;\n\nstatic void default_handler(log_level_t level, char const *src, char const *msg)\n{\n    (void)level;\n    fprintf(stderr, \"%s: %s\\n\", src, msg);\n}\n\nvoid r_logger_set_log_handler(r_logger_handler const handler, void *userdata)\n{\n    logger_handler = handler;\n    logger_handler_userdata = userdata;\n}\n\nvoid print_log(log_level_t level, char const *src, char const *msg)\n{\n    if (logger_handler) {\n        logger_handler(level, src, msg, logger_handler_userdata);\n    }\n    else {\n        default_handler(level, src, msg);\n    }\n}\n\nvoid print_logf(log_level_t level, char const *src, char const *fmt, ...)\n{\n    char msg[256];\n    va_list ap;\n    va_start(ap, fmt);\n    vsnprintf(msg, sizeof(msg), fmt, ap);\n    va_end(ap);\n    print_log(level, src, msg);\n}\n"
  },
  {
    "path": "src/mongoose.c",
    "content": "#include \"mongoose.h\"\n/* MSG_NOSIGNAL is Linux and most BSDs only, not macOS or Windows */\n#ifndef MSG_NOSIGNAL\n#define MSG_NOSIGNAL 0\n#endif\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_internal.h\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_INTERNAL_H_\n#define CS_MONGOOSE_SRC_INTERNAL_H_\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n\n#ifndef MBUF_REALLOC\n#define MBUF_REALLOC MG_REALLOC\n#endif\n\n#ifndef MBUF_FREE\n#define MBUF_FREE MG_FREE\n#endif\n\n#define MG_SET_PTRPTR(_ptr, _v) \\\n  do {                          \\\n    if (_ptr) *(_ptr) = _v;     \\\n  } while (0)\n\n#ifndef MG_INTERNAL\n#define MG_INTERNAL static\n#endif\n\n#ifdef PICOTCP\n#define NO_LIBC\n#define MG_DISABLE_PFS\n#endif\n\n/* Amalgamated: #include \"common/cs_dbg.h\" */\n/* Amalgamated: #include \"mg_http.h\" */\n/* Amalgamated: #include \"mg_net.h\" */\n\n#ifndef MG_CTL_MSG_MESSAGE_SIZE\n#define MG_CTL_MSG_MESSAGE_SIZE 8192\n#endif\n\n/* internals that need to be accessible in unit tests */\nMG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,\n                                                int proto,\n                                                union socket_address *sa);\n\nMG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,\n                                 int *proto, char *host, size_t host_len);\nMG_INTERNAL void mg_call(struct mg_connection *nc,\n                         mg_event_handler_t ev_handler, void *user_data, int ev,\n                         void *ev_data);\nvoid mg_forward(struct mg_connection *from, struct mg_connection *to);\nMG_INTERNAL void mg_add_conn(struct mg_mgr *mgr, struct mg_connection *c);\nMG_INTERNAL void mg_remove_conn(struct mg_connection *c);\nMG_INTERNAL struct mg_connection *mg_create_connection(\n    struct mg_mgr *mgr, mg_event_handler_t callback,\n    struct mg_add_sock_opts opts);\n#ifdef _WIN32\n/* Retur value is the same as for MultiByteToWideChar. */\nint to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len);\n#endif\n\nstruct ctl_msg {\n  mg_event_handler_t callback;\n  char message[MG_CTL_MSG_MESSAGE_SIZE];\n};\n\n#if MG_ENABLE_MQTT\nstruct mg_mqtt_message;\n\n#define MG_MQTT_ERROR_INCOMPLETE_MSG -1\n#define MG_MQTT_ERROR_MALFORMED_MSG -2\n\nMG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm);\n#endif\n\n/* Forward declarations for testing. */\nextern void *(*test_malloc)(size_t size);\nextern void *(*test_calloc)(size_t count, size_t size);\n\n#ifndef MIN\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\n#endif\n\n#if MG_ENABLE_HTTP\nstruct mg_serve_http_opts;\n\nMG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data(\n    struct mg_connection *c);\n\n/*\n * Reassemble the content of the buffer (buf, blen) which should be\n * in the HTTP chunked encoding, by collapsing data chunks to the\n * beginning of the buffer.\n *\n * If chunks get reassembled, modify hm->body to point to the reassembled\n * body and fire MG_EV_HTTP_CHUNK event. If handler sets MG_F_DELETE_CHUNK\n * in nc->flags, delete reassembled body from the mbuf.\n *\n * Return reassembled body size.\n */\nMG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,\n                                     struct http_message *hm, char *buf,\n                                     size_t blen);\n\n#if MG_ENABLE_FILESYSTEM\nMG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,\n                                     const struct mg_serve_http_opts *opts,\n                                     char **local_path,\n                                     struct mg_str *remainder);\nMG_INTERNAL time_t mg_parse_date_string(const char *datetime);\nMG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st);\n#endif\n#if MG_ENABLE_HTTP_CGI\nMG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,\n                               const struct mg_str *path_info,\n                               const struct http_message *hm,\n                               const struct mg_serve_http_opts *opts);\nstruct mg_http_proto_data_cgi;\nMG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d);\n#endif\n#if MG_ENABLE_HTTP_SSI\nMG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc,\n                                       struct http_message *hm,\n                                       const char *path,\n                                       const struct mg_serve_http_opts *opts);\n#endif\n#if MG_ENABLE_HTTP_WEBDAV\nMG_INTERNAL int mg_is_dav_request(const struct mg_str *s);\nMG_INTERNAL void mg_handle_propfind(struct mg_connection *nc, const char *path,\n                                    cs_stat_t *stp, struct http_message *hm,\n                                    struct mg_serve_http_opts *opts);\nMG_INTERNAL void mg_handle_lock(struct mg_connection *nc, const char *path);\nMG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path,\n                                 struct http_message *hm);\nMG_INTERNAL void mg_handle_move(struct mg_connection *c,\n                                const struct mg_serve_http_opts *opts,\n                                const char *path, struct http_message *hm);\nMG_INTERNAL void mg_handle_delete(struct mg_connection *nc,\n                                  const struct mg_serve_http_opts *opts,\n                                  const char *path);\nMG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,\n                               struct http_message *hm);\n#endif\n#if MG_ENABLE_HTTP_WEBSOCKET\nMG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev,\n                               void *ev_data MG_UD_ARG(void *user_data));\nMG_INTERNAL void mg_ws_handshake(struct mg_connection *nc,\n                                 const struct mg_str *key,\n                                 struct http_message *);\n#endif\n#endif /* MG_ENABLE_HTTP */\n\nMG_INTERNAL int mg_get_errno(void);\n\nMG_INTERNAL void mg_close_conn(struct mg_connection *conn);\n\n#if MG_ENABLE_SNTP\nMG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,\n                                    struct mg_sntp_message *msg);\n#endif\n\n#endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/mg_mem.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_MG_MEM_H_\n#define CS_COMMON_MG_MEM_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef MG_MALLOC\n#define MG_MALLOC malloc\n#endif\n\n#ifndef MG_CALLOC\n#define MG_CALLOC calloc\n#endif\n\n#ifndef MG_REALLOC\n#define MG_REALLOC realloc\n#endif\n\n#ifndef MG_FREE\n#define MG_FREE free\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_COMMON_MG_MEM_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_base64.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef EXCLUDE_COMMON\n\n/* Amalgamated: #include \"common/cs_base64.h\" */\n\n#include <string.h>\n\n/* Amalgamated: #include \"common/cs_dbg.h\" */\n\n/* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ */\n\n#define NUM_UPPERCASES ('Z' - 'A' + 1)\n#define NUM_LETTERS (NUM_UPPERCASES * 2)\n#define NUM_DIGITS ('9' - '0' + 1)\n\n/*\n * Emit a base64 code char.\n *\n * Doesn't use memory, thus it's safe to use to safely dump memory in crashdumps\n */\nstatic void cs_base64_emit_code(struct cs_base64_ctx *ctx, int v) {\n  if (v < NUM_UPPERCASES) {\n    ctx->b64_putc(v + 'A', ctx->user_data);\n  } else if (v < (NUM_LETTERS)) {\n    ctx->b64_putc(v - NUM_UPPERCASES + 'a', ctx->user_data);\n  } else if (v < (NUM_LETTERS + NUM_DIGITS)) {\n    ctx->b64_putc(v - NUM_LETTERS + '0', ctx->user_data);\n  } else {\n    ctx->b64_putc(v - NUM_LETTERS - NUM_DIGITS == 0 ? '+' : '/',\n                  ctx->user_data);\n  }\n}\n\nstatic void cs_base64_emit_chunk(struct cs_base64_ctx *ctx) {\n  int a, b, c;\n\n  a = ctx->chunk[0];\n  b = ctx->chunk[1];\n  c = ctx->chunk[2];\n\n  cs_base64_emit_code(ctx, a >> 2);\n  cs_base64_emit_code(ctx, ((a & 3) << 4) | (b >> 4));\n  if (ctx->chunk_size > 1) {\n    cs_base64_emit_code(ctx, (b & 15) << 2 | (c >> 6));\n  }\n  if (ctx->chunk_size > 2) {\n    cs_base64_emit_code(ctx, c & 63);\n  }\n}\n\nvoid cs_base64_init(struct cs_base64_ctx *ctx, cs_base64_putc_t b64_putc,\n                    void *user_data) {\n  ctx->chunk_size = 0;\n  ctx->b64_putc = b64_putc;\n  ctx->user_data = user_data;\n}\n\nvoid cs_base64_update(struct cs_base64_ctx *ctx, const char *str, size_t len) {\n  const unsigned char *src = (const unsigned char *) str;\n  size_t i;\n  for (i = 0; i < len; i++) {\n    ctx->chunk[ctx->chunk_size++] = src[i];\n    if (ctx->chunk_size == 3) {\n      cs_base64_emit_chunk(ctx);\n      ctx->chunk_size = 0;\n    }\n  }\n}\n\nvoid cs_base64_finish(struct cs_base64_ctx *ctx) {\n  if (ctx->chunk_size > 0) {\n    int i;\n    memset(&ctx->chunk[ctx->chunk_size], 0, 3 - ctx->chunk_size);\n    cs_base64_emit_chunk(ctx);\n    for (i = 0; i < (3 - ctx->chunk_size); i++) {\n      ctx->b64_putc('=', ctx->user_data);\n    }\n  }\n}\n\n#define BASE64_ENCODE_BODY                                                \\\n  static const char *b64 =                                                \\\n      \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"; \\\n  int i, j, a, b, c;                                                      \\\n                                                                          \\\n  for (i = j = 0; i < src_len; i += 3) {                                  \\\n    a = src[i];                                                           \\\n    b = i + 1 >= src_len ? 0 : src[i + 1];                                \\\n    c = i + 2 >= src_len ? 0 : src[i + 2];                                \\\n                                                                          \\\n    BASE64_OUT(b64[a >> 2]);                                              \\\n    BASE64_OUT(b64[((a & 3) << 4) | (b >> 4)]);                           \\\n    if (i + 1 < src_len) {                                                \\\n      BASE64_OUT(b64[(b & 15) << 2 | (c >> 6)]);                          \\\n    }                                                                     \\\n    if (i + 2 < src_len) {                                                \\\n      BASE64_OUT(b64[c & 63]);                                            \\\n    }                                                                     \\\n  }                                                                       \\\n                                                                          \\\n  while (j % 4 != 0) {                                                    \\\n    BASE64_OUT('=');                                                      \\\n  }                                                                       \\\n  BASE64_FLUSH()\n\n#define BASE64_OUT(ch) \\\n  do {                 \\\n    dst[j++] = (ch);   \\\n  } while (0)\n\n#define BASE64_FLUSH() \\\n  do {                 \\\n    dst[j++] = '\\0';   \\\n  } while (0)\n\nvoid cs_base64_encode(const unsigned char *src, int src_len, char *dst) {\n  BASE64_ENCODE_BODY;\n}\n\n#undef BASE64_OUT\n#undef BASE64_FLUSH\n\n#if CS_ENABLE_STDIO\n#define BASE64_OUT(ch)      \\\n  do {                      \\\n    fprintf(f, \"%c\", (ch)); \\\n    j++;                    \\\n  } while (0)\n\n#define BASE64_FLUSH()\n\nvoid cs_fprint_base64(FILE *f, const unsigned char *src, int src_len) {\n  BASE64_ENCODE_BODY;\n}\n\n#undef BASE64_OUT\n#undef BASE64_FLUSH\n#endif /* CS_ENABLE_STDIO */\n\n/* Convert one byte of encoded base64 input stream to 6-bit chunk */\nstatic unsigned char from_b64(unsigned char ch) {\n  /* Inverse lookup map */\n  static const unsigned char tab[128] = {\n      255, 255, 255, 255,\n      255, 255, 255, 255, /*  0 */\n      255, 255, 255, 255,\n      255, 255, 255, 255, /*  8 */\n      255, 255, 255, 255,\n      255, 255, 255, 255, /*  16 */\n      255, 255, 255, 255,\n      255, 255, 255, 255, /*  24 */\n      255, 255, 255, 255,\n      255, 255, 255, 255, /*  32 */\n      255, 255, 255, 62,\n      255, 255, 255, 63, /*  40 */\n      52,  53,  54,  55,\n      56,  57,  58,  59, /*  48 */\n      60,  61,  255, 255,\n      255, 200, 255, 255, /*  56   '=' is 200, on index 61 */\n      255, 0,   1,   2,\n      3,   4,   5,   6, /*  64 */\n      7,   8,   9,   10,\n      11,  12,  13,  14, /*  72 */\n      15,  16,  17,  18,\n      19,  20,  21,  22, /*  80 */\n      23,  24,  25,  255,\n      255, 255, 255, 255, /*  88 */\n      255, 26,  27,  28,\n      29,  30,  31,  32, /*  96 */\n      33,  34,  35,  36,\n      37,  38,  39,  40, /*  104 */\n      41,  42,  43,  44,\n      45,  46,  47,  48, /*  112 */\n      49,  50,  51,  255,\n      255, 255, 255, 255, /*  120 */\n  };\n  return tab[ch & 127];\n}\n\nint cs_base64_decode(const unsigned char *s, int len, char *dst, int *dec_len) {\n  unsigned char a, b, c, d;\n  int orig_len = len;\n  char *orig_dst = dst;\n  while (len >= 4 && (a = from_b64(s[0])) != 255 &&\n         (b = from_b64(s[1])) != 255 && (c = from_b64(s[2])) != 255 &&\n         (d = from_b64(s[3])) != 255) {\n    s += 4;\n    len -= 4;\n    if (a == 200 || b == 200) break; /* '=' can't be there */\n    *dst++ = a << 2 | b >> 4;\n    if (c == 200) break;\n    *dst++ = b << 4 | c >> 2;\n    if (d == 200) break;\n    *dst++ = c << 6 | d;\n  }\n  *dst = 0;\n  if (dec_len != NULL) *dec_len = (dst - orig_dst);\n  return orig_len - len;\n}\n\n#endif /* EXCLUDE_COMMON */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_dbg.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_CS_DBG_H_\n#define CS_COMMON_CS_DBG_H_\n\n/* Amalgamated: #include \"common/platform.h\" */\n\n#if CS_ENABLE_STDIO\n#include <stdio.h>\n#endif\n\n#ifndef CS_ENABLE_DEBUG\n#define CS_ENABLE_DEBUG 0\n#endif\n\n#ifndef CS_LOG_PREFIX_LEN\n#define CS_LOG_PREFIX_LEN 24\n#endif\n\n#ifndef CS_LOG_ENABLE_TS_DIFF\n#define CS_LOG_ENABLE_TS_DIFF 0\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it.\n */\nenum cs_log_level {\n  LL_NONE = -1,\n  LL_ERROR = 0,\n  LL_WARN = 1,\n  LL_INFO = 2,\n  LL_DEBUG = 3,\n  LL_VERBOSE_DEBUG = 4,\n\n  _LL_MIN = -2,\n  _LL_MAX = 5,\n};\n\n/*\n * Set max log level to print; messages with the level above the given one will\n * not be printed.\n */\nvoid cs_log_set_level(enum cs_log_level level);\n\n/*\n * A comma-separated set of prefix=level.\n * prefix is matched against the log prefix exactly as printed, including line\n * number, but partial match is ok. Check stops on first matching entry.\n * If nothing matches, default level is used.\n *\n * Examples:\n *   main.c:=4 - everything from main C at verbose debug level.\n *   mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_*\n *\n */\nvoid cs_log_set_file_level(const char *file_level);\n\n/*\n * Helper function which prints message prefix with the given `level`.\n * If message should be printed (according to the current log level\n * and filter), prints the prefix and returns 1, otherwise returns 0.\n *\n * Clients should typically just use `LOG()` macro.\n */\nint cs_log_print_prefix(enum cs_log_level level, const char *fname, int line);\n\nextern enum cs_log_level cs_log_level;\n\n#if CS_ENABLE_STDIO\n\n/*\n * Set file to write logs into. If `NULL`, logs go to `stderr`.\n */\nvoid cs_log_set_file(FILE *file);\n\n/*\n * Prints log to the current log file, appends \"\\n\" in the end and flushes the\n * stream.\n */\nvoid cs_log_printf(const char *fmt, ...) PRINTF_LIKE(1, 2);\n\n#if CS_ENABLE_STDIO\n\n/*\n * Format and print message `x` with the given level `l`. Example:\n *\n * ```c\n * LOG(LL_INFO, (\"my info message: %d\", 123));\n * LOG(LL_DEBUG, (\"my debug message: %d\", 123));\n * ```\n */\n#define LOG(l, x)                                     \\\n  do {                                                \\\n    if (cs_log_print_prefix(l, __FILE__, __LINE__)) { \\\n      cs_log_printf x;                                \\\n    }                                                 \\\n  } while (0)\n\n#else\n\n#define LOG(l, x) ((void) l)\n\n#endif\n\n#ifndef CS_NDEBUG\n\n/*\n * Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))`\n */\n#define DBG(x) LOG(LL_VERBOSE_DEBUG, x)\n\n#else /* NDEBUG */\n\n#define DBG(x)\n\n#endif\n\n#else /* CS_ENABLE_STDIO */\n\n#define LOG(l, x)\n#define DBG(x)\n\n#endif\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_CS_DBG_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_dbg.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Amalgamated: #include \"common/cs_dbg.h\" */\n\n#include <stdarg.h>\n#include <stdio.h>\n#include <string.h>\n\n/* Amalgamated: #include \"common/cs_time.h\" */\n/* Amalgamated: #include \"common/str_util.h\" */\n\nenum cs_log_level cs_log_level WEAK =\n#if CS_ENABLE_DEBUG\n    LL_VERBOSE_DEBUG;\n#else\n    LL_ERROR;\n#endif\n\n#if CS_ENABLE_STDIO\nstatic char *s_file_level = NULL;\n\nvoid cs_log_set_file_level(const char *file_level) WEAK;\n\nFILE *cs_log_file WEAK = NULL;\n\n#if CS_LOG_ENABLE_TS_DIFF\ndouble cs_log_ts WEAK;\n#endif\n\nenum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE;\n\nvoid cs_log_set_file_level(const char *file_level) {\n  char *fl = s_file_level;\n  if (file_level != NULL) {\n    s_file_level = strdup(file_level);\n  } else {\n    s_file_level = NULL;\n  }\n  free(fl);\n}\n\nint cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) WEAK;\nint cs_log_print_prefix(enum cs_log_level level, const char *file, int ln) {\n  char prefix[CS_LOG_PREFIX_LEN], *q;\n  const char *p;\n  size_t fl = 0, ll = 0, pl = 0;\n\n  if (level > cs_log_level && s_file_level == NULL) return 0;\n\n  p = file + strlen(file);\n\n  while (p != file) {\n    const char c = *(p - 1);\n    if (c == '/' || c == '\\\\') break;\n    p--;\n    fl++;\n  }\n\n  ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5);\n  if (fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2);\n\n  pl = fl + 1 + ll;\n  memcpy(prefix, p, fl);\n  q = prefix + pl;\n  memset(q, ' ', sizeof(prefix) - pl);\n  do {\n    *(--q) = '0' + (ln % 10);\n    ln /= 10;\n  } while (ln > 0);\n  *(--q) = ':';\n\n  if (s_file_level != NULL) {\n    enum cs_log_level pll = cs_log_level;\n    struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl);\n    struct mg_str k, v;\n    while ((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) {\n      bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0);\n      if (!yes) continue;\n      pll = (enum cs_log_level)(*v.p - '0');\n      break;\n    }\n    if (level > pll) return 0;\n  }\n\n  if (cs_log_file == NULL) cs_log_file = stderr;\n  cs_log_cur_msg_level = level;\n  fwrite(prefix, 1, sizeof(prefix), cs_log_file);\n#if CS_LOG_ENABLE_TS_DIFF\n  {\n    double now = cs_time();\n    fprintf(cs_log_file, \"%7u \", (unsigned int) ((now - cs_log_ts) * 1000000));\n    cs_log_ts = now;\n  }\n#endif\n  return 1;\n}\n\nvoid cs_log_printf(const char *fmt, ...) WEAK;\nvoid cs_log_printf(const char *fmt, ...) {\n  va_list ap;\n  va_start(ap, fmt);\n  vfprintf(cs_log_file, fmt, ap);\n  va_end(ap);\n  fputc('\\n', cs_log_file);\n  fflush(cs_log_file);\n  cs_log_cur_msg_level = LL_NONE;\n}\n\nvoid cs_log_set_file(FILE *file) WEAK;\nvoid cs_log_set_file(FILE *file) {\n  cs_log_file = file;\n}\n\n#else\n\nvoid cs_log_set_file_level(const char *file_level) {\n  (void) file_level;\n}\n\n#endif /* CS_ENABLE_STDIO */\n\nvoid cs_log_set_level(enum cs_log_level level) WEAK;\nvoid cs_log_set_level(enum cs_log_level level) {\n  cs_log_level = level;\n#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO\n  cs_log_ts = cs_time();\n#endif\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_dirent.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_CS_DIRENT_H_\n#define CS_COMMON_CS_DIRENT_H_\n\n#include <limits.h>\n\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifdef CS_DEFINE_DIRENT\ntypedef struct { int dummy; } DIR;\n\nstruct dirent {\n  int d_ino;\n#ifdef _WIN32\n  char d_name[MAX_PATH];\n#else\n  /* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */\n  char d_name[256];\n#endif\n};\n\nDIR *opendir(const char *dir_name);\nint closedir(DIR *dir);\nstruct dirent *readdir(DIR *dir);\n#endif /* CS_DEFINE_DIRENT */\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_CS_DIRENT_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_dirent.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef EXCLUDE_COMMON\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n/* Amalgamated: #include \"common/cs_dirent.h\" */\n\n/*\n * This file contains POSIX opendir/closedir/readdir API implementation\n * for systems which do not natively support it (e.g. Windows).\n */\n\n#ifdef _WIN32\nstruct win32_dir {\n  DIR d;\n  HANDLE handle;\n  WIN32_FIND_DATAW info;\n  struct dirent result;\n};\n\nDIR *opendir(const char *name) {\n  struct win32_dir *dir = NULL;\n  wchar_t wpath[MAX_PATH];\n  DWORD attrs;\n\n  if (name == NULL) {\n    SetLastError(ERROR_BAD_ARGUMENTS);\n  } else if ((dir = (struct win32_dir *) MG_MALLOC(sizeof(*dir))) == NULL) {\n    SetLastError(ERROR_NOT_ENOUGH_MEMORY);\n  } else {\n    to_wchar(name, wpath, ARRAY_SIZE(wpath));\n    attrs = GetFileAttributesW(wpath);\n    if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {\n      (void) wcscat(wpath, L\"\\\\*\");\n      dir->handle = FindFirstFileW(wpath, &dir->info);\n      dir->result.d_name[0] = '\\0';\n    } else {\n      MG_FREE(dir);\n      dir = NULL;\n    }\n  }\n\n  return (DIR *) dir;\n}\n\nint closedir(DIR *d) {\n  struct win32_dir *dir = (struct win32_dir *) d;\n  int result = 0;\n\n  if (dir != NULL) {\n    if (dir->handle != INVALID_HANDLE_VALUE)\n      result = FindClose(dir->handle) ? 0 : -1;\n    MG_FREE(dir);\n  } else {\n    result = -1;\n    SetLastError(ERROR_BAD_ARGUMENTS);\n  }\n\n  return result;\n}\n\nstruct dirent *readdir(DIR *d) {\n  struct win32_dir *dir = (struct win32_dir *) d;\n  struct dirent *result = NULL;\n\n  if (dir) {\n    memset(&dir->result, 0, sizeof(dir->result));\n    if (dir->handle != INVALID_HANDLE_VALUE) {\n      result = &dir->result;\n      (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1,\n                                 result->d_name, sizeof(result->d_name), NULL,\n                                 NULL);\n\n      if (!FindNextFileW(dir->handle, &dir->info)) {\n        (void) FindClose(dir->handle);\n        dir->handle = INVALID_HANDLE_VALUE;\n      }\n\n    } else {\n      SetLastError(ERROR_FILE_NOT_FOUND);\n    }\n  } else {\n    SetLastError(ERROR_BAD_ARGUMENTS);\n  }\n\n  return result;\n}\n#endif\n\n#endif /* EXCLUDE_COMMON */\n\n/* ISO C requires a translation unit to contain at least one declaration */\ntypedef int cs_dirent_dummy;\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_time.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Amalgamated: #include \"common/cs_time.h\" */\n\n#ifndef _WIN32\n#include <stddef.h>\n/*\n * There is no sys/time.h on ARMCC.\n */\n#if !(defined(__ARMCC_VERSION) || defined(__ICCARM__)) && \\\n    !defined(__TI_COMPILER_VERSION__) &&                  \\\n    (!defined(CS_PLATFORM) || CS_PLATFORM != CS_P_NXP_LPC)\n#include <sys/time.h>\n#endif\n#else\n#include <windows.h>\n#endif\n\ndouble cs_time(void) WEAK;\ndouble cs_time(void) {\n  double now;\n#ifndef _WIN32\n  struct timeval tv;\n  if (gettimeofday(&tv, NULL /* tz */) != 0) return 0;\n  now = (double) tv.tv_sec + (((double) tv.tv_usec) / 1000000.0);\n#else\n  SYSTEMTIME sysnow;\n  FILETIME ftime;\n  GetLocalTime(&sysnow);\n  SystemTimeToFileTime(&sysnow, &ftime);\n  /*\n   * 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64\n   * This should not cause a problems in this (21th) century\n   * 2. Windows FILETIME is a number of 100-nanosecond intervals since January\n   * 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC,\n   * thus, we need to convert to seconds and adjust amount (subtract 11644473600\n   * seconds)\n   */\n  now = (double) (((int64_t) ftime.dwLowDateTime +\n                   ((int64_t) ftime.dwHighDateTime << 32)) /\n                  10000000.0) -\n        11644473600;\n#endif /* _WIN32 */\n  return now;\n}\n\ndouble cs_timegm(const struct tm *tm) {\n  /* Month-to-day offset for non-leap-years. */\n  static const int month_day[12] = {0,   31,  59,  90,  120, 151,\n                                    181, 212, 243, 273, 304, 334};\n\n  /* Most of the calculation is easy; leap years are the main difficulty. */\n  int month = tm->tm_mon % 12;\n  int year = tm->tm_year + tm->tm_mon / 12;\n  int year_for_leap;\n  int64_t rt;\n\n  if (month < 0) { /* Negative values % 12 are still negative. */\n    month += 12;\n    --year;\n  }\n\n  /* This is the number of Februaries since 1900. */\n  year_for_leap = (month > 1) ? year + 1 : year;\n\n  rt =\n      tm->tm_sec /* Seconds */\n      +\n      60 *\n          (tm->tm_min /* Minute = 60 seconds */\n           +\n           60 * (tm->tm_hour /* Hour = 60 minutes */\n                 +\n                 24 * (month_day[month] + tm->tm_mday - 1 /* Day = 24 hours */\n                       + 365 * (year - 70)                /* Year = 365 days */\n                       + (year_for_leap - 69) / 4 /* Every 4 years is leap... */\n                       - (year_for_leap - 1) / 100 /* Except centuries... */\n                       + (year_for_leap + 299) / 400))); /* Except 400s. */\n  return rt < 0 ? -1 : (double) rt;\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_endian.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_CS_ENDIAN_H_\n#define CS_COMMON_CS_ENDIAN_H_\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/*\n * clang with std=-c99 uses __LITTLE_ENDIAN, by default\n * while for ex, RTOS gcc - LITTLE_ENDIAN, by default\n * it depends on __USE_BSD, but let's have everything\n */\n#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER)\n#define BYTE_ORDER __BYTE_ORDER\n#ifndef LITTLE_ENDIAN\n#define LITTLE_ENDIAN __LITTLE_ENDIAN\n#endif /* LITTLE_ENDIAN */\n#ifndef BIG_ENDIAN\n#define BIG_ENDIAN __LITTLE_ENDIAN\n#endif /* BIG_ENDIAN */\n#endif /* BYTE_ORDER */\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* CS_COMMON_CS_ENDIAN_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_md5.c\"\n#endif\n/*\n * This code implements the MD5 message-digest algorithm.\n * The algorithm is due to Ron Rivest.  This code was\n * written by Colin Plumb in 1993, no copyright is claimed.\n * This code is in the public domain; do with it what you wish.\n *\n * Equivalent code is available from RSA Data Security, Inc.\n * This code has been tested against that, and is equivalent,\n * except that you don't need to include two pages of legalese\n * with every copy.\n *\n * To compute the message digest of a chunk of bytes, declare an\n * MD5Context structure, pass it to MD5Init, call MD5Update as\n * needed on buffers full of bytes, and then call MD5Final, which\n * will fill a supplied 16-byte array with the digest.\n */\n\n/* Amalgamated: #include \"common/cs_md5.h\" */\n/* Amalgamated: #include \"common/str_util.h\" */\n\n#if !defined(EXCLUDE_COMMON)\n#if !CS_DISABLE_MD5\n\n/* Amalgamated: #include \"common/cs_endian.h\" */\n\nstatic void byteReverse(unsigned char *buf, unsigned longs) {\n/* Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN */\n#if BYTE_ORDER == BIG_ENDIAN\n  do {\n    uint32_t t = (uint32_t)((unsigned) buf[3] << 8 | buf[2]) << 16 |\n                 ((unsigned) buf[1] << 8 | buf[0]);\n    *(uint32_t *) buf = t;\n    buf += 4;\n  } while (--longs);\n#else\n  (void) buf;\n  (void) longs;\n#endif\n}\n\n#define F1(x, y, z) (z ^ (x & (y ^ z)))\n#define F2(x, y, z) F1(z, x, y)\n#define F3(x, y, z) (x ^ y ^ z)\n#define F4(x, y, z) (y ^ (x | ~z))\n\n#define MD5STEP(f, w, x, y, z, data, s) \\\n  (w += f(x, y, z) + data, w = w << s | w >> (32 - s), w += x)\n\n/*\n * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious\n * initialization constants.\n */\nvoid cs_md5_init(cs_md5_ctx *ctx) {\n  ctx->buf[0] = 0x67452301;\n  ctx->buf[1] = 0xefcdab89;\n  ctx->buf[2] = 0x98badcfe;\n  ctx->buf[3] = 0x10325476;\n\n  ctx->bits[0] = 0;\n  ctx->bits[1] = 0;\n}\n\nstatic void cs_md5_transform(uint32_t buf[4], uint32_t const in[16]) {\n  register uint32_t a, b, c, d;\n\n  a = buf[0];\n  b = buf[1];\n  c = buf[2];\n  d = buf[3];\n\n  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);\n  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);\n  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);\n  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);\n  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);\n  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);\n  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);\n  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);\n  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);\n  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);\n  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);\n  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);\n  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);\n  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);\n  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);\n  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);\n\n  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);\n  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);\n  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);\n  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);\n  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);\n  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);\n  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);\n  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);\n  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);\n  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);\n  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);\n  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);\n  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);\n  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);\n  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);\n  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);\n\n  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);\n  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);\n  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);\n  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);\n  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);\n  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);\n  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);\n  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);\n  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);\n  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);\n  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);\n  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);\n  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);\n  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);\n  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);\n  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);\n\n  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);\n  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);\n  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);\n  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);\n  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);\n  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);\n  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);\n  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);\n  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);\n  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);\n  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);\n  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);\n  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);\n  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);\n  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);\n  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);\n\n  buf[0] += a;\n  buf[1] += b;\n  buf[2] += c;\n  buf[3] += d;\n}\n\nvoid cs_md5_update(cs_md5_ctx *ctx, const unsigned char *buf, size_t len) {\n  uint32_t t;\n\n  t = ctx->bits[0];\n  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++;\n  ctx->bits[1] += (uint32_t) len >> 29;\n\n  t = (t >> 3) & 0x3f;\n\n  if (t) {\n    unsigned char *p = (unsigned char *) ctx->in + t;\n\n    t = 64 - t;\n    if (len < t) {\n      memcpy(p, buf, len);\n      return;\n    }\n    memcpy(p, buf, t);\n    byteReverse(ctx->in, 16);\n    cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);\n    buf += t;\n    len -= t;\n  }\n\n  while (len >= 64) {\n    memcpy(ctx->in, buf, 64);\n    byteReverse(ctx->in, 16);\n    cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);\n    buf += 64;\n    len -= 64;\n  }\n\n  memcpy(ctx->in, buf, len);\n}\n\nvoid cs_md5_final(unsigned char *digest, cs_md5_ctx *ctx) {\n  unsigned count;\n  unsigned char *p;\n  uint32_t *a;\n\n  count = (ctx->bits[0] >> 3) & 0x3F;\n\n  p = ctx->in + count;\n  *p++ = 0x80;\n  count = 64 - 1 - count;\n  if (count < 8) {\n    memset(p, 0, count);\n    byteReverse(ctx->in, 16);\n    cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);\n    memset(ctx->in, 0, 56);\n  } else {\n    memset(p, 0, count - 8);\n  }\n  byteReverse(ctx->in, 14);\n\n  a = (uint32_t *) ctx->in;\n  a[14] = ctx->bits[0];\n  a[15] = ctx->bits[1];\n\n  cs_md5_transform(ctx->buf, (uint32_t *) ctx->in);\n  byteReverse((unsigned char *) ctx->buf, 4);\n  memcpy(digest, ctx->buf, 16);\n  memset((char *) ctx, 0, sizeof(*ctx));\n}\n\n#endif /* CS_DISABLE_MD5 */\n#endif /* EXCLUDE_COMMON */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/cs_sha1.c\"\n#endif\n/* Copyright(c) By Steve Reid <steve@edmweb.com> */\n/* 100% Public Domain */\n\n/* Amalgamated: #include \"common/cs_sha1.h\" */\n\n#if !CS_DISABLE_SHA1 && !defined(EXCLUDE_COMMON)\n\n/* Amalgamated: #include \"common/cs_endian.h\" */\n\n#define SHA1HANDSOFF\n#if defined(__sun)\n/* Amalgamated: #include \"common/solarisfixes.h\" */\n#endif\n\nunion char64long16 {\n  unsigned char c[64];\n  uint32_t l[16];\n};\n\n#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))\n\nstatic uint32_t blk0(union char64long16 *block, int i) {\n/* Forrest: SHA expect BIG_ENDIAN, swap if LITTLE_ENDIAN */\n#if BYTE_ORDER == LITTLE_ENDIAN\n  block->l[i] =\n      (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF);\n#endif\n  return block->l[i];\n}\n\n/* Avoid redefine warning (ARM /usr/include/sys/ucontext.h define R0~R4) */\n#undef blk\n#undef R0\n#undef R1\n#undef R2\n#undef R3\n#undef R4\n\n#define blk(i)                                                               \\\n  (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ \\\n                              block->l[(i + 2) & 15] ^ block->l[i & 15],     \\\n                          1))\n#define R0(v, w, x, y, z, i)                                          \\\n  z += ((w & (x ^ y)) ^ y) + blk0(block, i) + 0x5A827999 + rol(v, 5); \\\n  w = rol(w, 30);\n#define R1(v, w, x, y, z, i)                                  \\\n  z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \\\n  w = rol(w, 30);\n#define R2(v, w, x, y, z, i)                          \\\n  z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \\\n  w = rol(w, 30);\n#define R3(v, w, x, y, z, i)                                        \\\n  z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \\\n  w = rol(w, 30);\n#define R4(v, w, x, y, z, i)                          \\\n  z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \\\n  w = rol(w, 30);\n\nvoid cs_sha1_transform(uint32_t state[5], const unsigned char buffer[64]) {\n  uint32_t a, b, c, d, e;\n  union char64long16 block[1];\n\n  memcpy(block, buffer, 64);\n  a = state[0];\n  b = state[1];\n  c = state[2];\n  d = state[3];\n  e = state[4];\n  R0(a, b, c, d, e, 0);\n  R0(e, a, b, c, d, 1);\n  R0(d, e, a, b, c, 2);\n  R0(c, d, e, a, b, 3);\n  R0(b, c, d, e, a, 4);\n  R0(a, b, c, d, e, 5);\n  R0(e, a, b, c, d, 6);\n  R0(d, e, a, b, c, 7);\n  R0(c, d, e, a, b, 8);\n  R0(b, c, d, e, a, 9);\n  R0(a, b, c, d, e, 10);\n  R0(e, a, b, c, d, 11);\n  R0(d, e, a, b, c, 12);\n  R0(c, d, e, a, b, 13);\n  R0(b, c, d, e, a, 14);\n  R0(a, b, c, d, e, 15);\n  R1(e, a, b, c, d, 16);\n  R1(d, e, a, b, c, 17);\n  R1(c, d, e, a, b, 18);\n  R1(b, c, d, e, a, 19);\n  R2(a, b, c, d, e, 20);\n  R2(e, a, b, c, d, 21);\n  R2(d, e, a, b, c, 22);\n  R2(c, d, e, a, b, 23);\n  R2(b, c, d, e, a, 24);\n  R2(a, b, c, d, e, 25);\n  R2(e, a, b, c, d, 26);\n  R2(d, e, a, b, c, 27);\n  R2(c, d, e, a, b, 28);\n  R2(b, c, d, e, a, 29);\n  R2(a, b, c, d, e, 30);\n  R2(e, a, b, c, d, 31);\n  R2(d, e, a, b, c, 32);\n  R2(c, d, e, a, b, 33);\n  R2(b, c, d, e, a, 34);\n  R2(a, b, c, d, e, 35);\n  R2(e, a, b, c, d, 36);\n  R2(d, e, a, b, c, 37);\n  R2(c, d, e, a, b, 38);\n  R2(b, c, d, e, a, 39);\n  R3(a, b, c, d, e, 40);\n  R3(e, a, b, c, d, 41);\n  R3(d, e, a, b, c, 42);\n  R3(c, d, e, a, b, 43);\n  R3(b, c, d, e, a, 44);\n  R3(a, b, c, d, e, 45);\n  R3(e, a, b, c, d, 46);\n  R3(d, e, a, b, c, 47);\n  R3(c, d, e, a, b, 48);\n  R3(b, c, d, e, a, 49);\n  R3(a, b, c, d, e, 50);\n  R3(e, a, b, c, d, 51);\n  R3(d, e, a, b, c, 52);\n  R3(c, d, e, a, b, 53);\n  R3(b, c, d, e, a, 54);\n  R3(a, b, c, d, e, 55);\n  R3(e, a, b, c, d, 56);\n  R3(d, e, a, b, c, 57);\n  R3(c, d, e, a, b, 58);\n  R3(b, c, d, e, a, 59);\n  R4(a, b, c, d, e, 60);\n  R4(e, a, b, c, d, 61);\n  R4(d, e, a, b, c, 62);\n  R4(c, d, e, a, b, 63);\n  R4(b, c, d, e, a, 64);\n  R4(a, b, c, d, e, 65);\n  R4(e, a, b, c, d, 66);\n  R4(d, e, a, b, c, 67);\n  R4(c, d, e, a, b, 68);\n  R4(b, c, d, e, a, 69);\n  R4(a, b, c, d, e, 70);\n  R4(e, a, b, c, d, 71);\n  R4(d, e, a, b, c, 72);\n  R4(c, d, e, a, b, 73);\n  R4(b, c, d, e, a, 74);\n  R4(a, b, c, d, e, 75);\n  R4(e, a, b, c, d, 76);\n  R4(d, e, a, b, c, 77);\n  R4(c, d, e, a, b, 78);\n  R4(b, c, d, e, a, 79);\n  state[0] += a;\n  state[1] += b;\n  state[2] += c;\n  state[3] += d;\n  state[4] += e;\n  /* Erase working structures. The order of operations is important,\n   * used to ensure that compiler doesn't optimize those out. */\n  memset(block, 0, sizeof(block));\n  a = b = c = d = e = 0;\n  (void) a;\n  (void) b;\n  (void) c;\n  (void) d;\n  (void) e;\n}\n\nvoid cs_sha1_init(cs_sha1_ctx *context) {\n  context->state[0] = 0x67452301;\n  context->state[1] = 0xEFCDAB89;\n  context->state[2] = 0x98BADCFE;\n  context->state[3] = 0x10325476;\n  context->state[4] = 0xC3D2E1F0;\n  context->count[0] = context->count[1] = 0;\n}\n\nvoid cs_sha1_update(cs_sha1_ctx *context, const unsigned char *data,\n                    uint32_t len) {\n  uint32_t i, j;\n\n  j = context->count[0];\n  if ((context->count[0] += len << 3) < j) context->count[1]++;\n  context->count[1] += (len >> 29);\n  j = (j >> 3) & 63;\n  if ((j + len) > 63) {\n    memcpy(&context->buffer[j], data, (i = 64 - j));\n    cs_sha1_transform(context->state, context->buffer);\n    for (; i + 63 < len; i += 64) {\n      cs_sha1_transform(context->state, &data[i]);\n    }\n    j = 0;\n  } else\n    i = 0;\n  memcpy(&context->buffer[j], &data[i], len - i);\n}\n\nvoid cs_sha1_final(unsigned char digest[20], cs_sha1_ctx *context) {\n  unsigned i;\n  unsigned char finalcount[8], c;\n\n  for (i = 0; i < 8; i++) {\n    finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >>\n                                      ((3 - (i & 3)) * 8)) &\n                                     255);\n  }\n  c = 0200;\n  cs_sha1_update(context, &c, 1);\n  while ((context->count[0] & 504) != 448) {\n    c = 0000;\n    cs_sha1_update(context, &c, 1);\n  }\n  cs_sha1_update(context, finalcount, 8);\n  for (i = 0; i < 20; i++) {\n    digest[i] =\n        (unsigned char) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);\n  }\n  memset(context, '\\0', sizeof(*context));\n  memset(&finalcount, '\\0', sizeof(finalcount));\n}\n\nvoid cs_hmac_sha1(const unsigned char *key, size_t keylen,\n                  const unsigned char *data, size_t datalen,\n                  unsigned char out[20]) {\n  cs_sha1_ctx ctx;\n  unsigned char buf1[64], buf2[64], tmp_key[20], i;\n\n  if (keylen > sizeof(buf1)) {\n    cs_sha1_init(&ctx);\n    cs_sha1_update(&ctx, key, keylen);\n    cs_sha1_final(tmp_key, &ctx);\n    key = tmp_key;\n    keylen = sizeof(tmp_key);\n  }\n\n  memset(buf1, 0, sizeof(buf1));\n  memset(buf2, 0, sizeof(buf2));\n  memcpy(buf1, key, keylen);\n  memcpy(buf2, key, keylen);\n\n  for (i = 0; i < sizeof(buf1); i++) {\n    buf1[i] ^= 0x36;\n    buf2[i] ^= 0x5c;\n  }\n\n  cs_sha1_init(&ctx);\n  cs_sha1_update(&ctx, buf1, sizeof(buf1));\n  cs_sha1_update(&ctx, data, datalen);\n  cs_sha1_final(out, &ctx);\n\n  cs_sha1_init(&ctx);\n  cs_sha1_update(&ctx, buf2, sizeof(buf2));\n  cs_sha1_update(&ctx, out, 20);\n  cs_sha1_final(out, &ctx);\n}\n\n#endif /* EXCLUDE_COMMON */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/mbuf.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef EXCLUDE_COMMON\n\n#include <assert.h>\n#include <string.h>\n/* Amalgamated: #include \"common/mbuf.h\" */\n\n#ifndef MBUF_REALLOC\n#define MBUF_REALLOC realloc\n#endif\n\n#ifndef MBUF_FREE\n#define MBUF_FREE free\n#endif\n\nvoid mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK;\nvoid mbuf_init(struct mbuf *mbuf, size_t initial_size) {\n  mbuf->len = mbuf->size = 0;\n  mbuf->buf = NULL;\n  mbuf_resize(mbuf, initial_size);\n}\n\nvoid mbuf_free(struct mbuf *mbuf) WEAK;\nvoid mbuf_free(struct mbuf *mbuf) {\n  if (mbuf->buf != NULL) {\n    MBUF_FREE(mbuf->buf);\n    mbuf_init(mbuf, 0);\n  }\n}\n\nvoid mbuf_resize(struct mbuf *a, size_t new_size) WEAK;\nvoid mbuf_resize(struct mbuf *a, size_t new_size) {\n  if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {\n    char *buf = (char *) MBUF_REALLOC(a->buf, new_size);\n    /*\n     * In case realloc fails, there's not much we can do, except keep things as\n     * they are. Note that NULL is a valid return value from realloc when\n     * size == 0, but that is covered too.\n     */\n    if (buf == NULL && new_size != 0) return;\n    a->buf = buf;\n    a->size = new_size;\n  }\n}\n\nvoid mbuf_trim(struct mbuf *mbuf) WEAK;\nvoid mbuf_trim(struct mbuf *mbuf) {\n  mbuf_resize(mbuf, mbuf->len);\n}\n\nsize_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK;\nsize_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {\n  char *p = NULL;\n\n  assert(a != NULL);\n  assert(a->len <= a->size);\n  assert(off <= a->len);\n\n  /* check overflow */\n  if (~(size_t) 0 - (size_t) a->buf < len) return 0;\n\n  if (a->len + len <= a->size) {\n    memmove(a->buf + off + len, a->buf + off, a->len - off);\n    if (buf != NULL) {\n      memcpy(a->buf + off, buf, len);\n    }\n    a->len += len;\n  } else {\n    size_t min_size = (a->len + len);\n    size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER);\n    if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) {\n      new_size = min_size + MBUF_SIZE_MAX_HEADROOM;\n    }\n    p = (char *) MBUF_REALLOC(a->buf, new_size);\n    if (p == NULL && new_size != min_size) {\n      new_size = min_size;\n      p = (char *) MBUF_REALLOC(a->buf, new_size);\n    }\n    if (p != NULL) {\n      a->buf = p;\n      if (off != a->len) {\n        memmove(a->buf + off + len, a->buf + off, a->len - off);\n      }\n      if (buf != NULL) memcpy(a->buf + off, buf, len);\n      a->len += len;\n      a->size = new_size;\n    } else {\n      len = 0;\n    }\n  }\n\n  return len;\n}\n\nsize_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK;\nsize_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {\n  return mbuf_insert(a, a->len, buf, len);\n}\n\nsize_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK;\nsize_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) {\n  size_t ret;\n  /* Optimization: if the buffer is currently empty,\n   * take over the user-provided buffer. */\n  if (a->len == 0) {\n    if (a->buf != NULL) free(a->buf);\n    a->buf = (char *) data;\n    a->len = a->size = len;\n    return len;\n  }\n  ret = mbuf_insert(a, a->len, data, len);\n  free(data);\n  return ret;\n}\n\nvoid mbuf_remove(struct mbuf *mb, size_t n) WEAK;\nvoid mbuf_remove(struct mbuf *mb, size_t n) {\n  if (n > 0 && n <= mb->len) {\n    memmove(mb->buf, mb->buf + n, mb->len - n);\n    mb->len -= n;\n  }\n}\n\nvoid mbuf_clear(struct mbuf *mb) WEAK;\nvoid mbuf_clear(struct mbuf *mb) {\n  mb->len = 0;\n}\n\nvoid mbuf_move(struct mbuf *from, struct mbuf *to) WEAK;\nvoid mbuf_move(struct mbuf *from, struct mbuf *to) {\n  memcpy(to, from, sizeof(*to));\n  memset(from, 0, sizeof(*from));\n}\n\n#endif /* EXCLUDE_COMMON */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/mg_str.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n/* Amalgamated: #include \"common/mg_str.h\" */\n/* Amalgamated: #include \"common/platform.h\" */\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n\nint mg_ncasecmp(const char *s1, const char *s2, size_t len) WEAK;\n\nstruct mg_str mg_mk_str(const char *s) WEAK;\nstruct mg_str mg_mk_str(const char *s) {\n  struct mg_str ret = {s, 0};\n  if (s != NULL) ret.len = strlen(s);\n  return ret;\n}\n\nstruct mg_str mg_mk_str_n(const char *s, size_t len) WEAK;\nstruct mg_str mg_mk_str_n(const char *s, size_t len) {\n  struct mg_str ret = {s, len};\n  return ret;\n}\n\nint mg_vcmp(const struct mg_str *str1, const char *str2) WEAK;\nint mg_vcmp(const struct mg_str *str1, const char *str2) {\n  size_t n2 = strlen(str2), n1 = str1->len;\n  int r = strncmp(str1->p, str2, (n1 < n2) ? n1 : n2);\n  if (r == 0) {\n    return n1 - n2;\n  }\n  return r;\n}\n\nint mg_vcasecmp(const struct mg_str *str1, const char *str2) WEAK;\nint mg_vcasecmp(const struct mg_str *str1, const char *str2) {\n  size_t n2 = strlen(str2), n1 = str1->len;\n  int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2);\n  if (r == 0) {\n    return n1 - n2;\n  }\n  return r;\n}\n\nstatic struct mg_str mg_strdup_common(const struct mg_str s,\n                                      int nul_terminate) {\n  struct mg_str r = {NULL, 0};\n  if (s.len > 0 && s.p != NULL) {\n    char *sc = (char *) MG_MALLOC(s.len + (nul_terminate ? 1 : 0));\n    if (sc != NULL) {\n      memcpy(sc, s.p, s.len);\n      if (nul_terminate) sc[s.len] = '\\0';\n      r.p = sc;\n      r.len = s.len;\n    }\n  }\n  return r;\n}\n\nstruct mg_str mg_strdup(const struct mg_str s) WEAK;\nstruct mg_str mg_strdup(const struct mg_str s) {\n  return mg_strdup_common(s, 0 /* NUL-terminate */);\n}\n\nstruct mg_str mg_strdup_nul(const struct mg_str s) WEAK;\nstruct mg_str mg_strdup_nul(const struct mg_str s) {\n  return mg_strdup_common(s, 1 /* NUL-terminate */);\n}\n\nconst char *mg_strchr(const struct mg_str s, int c) WEAK;\nconst char *mg_strchr(const struct mg_str s, int c) {\n  size_t i;\n  for (i = 0; i < s.len; i++) {\n    if (s.p[i] == c) return &s.p[i];\n  }\n  return NULL;\n}\n\nint mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK;\nint mg_strcmp(const struct mg_str str1, const struct mg_str str2) {\n  size_t i = 0;\n  while (i < str1.len && i < str2.len) {\n    if (str1.p[i] < str2.p[i]) return -1;\n    if (str1.p[i] > str2.p[i]) return 1;\n    i++;\n  }\n  if (i < str1.len) return 1;\n  if (i < str2.len) return -1;\n  return 0;\n}\n\nint mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK;\nint mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {\n  struct mg_str s1 = str1;\n  struct mg_str s2 = str2;\n\n  if (s1.len > n) {\n    s1.len = n;\n  }\n  if (s2.len > n) {\n    s2.len = n;\n  }\n  return mg_strcmp(s1, s2);\n}\n\nvoid mg_strfree(struct mg_str *s) WEAK;\nvoid mg_strfree(struct mg_str *s) {\n  char *sp = (char *) s->p;\n  s->p = NULL;\n  s->len = 0;\n  if (sp != NULL) free(sp);\n}\n\nconst char *mg_strstr(const struct mg_str haystack,\n                      const struct mg_str needle) WEAK;\nconst char *mg_strstr(const struct mg_str haystack,\n                      const struct mg_str needle) {\n  size_t i;\n  if (needle.len > haystack.len) return NULL;\n  for (i = 0; i <= haystack.len - needle.len; i++) {\n    if (memcmp(haystack.p + i, needle.p, needle.len) == 0) {\n      return haystack.p + i;\n    }\n  }\n  return NULL;\n}\n\nstruct mg_str mg_strstrip(struct mg_str s) WEAK;\nstruct mg_str mg_strstrip(struct mg_str s) {\n  while (s.len > 0 && isspace((int) *s.p)) {\n    s.p++;\n    s.len--;\n  }\n  while (s.len > 0 && isspace((int) *(s.p + s.len - 1))) {\n    s.len--;\n  }\n  return s;\n}\n\nint mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK;\nint mg_str_starts_with(struct mg_str s, struct mg_str prefix) {\n  const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len);\n  if (s.len < prefix.len) return 0;\n  return (mg_strcmp(sp, prefix) == 0);\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"common/str_util.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef EXCLUDE_COMMON\n\n/* Amalgamated: #include \"common/str_util.h\" */\n/* Amalgamated: #include \"common/mg_mem.h\" */\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifndef C_DISABLE_BUILTIN_SNPRINTF\n#define C_DISABLE_BUILTIN_SNPRINTF 0\n#endif\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n\nsize_t c_strnlen(const char *s, size_t maxlen) WEAK;\nsize_t c_strnlen(const char *s, size_t maxlen) {\n  size_t l = 0;\n  for (; l < maxlen && s[l] != '\\0'; l++) {\n  }\n  return l;\n}\n\n#define C_SNPRINTF_APPEND_CHAR(ch)       \\\n  do {                                   \\\n    if (i < (int) buf_size) buf[i] = ch; \\\n    i++;                                 \\\n  } while (0)\n\n#define C_SNPRINTF_FLAG_ZERO 1\n\n#if C_DISABLE_BUILTIN_SNPRINTF\nint c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) WEAK;\nint c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) {\n  return vsnprintf(buf, buf_size, fmt, ap);\n}\n#else\nstatic int c_itoa(char *buf, size_t buf_size, int64_t num, int base, int flags,\n                  int field_width) {\n  char tmp[40];\n  int i = 0, k = 0, neg = 0;\n\n  if (num < 0) {\n    neg++;\n    num = -num;\n  }\n\n  /* Print into temporary buffer - in reverse order */\n  do {\n    int rem = num % base;\n    if (rem < 10) {\n      tmp[k++] = '0' + rem;\n    } else {\n      tmp[k++] = 'a' + (rem - 10);\n    }\n    num /= base;\n  } while (num > 0);\n\n  /* Zero padding */\n  if (flags && C_SNPRINTF_FLAG_ZERO) {\n    while (k < field_width && k < (int) sizeof(tmp) - 1) {\n      tmp[k++] = '0';\n    }\n  }\n\n  /* And sign */\n  if (neg) {\n    tmp[k++] = '-';\n  }\n\n  /* Now output */\n  while (--k >= 0) {\n    C_SNPRINTF_APPEND_CHAR(tmp[k]);\n  }\n\n  return i;\n}\n\nint c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) WEAK;\nint c_vsnprintf(char *buf, size_t buf_size, const char *fmt, va_list ap) {\n  int ch, i = 0, len_mod, flags, precision, field_width;\n\n  while ((ch = *fmt++) != '\\0') {\n    if (ch != '%') {\n      C_SNPRINTF_APPEND_CHAR(ch);\n    } else {\n      /*\n       * Conversion specification:\n       *   zero or more flags (one of: # 0 - <space> + ')\n       *   an optional minimum  field  width (digits)\n       *   an  optional precision (. followed by digits, or *)\n       *   an optional length modifier (one of: hh h l ll L q j z t)\n       *   conversion specifier (one of: d i o u x X e E f F g G a A c s p n)\n       */\n      flags = field_width = precision = len_mod = 0;\n\n      /* Flags. only zero-pad flag is supported. */\n      if (*fmt == '0') {\n        flags |= C_SNPRINTF_FLAG_ZERO;\n      }\n\n      /* Field width */\n      while (*fmt >= '0' && *fmt <= '9') {\n        field_width *= 10;\n        field_width += *fmt++ - '0';\n      }\n      /* Dynamic field width */\n      if (*fmt == '*') {\n        field_width = va_arg(ap, int);\n        fmt++;\n      }\n\n      /* Precision */\n      if (*fmt == '.') {\n        fmt++;\n        if (*fmt == '*') {\n          precision = va_arg(ap, int);\n          fmt++;\n        } else {\n          while (*fmt >= '0' && *fmt <= '9') {\n            precision *= 10;\n            precision += *fmt++ - '0';\n          }\n        }\n      }\n\n      /* Length modifier */\n      switch (*fmt) {\n        case 'h':\n        case 'l':\n        case 'L':\n        case 'I':\n        case 'q':\n        case 'j':\n        case 'z':\n        case 't':\n          len_mod = *fmt++;\n          if (*fmt == 'h') {\n            len_mod = 'H';\n            fmt++;\n          }\n          if (*fmt == 'l') {\n            len_mod = 'q';\n            fmt++;\n          }\n          break;\n      }\n\n      ch = *fmt++;\n      if (ch == 's') {\n        const char *s = va_arg(ap, const char *); /* Always fetch parameter */\n        int j;\n        int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0);\n        for (j = 0; j < pad; j++) {\n          C_SNPRINTF_APPEND_CHAR(' ');\n        }\n\n        /* `s` may be NULL in case of %.*s */\n        if (s != NULL) {\n          /* Ignore negative and 0 precisions */\n          for (j = 0; (precision <= 0 || j < precision) && s[j] != '\\0'; j++) {\n            C_SNPRINTF_APPEND_CHAR(s[j]);\n          }\n        }\n      } else if (ch == 'c') {\n        ch = va_arg(ap, int); /* Always fetch parameter */\n        C_SNPRINTF_APPEND_CHAR(ch);\n      } else if (ch == 'd' && len_mod == 0) {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags,\n                    field_width);\n      } else if (ch == 'd' && len_mod == 'l') {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags,\n                    field_width);\n#ifdef SSIZE_MAX\n      } else if (ch == 'd' && len_mod == 'z') {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags,\n                    field_width);\n#endif\n      } else if (ch == 'd' && len_mod == 'q') {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags,\n                    field_width);\n      } else if ((ch == 'x' || ch == 'u') && len_mod == 0) {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, unsigned),\n                    ch == 'x' ? 16 : 10, flags, field_width);\n      } else if ((ch == 'x' || ch == 'u') && len_mod == 'l') {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, unsigned long),\n                    ch == 'x' ? 16 : 10, flags, field_width);\n      } else if ((ch == 'x' || ch == 'u') && len_mod == 'z') {\n        i += c_itoa(buf + i, buf_size - i, va_arg(ap, size_t),\n                    ch == 'x' ? 16 : 10, flags, field_width);\n      } else if (ch == 'p') {\n        unsigned long num = (unsigned long) (uintptr_t) va_arg(ap, void *);\n        C_SNPRINTF_APPEND_CHAR('0');\n        C_SNPRINTF_APPEND_CHAR('x');\n        i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0);\n      } else {\n#ifndef NO_LIBC\n        /*\n         * TODO(lsm): abort is not nice in a library, remove it\n         * Also, ESP8266 SDK doesn't have it\n         */\n        abort();\n#endif\n      }\n    }\n  }\n\n  /* Zero-terminate the result */\n  if (buf_size > 0) {\n    buf[i < (int) buf_size ? i : (int) buf_size - 1] = '\\0';\n  }\n\n  return i;\n}\n#endif\n\nint c_snprintf(char *buf, size_t buf_size, const char *fmt, ...) WEAK;\nint c_snprintf(char *buf, size_t buf_size, const char *fmt, ...) {\n  int result;\n  va_list ap;\n  va_start(ap, fmt);\n  result = c_vsnprintf(buf, buf_size, fmt, ap);\n  va_end(ap);\n  return result;\n}\n\n#ifdef _WIN32\nint to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len) {\n  int ret;\n  char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;\n\n  strncpy(buf, path, sizeof(buf));\n  buf[sizeof(buf) - 1] = '\\0';\n\n  /* Trim trailing slashes. Leave backslash for paths like \"X:\\\" */\n  p = buf + strlen(buf) - 1;\n  while (p > buf && p[-1] != ':' && (p[0] == '\\\\' || p[0] == '/')) *p-- = '\\0';\n\n  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));\n  ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);\n\n  /*\n   * Convert back to Unicode. If doubly-converted string does not match the\n   * original, something is fishy, reject.\n   */\n  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),\n                      NULL, NULL);\n  if (strcmp(buf, buf2) != 0) {\n    wbuf[0] = L'\\0';\n    ret = 0;\n  }\n\n  return ret;\n}\n#endif /* _WIN32 */\n\n/* The simplest O(mn) algorithm. Better implementation are GPLed */\nconst char *c_strnstr(const char *s, const char *find, size_t slen) WEAK;\nconst char *c_strnstr(const char *s, const char *find, size_t slen) {\n  size_t find_length = strlen(find);\n  size_t i;\n\n  for (i = 0; i < slen; i++) {\n    if (i + find_length > slen) {\n      return NULL;\n    }\n\n    if (strncmp(&s[i], find, find_length) == 0) {\n      return &s[i];\n    }\n  }\n\n  return NULL;\n}\n\n#if CS_ENABLE_STRDUP\nchar *strdup(const char *src) WEAK;\nchar *strdup(const char *src) {\n  size_t len = strlen(src) + 1;\n  char *ret = MG_MALLOC(len);\n  if (ret != NULL) {\n    strcpy(ret, src);\n  }\n  return ret;\n}\n#endif\n\nvoid cs_to_hex(char *to, const unsigned char *p, size_t len) WEAK;\nvoid cs_to_hex(char *to, const unsigned char *p, size_t len) {\n  static const char *hex = \"0123456789abcdef\";\n\n  for (; len--; p++) {\n    *to++ = hex[p[0] >> 4];\n    *to++ = hex[p[0] & 0x0f];\n  }\n  *to = '\\0';\n}\n\nstatic int fourbit(int ch) {\n  if (ch >= '0' && ch <= '9') {\n    return ch - '0';\n  } else if (ch >= 'a' && ch <= 'f') {\n    return ch - 'a' + 10;\n  } else if (ch >= 'A' && ch <= 'F') {\n    return ch - 'A' + 10;\n  }\n  return 0;\n}\n\nvoid cs_from_hex(char *to, const char *p, size_t len) WEAK;\nvoid cs_from_hex(char *to, const char *p, size_t len) {\n  size_t i;\n\n  for (i = 0; i < len; i += 2) {\n    *to++ = (fourbit(p[i]) << 4) + fourbit(p[i + 1]);\n  }\n  *to = '\\0';\n}\n\n#if CS_ENABLE_TO64\nint64_t cs_to64(const char *s) WEAK;\nint64_t cs_to64(const char *s) {\n  int64_t result = 0;\n  int64_t neg = 1;\n  while (*s && isspace((unsigned char) *s)) s++;\n  if (*s == '-') {\n    neg = -1;\n    s++;\n  }\n  while (isdigit((unsigned char) *s)) {\n    result *= 10;\n    result += (*s - '0');\n    s++;\n  }\n  return result * neg;\n}\n#endif\n\nstatic int str_util_lowercase(const char *s) {\n  return tolower(*(const unsigned char *) s);\n}\n\nint mg_ncasecmp(const char *s1, const char *s2, size_t len) WEAK;\nint mg_ncasecmp(const char *s1, const char *s2, size_t len) {\n  int diff = 0;\n\n  if (len > 0) do {\n      diff = str_util_lowercase(s1++) - str_util_lowercase(s2++);\n    } while (diff == 0 && s1[-1] != '\\0' && --len > 0);\n\n  return diff;\n}\n\nint mg_casecmp(const char *s1, const char *s2) WEAK;\nint mg_casecmp(const char *s1, const char *s2) {\n  return mg_ncasecmp(s1, s2, (size_t) ~0);\n}\n\nint mg_asprintf(char **buf, size_t size, const char *fmt, ...) WEAK;\nint mg_asprintf(char **buf, size_t size, const char *fmt, ...) {\n  int ret;\n  va_list ap;\n  va_start(ap, fmt);\n  ret = mg_avprintf(buf, size, fmt, ap);\n  va_end(ap);\n  return ret;\n}\n\nint mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) WEAK;\nint mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) {\n  va_list ap_copy;\n  int len;\n\n  va_copy(ap_copy, ap);\n  len = vsnprintf(*buf, size, fmt, ap_copy);\n  va_end(ap_copy);\n\n  if (len < 0) {\n    /* eCos and Windows are not standard-compliant and return -1 when\n     * the buffer is too small. Keep allocating larger buffers until we\n     * succeed or out of memory. */\n    *buf = NULL; /* LCOV_EXCL_START */\n    while (len < 0) {\n      MG_FREE(*buf);\n      if (size == 0) {\n        size = 5;\n      }\n      size *= 2;\n      if ((*buf = (char *) MG_MALLOC(size)) == NULL) {\n        len = -1;\n        break;\n      }\n      va_copy(ap_copy, ap);\n      len = vsnprintf(*buf, size - 1, fmt, ap_copy);\n      va_end(ap_copy);\n    }\n\n    /*\n     * Microsoft version of vsnprintf() is not always null-terminated, so put\n     * the terminator manually\n     */\n    (*buf)[len] = 0;\n    /* LCOV_EXCL_STOP */\n  } else if (len >= (int) size) {\n    /* Standard-compliant code path. Allocate a buffer that is large enough. */\n    if ((*buf = (char *) MG_MALLOC(len + 1)) == NULL) {\n      len = -1; /* LCOV_EXCL_LINE */\n    } else {    /* LCOV_EXCL_LINE */\n      va_copy(ap_copy, ap);\n      len = vsnprintf(*buf, len + 1, fmt, ap_copy);\n      va_end(ap_copy);\n    }\n  }\n\n  return len;\n}\n\nconst char *mg_next_comma_list_entry(const char *, struct mg_str *,\n                                     struct mg_str *) WEAK;\nconst char *mg_next_comma_list_entry(const char *list, struct mg_str *val,\n                                     struct mg_str *eq_val) {\n  struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val);\n  return ret.p;\n}\n\nstruct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,\n                                         struct mg_str *eq_val) WEAK;\nstruct mg_str mg_next_comma_list_entry_n(struct mg_str list, struct mg_str *val,\n                                         struct mg_str *eq_val) {\n  if (list.len == 0) {\n    /* End of the list */\n    list = mg_mk_str(NULL);\n  } else {\n    const char *chr = NULL;\n    *val = list;\n\n    if ((chr = mg_strchr(*val, ',')) != NULL) {\n      /* Comma found. Store length and shift the list ptr */\n      val->len = chr - val->p;\n      chr++;\n      list.len -= (chr - list.p);\n      list.p = chr;\n    } else {\n      /* This value is the last one */\n      list = mg_mk_str_n(list.p + list.len, 0);\n    }\n\n    if (eq_val != NULL) {\n      /* Value has form \"x=y\", adjust pointers and lengths */\n      /* so that val points to \"x\", and eq_val points to \"y\". */\n      eq_val->len = 0;\n      eq_val->p = (const char *) memchr(val->p, '=', val->len);\n      if (eq_val->p != NULL) {\n        eq_val->p++; /* Skip over '=' character */\n        eq_val->len = val->p + val->len - eq_val->p;\n        val->len = (eq_val->p - val->p) - 1;\n      }\n    }\n  }\n\n  return list;\n}\n\nsize_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK;\nsize_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {\n  const char *or_str;\n  size_t res = 0, len = 0, i = 0, j = 0;\n\n  if ((or_str = (const char *) memchr(pattern.p, '|', pattern.len)) != NULL ||\n      (or_str = (const char *) memchr(pattern.p, ',', pattern.len)) != NULL) {\n    struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)};\n    res = mg_match_prefix_n(pstr, str);\n    if (res > 0) return res;\n    pstr.p = or_str + 1;\n    pstr.len = (pattern.p + pattern.len) - (or_str + 1);\n    return mg_match_prefix_n(pstr, str);\n  }\n\n  for (; i < pattern.len && j < str.len; i++, j++) {\n    if (pattern.p[i] == '?') {\n      continue;\n    } else if (pattern.p[i] == '*') {\n      i++;\n      if (i < pattern.len && pattern.p[i] == '*') {\n        i++;\n        len = str.len - j;\n      } else {\n        len = 0;\n        while (j + len < str.len && str.p[j + len] != '/') len++;\n      }\n      if (i == pattern.len || (pattern.p[i] == '$' && i == pattern.len - 1))\n        return j + len;\n      do {\n        const struct mg_str pstr = {pattern.p + i, pattern.len - i};\n        const struct mg_str sstr = {str.p + j + len, str.len - j - len};\n        res = mg_match_prefix_n(pstr, sstr);\n      } while (res == 0 && len != 0 && --len > 0);\n      return res == 0 ? 0 : j + res + len;\n    } else if (str_util_lowercase(&pattern.p[i]) !=\n               str_util_lowercase(&str.p[j])) {\n      break;\n    }\n  }\n  if (i < pattern.len && pattern.p[i] == '$') {\n    return j == str.len ? str.len : 0;\n  }\n  return i == pattern.len ? j : 0;\n}\n\nsize_t mg_match_prefix(const char *, int, const char *) WEAK;\nsize_t mg_match_prefix(const char *pattern, int pattern_len, const char *str) {\n  const struct mg_str pstr = {pattern, (size_t) pattern_len};\n  struct mg_str s = {str, 0};\n  if (str != NULL) s.len = strlen(str);\n  return mg_match_prefix_n(pstr, s);\n}\n\n#endif /* EXCLUDE_COMMON */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n *\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n/* Amalgamated: #include \"common/cs_time.h\" */\n/* Amalgamated: #include \"mg_dns.h\" */\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_resolv.h\" */\n/* Amalgamated: #include \"mg_util.h\" */\n\n#define MG_MAX_HOST_LEN 200\n\n#ifndef MG_TCP_IO_SIZE\n#define MG_TCP_IO_SIZE 1460\n#endif\n#ifndef MG_UDP_IO_SIZE\n#define MG_UDP_IO_SIZE 1460\n#endif\n\n#define MG_COPY_COMMON_CONNECTION_OPTIONS(dst, src) \\\n  memcpy(dst, src, sizeof(*dst));\n\n/* Which flags can be pre-set by the user at connection creation time. */\n#define _MG_ALLOWED_CONNECT_FLAGS_MASK                                   \\\n  (MG_F_USER_1 | MG_F_USER_2 | MG_F_USER_3 | MG_F_USER_4 | MG_F_USER_5 | \\\n   MG_F_USER_6 | MG_F_WEBSOCKET_NO_DEFRAG | MG_F_ENABLE_BROADCAST)\n/* Which flags should be modifiable by user's callbacks. */\n#define _MG_CALLBACK_MODIFIABLE_FLAGS_MASK                               \\\n  (MG_F_USER_1 | MG_F_USER_2 | MG_F_USER_3 | MG_F_USER_4 | MG_F_USER_5 | \\\n   MG_F_USER_6 | MG_F_WEBSOCKET_NO_DEFRAG | MG_F_SEND_AND_CLOSE |        \\\n   MG_F_CLOSE_IMMEDIATELY | MG_F_IS_WEBSOCKET | MG_F_DELETE_CHUNK)\n\n#ifndef intptr_t\n#define intptr_t long\n#endif\n\nMG_INTERNAL void mg_add_conn(struct mg_mgr *mgr, struct mg_connection *c) {\n  DBG((\"%p %p\", mgr, c));\n  c->mgr = mgr;\n  c->next = mgr->active_connections;\n  mgr->active_connections = c;\n  c->prev = NULL;\n  if (c->next != NULL) c->next->prev = c;\n  if (c->sock != INVALID_SOCKET) {\n    c->iface->vtable->add_conn(c);\n  }\n}\n\nMG_INTERNAL void mg_remove_conn(struct mg_connection *conn) {\n  if (conn->prev == NULL) conn->mgr->active_connections = conn->next;\n  if (conn->prev) conn->prev->next = conn->next;\n  if (conn->next) conn->next->prev = conn->prev;\n  conn->prev = conn->next = NULL;\n  conn->iface->vtable->remove_conn(conn);\n}\n\nMG_INTERNAL void mg_call(struct mg_connection *nc,\n                         mg_event_handler_t ev_handler, void *user_data, int ev,\n                         void *ev_data) {\n  if (ev_handler == NULL) {\n    /*\n     * If protocol handler is specified, call it. Otherwise, call user-specified\n     * event handler.\n     */\n    ev_handler = nc->proto_handler ? nc->proto_handler : nc->handler;\n  }\n  if (ev != MG_EV_POLL) {\n    DBG((\"%p %s ev=%d ev_data=%p flags=0x%lx rmbl=%d smbl=%d\", nc,\n         ev_handler == nc->handler ? \"user\" : \"proto\", ev, ev_data, nc->flags,\n         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));\n  }\n\n#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP\n  if (nc->mgr->hexdump_file != NULL && ev != MG_EV_POLL && ev != MG_EV_RECV &&\n      ev != MG_EV_SEND /* handled separately */) {\n    mg_hexdump_connection(nc, nc->mgr->hexdump_file, NULL, 0, ev);\n  }\n#endif\n  if (ev_handler != NULL) {\n    unsigned long flags_before = nc->flags;\n    ev_handler(nc, ev, ev_data MG_UD_ARG(user_data));\n    /* Prevent user handler from fiddling with system flags. */\n    if (ev_handler == nc->handler && nc->flags != flags_before) {\n      nc->flags = (flags_before & ~_MG_CALLBACK_MODIFIABLE_FLAGS_MASK) |\n                  (nc->flags & _MG_CALLBACK_MODIFIABLE_FLAGS_MASK);\n    }\n  }\n  if (ev != MG_EV_POLL) nc->mgr->num_calls++;\n  if (ev != MG_EV_POLL) {\n    DBG((\"%p after %s flags=0x%lx rmbl=%d smbl=%d\", nc,\n         ev_handler == nc->handler ? \"user\" : \"proto\", nc->flags,\n         (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));\n  }\n#if !MG_ENABLE_CALLBACK_USERDATA\n  (void) user_data;\n#endif\n}\n\nMG_INTERNAL void mg_timer(struct mg_connection *c, double now) {\n  if (c->ev_timer_time > 0 && now >= c->ev_timer_time) {\n    double old_value = c->ev_timer_time;\n    c->ev_timer_time = 0;\n    mg_call(c, NULL, c->user_data, MG_EV_TIMER, &old_value);\n  }\n}\n\nMG_INTERNAL size_t recv_avail_size(struct mg_connection *conn, size_t max) {\n  size_t avail;\n  if (conn->recv_mbuf_limit < conn->recv_mbuf.len) return 0;\n  avail = conn->recv_mbuf_limit - conn->recv_mbuf.len;\n  return avail > max ? max : avail;\n}\n\nstatic int mg_do_recv(struct mg_connection *nc);\n\nint mg_if_poll(struct mg_connection *nc, double now) {\n  if (nc->flags & MG_F_CLOSE_IMMEDIATELY) {\n    mg_close_conn(nc);\n    return 0;\n  } else if (nc->flags & MG_F_SEND_AND_CLOSE) {\n    if (nc->send_mbuf.len == 0) {\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      mg_close_conn(nc);\n      return 0;\n    }\n  } else if (nc->flags & MG_F_RECV_AND_CLOSE) {\n    mg_close_conn(nc);\n    return 0;\n  }\n#if MG_ENABLE_SSL\n  if ((nc->flags & (MG_F_SSL | MG_F_LISTENING | MG_F_CONNECTING)) == MG_F_SSL) {\n    /* SSL library may have data to be delivered to the app in its buffers,\n     * drain them. */\n    int recved = 0;\n    do {\n      if (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE)) break;\n      if (recv_avail_size(nc, MG_TCP_IO_SIZE) <= 0) break;\n      recved = mg_do_recv(nc);\n    } while (recved > 0);\n  }\n#endif /* MG_ENABLE_SSL */\n  mg_timer(nc, now);\n  {\n    time_t now_t = (time_t) now;\n    mg_call(nc, NULL, nc->user_data, MG_EV_POLL, &now_t);\n  }\n  return 1;\n}\n\nvoid mg_destroy_conn(struct mg_connection *conn, int destroy_if) {\n  if (conn->sock != INVALID_SOCKET) { /* Don't print timer-only conns */\n    LOG(LL_DEBUG, (\"%p 0x%lx %d\", conn, conn->flags, destroy_if));\n  }\n  if (destroy_if) conn->iface->vtable->destroy_conn(conn);\n  if (conn->proto_data != NULL && conn->proto_data_destructor != NULL) {\n    conn->proto_data_destructor(conn->proto_data);\n  }\n#if MG_ENABLE_SSL\n  mg_ssl_if_conn_free(conn);\n#endif\n  mbuf_free(&conn->recv_mbuf);\n  mbuf_free(&conn->send_mbuf);\n\n  memset(conn, 0, sizeof(*conn));\n  MG_FREE(conn);\n}\n\nvoid mg_close_conn(struct mg_connection *conn) {\n  /* See if there's any remaining data to deliver. Skip if user completely\n   * throttled the connection there will be no progress anyway. */\n  if (conn->sock != INVALID_SOCKET && mg_do_recv(conn) == -2) {\n    /* Receive is throttled, wait. */\n    conn->flags |= MG_F_RECV_AND_CLOSE;\n    return;\n  }\n#if MG_ENABLE_SSL\n  if (conn->flags & MG_F_SSL_HANDSHAKE_DONE) {\n    mg_ssl_if_conn_close_notify(conn);\n  }\n#endif\n  /*\n   * Clearly mark the connection as going away (if not already).\n   * Some net_if impls (LwIP) need this for cleanly handling half-dead conns.\n   */\n  conn->flags |= MG_F_CLOSE_IMMEDIATELY;\n  mg_remove_conn(conn);\n  conn->iface->vtable->destroy_conn(conn);\n  mg_call(conn, NULL, conn->user_data, MG_EV_CLOSE, NULL);\n  mg_destroy_conn(conn, 0 /* destroy_if */);\n}\n\nvoid mg_mgr_init(struct mg_mgr *m, void *user_data) {\n  struct mg_mgr_init_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  mg_mgr_init_opt(m, user_data, opts);\n}\n\nvoid mg_mgr_init_opt(struct mg_mgr *m, void *user_data,\n                     struct mg_mgr_init_opts opts) {\n  memset(m, 0, sizeof(*m));\n#if MG_ENABLE_BROADCAST\n  m->ctl[0] = m->ctl[1] = INVALID_SOCKET;\n#endif\n  m->user_data = user_data;\n\n#ifdef _WIN32\n  {\n    WSADATA data;\n    WSAStartup(MAKEWORD(2, 2), &data);\n  }\n#elif defined(__unix__)\n  /* Ignore SIGPIPE signal, so if client cancels the request, it\n   * won't kill the whole process. */\n  signal(SIGPIPE, SIG_IGN);\n#endif\n\n  {\n    int i;\n    if (opts.num_ifaces == 0) {\n      opts.num_ifaces = mg_num_ifaces;\n      opts.ifaces = mg_ifaces;\n    }\n    if (opts.main_iface != NULL) {\n      opts.ifaces[MG_MAIN_IFACE] = opts.main_iface;\n    }\n    m->num_ifaces = opts.num_ifaces;\n    m->ifaces =\n        (struct mg_iface **) MG_MALLOC(sizeof(*m->ifaces) * opts.num_ifaces);\n    for (i = 0; i < opts.num_ifaces; i++) {\n      m->ifaces[i] = mg_if_create_iface(opts.ifaces[i], m);\n      m->ifaces[i]->vtable->init(m->ifaces[i]);\n    }\n  }\n  if (opts.nameserver != NULL) {\n    m->nameserver = strdup(opts.nameserver);\n  }\n  DBG((\"==================================\"));\n  DBG((\"init mgr=%p\", m));\n#if MG_ENABLE_SSL\n  {\n    static int init_done;\n    if (!init_done) {\n      mg_ssl_if_init();\n      init_done++;\n    }\n  }\n#endif\n}\n\nvoid mg_mgr_free(struct mg_mgr *m) {\n  struct mg_connection *conn, *tmp_conn;\n\n  DBG((\"%p\", m));\n  if (m == NULL) return;\n  /* Do one last poll, see https://github.com/cesanta/mongoose/issues/286 */\n  mg_mgr_poll(m, 0);\n\n#if MG_ENABLE_BROADCAST\n  if (m->ctl[0] != INVALID_SOCKET) closesocket(m->ctl[0]);\n  if (m->ctl[1] != INVALID_SOCKET) closesocket(m->ctl[1]);\n  m->ctl[0] = m->ctl[1] = INVALID_SOCKET;\n#endif\n\n  for (conn = m->active_connections; conn != NULL; conn = tmp_conn) {\n    tmp_conn = conn->next;\n    conn->flags |= MG_F_CLOSE_IMMEDIATELY;\n    mg_close_conn(conn);\n  }\n\n  {\n    int i;\n    for (i = 0; i < m->num_ifaces; i++) {\n      m->ifaces[i]->vtable->_free(m->ifaces[i]);\n      MG_FREE(m->ifaces[i]);\n    }\n    MG_FREE(m->ifaces);\n  }\n\n  MG_FREE((char *) m->nameserver);\n}\n\nint mg_mgr_poll(struct mg_mgr *m, int timeout_ms) {\n  int i, num_calls_before = m->num_calls;\n\n  for (i = 0; i < m->num_ifaces; i++) {\n    m->ifaces[i]->vtable->poll(m->ifaces[i], timeout_ms);\n  }\n\n  return (m->num_calls - num_calls_before);\n}\n\nint mg_vprintf(struct mg_connection *nc, const char *fmt, va_list ap) {\n  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;\n  int len;\n\n  if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {\n    mg_send(nc, buf, len);\n  }\n  if (buf != mem && buf != NULL) {\n    MG_FREE(buf); /* LCOV_EXCL_LINE */\n  }               /* LCOV_EXCL_LINE */\n\n  return len;\n}\n\nint mg_printf(struct mg_connection *conn, const char *fmt, ...) {\n  int len;\n  va_list ap;\n  va_start(ap, fmt);\n  len = mg_vprintf(conn, fmt, ap);\n  va_end(ap);\n  return len;\n}\n\n#if MG_ENABLE_SYNC_RESOLVER\n/* TODO(lsm): use non-blocking resolver */\nstatic int mg_resolve2(const char *host, struct in_addr *ina) {\n#if MG_ENABLE_GETADDRINFO\n  int rv = 0;\n  struct addrinfo hints, *servinfo, *p;\n  struct sockaddr_in *h = NULL;\n  memset(&hints, 0, sizeof hints);\n  hints.ai_family = AF_INET;\n  hints.ai_socktype = SOCK_STREAM;\n  if ((rv = getaddrinfo(host, NULL, NULL, &servinfo)) != 0) {\n    DBG((\"getaddrinfo(%s) failed: %s\", host, strerror(mg_get_errno())));\n    return 0;\n  }\n  for (p = servinfo; p != NULL; p = p->ai_next) {\n    memcpy(&h, &p->ai_addr, sizeof(h));\n    memcpy(ina, &h->sin_addr, sizeof(*ina));\n  }\n  freeaddrinfo(servinfo);\n  return 1;\n#else\n  struct hostent *he;\n  if ((he = gethostbyname(host)) == NULL) {\n    DBG((\"gethostbyname(%s) failed: %s\", host, strerror(mg_get_errno())));\n  } else {\n    memcpy(ina, he->h_addr_list[0], sizeof(*ina));\n    return 1;\n  }\n  return 0;\n#endif /* MG_ENABLE_GETADDRINFO */\n}\n\nint mg_resolve(const char *host, char *buf, size_t n) {\n  struct in_addr ad;\n  return mg_resolve2(host, &ad) ? snprintf(buf, n, \"%s\", inet_ntoa(ad)) : 0;\n}\n#endif /* MG_ENABLE_SYNC_RESOLVER */\n\nMG_INTERNAL struct mg_connection *mg_create_connection_base(\n    struct mg_mgr *mgr, mg_event_handler_t callback,\n    struct mg_add_sock_opts opts) {\n  struct mg_connection *conn;\n\n  if ((conn = (struct mg_connection *) MG_CALLOC(1, sizeof(*conn))) != NULL) {\n    conn->sock = INVALID_SOCKET;\n    conn->handler = callback;\n    conn->mgr = mgr;\n    conn->last_io_time = (time_t) mg_time();\n    conn->iface =\n        (opts.iface != NULL ? opts.iface : mgr->ifaces[MG_MAIN_IFACE]);\n    conn->flags = opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;\n    conn->user_data = opts.user_data;\n    /*\n     * SIZE_MAX is defined as a long long constant in\n     * system headers on some platforms and so it\n     * doesn't compile with pedantic ansi flags.\n     */\n    conn->recv_mbuf_limit = ~0;\n  } else {\n    MG_SET_PTRPTR(opts.error_string, \"failed to create connection\");\n  }\n\n  return conn;\n}\n\nMG_INTERNAL struct mg_connection *mg_create_connection(\n    struct mg_mgr *mgr, mg_event_handler_t callback,\n    struct mg_add_sock_opts opts) {\n  struct mg_connection *conn = mg_create_connection_base(mgr, callback, opts);\n\n  if (conn != NULL && !conn->iface->vtable->create_conn(conn)) {\n    MG_FREE(conn);\n    conn = NULL;\n  }\n  if (conn == NULL) {\n    MG_SET_PTRPTR(opts.error_string, \"failed to init connection\");\n  }\n\n  return conn;\n}\n\n/*\n * Address format: [PROTO://][HOST]:PORT\n *\n * HOST could be IPv4/IPv6 address or a host name.\n * `host` is a destination buffer to hold parsed HOST part. Should be at least\n * MG_MAX_HOST_LEN bytes long.\n * `proto` is a returned socket type, either SOCK_STREAM or SOCK_DGRAM\n *\n * Return:\n *   -1   on parse error\n *    0   if HOST needs DNS lookup\n *   >0   length of the address string\n */\nMG_INTERNAL int mg_parse_address(const char *str, union socket_address *sa,\n                                 int *proto, char *host, size_t host_len) {\n  unsigned int a, b, c, d, port = 0;\n  int ch, len = 0;\n#if MG_ENABLE_IPV6\n  char buf[100];\n#endif\n\n  /*\n   * MacOS needs that. If we do not zero it, subsequent bind() will fail.\n   * Also, all-zeroes in the socket address means binding to all addresses\n   * for both IPv4 and IPv6 (INADDR_ANY and IN6ADDR_ANY_INIT).\n   */\n  memset(sa, 0, sizeof(*sa));\n  sa->sin.sin_family = AF_INET;\n\n  *proto = SOCK_STREAM;\n\n  if (strncmp(str, \"udp://\", 6) == 0) {\n    str += 6;\n    *proto = SOCK_DGRAM;\n  } else if (strncmp(str, \"tcp://\", 6) == 0) {\n    str += 6;\n  }\n\n  if (sscanf(str, \"%u.%u.%u.%u:%u%n\", &a, &b, &c, &d, &port, &len) == 5) {\n    /* Bind to a specific IPv4 address, e.g. 192.168.1.5:8080 */\n    sa->sin.sin_addr.s_addr =\n        htonl(((uint32_t) a << 24) | ((uint32_t) b << 16) | c << 8 | d);\n    sa->sin.sin_port = htons((uint16_t) port);\n#if MG_ENABLE_IPV6\n  } else if (sscanf(str, \"[%99[^]]]:%u%n\", buf, &port, &len) == 2 &&\n             inet_pton(AF_INET6, buf, &sa->sin6.sin6_addr)) {\n    /* IPv6 address, e.g. [3ffe:2a00:100:7031::1]:8080 */\n    sa->sin6.sin6_family = AF_INET6;\n    sa->sin.sin_port = htons((uint16_t) port);\n#endif\n#if MG_ENABLE_ASYNC_RESOLVER\n  } else if (strlen(str) < host_len &&\n             sscanf(str, \"%[^ :]:%u%n\", host, &port, &len) == 2) {\n    sa->sin.sin_port = htons((uint16_t) port);\n    if (mg_resolve_from_hosts_file(host, sa) != 0) {\n      /*\n       * if resolving from hosts file failed and the host\n       * we are trying to resolve is `localhost` - we should\n       * try to resolve it using `gethostbyname` and do not try\n       * to resolve it via DNS server if gethostbyname has failed too\n       */\n      if (mg_ncasecmp(host, \"localhost\", 9) != 0) {\n        return 0;\n      }\n\n#if MG_ENABLE_SYNC_RESOLVER\n      if (!mg_resolve2(host, &sa->sin.sin_addr)) {\n        return -1;\n      }\n#else\n      return -1;\n#endif\n    }\n#endif\n  } else if (sscanf(str, \":%u%n\", &port, &len) == 1 ||\n             sscanf(str, \"%u%n\", &port, &len) == 1) {\n    /* If only port is specified, bind to IPv4, INADDR_ANY */\n    sa->sin.sin_port = htons((uint16_t) port);\n  } else {\n    return -1;\n  }\n\n  /* Required for MG_ENABLE_ASYNC_RESOLVER=0 */\n  (void) host;\n  (void) host_len;\n\n  ch = str[len]; /* Character that follows the address */\n  return port < 0xffffUL && (ch == '\\0' || ch == ',' || isspace(ch)) ? len : -1;\n}\n\n#if MG_ENABLE_SSL\nMG_INTERNAL void mg_ssl_handshake(struct mg_connection *nc) {\n  int err = 0;\n  int server_side = (nc->listener != NULL);\n  enum mg_ssl_if_result res;\n  if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) return;\n  res = mg_ssl_if_handshake(nc);\n\n  if (res == MG_SSL_OK) {\n    nc->flags |= MG_F_SSL_HANDSHAKE_DONE;\n    nc->flags &= ~(MG_F_WANT_READ | MG_F_WANT_WRITE);\n    if (server_side) {\n      mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);\n    } else {\n      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);\n    }\n  } else if (res == MG_SSL_WANT_READ) {\n    nc->flags |= MG_F_WANT_READ;\n  } else if (res == MG_SSL_WANT_WRITE) {\n    nc->flags |= MG_F_WANT_WRITE;\n  } else {\n    if (!server_side) {\n      err = res;\n      mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);\n    }\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  }\n}\n#endif /* MG_ENABLE_SSL */\n\nstruct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) {\n  struct mg_add_sock_opts opts;\n  struct mg_connection *nc;\n  memset(&opts, 0, sizeof(opts));\n  nc = mg_create_connection(lc->mgr, lc->handler, opts);\n  if (nc == NULL) return NULL;\n  nc->listener = lc;\n  nc->proto_handler = lc->proto_handler;\n  nc->user_data = lc->user_data;\n  nc->recv_mbuf_limit = lc->recv_mbuf_limit;\n  nc->iface = lc->iface;\n  if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL;\n  mg_add_conn(nc->mgr, nc);\n  LOG(LL_DEBUG, (\"%p %p %d %d\", lc, nc, nc->sock, (int) nc->flags));\n  return nc;\n}\n\nvoid mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa,\n                         size_t sa_len) {\n  LOG(LL_DEBUG, (\"%p %s://%s:%hu\", nc, (nc->flags & MG_F_UDP ? \"udp\" : \"tcp\"),\n                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));\n  nc->sa = *sa;\n#if MG_ENABLE_SSL\n  if (nc->listener->flags & MG_F_SSL) {\n    nc->flags |= MG_F_SSL;\n    if (mg_ssl_if_conn_accept(nc, nc->listener) == MG_SSL_OK) {\n      mg_ssl_handshake(nc);\n    } else {\n      mg_close_conn(nc);\n    }\n  } else\n#endif\n  {\n    mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);\n  }\n  (void) sa_len;\n}\n\nvoid mg_send(struct mg_connection *nc, const void *buf, int len) {\n  nc->last_io_time = (time_t) mg_time();\n  mbuf_append(&nc->send_mbuf, buf, len);\n}\n\nstatic int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len);\nstatic int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len);\n\nstatic int mg_do_recv(struct mg_connection *nc) {\n  int res = 0;\n  char *buf = NULL;\n  size_t len = (nc->flags & MG_F_UDP ? MG_UDP_IO_SIZE : MG_TCP_IO_SIZE);\n  if ((nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) ||\n      ((nc->flags & MG_F_LISTENING) && !(nc->flags & MG_F_UDP))) {\n    return -1;\n  }\n  do {\n    len = recv_avail_size(nc, len);\n    if (len == 0) {\n      res = -2;\n      break;\n    }\n    if (nc->recv_mbuf.size < nc->recv_mbuf.len + len) {\n      mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.len + len);\n    }\n    buf = nc->recv_mbuf.buf + nc->recv_mbuf.len;\n    len = nc->recv_mbuf.size - nc->recv_mbuf.len;\n    if (nc->flags & MG_F_UDP) {\n      res = mg_recv_udp(nc, buf, len);\n    } else {\n      res = mg_recv_tcp(nc, buf, len);\n    }\n  } while (res > 0 && !(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_UDP)));\n  return res;\n}\n\nvoid mg_if_can_recv_cb(struct mg_connection *nc) {\n  mg_do_recv(nc);\n}\n\nstatic int mg_recv_tcp(struct mg_connection *nc, char *buf, size_t len) {\n  int n = 0;\n#if MG_ENABLE_SSL\n  if (nc->flags & MG_F_SSL) {\n    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {\n      n = mg_ssl_if_read(nc, buf, len);\n      DBG((\"%p <- %d bytes (SSL)\", nc, n));\n      if (n < 0) {\n        if (n == MG_SSL_WANT_READ) {\n          nc->flags |= MG_F_WANT_READ;\n          n = 0;\n        } else {\n          nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        }\n      } else if (n > 0) {\n        nc->flags &= ~MG_F_WANT_READ;\n      }\n    } else {\n      mg_ssl_handshake(nc);\n    }\n  } else\n#endif\n  {\n    n = nc->iface->vtable->tcp_recv(nc, buf, len);\n    DBG((\"%p <- %d bytes\", nc, n));\n  }\n  if (n > 0) {\n    nc->recv_mbuf.len += n;\n    nc->last_io_time = (time_t) mg_time();\n#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP\n    if (nc->mgr && nc->mgr->hexdump_file != NULL) {\n      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);\n    }\n#endif\n    mbuf_trim(&nc->recv_mbuf);\n    mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);\n  } else if (n < 0) {\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  }\n  mbuf_trim(&nc->recv_mbuf);\n  return n;\n}\n\nstatic int mg_recv_udp(struct mg_connection *nc, char *buf, size_t len) {\n  int n = 0;\n  struct mg_connection *lc = nc;\n  union socket_address sa;\n  size_t sa_len = sizeof(sa);\n  n = nc->iface->vtable->udp_recv(lc, buf, len, &sa, &sa_len);\n  if (n < 0) {\n    lc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    goto out;\n  }\n  if (nc->flags & MG_F_LISTENING) {\n    /*\n     * Do we have an existing connection for this source?\n     * This is very inefficient for long connection lists.\n     */\n    lc = nc;\n    for (nc = mg_next(lc->mgr, NULL); nc != NULL; nc = mg_next(lc->mgr, nc)) {\n      if (memcmp(&nc->sa.sa, &sa.sa, sa_len) == 0 && nc->listener == lc) {\n        break;\n      }\n    }\n    if (nc == NULL) {\n      struct mg_add_sock_opts opts;\n      memset(&opts, 0, sizeof(opts));\n      /* Create fake connection w/out sock initialization */\n      nc = mg_create_connection_base(lc->mgr, lc->handler, opts);\n      if (nc != NULL) {\n        nc->sock = lc->sock;\n        nc->listener = lc;\n        nc->sa = sa;\n        nc->proto_handler = lc->proto_handler;\n        nc->user_data = lc->user_data;\n        nc->recv_mbuf_limit = lc->recv_mbuf_limit;\n        nc->flags = MG_F_UDP;\n        /*\n         * Long-lived UDP \"connections\" i.e. interactions that involve more\n         * than one request and response are rare, most are transactional:\n         * response is sent and the \"connection\" is closed. Or - should be.\n         * But users (including ourselves) tend to forget about that part,\n         * because UDP is connectionless and one does not think about\n         * processing a UDP request as handling a connection that needs to be\n         * closed. Thus, we begin with SEND_AND_CLOSE flag set, which should\n         * be a reasonable default for most use cases, but it is possible to\n         * turn it off the connection should be kept alive after processing.\n         */\n        nc->flags |= MG_F_SEND_AND_CLOSE;\n        mg_add_conn(lc->mgr, nc);\n        mg_call(nc, NULL, nc->user_data, MG_EV_ACCEPT, &nc->sa);\n      }\n    }\n  }\n  if (nc != NULL) {\n    DBG((\"%p <- %d bytes from %s:%d\", nc, n, inet_ntoa(nc->sa.sin.sin_addr),\n         ntohs(nc->sa.sin.sin_port)));\n    if (nc == lc) {\n      nc->recv_mbuf.len += n;\n    } else {\n      mbuf_append(&nc->recv_mbuf, buf, n);\n    }\n    mbuf_trim(&lc->recv_mbuf);\n    lc->last_io_time = nc->last_io_time = (time_t) mg_time();\n#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP\n    if (nc->mgr && nc->mgr->hexdump_file != NULL) {\n      mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_RECV);\n    }\n#endif\n    if (n != 0) {\n      mg_call(nc, NULL, nc->user_data, MG_EV_RECV, &n);\n    }\n  }\n\nout:\n  mbuf_free(&lc->recv_mbuf);\n  return n;\n}\n\nvoid mg_if_can_send_cb(struct mg_connection *nc) {\n  int n = 0;\n  const char *buf = nc->send_mbuf.buf;\n  size_t len = nc->send_mbuf.len;\n\n  if (nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_CONNECTING)) {\n    return;\n  }\n  if (!(nc->flags & MG_F_UDP)) {\n    if (nc->flags & MG_F_LISTENING) return;\n    if (len > MG_TCP_IO_SIZE) len = MG_TCP_IO_SIZE;\n  }\n#if MG_ENABLE_SSL\n  if (nc->flags & MG_F_SSL) {\n    if (nc->flags & MG_F_SSL_HANDSHAKE_DONE) {\n      if (len > 0) {\n        n = mg_ssl_if_write(nc, buf, len);\n        DBG((\"%p -> %d bytes (SSL)\", nc, n));\n      }\n      if (n < 0) {\n        if (n == MG_SSL_WANT_WRITE) {\n          nc->flags |= MG_F_WANT_WRITE;\n          n = 0;\n        } else {\n          nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        }\n      } else {\n        nc->flags &= ~MG_F_WANT_WRITE;\n      }\n    } else {\n      mg_ssl_handshake(nc);\n    }\n  } else\n#endif\n      if (len > 0) {\n    if (nc->flags & MG_F_UDP) {\n      n = nc->iface->vtable->udp_send(nc, buf, len);\n    } else {\n      n = nc->iface->vtable->tcp_send(nc, buf, len);\n    }\n    DBG((\"%p -> %d bytes\", nc, n));\n  }\n\n#if !defined(NO_LIBC) && MG_ENABLE_HEXDUMP\n  if (n > 0 && nc->mgr && nc->mgr->hexdump_file != NULL) {\n    mg_hexdump_connection(nc, nc->mgr->hexdump_file, buf, n, MG_EV_SEND);\n  }\n#endif\n  if (n < 0) {\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  } else if (n > 0) {\n    nc->last_io_time = (time_t) mg_time();\n    mbuf_remove(&nc->send_mbuf, n);\n    mbuf_trim(&nc->send_mbuf);\n  }\n  if (n != 0) mg_call(nc, NULL, nc->user_data, MG_EV_SEND, &n);\n}\n\n/*\n * Schedules an async connect for a resolved address and proto.\n * Called from two places: `mg_connect_opt()` and from async resolver.\n * When called from the async resolver, it must trigger `MG_EV_CONNECT` event\n * with a failure flag to indicate connection failure.\n */\nMG_INTERNAL struct mg_connection *mg_do_connect(struct mg_connection *nc,\n                                                int proto,\n                                                union socket_address *sa) {\n  LOG(LL_DEBUG, (\"%p %s://%s:%hu\", nc, proto == SOCK_DGRAM ? \"udp\" : \"tcp\",\n                 inet_ntoa(sa->sin.sin_addr), ntohs(sa->sin.sin_port)));\n\n  nc->flags |= MG_F_CONNECTING;\n  if (proto == SOCK_DGRAM) {\n    nc->iface->vtable->connect_udp(nc);\n  } else {\n    nc->iface->vtable->connect_tcp(nc, sa);\n  }\n  mg_add_conn(nc->mgr, nc);\n  return nc;\n}\n\nvoid mg_if_connect_cb(struct mg_connection *nc, int err) {\n  LOG(LL_DEBUG,\n      (\"%p %s://%s:%hu -> %d\", nc, (nc->flags & MG_F_UDP ? \"udp\" : \"tcp\"),\n       inet_ntoa(nc->sa.sin.sin_addr), ntohs(nc->sa.sin.sin_port), err));\n  nc->flags &= ~MG_F_CONNECTING;\n  if (err != 0) {\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  }\n#if MG_ENABLE_SSL\n  if (err == 0 && (nc->flags & MG_F_SSL)) {\n    mg_ssl_handshake(nc);\n  } else\n#endif\n  {\n    mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &err);\n  }\n}\n\n#if MG_ENABLE_ASYNC_RESOLVER\n/*\n * Callback for the async resolver on mg_connect_opt() call.\n * Main task of this function is to trigger MG_EV_CONNECT event with\n *    either failure (and dealloc the connection)\n *    or success (and proceed with connect()\n */\nstatic void resolve_cb(struct mg_dns_message *msg, void *data,\n                       enum mg_resolve_err e) {\n  struct mg_connection *nc = (struct mg_connection *) data;\n  int i;\n  int failure = -1;\n\n  nc->flags &= ~MG_F_RESOLVING;\n  if (msg != NULL) {\n    /*\n     * Take the first DNS A answer and run...\n     */\n    for (i = 0; i < msg->num_answers; i++) {\n      if (msg->answers[i].rtype == MG_DNS_A_RECORD) {\n        /*\n         * Async resolver guarantees that there is at least one answer.\n         * TODO(lsm): handle IPv6 answers too\n         */\n        mg_dns_parse_record_data(msg, &msg->answers[i], &nc->sa.sin.sin_addr,\n                                 4);\n        mg_do_connect(nc, nc->flags & MG_F_UDP ? SOCK_DGRAM : SOCK_STREAM,\n                      &nc->sa);\n        return;\n      }\n    }\n  }\n\n  if (e == MG_RESOLVE_TIMEOUT) {\n    double now = mg_time();\n    mg_call(nc, NULL, nc->user_data, MG_EV_TIMER, &now);\n  }\n\n  /*\n   * If we get there was no MG_DNS_A_RECORD in the answer\n   */\n  mg_call(nc, NULL, nc->user_data, MG_EV_CONNECT, &failure);\n  mg_call(nc, NULL, nc->user_data, MG_EV_CLOSE, NULL);\n  mg_destroy_conn(nc, 1 /* destroy_if */);\n}\n#endif\n\nstruct mg_connection *mg_connect(struct mg_mgr *mgr, const char *address,\n                                 MG_CB(mg_event_handler_t callback,\n                                       void *user_data)) {\n  struct mg_connect_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  return mg_connect_opt(mgr, address, MG_CB(callback, user_data), opts);\n}\n\nvoid mg_ev_handler_empty(struct mg_connection *c, int ev,\n                         void *ev_data MG_UD_ARG(void *user_data)) {\n  (void) c;\n  (void) ev;\n  (void) ev_data;\n#if MG_ENABLE_CALLBACK_USERDATA\n  (void) user_data;\n#endif\n}\n\nstruct mg_connection *mg_connect_opt(struct mg_mgr *mgr, const char *address,\n                                     MG_CB(mg_event_handler_t callback,\n                                           void *user_data),\n                                     struct mg_connect_opts opts) {\n  struct mg_connection *nc = NULL;\n  int proto, rc;\n  struct mg_add_sock_opts add_sock_opts;\n  char host[MG_MAX_HOST_LEN];\n\n  MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);\n\n  if (callback == NULL) callback = mg_ev_handler_empty;\n\n  if ((nc = mg_create_connection(mgr, callback, add_sock_opts)) == NULL) {\n    return NULL;\n  }\n\n  if ((rc = mg_parse_address(address, &nc->sa, &proto, host, sizeof(host))) <\n      0) {\n    /* Address is malformed */\n    MG_SET_PTRPTR(opts.error_string, \"cannot parse address\");\n    mg_destroy_conn(nc, 1 /* destroy_if */);\n    return NULL;\n  }\n\n  nc->flags |= opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK;\n  nc->flags |= (proto == SOCK_DGRAM) ? MG_F_UDP : 0;\n#if MG_ENABLE_CALLBACK_USERDATA\n  nc->user_data = user_data;\n#else\n  nc->user_data = opts.user_data;\n#endif\n\n#if MG_ENABLE_SSL\n  LOG(LL_DEBUG,\n      (\"%p %s %s,%s,%s\", nc, address, (opts.ssl_cert ? opts.ssl_cert : \"-\"),\n       (opts.ssl_key ? opts.ssl_key : \"-\"),\n       (opts.ssl_ca_cert ? opts.ssl_ca_cert : \"-\")));\n\n  if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL ||\n      opts.ssl_psk_identity != NULL) {\n    const char *err_msg = NULL;\n    struct mg_ssl_if_conn_params params;\n    if (nc->flags & MG_F_UDP) {\n      MG_SET_PTRPTR(opts.error_string, \"SSL for UDP is not supported\");\n      mg_destroy_conn(nc, 1 /* destroy_if */);\n      return NULL;\n    }\n    memset(&params, 0, sizeof(params));\n    params.cert = opts.ssl_cert;\n    params.key = opts.ssl_key;\n    params.ca_cert = opts.ssl_ca_cert;\n    params.cipher_suites = opts.ssl_cipher_suites;\n    params.psk_identity = opts.ssl_psk_identity;\n    params.psk_key = opts.ssl_psk_key;\n    if (opts.ssl_ca_cert != NULL) {\n      if (opts.ssl_server_name != NULL) {\n        if (strcmp(opts.ssl_server_name, \"*\") != 0) {\n          params.server_name = opts.ssl_server_name;\n        }\n      } else if (rc == 0) { /* If it's a DNS name, use host. */\n        params.server_name = host;\n      }\n    }\n    if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {\n      MG_SET_PTRPTR(opts.error_string, err_msg);\n      mg_destroy_conn(nc, 1 /* destroy_if */);\n      return NULL;\n    }\n    nc->flags |= MG_F_SSL;\n  }\n#endif /* MG_ENABLE_SSL */\n\n  if (rc == 0) {\n#if MG_ENABLE_ASYNC_RESOLVER\n    /*\n     * DNS resolution is required for host.\n     * mg_parse_address() fills port in nc->sa, which we pass to resolve_cb()\n     */\n    struct mg_connection *dns_conn = NULL;\n    struct mg_resolve_async_opts o;\n    memset(&o, 0, sizeof(o));\n    o.dns_conn = &dns_conn;\n    o.nameserver = opts.nameserver;\n    if (mg_resolve_async_opt(nc->mgr, host, MG_DNS_A_RECORD, resolve_cb, nc,\n                             o) != 0) {\n      MG_SET_PTRPTR(opts.error_string, \"cannot schedule DNS lookup\");\n      mg_destroy_conn(nc, 1 /* destroy_if */);\n      return NULL;\n    }\n    nc->priv_2 = dns_conn;\n    nc->flags |= MG_F_RESOLVING;\n    return nc;\n#else\n    MG_SET_PTRPTR(opts.error_string, \"Resolver is disabled\");\n    mg_destroy_conn(nc, 1 /* destroy_if */);\n    return NULL;\n#endif\n  } else {\n    /* Address is parsed and resolved to IP. proceed with connect() */\n    return mg_do_connect(nc, proto, &nc->sa);\n  }\n}\n\nstruct mg_connection *mg_bind(struct mg_mgr *srv, const char *address,\n                              MG_CB(mg_event_handler_t event_handler,\n                                    void *user_data)) {\n  struct mg_bind_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  return mg_bind_opt(srv, address, MG_CB(event_handler, user_data), opts);\n}\n\nstruct mg_connection *mg_bind_opt(struct mg_mgr *mgr, const char *address,\n                                  MG_CB(mg_event_handler_t callback,\n                                        void *user_data),\n                                  struct mg_bind_opts opts) {\n  union socket_address sa;\n  struct mg_connection *nc = NULL;\n  int proto, rc;\n  struct mg_add_sock_opts add_sock_opts;\n  char host[MG_MAX_HOST_LEN];\n\n#if MG_ENABLE_CALLBACK_USERDATA\n  opts.user_data = user_data;\n#endif\n\n  if (callback == NULL) callback = mg_ev_handler_empty;\n\n  MG_COPY_COMMON_CONNECTION_OPTIONS(&add_sock_opts, &opts);\n\n  if (mg_parse_address(address, &sa, &proto, host, sizeof(host)) <= 0) {\n    MG_SET_PTRPTR(opts.error_string, \"cannot parse address\");\n    return NULL;\n  }\n\n  nc = mg_create_connection(mgr, callback, add_sock_opts);\n  if (nc == NULL) {\n    return NULL;\n  }\n\n  nc->sa = sa;\n  nc->flags |= MG_F_LISTENING;\n  if (proto == SOCK_DGRAM) nc->flags |= MG_F_UDP;\n\n#if MG_ENABLE_SSL\n  DBG((\"%p %s %s,%s,%s\", nc, address, (opts.ssl_cert ? opts.ssl_cert : \"-\"),\n       (opts.ssl_key ? opts.ssl_key : \"-\"),\n       (opts.ssl_ca_cert ? opts.ssl_ca_cert : \"-\")));\n\n  if (opts.ssl_cert != NULL || opts.ssl_ca_cert != NULL) {\n    const char *err_msg = NULL;\n    struct mg_ssl_if_conn_params params;\n    if (nc->flags & MG_F_UDP) {\n      MG_SET_PTRPTR(opts.error_string, \"SSL for UDP is not supported\");\n      mg_destroy_conn(nc, 1 /* destroy_if */);\n      return NULL;\n    }\n    memset(&params, 0, sizeof(params));\n    params.cert = opts.ssl_cert;\n    params.key = opts.ssl_key;\n    params.ca_cert = opts.ssl_ca_cert;\n    params.cipher_suites = opts.ssl_cipher_suites;\n    if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {\n      MG_SET_PTRPTR(opts.error_string, err_msg);\n      mg_destroy_conn(nc, 1 /* destroy_if */);\n      return NULL;\n    }\n    nc->flags |= MG_F_SSL;\n  }\n#endif /* MG_ENABLE_SSL */\n\n  if (nc->flags & MG_F_UDP) {\n    rc = nc->iface->vtable->listen_udp(nc, &nc->sa);\n  } else {\n    rc = nc->iface->vtable->listen_tcp(nc, &nc->sa);\n  }\n  if (rc != 0) {\n    DBG((\"Failed to open listener: %d\", rc));\n    MG_SET_PTRPTR(opts.error_string, \"failed to open listener\");\n    mg_destroy_conn(nc, 1 /* destroy_if */);\n    return NULL;\n  }\n  mg_add_conn(nc->mgr, nc);\n\n  return nc;\n}\n\nstruct mg_connection *mg_next(struct mg_mgr *s, struct mg_connection *conn) {\n  return conn == NULL ? s->active_connections : conn->next;\n}\n\n#if MG_ENABLE_BROADCAST\nvoid mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,\n                  size_t len) {\n  struct ctl_msg ctl_msg;\n\n  /*\n   * Mongoose manager has a socketpair, `struct mg_mgr::ctl`,\n   * where `mg_broadcast()` pushes the message.\n   * `mg_mgr_poll()` wakes up, reads a message from the socket pair, and calls\n   * specified callback for each connection. Thus the callback function executes\n   * in event manager thread.\n   */\n  if (data == NULL || len == 0) {\n    data = \"\";\n    len = 1;\n  }\n  if (mgr->ctl[0] != INVALID_SOCKET && data != NULL &&\n      len < sizeof(ctl_msg.message)) {\n    size_t ret;\n\n    ctl_msg.callback = cb;\n    memcpy(ctl_msg.message, data, len);\n    ret = MG_SEND_FUNC(mgr->ctl[0], (char *) &ctl_msg,\n                         offsetof(struct ctl_msg, message) + len, 0);\n    if (ret < 0)\n      perror(\"mg_broadcast() send failed, check UDP loopback device\");\n/*\n    ret = MG_RECV_FUNC(mgr->ctl[0], (char *) &len, 1, 0);\n    if (ret < 0)\n      perror(\"mg_broadcast() recv failed, check firewall UDP loopback rules\");\n*/\n  }\n}\n#endif /* MG_ENABLE_BROADCAST */\n\nstatic int isbyte(int n) {\n  return n >= 0 && n <= 255;\n}\n\nstatic int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {\n  int n, a, b, c, d, slash = 32, len = 0;\n\n  if ((sscanf(spec, \"%d.%d.%d.%d/%d%n\", &a, &b, &c, &d, &slash, &n) == 5 ||\n       sscanf(spec, \"%d.%d.%d.%d%n\", &a, &b, &c, &d, &n) == 4) &&\n      isbyte(a) && isbyte(b) && isbyte(c) && isbyte(d) && slash >= 0 &&\n      slash < 33) {\n    len = n;\n    *net =\n        ((uint32_t) a << 24) | ((uint32_t) b << 16) | ((uint32_t) c << 8) | d;\n    *mask = slash ? 0xffffffffU << (32 - slash) : 0;\n  }\n\n  return len;\n}\n\nint mg_check_ip_acl(const char *acl, uint32_t remote_ip) {\n  int allowed, flag;\n  uint32_t net, mask;\n  struct mg_str vec;\n\n  /* If any ACL is set, deny by default */\n  allowed = (acl == NULL || *acl == '\\0') ? '+' : '-';\n\n  while ((acl = mg_next_comma_list_entry(acl, &vec, NULL)) != NULL) {\n    flag = vec.p[0];\n    if ((flag != '+' && flag != '-') ||\n        parse_net(&vec.p[1], &net, &mask) == 0) {\n      return -1;\n    }\n\n    if (net == (remote_ip & mask)) {\n      allowed = flag;\n    }\n  }\n\n  DBG((\"%08x %c\", (unsigned int) remote_ip, allowed));\n  return allowed == '+';\n}\n\n/* Move data from one connection to another */\nvoid mg_forward(struct mg_connection *from, struct mg_connection *to) {\n  mg_send(to, from->recv_mbuf.buf, from->recv_mbuf.len);\n  mbuf_remove(&from->recv_mbuf, from->recv_mbuf.len);\n}\n\ndouble mg_set_timer(struct mg_connection *c, double timestamp) {\n  double result = c->ev_timer_time;\n  c->ev_timer_time = timestamp;\n  /*\n   * If this connection is resolving, it's not in the list of active\n   * connections, so not processed yet. It has a DNS resolver connection\n   * linked to it. Set up a timer for the DNS connection.\n   */\n  DBG((\"%p %p %d -> %lu\", c, c->priv_2, (c->flags & MG_F_RESOLVING ? 1 : 0),\n       (unsigned long) timestamp));\n  if ((c->flags & MG_F_RESOLVING) && c->priv_2 != NULL) {\n    mg_set_timer((struct mg_connection *) c->priv_2, timestamp);\n  }\n  return result;\n}\n\nvoid mg_sock_set(struct mg_connection *nc, sock_t sock) {\n  if (sock != INVALID_SOCKET) {\n    nc->iface->vtable->sock_set(nc, sock);\n  }\n}\n\nvoid mg_if_get_conn_addr(struct mg_connection *nc, int remote,\n                         union socket_address *sa) {\n  nc->iface->vtable->get_conn_addr(nc, remote, sa);\n}\n\nstruct mg_connection *mg_add_sock_opt(struct mg_mgr *s, sock_t sock,\n                                      MG_CB(mg_event_handler_t callback,\n                                            void *user_data),\n                                      struct mg_add_sock_opts opts) {\n#if MG_ENABLE_CALLBACK_USERDATA\n  opts.user_data = user_data;\n#endif\n\n  struct mg_connection *nc = mg_create_connection_base(s, callback, opts);\n  if (nc != NULL) {\n    mg_sock_set(nc, sock);\n    mg_add_conn(nc->mgr, nc);\n  }\n  return nc;\n}\n\nstruct mg_connection *mg_add_sock(struct mg_mgr *s, sock_t sock,\n                                  MG_CB(mg_event_handler_t callback,\n                                        void *user_data)) {\n  struct mg_add_sock_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  return mg_add_sock_opt(s, sock, MG_CB(callback, user_data), opts);\n}\n\ndouble mg_time(void) {\n  return cs_time();\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if_socket.h\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef CS_MONGOOSE_SRC_NET_IF_SOCKET_H_\n#define CS_MONGOOSE_SRC_NET_IF_SOCKET_H_\n\n/* Amalgamated: #include \"mg_net_if.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifndef MG_ENABLE_NET_IF_SOCKET\n#define MG_ENABLE_NET_IF_SOCKET MG_NET_IF == MG_NET_IF_SOCKET\n#endif\n\nextern const struct mg_iface_vtable mg_socket_iface_vtable;\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_MONGOOSE_SRC_NET_IF_SOCKET_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if_socks.h\"\n#endif\n/*\n* Copyright (c) 2014-2017 Cesanta Software Limited\n* All rights reserved\n*/\n\n#ifndef CS_MONGOOSE_SRC_NET_IF_SOCKS_H_\n#define CS_MONGOOSE_SRC_NET_IF_SOCKS_H_\n\n#if MG_ENABLE_SOCKS\n/* Amalgamated: #include \"mg_net_if.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nextern const struct mg_iface_vtable mg_socks_iface_vtable;\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* MG_ENABLE_SOCKS */\n#endif /* CS_MONGOOSE_SRC_NET_IF_SOCKS_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if.c\"\n#endif\n/* Amalgamated: #include \"mg_net_if.h\" */\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_net_if_socket.h\" */\n\nextern const struct mg_iface_vtable mg_default_iface_vtable;\n\nconst struct mg_iface_vtable *mg_ifaces[] = {\n    &mg_default_iface_vtable,\n};\n\nint mg_num_ifaces = (int) (sizeof(mg_ifaces) / sizeof(mg_ifaces[0]));\n\nstruct mg_iface *mg_if_create_iface(const struct mg_iface_vtable *vtable,\n                                    struct mg_mgr *mgr) {\n  struct mg_iface *iface = (struct mg_iface *) MG_CALLOC(1, sizeof(*iface));\n  iface->mgr = mgr;\n  iface->data = NULL;\n  iface->vtable = vtable;\n  return iface;\n}\n\nstruct mg_iface *mg_find_iface(struct mg_mgr *mgr,\n                               const struct mg_iface_vtable *vtable,\n                               struct mg_iface *from) {\n  int i = 0;\n  if (from != NULL) {\n    for (i = 0; i < mgr->num_ifaces; i++) {\n      if (mgr->ifaces[i] == from) {\n        i++;\n        break;\n      }\n    }\n  }\n\n  for (; i < mgr->num_ifaces; i++) {\n    if (mgr->ifaces[i]->vtable == vtable) {\n      return mgr->ifaces[i];\n    }\n  }\n  return NULL;\n}\n\ndouble mg_mgr_min_timer(const struct mg_mgr *mgr) {\n  double min_timer = 0;\n  struct mg_connection *nc;\n  for (nc = mgr->active_connections; nc != NULL; nc = nc->next) {\n    if (nc->ev_timer_time <= 0) continue;\n    if (min_timer == 0 || nc->ev_timer_time < min_timer) {\n      min_timer = nc->ev_timer_time;\n    }\n  }\n  return min_timer;\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if_null.c\"\n#endif\n/*\n * Copyright (c) 2018 Cesanta Software Limited\n * All rights reserved\n *\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\nstatic void mg_null_if_connect_tcp(struct mg_connection *c,\n                                   const union socket_address *sa) {\n  c->flags |= MG_F_CLOSE_IMMEDIATELY;\n  (void) sa;\n}\n\nstatic void mg_null_if_connect_udp(struct mg_connection *c) {\n  c->flags |= MG_F_CLOSE_IMMEDIATELY;\n}\n\nstatic int mg_null_if_listen_tcp(struct mg_connection *c,\n                                 union socket_address *sa) {\n  (void) c;\n  (void) sa;\n  return -1;\n}\n\nstatic int mg_null_if_listen_udp(struct mg_connection *c,\n                                 union socket_address *sa) {\n  (void) c;\n  (void) sa;\n  return -1;\n}\n\nstatic int mg_null_if_tcp_send(struct mg_connection *c, const void *buf,\n                               size_t len) {\n  (void) c;\n  (void) buf;\n  (void) len;\n  return -1;\n}\n\nstatic int mg_null_if_udp_send(struct mg_connection *c, const void *buf,\n                               size_t len) {\n  (void) c;\n  (void) buf;\n  (void) len;\n  return -1;\n}\n\nint mg_null_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) {\n  (void) c;\n  (void) buf;\n  (void) len;\n  return -1;\n}\n\nint mg_null_if_udp_recv(struct mg_connection *c, void *buf, size_t len,\n                        union socket_address *sa, size_t *sa_len) {\n  (void) c;\n  (void) buf;\n  (void) len;\n  (void) sa;\n  (void) sa_len;\n  return -1;\n}\n\nstatic int mg_null_if_create_conn(struct mg_connection *c) {\n  (void) c;\n  return 1;\n}\n\nstatic void mg_null_if_destroy_conn(struct mg_connection *c) {\n  (void) c;\n}\n\nstatic void mg_null_if_sock_set(struct mg_connection *c, sock_t sock) {\n  (void) c;\n  (void) sock;\n}\n\nstatic void mg_null_if_init(struct mg_iface *iface) {\n  (void) iface;\n}\n\nstatic void mg_null_if_free(struct mg_iface *iface) {\n  (void) iface;\n}\n\nstatic void mg_null_if_add_conn(struct mg_connection *c) {\n  c->sock = INVALID_SOCKET;\n  c->flags |= MG_F_CLOSE_IMMEDIATELY;\n}\n\nstatic void mg_null_if_remove_conn(struct mg_connection *c) {\n  (void) c;\n}\n\nstatic time_t mg_null_if_poll(struct mg_iface *iface, int timeout_ms) {\n  struct mg_mgr *mgr = iface->mgr;\n  struct mg_connection *nc, *tmp;\n  double now = mg_time();\n  /* We basically just run timers and poll. */\n  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {\n    tmp = nc->next;\n    mg_if_poll(nc, now);\n  }\n  (void) timeout_ms;\n  return (time_t) now;\n}\n\nstatic void mg_null_if_get_conn_addr(struct mg_connection *c, int remote,\n                                     union socket_address *sa) {\n  (void) c;\n  (void) remote;\n  (void) sa;\n}\n\n#define MG_NULL_IFACE_VTABLE                                                   \\\n  {                                                                            \\\n    mg_null_if_init, mg_null_if_free, mg_null_if_add_conn,                     \\\n        mg_null_if_remove_conn, mg_null_if_poll, mg_null_if_listen_tcp,        \\\n        mg_null_if_listen_udp, mg_null_if_connect_tcp, mg_null_if_connect_udp, \\\n        mg_null_if_tcp_send, mg_null_if_udp_send, mg_null_if_tcp_recv,         \\\n        mg_null_if_udp_recv, mg_null_if_create_conn, mg_null_if_destroy_conn,  \\\n        mg_null_if_sock_set, mg_null_if_get_conn_addr,                         \\\n  }\n\nconst struct mg_iface_vtable mg_null_iface_vtable = MG_NULL_IFACE_VTABLE;\n\n#if MG_NET_IF == MG_NET_IF_NULL\nconst struct mg_iface_vtable mg_default_iface_vtable = MG_NULL_IFACE_VTABLE;\n#endif /* MG_NET_IF == MG_NET_IF_NULL */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if_socket.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_NET_IF_SOCKET\n\n/* Amalgamated: #include \"mg_net_if_socket.h\" */\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_util.h\" */\n\nstatic sock_t mg_open_listening_socket(union socket_address *sa, int type,\n                                       int proto);\n\nvoid mg_set_non_blocking_mode(sock_t sock) {\n#ifdef _WIN32\n  unsigned long on = 1;\n  ioctlsocket(sock, FIONBIO, &on);\n#else\n  int flags = fcntl(sock, F_GETFL, 0);\n  fcntl(sock, F_SETFL, flags | O_NONBLOCK);\n#endif\n}\n\nstatic int mg_is_error(void) {\n  int err = mg_get_errno();\n  return err != EINPROGRESS && err != EWOULDBLOCK\n#ifndef WINCE\n         && err != EAGAIN && err != EINTR\n#endif\n#ifdef _WIN32\n         && WSAGetLastError() != WSAEINTR && WSAGetLastError() != WSAEWOULDBLOCK\n#endif\n      ;\n}\n\nvoid mg_socket_if_connect_tcp(struct mg_connection *nc,\n                              const union socket_address *sa) {\n  int rc, proto = 0;\n#ifdef SO_NOSIGPIPE\n  int nopipe = 1;\n#endif\n  nc->sock = socket(sa->sa.sa_family, SOCK_STREAM, proto);\n  if (nc->sock == INVALID_SOCKET\n#ifdef SO_NOSIGPIPE\n      /* Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs */\n      || setsockopt(nc->sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nopipe, sizeof(nopipe))\n#endif\n  ) {\n    nc->err = mg_get_errno() ? mg_get_errno() : 1;\n    return;\n  }\n#if !defined(MG_ESP8266)\n  mg_set_non_blocking_mode(nc->sock);\n#endif\n  socklen_t sa_len =\n      (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);\n  rc = connect(nc->sock, &sa->sa, sa_len);\n  nc->err = rc < 0 && mg_is_error() ? mg_get_errno() : 0;\n  DBG((\"%p sock %d rc %d errno %d err %d\", nc, nc->sock, rc, mg_get_errno(),\n       nc->err));\n}\n\nvoid mg_socket_if_connect_udp(struct mg_connection *nc) {\n#ifdef SO_NOSIGPIPE\n  int nopipe = 1;\n#endif\n  nc->sock = socket(AF_INET, SOCK_DGRAM, 0);\n  if (nc->sock == INVALID_SOCKET\n#ifdef SO_NOSIGPIPE\n      /* Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs */\n      || setsockopt(nc->sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nopipe, sizeof(nopipe))\n#endif\n  ) {\n    nc->err = mg_get_errno() ? mg_get_errno() : 1;\n    return;\n  }\n  if (nc->flags & MG_F_ENABLE_BROADCAST) {\n    int optval = 1;\n    if (setsockopt(nc->sock, SOL_SOCKET, SO_BROADCAST, (const char *) &optval,\n                   sizeof(optval)) < 0) {\n      nc->err = mg_get_errno() ? mg_get_errno() : 1;\n      return;\n    }\n  }\n  nc->err = 0;\n}\n\nint mg_socket_if_listen_tcp(struct mg_connection *nc,\n                            union socket_address *sa) {\n  int proto = 0;\n  sock_t sock = mg_open_listening_socket(sa, SOCK_STREAM, proto);\n  if (sock == INVALID_SOCKET) {\n    return (mg_get_errno() ? mg_get_errno() : 1);\n  }\n  mg_sock_set(nc, sock);\n  return 0;\n}\n\nstatic int mg_socket_if_listen_udp(struct mg_connection *nc,\n                                   union socket_address *sa) {\n  sock_t sock = mg_open_listening_socket(sa, SOCK_DGRAM, 0);\n  if (sock == INVALID_SOCKET) return (mg_get_errno() ? mg_get_errno() : 1);\n  mg_sock_set(nc, sock);\n  return 0;\n}\n\nstatic int mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,\n                                 size_t len) {\n  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, MSG_NOSIGNAL);\n  if (n < 0 && !mg_is_error()) n = 0;\n  return n;\n}\n\nstatic int mg_socket_if_udp_send(struct mg_connection *nc, const void *buf,\n                                 size_t len) {\n  int n = sendto(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));\n  if (n < 0 && !mg_is_error()) n = 0;\n  return n;\n}\n\nstatic int mg_socket_if_tcp_recv(struct mg_connection *nc, void *buf,\n                                 size_t len) {\n  int n = (int) MG_RECV_FUNC(nc->sock, buf, len, 0);\n  if (n == 0) {\n    /* Orderly shutdown of the socket, try flushing output. */\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n  } else if (n < 0 && !mg_is_error()) {\n    n = 0;\n  }\n  return n;\n}\n\nstatic int mg_socket_if_udp_recv(struct mg_connection *nc, void *buf,\n                                 size_t len, union socket_address *sa,\n                                 size_t *sa_len) {\n  socklen_t sa_len_st = *sa_len;\n  int n = recvfrom(nc->sock, buf, len, 0, &sa->sa, &sa_len_st);\n  *sa_len = sa_len_st;\n  if (n < 0 && !mg_is_error()) n = 0;\n  return n;\n}\n\nint mg_socket_if_create_conn(struct mg_connection *nc) {\n  (void) nc;\n  return 1;\n}\n\nvoid mg_socket_if_destroy_conn(struct mg_connection *nc) {\n  if (nc->sock == INVALID_SOCKET) return;\n  if (!(nc->flags & MG_F_UDP)) {\n    closesocket(nc->sock);\n  } else {\n    /* Only close outgoing UDP sockets or listeners. */\n    if (nc->listener == NULL) closesocket(nc->sock);\n  }\n  nc->sock = INVALID_SOCKET;\n}\n\nstatic int mg_accept_conn(struct mg_connection *lc) {\n  struct mg_connection *nc;\n  union socket_address sa;\n  socklen_t sa_len = sizeof(sa);\n  /* NOTE(lsm): on Windows, sock is always > FD_SETSIZE */\n  sock_t sock = accept(lc->sock, &sa.sa, &sa_len);\n  if (sock == INVALID_SOCKET) {\n    if (mg_is_error()) {\n      DBG((\"%p: failed to accept: %d\", lc, mg_get_errno()));\n    }\n    return 0;\n  }\n  nc = mg_if_accept_new_conn(lc);\n  if (nc == NULL) {\n    closesocket(sock);\n    return 0;\n  }\n  DBG((\"%p conn from %s:%d\", nc, inet_ntoa(sa.sin.sin_addr),\n       ntohs(sa.sin.sin_port)));\n  mg_sock_set(nc, sock);\n  mg_if_accept_tcp_cb(nc, &sa, sa_len);\n  return 1;\n}\n\n/* 'sa' must be an initialized address to bind to */\nstatic sock_t mg_open_listening_socket(union socket_address *sa, int type,\n                                       int proto) {\n  socklen_t sa_len =\n      (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);\n  sock_t sock = INVALID_SOCKET;\n#ifdef SO_NOSIGPIPE\n  int nopipe = 1;\n#endif\n#if !MG_LWIP\n  int on = 1;\n#endif\n\n  if ((sock = socket(sa->sa.sa_family, type, proto)) != INVALID_SOCKET &&\n#ifdef SO_NOSIGPIPE\n      /* Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs */\n      !setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nopipe, sizeof(nopipe)) &&\n#endif\n\n#if !MG_LWIP /* LWIP doesn't support either */\n#if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)\n      /* \"Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE\" http://goo.gl/RmrFTm */\n      !setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *) &on,\n                  sizeof(on)) &&\n#endif\n\n#if !defined(_WIN32) || !defined(SO_EXCLUSIVEADDRUSE)\n      /*\n       * SO_RESUSEADDR is not enabled on Windows because the semantics of\n       * SO_REUSEADDR on UNIX and Windows is different. On Windows,\n       * SO_REUSEADDR allows to bind a socket to a port without error even if\n       * the port is already open by another program. This is not the behavior\n       * SO_REUSEADDR was designed for, and leads to hard-to-track failure\n       * scenarios. Therefore, SO_REUSEADDR was disabled on Windows unless\n       * SO_EXCLUSIVEADDRUSE is supported and set on a socket.\n       */\n      !setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &on, sizeof(on)) &&\n#endif\n#endif /* !MG_LWIP */\n\n      !bind(sock, &sa->sa, sa_len) &&\n      (type == SOCK_DGRAM || listen(sock, SOMAXCONN) == 0)) {\n#if !MG_LWIP\n    mg_set_non_blocking_mode(sock);\n    /* In case port was set to 0, get the real port number */\n    (void) getsockname(sock, &sa->sa, &sa_len);\n#endif\n  } else if (sock != INVALID_SOCKET) {\n    closesocket(sock);\n    sock = INVALID_SOCKET;\n  }\n\n  return sock;\n}\n\n#define _MG_F_FD_CAN_READ 1\n#define _MG_F_FD_CAN_WRITE 1 << 1\n#define _MG_F_FD_ERROR 1 << 2\n\nvoid mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {\n  int worth_logging =\n      fd_flags != 0 || (nc->flags & (MG_F_WANT_READ | MG_F_WANT_WRITE));\n  if (worth_logging) {\n    DBG((\"%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d\", nc, nc->sock,\n         fd_flags, nc->flags, (int) nc->recv_mbuf.len,\n         (int) nc->send_mbuf.len));\n  }\n\n  if (!mg_if_poll(nc, now)) return;\n\n  if (nc->flags & MG_F_CONNECTING) {\n    if (fd_flags != 0) {\n      int err = 0;\n#if !defined(MG_ESP8266)\n      if (!(nc->flags & MG_F_UDP)) {\n        socklen_t len = sizeof(err);\n        int ret =\n            getsockopt(nc->sock, SOL_SOCKET, SO_ERROR, (char *) &err, &len);\n        if (ret != 0) {\n          err = 1;\n        } else if (err == EAGAIN || err == EWOULDBLOCK) {\n          err = 0;\n        }\n      }\n#else\n      /*\n       * On ESP8266 we use blocking connect.\n       */\n      err = nc->err;\n#endif\n      mg_if_connect_cb(nc, err);\n    } else if (nc->err != 0) {\n      mg_if_connect_cb(nc, nc->err);\n    }\n  }\n\n  if (fd_flags & _MG_F_FD_CAN_READ) {\n    if (nc->flags & MG_F_UDP) {\n      mg_if_can_recv_cb(nc);\n    } else {\n      if (nc->flags & MG_F_LISTENING) {\n        /*\n         * We're not looping here, and accepting just one connection at\n         * a time. The reason is that eCos does not respect non-blocking\n         * flag on a listening socket and hangs in a loop.\n         */\n        mg_accept_conn(nc);\n      } else {\n        mg_if_can_recv_cb(nc);\n      }\n    }\n  }\n\n  if (fd_flags & _MG_F_FD_CAN_WRITE) mg_if_can_send_cb(nc);\n\n  if (worth_logging) {\n    DBG((\"%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d\", nc, nc->sock,\n         nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));\n  }\n}\n\n#if MG_ENABLE_BROADCAST\nstatic void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {\n  struct ctl_msg ctl_msg;\n  int len =\n      (int) MG_RECV_FUNC(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0);\n  if (len < 0)\n    perror(\"mg_mgr_handle_ctl_sock() recv failed, check firewall UDP loopback rules\");\n/*\n  size_t ret = MG_SEND_FUNC(mgr->ctl[1], ctl_msg.message, 1, 0);\n  if (ret < 0)\n    perror(\"mg_mgr_handle_ctl_sock() send failed, check UDP loopback device\");\n  DBG((\"read %d from ctl socket\", len));\n*/\n  if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {\n    struct mg_connection *nc;\n    for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {\n      ctl_msg.callback(nc, MG_EV_POLL,\n                       ctl_msg.message MG_UD_ARG(nc->user_data));\n    }\n  }\n}\n#endif\n\n/* Associate a socket to a connection. */\nvoid mg_socket_if_sock_set(struct mg_connection *nc, sock_t sock) {\n  mg_set_non_blocking_mode(sock);\n  mg_set_close_on_exec(sock);\n  nc->sock = sock;\n  DBG((\"%p %d\", nc, sock));\n}\n\nvoid mg_socket_if_init(struct mg_iface *iface) {\n  (void) iface;\n  DBG((\"%p using select()\", iface->mgr));\n#if MG_ENABLE_BROADCAST\n  mg_socketpair(iface->mgr->ctl, SOCK_DGRAM);\n#endif\n}\n\nvoid mg_socket_if_free(struct mg_iface *iface) {\n  (void) iface;\n}\n\nvoid mg_socket_if_add_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\nvoid mg_socket_if_remove_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\nvoid mg_add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {\n  if (sock != INVALID_SOCKET\n#ifdef __unix__\n      && sock < (sock_t) FD_SETSIZE\n#endif\n      ) {\n    FD_SET(sock, set);\n    if (*max_fd == INVALID_SOCKET || sock > *max_fd) {\n      *max_fd = sock;\n    }\n  }\n}\n\ntime_t mg_socket_if_poll(struct mg_iface *iface, int timeout_ms) {\n  struct mg_mgr *mgr = iface->mgr;\n  double now = mg_time();\n  double min_timer;\n  struct mg_connection *nc, *tmp;\n  struct timeval tv;\n  fd_set read_set, write_set, err_set;\n  sock_t max_fd = INVALID_SOCKET;\n  int num_fds, num_ev, num_timers = 0;\n#ifdef __unix__\n  int try_dup = 1;\n#endif\n\n  FD_ZERO(&read_set);\n  FD_ZERO(&write_set);\n  FD_ZERO(&err_set);\n#if MG_ENABLE_BROADCAST\n  mg_add_to_set(mgr->ctl[1], &read_set, &max_fd);\n#endif\n\n  /*\n   * Note: it is ok to have connections with sock == INVALID_SOCKET in the list,\n   * e.g. timer-only \"connections\".\n   */\n  min_timer = 0;\n  for (nc = mgr->active_connections, num_fds = 0; nc != NULL; nc = tmp) {\n    tmp = nc->next;\n\n    if (nc->sock != INVALID_SOCKET) {\n      num_fds++;\n\n#ifdef __unix__\n      /* A hack to make sure all our file descriptos fit into FD_SETSIZE. */\n      if (nc->sock >= (sock_t) FD_SETSIZE && try_dup) {\n        int new_sock = dup(nc->sock);\n        if (new_sock >= 0) {\n          if (new_sock < (sock_t) FD_SETSIZE) {\n            closesocket(nc->sock);\n            DBG((\"new sock %d -> %d\", nc->sock, new_sock));\n            nc->sock = new_sock;\n          } else {\n            closesocket(new_sock);\n            DBG((\"new sock is still larger than FD_SETSIZE, disregard\"));\n            try_dup = 0;\n          }\n        } else {\n          try_dup = 0;\n        }\n      }\n#endif\n\n      if (nc->recv_mbuf.len < nc->recv_mbuf_limit &&\n          (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {\n        mg_add_to_set(nc->sock, &read_set, &max_fd);\n      }\n\n      if (((nc->flags & MG_F_CONNECTING) && !(nc->flags & MG_F_WANT_READ)) ||\n          (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING))) {\n        mg_add_to_set(nc->sock, &write_set, &max_fd);\n        mg_add_to_set(nc->sock, &err_set, &max_fd);\n      }\n    }\n\n    if (nc->ev_timer_time > 0) {\n      if (num_timers == 0 || nc->ev_timer_time < min_timer) {\n        min_timer = nc->ev_timer_time;\n      }\n      num_timers++;\n    }\n  }\n\n  /*\n   * If there is a timer to be fired earlier than the requested timeout,\n   * adjust the timeout.\n   */\n  if (num_timers > 0) {\n    double timer_timeout_ms = (min_timer - mg_time()) * 1000 + 1 /* rounding */;\n    if (timer_timeout_ms < timeout_ms) {\n      timeout_ms = (int) timer_timeout_ms;\n    }\n  }\n  if (timeout_ms < 0) timeout_ms = 0;\n\n  tv.tv_sec = timeout_ms / 1000;\n  tv.tv_usec = (timeout_ms % 1000) * 1000;\n\n  num_ev = select((int) max_fd + 1, &read_set, &write_set, &err_set, &tv);\n  now = mg_time();\n#if 0\n  DBG((\"select @ %ld num_ev=%d of %d, timeout=%d\", (long) now, num_ev, num_fds,\n       timeout_ms));\n#endif\n\n#if MG_ENABLE_BROADCAST\n  if (num_ev > 0 && mgr->ctl[1] != INVALID_SOCKET &&\n      FD_ISSET(mgr->ctl[1], &read_set)) {\n    mg_mgr_handle_ctl_sock(mgr);\n  }\n#endif\n\n  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {\n    int fd_flags = 0;\n    if (nc->sock != INVALID_SOCKET) {\n      if (num_ev > 0) {\n        fd_flags = (FD_ISSET(nc->sock, &read_set) &&\n                            (!(nc->flags & MG_F_UDP) || nc->listener == NULL)\n                        ? _MG_F_FD_CAN_READ\n                        : 0) |\n                   (FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE : 0) |\n                   (FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);\n      }\n#if MG_LWIP\n      /* With LWIP socket emulation layer, we don't get write events for UDP */\n      if ((nc->flags & MG_F_UDP) && nc->listener == NULL) {\n        fd_flags |= _MG_F_FD_CAN_WRITE;\n      }\n#endif\n    }\n    tmp = nc->next;\n    mg_mgr_handle_conn(nc, fd_flags, now);\n  }\n\n  return (time_t) now;\n}\n\n#if MG_ENABLE_BROADCAST\nMG_INTERNAL void mg_socketpair_close(sock_t *sock) {\n  while (1) {\n    if (closesocket(*sock) == -1 && errno == EINTR) continue;\n    break;\n  }\n  *sock = INVALID_SOCKET;\n}\n\nMG_INTERNAL sock_t\nmg_socketpair_accept(sock_t sock, union socket_address *sa, socklen_t sa_len) {\n  sock_t rc;\n  while (1) {\n    if ((rc = accept(sock, &sa->sa, &sa_len)) == INVALID_SOCKET &&\n        errno == EINTR)\n      continue;\n    break;\n  }\n  return rc;\n}\n\nint mg_socketpair(sock_t sp[2], int sock_type) {\n  union socket_address sa, sa2;\n  sock_t sock;\n  socklen_t len = sizeof(sa.sin);\n  int ret = 0;\n\n  sock = sp[0] = sp[1] = INVALID_SOCKET;\n\n  (void) memset(&sa, 0, sizeof(sa));\n  sa.sin.sin_family = AF_INET;\n  sa.sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */\n  sa2 = sa;\n\n  if ((sock = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {\n    // abort\n  } else if (bind(sock, &sa.sa, len) != 0) {\n    // abort\n  } else if (sock_type == SOCK_STREAM && listen(sock, 1) != 0) {\n    // abort\n  } else if (getsockname(sock, &sa.sa, &len) != 0) {\n    // abort\n  } else if ((sp[0] = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {\n    // abort\n  } else if (sock_type == SOCK_STREAM && connect(sp[0], &sa.sa, len) != 0) {\n    // abort\n  } else if (sock_type == SOCK_DGRAM &&\n             (bind(sp[0], &sa2.sa, len) != 0 ||\n              getsockname(sp[0], &sa2.sa, &len) != 0 ||\n              connect(sp[0], &sa.sa, len) != 0 ||\n              connect(sock, &sa2.sa, len) != 0)) {\n    // abort\n  } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock : mg_socketpair_accept(\n                                                            sock, &sa, len))) ==\n             INVALID_SOCKET) {\n    // abort\n  } else {\n    mg_set_close_on_exec(sp[0]);\n    mg_set_close_on_exec(sp[1]);\n    if (sock_type == SOCK_STREAM) mg_socketpair_close(&sock);\n    ret = 1;\n  }\n\n  if (!ret) {\n    if (sp[0] != INVALID_SOCKET) mg_socketpair_close(&sp[0]);\n    if (sp[1] != INVALID_SOCKET) mg_socketpair_close(&sp[1]);\n    if (sock != INVALID_SOCKET) mg_socketpair_close(&sock);\n  }\n\n  return ret;\n}\n#endif /* MG_ENABLE_BROADCAST */\n\nstatic void mg_sock_get_addr(sock_t sock, int remote,\n                             union socket_address *sa) {\n  socklen_t slen = sizeof(*sa);\n  memset(sa, 0, slen);\n  if (remote) {\n    getpeername(sock, &sa->sa, &slen);\n  } else {\n    getsockname(sock, &sa->sa, &slen);\n  }\n}\n\nvoid mg_sock_to_str(sock_t sock, char *buf, size_t len, int flags) {\n  union socket_address sa;\n  mg_sock_get_addr(sock, flags & MG_SOCK_STRINGIFY_REMOTE, &sa);\n  mg_sock_addr_to_str(&sa, buf, len, flags);\n}\n\nvoid mg_socket_if_get_conn_addr(struct mg_connection *nc, int remote,\n                                union socket_address *sa) {\n  if ((nc->flags & MG_F_UDP) && remote) {\n    memcpy(sa, &nc->sa, sizeof(*sa));\n    return;\n  }\n  mg_sock_get_addr(nc->sock, remote, sa);\n}\n\n/* clang-format off */\n#define MG_SOCKET_IFACE_VTABLE                                          \\\n  {                                                                     \\\n    mg_socket_if_init,                                                  \\\n    mg_socket_if_free,                                                  \\\n    mg_socket_if_add_conn,                                              \\\n    mg_socket_if_remove_conn,                                           \\\n    mg_socket_if_poll,                                                  \\\n    mg_socket_if_listen_tcp,                                            \\\n    mg_socket_if_listen_udp,                                            \\\n    mg_socket_if_connect_tcp,                                           \\\n    mg_socket_if_connect_udp,                                           \\\n    mg_socket_if_tcp_send,                                              \\\n    mg_socket_if_udp_send,                                              \\\n    mg_socket_if_tcp_recv,                                              \\\n    mg_socket_if_udp_recv,                                              \\\n    mg_socket_if_create_conn,                                           \\\n    mg_socket_if_destroy_conn,                                          \\\n    mg_socket_if_sock_set,                                              \\\n    mg_socket_if_get_conn_addr,                                         \\\n  }\n/* clang-format on */\n\nconst struct mg_iface_vtable mg_socket_iface_vtable = MG_SOCKET_IFACE_VTABLE;\n#if MG_NET_IF == MG_NET_IF_SOCKET\nconst struct mg_iface_vtable mg_default_iface_vtable = MG_SOCKET_IFACE_VTABLE;\n#endif\n\n#endif /* MG_ENABLE_NET_IF_SOCKET */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_net_if_socks.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_SOCKS\n\nstruct socksdata {\n  char *proxy_addr;        /* HOST:PORT of the socks5 proxy server */\n  struct mg_connection *s; /* Respective connection to the server */\n  struct mg_connection *c; /* Connection to the client */\n};\n\nstatic void socks_if_disband(struct socksdata *d) {\n  LOG(LL_DEBUG, (\"disbanding proxy %p %p\", d->c, d->s));\n  if (d->c) {\n    d->c->flags |= MG_F_SEND_AND_CLOSE;\n    d->c->user_data = NULL;\n    d->c = NULL;\n  }\n  if (d->s) {\n    d->s->flags |= MG_F_SEND_AND_CLOSE;\n    d->s->user_data = NULL;\n    d->s = NULL;\n  }\n}\n\nstatic void socks_if_relay(struct mg_connection *s) {\n  struct socksdata *d = (struct socksdata *) s->user_data;\n  if (d == NULL || d->c == NULL || !(s->flags & MG_SOCKS_CONNECT_DONE) ||\n      d->s == NULL) {\n    return;\n  }\n  if (s->recv_mbuf.len > 0) mg_if_can_recv_cb(d->c);\n  if (d->c->send_mbuf.len > 0 && s->send_mbuf.len == 0) mg_if_can_send_cb(d->c);\n}\n\nstatic void socks_if_handler(struct mg_connection *c, int ev, void *ev_data) {\n  struct socksdata *d = (struct socksdata *) c->user_data;\n  if (d == NULL) return;\n  if (ev == MG_EV_CONNECT) {\n    int res = *(int *) ev_data;\n    if (res == 0) {\n      /* Send handshake to the proxy server */\n      unsigned char buf[] = {MG_SOCKS_VERSION, 1, MG_SOCKS_HANDSHAKE_NOAUTH};\n      mg_send(d->s, buf, sizeof(buf));\n      LOG(LL_DEBUG, (\"Sent handshake to %s\", d->proxy_addr));\n    } else {\n      LOG(LL_ERROR, (\"Cannot connect to %s: %d\", d->proxy_addr, res));\n      d->c->flags |= MG_F_CLOSE_IMMEDIATELY;\n    }\n  } else if (ev == MG_EV_CLOSE) {\n    socks_if_disband(d);\n  } else if (ev == MG_EV_RECV) {\n    /* Handle handshake reply */\n    if (!(c->flags & MG_SOCKS_HANDSHAKE_DONE)) {\n      /* TODO(lsm): process IPv6 too */\n      unsigned char buf[10] = {MG_SOCKS_VERSION, MG_SOCKS_CMD_CONNECT, 0,\n                               MG_SOCKS_ADDR_IPV4};\n      if (c->recv_mbuf.len < 2) return;\n      if ((unsigned char) c->recv_mbuf.buf[1] == MG_SOCKS_HANDSHAKE_FAILURE) {\n        LOG(LL_ERROR, (\"Server kicked us out\"));\n        socks_if_disband(d);\n        return;\n      }\n      mbuf_remove(&c->recv_mbuf, 2);\n      c->flags |= MG_SOCKS_HANDSHAKE_DONE;\n\n      /* Send connect request */\n      memcpy(buf + 4, &d->c->sa.sin.sin_addr, 4);\n      memcpy(buf + 8, &d->c->sa.sin.sin_port, 2);\n      mg_send(c, buf, sizeof(buf));\n      LOG(LL_DEBUG, (\"%p Sent connect request\", c));\n    }\n    /* Process connect request */\n    if ((c->flags & MG_SOCKS_HANDSHAKE_DONE) &&\n        !(c->flags & MG_SOCKS_CONNECT_DONE)) {\n      if (c->recv_mbuf.len < 10) return;\n      if (c->recv_mbuf.buf[1] != MG_SOCKS_SUCCESS) {\n        LOG(LL_ERROR, (\"Socks connection error: %d\", c->recv_mbuf.buf[1]));\n        socks_if_disband(d);\n        return;\n      }\n      mbuf_remove(&c->recv_mbuf, 10);\n      c->flags |= MG_SOCKS_CONNECT_DONE;\n      LOG(LL_DEBUG, (\"%p Connect done %p\", c, d->c));\n      mg_if_connect_cb(d->c, 0);\n    }\n    socks_if_relay(c);\n  } else if (ev == MG_EV_SEND || ev == MG_EV_POLL) {\n    socks_if_relay(c);\n  }\n}\n\nstatic void mg_socks_if_connect_tcp(struct mg_connection *c,\n                                    const union socket_address *sa) {\n  struct socksdata *d = (struct socksdata *) c->iface->data;\n  d->c = c;\n  d->s = mg_connect(c->mgr, d->proxy_addr, socks_if_handler);\n  d->s->user_data = d;\n  LOG(LL_DEBUG, (\"%p %s %p %p\", c, d->proxy_addr, d, d->s));\n  (void) sa;\n}\n\nstatic void mg_socks_if_connect_udp(struct mg_connection *c) {\n  (void) c;\n}\n\nstatic int mg_socks_if_listen_tcp(struct mg_connection *c,\n                                  union socket_address *sa) {\n  (void) c;\n  (void) sa;\n  return 0;\n}\n\nstatic int mg_socks_if_listen_udp(struct mg_connection *c,\n                                  union socket_address *sa) {\n  (void) c;\n  (void) sa;\n  return -1;\n}\n\nstatic int mg_socks_if_tcp_send(struct mg_connection *c, const void *buf,\n                                size_t len) {\n  int res;\n  struct socksdata *d = (struct socksdata *) c->iface->data;\n  if (d->s == NULL) return -1;\n  res = (int) mbuf_append(&d->s->send_mbuf, buf, len);\n  DBG((\"%p -> %d -> %p\", c, res, d->s));\n  return res;\n}\n\nstatic int mg_socks_if_udp_send(struct mg_connection *c, const void *buf,\n                                size_t len) {\n  (void) c;\n  (void) buf;\n  (void) len;\n  return -1;\n}\n\nint mg_socks_if_tcp_recv(struct mg_connection *c, void *buf, size_t len) {\n  struct socksdata *d = (struct socksdata *) c->iface->data;\n  if (d->s == NULL) return -1;\n  if (len > d->s->recv_mbuf.len) len = d->s->recv_mbuf.len;\n  if (len > 0) {\n    memcpy(buf, d->s->recv_mbuf.buf, len);\n    mbuf_remove(&d->s->recv_mbuf, len);\n  }\n  DBG((\"%p <- %d <- %p\", c, (int) len, d->s));\n  return len;\n}\n\nint mg_socks_if_udp_recv(struct mg_connection *c, void *buf, size_t len,\n                         union socket_address *sa, size_t *sa_len) {\n  (void) c;\n  (void) buf;\n  (void) len;\n  (void) sa;\n  (void) sa_len;\n  return -1;\n}\n\nstatic int mg_socks_if_create_conn(struct mg_connection *c) {\n  (void) c;\n  return 1;\n}\n\nstatic void mg_socks_if_destroy_conn(struct mg_connection *c) {\n  c->iface->vtable->_free(c->iface);\n  MG_FREE(c->iface);\n  c->iface = NULL;\n  LOG(LL_DEBUG, (\"%p\", c));\n}\n\nstatic void mg_socks_if_sock_set(struct mg_connection *c, sock_t sock) {\n  (void) c;\n  (void) sock;\n}\n\nstatic void mg_socks_if_init(struct mg_iface *iface) {\n  (void) iface;\n}\n\nstatic void mg_socks_if_free(struct mg_iface *iface) {\n  struct socksdata *d = (struct socksdata *) iface->data;\n  LOG(LL_DEBUG, (\"%p\", iface));\n  if (d != NULL) {\n    socks_if_disband(d);\n    MG_FREE(d->proxy_addr);\n    MG_FREE(d);\n    iface->data = NULL;\n  }\n}\n\nstatic void mg_socks_if_add_conn(struct mg_connection *c) {\n  c->sock = INVALID_SOCKET;\n}\n\nstatic void mg_socks_if_remove_conn(struct mg_connection *c) {\n  (void) c;\n}\n\nstatic time_t mg_socks_if_poll(struct mg_iface *iface, int timeout_ms) {\n  LOG(LL_DEBUG, (\"%p\", iface));\n  (void) iface;\n  (void) timeout_ms;\n  return (time_t) cs_time();\n}\n\nstatic void mg_socks_if_get_conn_addr(struct mg_connection *c, int remote,\n                                      union socket_address *sa) {\n  LOG(LL_DEBUG, (\"%p\", c));\n  (void) c;\n  (void) remote;\n  (void) sa;\n}\n\nconst struct mg_iface_vtable mg_socks_iface_vtable = {\n    mg_socks_if_init,          mg_socks_if_free,\n    mg_socks_if_add_conn,      mg_socks_if_remove_conn,\n    mg_socks_if_poll,          mg_socks_if_listen_tcp,\n    mg_socks_if_listen_udp,    mg_socks_if_connect_tcp,\n    mg_socks_if_connect_udp,   mg_socks_if_tcp_send,\n    mg_socks_if_udp_send,      mg_socks_if_tcp_recv,\n    mg_socks_if_udp_recv,      mg_socks_if_create_conn,\n    mg_socks_if_destroy_conn,  mg_socks_if_sock_set,\n    mg_socks_if_get_conn_addr,\n};\n\nstruct mg_iface *mg_socks_mk_iface(struct mg_mgr *mgr, const char *proxy_addr) {\n  struct mg_iface *iface = mg_if_create_iface(&mg_socks_iface_vtable, mgr);\n  iface->data = MG_CALLOC(1, sizeof(struct socksdata));\n  ((struct socksdata *) iface->data)->proxy_addr = strdup(proxy_addr);\n  return iface;\n}\n\n#endif\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_ssl_if_openssl.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_OPENSSL\n\n#ifdef __APPLE__\n#pragma GCC diagnostic ignored \"-Wdeprecated-declarations\"\n#endif\n\n#include <openssl/ssl.h>\n#ifndef KR_VERSION\n#include <openssl/tls1.h>\n#endif\n\nstruct mg_ssl_if_ctx {\n  SSL *ssl;\n  SSL_CTX *ssl_ctx;\n  struct mbuf psk;\n  size_t identity_len;\n};\n\nvoid mg_ssl_if_init() {\n  SSL_library_init();\n}\n\nenum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,\n                                            struct mg_connection *lc) {\n  struct mg_ssl_if_ctx *ctx =\n      (struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));\n  struct mg_ssl_if_ctx *lc_ctx = (struct mg_ssl_if_ctx *) lc->ssl_if_data;\n  nc->ssl_if_data = ctx;\n  if (ctx == NULL || lc_ctx == NULL) return MG_SSL_ERROR;\n  ctx->ssl_ctx = lc_ctx->ssl_ctx;\n  if ((ctx->ssl = SSL_new(ctx->ssl_ctx)) == NULL) {\n    return MG_SSL_ERROR;\n  }\n  return MG_SSL_OK;\n}\n\nstatic enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,\n                                         const char *key, const char **err_msg);\nstatic enum mg_ssl_if_result mg_use_ca_cert(SSL_CTX *ctx, const char *cert);\nstatic enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl);\nstatic enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,\n                                                    const char *identity,\n                                                    const char *key_str);\n\nenum mg_ssl_if_result mg_ssl_if_conn_init(\n    struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,\n    const char **err_msg) {\n  struct mg_ssl_if_ctx *ctx =\n      (struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));\n  DBG((\"%p %s,%s,%s\", nc, (params->cert ? params->cert : \"\"),\n       (params->key ? params->key : \"\"),\n       (params->ca_cert ? params->ca_cert : \"\")));\n  if (ctx == NULL) {\n    MG_SET_PTRPTR(err_msg, \"Out of memory\");\n    return MG_SSL_ERROR;\n  }\n  nc->ssl_if_data = ctx;\n  if (nc->flags & MG_F_LISTENING) {\n    ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method());\n  } else {\n    ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());\n  }\n  if (ctx->ssl_ctx == NULL) {\n    MG_SET_PTRPTR(err_msg, \"Failed to create SSL context\");\n    return MG_SSL_ERROR;\n  }\n\n#ifndef KR_VERSION\n  /* Disable deprecated protocols. */\n  SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2);\n  SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv3);\n  SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_TLSv1);\n#ifdef MG_SSL_OPENSSL_NO_COMPRESSION\n  SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_COMPRESSION);\n#endif\n#ifdef MG_SSL_OPENSSL_CIPHER_SERVER_PREFERENCE\n  SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);\n#endif\n#else\n/* Krypton only supports TLSv1.2 anyway. */\n#endif\n\n  if (params->cert != NULL &&\n      mg_use_cert(ctx->ssl_ctx, params->cert, params->key, err_msg) !=\n          MG_SSL_OK) {\n    return MG_SSL_ERROR;\n  }\n\n  if (params->ca_cert != NULL &&\n      mg_use_ca_cert(ctx->ssl_ctx, params->ca_cert) != MG_SSL_OK) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL CA cert\");\n    return MG_SSL_ERROR;\n  }\n\n  if (mg_set_cipher_list(ctx->ssl_ctx, params->cipher_suites) != MG_SSL_OK) {\n    MG_SET_PTRPTR(err_msg, \"Invalid cipher suite list\");\n    return MG_SSL_ERROR;\n  }\n\n  mbuf_init(&ctx->psk, 0);\n  if (mg_ssl_if_ossl_set_psk(ctx, params->psk_identity, params->psk_key) !=\n      MG_SSL_OK) {\n    MG_SET_PTRPTR(err_msg, \"Invalid PSK settings\");\n    return MG_SSL_ERROR;\n  }\n\n  if (!(nc->flags & MG_F_LISTENING) &&\n      (ctx->ssl = SSL_new(ctx->ssl_ctx)) == NULL) {\n    MG_SET_PTRPTR(err_msg, \"Failed to create SSL session\");\n    return MG_SSL_ERROR;\n  }\n\n  if (params->server_name != NULL) {\n#ifdef KR_VERSION\n    SSL_CTX_kr_set_verify_name(ctx->ssl_ctx, params->server_name);\n#else\n    SSL_set_tlsext_host_name(ctx->ssl, params->server_name);\n#endif\n  }\n\n  nc->flags |= MG_F_SSL;\n\n  return MG_SSL_OK;\n}\n\nstatic enum mg_ssl_if_result mg_ssl_if_ssl_err(struct mg_connection *nc,\n                                               int res) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  int err = SSL_get_error(ctx->ssl, res);\n  if (err == SSL_ERROR_WANT_READ) return MG_SSL_WANT_READ;\n  if (err == SSL_ERROR_WANT_WRITE) return MG_SSL_WANT_WRITE;\n  DBG((\"%p %p SSL error: %d %d\", nc, ctx->ssl_ctx, res, err));\n  nc->err = err;\n  return MG_SSL_ERROR;\n}\n\nenum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  int server_side = (nc->listener != NULL);\n  int res;\n  /* If descriptor is not yet set, do it now. */\n  if (SSL_get_fd(ctx->ssl) < 0) {\n    if (SSL_set_fd(ctx->ssl, nc->sock) != 1) return MG_SSL_ERROR;\n  }\n  res = server_side ? SSL_accept(ctx->ssl) : SSL_connect(ctx->ssl);\n  if (res != 1) return mg_ssl_if_ssl_err(nc, res);\n  return MG_SSL_OK;\n}\n\nint mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t buf_size) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  int n = SSL_read(ctx->ssl, buf, buf_size);\n  DBG((\"%p %d -> %d\", nc, (int) buf_size, n));\n  if (n < 0) return mg_ssl_if_ssl_err(nc, n);\n  if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  return n;\n}\n\nint mg_ssl_if_write(struct mg_connection *nc, const void *data, size_t len) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  int n = SSL_write(ctx->ssl, data, len);\n  DBG((\"%p %d -> %d\", nc, (int) len, n));\n  if (n <= 0) return mg_ssl_if_ssl_err(nc, n);\n  return n;\n}\n\nvoid mg_ssl_if_conn_close_notify(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  if (ctx == NULL) return;\n  SSL_shutdown(ctx->ssl);\n}\n\nvoid mg_ssl_if_conn_free(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  if (ctx == NULL) return;\n  nc->ssl_if_data = NULL;\n  if (ctx->ssl != NULL) SSL_free(ctx->ssl);\n  if (ctx->ssl_ctx != NULL && nc->listener == NULL) SSL_CTX_free(ctx->ssl_ctx);\n  mbuf_free(&ctx->psk);\n  memset(ctx, 0, sizeof(*ctx));\n  MG_FREE(ctx);\n}\n\n/*\n * Cipher suite options used for TLS negotiation.\n * https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations\n */\nstatic const char mg_s_cipher_list[] =\n#if defined(MG_SSL_CRYPTO_MODERN)\n    \"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:\"\n    \"ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:\"\n    \"DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:\"\n    \"ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:\"\n    \"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:\"\n    \"ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:\"\n    \"DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:\"\n    \"DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:\"\n    \"!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK\"\n#elif defined(MG_SSL_CRYPTO_OLD)\n    \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:\"\n    \"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:\"\n    \"DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:\"\n    \"ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:\"\n    \"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:\"\n    \"ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:\"\n    \"DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:\"\n    \"DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:\"\n    \"ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:\"\n    \"AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:\"\n    \"HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:\"\n    \"!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA\"\n#else /* Default - intermediate. */\n    \"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:\"\n    \"ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:\"\n    \"DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:\"\n    \"ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:\"\n    \"ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:\"\n    \"ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:\"\n    \"DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:\"\n    \"DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:\"\n    \"AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:\"\n    \"DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:\"\n    \"!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA\"\n#endif\n    ;\n\n/*\n * Default DH params for PFS cipher negotiation. This is a 2048-bit group.\n * Will be used if none are provided by the user in the certificate file.\n */\n#if !MG_DISABLE_PFS && !defined(KR_VERSION)\nstatic const char mg_s_default_dh_params[] =\n    \"\\\n-----BEGIN DH PARAMETERS-----\\n\\\nMIIBCAKCAQEAlvbgD/qh9znWIlGFcV0zdltD7rq8FeShIqIhkQ0C7hYFThrBvF2E\\n\\\nZ9bmgaP+sfQwGpVlv9mtaWjvERbu6mEG7JTkgmVUJrUt/wiRzwTaCXBqZkdUO8Tq\\n\\\n+E6VOEQAilstG90ikN1Tfo+K6+X68XkRUIlgawBTKuvKVwBhuvlqTGerOtnXWnrt\\n\\\nym//hd3cd5PBYGBix0i7oR4xdghvfR2WLVu0LgdThTBb6XP7gLd19cQ1JuBtAajZ\\n\\\nwMuPn7qlUkEFDIkAZy59/Hue/H2Q2vU/JsvVhHWCQBL4F1ofEAt50il6ZxR1QfFK\\n\\\n9VGKDC4oOgm9DlxwwBoC2FjqmvQlqVV3kwIBAg==\\n\\\n-----END DH PARAMETERS-----\\n\";\n#endif\n\nstatic enum mg_ssl_if_result mg_use_ca_cert(SSL_CTX *ctx, const char *cert) {\n  if (cert == NULL || strcmp(cert, \"*\") == 0) {\n    return MG_SSL_OK;\n  }\n  SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 0);\n  return SSL_CTX_load_verify_locations(ctx, cert, NULL) == 1 ? MG_SSL_OK\n                                                             : MG_SSL_ERROR;\n}\n\nstatic enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,\n                                         const char *key,\n                                         const char **err_msg) {\n  if (key == NULL) key = cert;\n  if (cert == NULL || cert[0] == '\\0' || key == NULL || key[0] == '\\0') {\n    return MG_SSL_OK;\n  } else if (SSL_CTX_use_certificate_file(ctx, cert, 1) == 0) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL cert\");\n    return MG_SSL_ERROR;\n  } else if (SSL_CTX_use_PrivateKey_file(ctx, key, 1) == 0) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL key\");\n    return MG_SSL_ERROR;\n  } else if (SSL_CTX_use_certificate_chain_file(ctx, cert) == 0) {\n    MG_SET_PTRPTR(err_msg, \"Invalid CA bundle\");\n    return MG_SSL_ERROR;\n  } else {\n    SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);\n#if !MG_DISABLE_PFS && !defined(KR_VERSION)\n    BIO *bio = NULL;\n    DH *dh = NULL;\n\n    /* Try to read DH parameters from the cert/key file. */\n    bio = BIO_new_file(cert, \"r\");\n    if (bio != NULL) {\n      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);\n      BIO_free(bio);\n    }\n    /*\n     * If there are no DH params in the file, fall back to hard-coded ones.\n     * Not ideal, but better than nothing.\n     */\n    if (dh == NULL) {\n      bio = BIO_new_mem_buf((void *) mg_s_default_dh_params, -1);\n      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);\n      BIO_free(bio);\n    }\n    if (dh != NULL) {\n      SSL_CTX_set_tmp_dh(ctx, dh);\n      SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);\n      DH_free(dh);\n    }\n#if OPENSSL_VERSION_NUMBER > 0x10002000L\n    (void) SSL_CTX_set_ecdh_auto(ctx, 1);\n#endif\n#endif\n  }\n  return MG_SSL_OK;\n}\n\nstatic enum mg_ssl_if_result mg_set_cipher_list(SSL_CTX *ctx, const char *cl) {\n  return (SSL_CTX_set_cipher_list(ctx, cl ? cl : mg_s_cipher_list) == 1\n              ? MG_SSL_OK\n              : MG_SSL_ERROR);\n}\n\n#if !defined(KR_VERSION) && !defined(LIBRESSL_VERSION_NUMBER)\nstatic unsigned int mg_ssl_if_ossl_psk_cb(SSL *ssl, const char *hint,\n                                          char *identity,\n                                          unsigned int max_identity_len,\n                                          unsigned char *psk,\n                                          unsigned int max_psk_len) {\n  struct mg_ssl_if_ctx *ctx =\n      (struct mg_ssl_if_ctx *) SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl));\n  size_t key_len = ctx->psk.len - ctx->identity_len - 1;\n  DBG((\"hint: '%s'\", (hint ? hint : \"\")));\n  if (ctx->identity_len + 1 > max_identity_len) {\n    DBG((\"identity too long\"));\n    return 0;\n  }\n  if (key_len > max_psk_len) {\n    DBG((\"key too long\"));\n    return 0;\n  }\n  memcpy(identity, ctx->psk.buf, ctx->identity_len + 1);\n  memcpy(psk, ctx->psk.buf + ctx->identity_len + 1, key_len);\n  (void) ssl;\n  return key_len;\n}\n\nstatic enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,\n                                                    const char *identity,\n                                                    const char *key_str) {\n  unsigned char key[32];\n  size_t key_len;\n  size_t i = 0;\n  if (identity == NULL && key_str == NULL) return MG_SSL_OK;\n  if (identity == NULL || key_str == NULL) return MG_SSL_ERROR;\n  key_len = strlen(key_str);\n  if (key_len != 32 && key_len != 64) return MG_SSL_ERROR;\n  memset(key, 0, sizeof(key));\n  key_len = 0;\n  for (i = 0; key_str[i] != '\\0'; i++) {\n    unsigned char c;\n    char hc = tolower((int) key_str[i]);\n    if (hc >= '0' && hc <= '9') {\n      c = hc - '0';\n    } else if (hc >= 'a' && hc <= 'f') {\n      c = hc - 'a' + 0xa;\n    } else {\n      return MG_SSL_ERROR;\n    }\n    key_len = i / 2;\n    key[key_len] <<= 4;\n    key[key_len] |= c;\n  }\n  key_len++;\n  DBG((\"identity = '%s', key = (%u)\", identity, (unsigned int) key_len));\n  ctx->identity_len = strlen(identity);\n  mbuf_append(&ctx->psk, identity, ctx->identity_len + 1);\n  mbuf_append(&ctx->psk, key, key_len);\n  SSL_CTX_set_psk_client_callback(ctx->ssl_ctx, mg_ssl_if_ossl_psk_cb);\n  SSL_CTX_set_app_data(ctx->ssl_ctx, ctx);\n  return MG_SSL_OK;\n}\n#else\nstatic enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,\n                                                    const char *identity,\n                                                    const char *key_str) {\n  (void) ctx;\n  if (identity == NULL && key_str == NULL) return MG_SSL_OK;\n  /* Krypton / LibreSSL does not support PSK. */\n  return MG_SSL_ERROR;\n}\n#endif /* !defined(KR_VERSION) && !defined(LIBRESSL_VERSION_NUMBER) */\n\nconst char *mg_set_ssl(struct mg_connection *nc, const char *cert,\n                       const char *ca_cert) {\n  const char *err_msg = NULL;\n  struct mg_ssl_if_conn_params params;\n  memset(&params, 0, sizeof(params));\n  params.cert = cert;\n  params.ca_cert = ca_cert;\n  if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {\n    return err_msg;\n  }\n  return NULL;\n}\n\n#endif /* MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_OPENSSL */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_ssl_if_mbedtls.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS\n\n#include <mbedtls/debug.h>\n#include <mbedtls/ecp.h>\n#include <mbedtls/net.h>\n#include <mbedtls/platform.h>\n#include <mbedtls/ssl.h>\n#include <mbedtls/ssl_internal.h>\n#include <mbedtls/x509_crt.h>\n#include <mbedtls/version.h>\n\nstatic void mg_ssl_mbed_log(void *ctx, int level, const char *file, int line,\n                            const char *str) {\n  enum cs_log_level cs_level;\n  switch (level) {\n    case 1:\n      cs_level = LL_ERROR;\n      break;\n    case 2:\n      cs_level = LL_INFO;\n      break;\n    case 3:\n      cs_level = LL_DEBUG;\n      break;\n    default:\n      cs_level = LL_VERBOSE_DEBUG;\n  }\n  /* mbedTLS passes strings with \\n at the end, strip it. */\n  LOG(cs_level, (\"%p %.*s\", ctx, (int) (strlen(str) - 1), str));\n  (void) ctx;\n  (void) str;\n  (void) file;\n  (void) line;\n  (void) cs_level;\n}\n\nstruct mg_ssl_if_ctx {\n  mbedtls_ssl_config *conf;\n  mbedtls_ssl_context *ssl;\n  mbedtls_x509_crt *cert;\n  mbedtls_pk_context *key;\n  mbedtls_x509_crt *ca_cert;\n  struct mbuf cipher_suites;\n  size_t saved_len;\n};\n\n/* Must be provided by the platform. ctx is struct mg_connection. */\nextern int mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len);\n\nvoid mg_ssl_if_init() {\n  LOG(LL_INFO, (\"%s\", MBEDTLS_VERSION_STRING_FULL));\n}\n\nenum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,\n                                            struct mg_connection *lc) {\n  struct mg_ssl_if_ctx *ctx =\n      (struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));\n  struct mg_ssl_if_ctx *lc_ctx = (struct mg_ssl_if_ctx *) lc->ssl_if_data;\n  nc->ssl_if_data = ctx;\n  if (ctx == NULL || lc_ctx == NULL) return MG_SSL_ERROR;\n  ctx->ssl = (mbedtls_ssl_context *) MG_CALLOC(1, sizeof(*ctx->ssl));\n  if (mbedtls_ssl_setup(ctx->ssl, lc_ctx->conf) != 0) {\n    return MG_SSL_ERROR;\n  }\n  return MG_SSL_OK;\n}\n\nstatic enum mg_ssl_if_result mg_use_cert(struct mg_ssl_if_ctx *ctx,\n                                         const char *cert, const char *key,\n                                         const char **err_msg);\nstatic enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx,\n                                            const char *cert);\nstatic enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx,\n                                                const char *ciphers);\n#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED\nstatic enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,\n                                                    const char *identity,\n                                                    const char *key);\n#endif\n\nenum mg_ssl_if_result mg_ssl_if_conn_init(\n    struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,\n    const char **err_msg) {\n  struct mg_ssl_if_ctx *ctx =\n      (struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));\n  DBG((\"%p %s,%s,%s\", nc, (params->cert ? params->cert : \"\"),\n       (params->key ? params->key : \"\"),\n       (params->ca_cert ? params->ca_cert : \"\")));\n\n  if (ctx == NULL) {\n    MG_SET_PTRPTR(err_msg, \"Out of memory\");\n    return MG_SSL_ERROR;\n  }\n  nc->ssl_if_data = ctx;\n  ctx->conf = (mbedtls_ssl_config *) MG_CALLOC(1, sizeof(*ctx->conf));\n  mbuf_init(&ctx->cipher_suites, 0);\n  mbedtls_ssl_config_init(ctx->conf);\n  mbedtls_ssl_conf_dbg(ctx->conf, mg_ssl_mbed_log, nc);\n  if (mbedtls_ssl_config_defaults(\n          ctx->conf, (nc->flags & MG_F_LISTENING ? MBEDTLS_SSL_IS_SERVER\n                                                 : MBEDTLS_SSL_IS_CLIENT),\n          MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) {\n    MG_SET_PTRPTR(err_msg, \"Failed to init SSL config\");\n    return MG_SSL_ERROR;\n  }\n\n  /* TLS 1.2 and up */\n  mbedtls_ssl_conf_min_version(ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,\n                               MBEDTLS_SSL_MINOR_VERSION_3);\n  mbedtls_ssl_conf_rng(ctx->conf, mg_ssl_if_mbed_random, nc);\n\n  if (params->cert != NULL &&\n      mg_use_cert(ctx, params->cert, params->key, err_msg) != MG_SSL_OK) {\n    return MG_SSL_ERROR;\n  }\n\n  if (params->ca_cert != NULL &&\n      mg_use_ca_cert(ctx, params->ca_cert) != MG_SSL_OK) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL CA cert\");\n    return MG_SSL_ERROR;\n  }\n\n  if (mg_set_cipher_list(ctx, params->cipher_suites) != MG_SSL_OK) {\n    MG_SET_PTRPTR(err_msg, \"Invalid cipher suite list\");\n    return MG_SSL_ERROR;\n  }\n\n#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED\n  if (mg_ssl_if_mbed_set_psk(ctx, params->psk_identity, params->psk_key) !=\n      MG_SSL_OK) {\n    MG_SET_PTRPTR(err_msg, \"Invalid PSK settings\");\n    return MG_SSL_ERROR;\n  }\n#endif\n\n  if (!(nc->flags & MG_F_LISTENING)) {\n    ctx->ssl = (mbedtls_ssl_context *) MG_CALLOC(1, sizeof(*ctx->ssl));\n    mbedtls_ssl_init(ctx->ssl);\n    if (mbedtls_ssl_setup(ctx->ssl, ctx->conf) != 0) {\n      MG_SET_PTRPTR(err_msg, \"Failed to create SSL session\");\n      return MG_SSL_ERROR;\n    }\n    if (params->server_name != NULL &&\n        mbedtls_ssl_set_hostname(ctx->ssl, params->server_name) != 0) {\n      return MG_SSL_ERROR;\n    }\n  }\n\n#ifdef MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN\n  if (mbedtls_ssl_conf_max_frag_len(ctx->conf,\n#if MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 512\n                                    MBEDTLS_SSL_MAX_FRAG_LEN_512\n#elif MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 1024\n                                    MBEDTLS_SSL_MAX_FRAG_LEN_1024\n#elif MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 2048\n                                    MBEDTLS_SSL_MAX_FRAG_LEN_2048\n#elif MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN == 4096\n                                    MBEDTLS_SSL_MAX_FRAG_LEN_4096\n#else\n#error Invalid MG_SSL_IF_MBEDTLS_MAX_FRAG_LEN\n#endif\n                                    ) != 0) {\n    return MG_SSL_ERROR;\n  }\n#endif\n\n  nc->flags |= MG_F_SSL;\n\n  return MG_SSL_OK;\n}\n\nstatic int mg_ssl_if_mbed_send(void *ctx, const unsigned char *buf,\n                               size_t len) {\n  struct mg_connection *nc = (struct mg_connection *) ctx;\n  int n = nc->iface->vtable->tcp_send(nc, buf, len);\n  if (n > 0) return n;\n  if (n == 0) return MBEDTLS_ERR_SSL_WANT_WRITE;\n  return MBEDTLS_ERR_NET_SEND_FAILED;\n}\n\nstatic int mg_ssl_if_mbed_recv(void *ctx, unsigned char *buf, size_t len) {\n  struct mg_connection *nc = (struct mg_connection *) ctx;\n  int n = nc->iface->vtable->tcp_recv(nc, buf, len);\n  if (n > 0) return n;\n  if (n == 0) return MBEDTLS_ERR_SSL_WANT_READ;\n  return MBEDTLS_ERR_NET_RECV_FAILED;\n}\n\nstatic enum mg_ssl_if_result mg_ssl_if_mbed_err(struct mg_connection *nc,\n                                                int ret) {\n  enum mg_ssl_if_result res = MG_SSL_OK;\n  if (ret == MBEDTLS_ERR_SSL_WANT_READ) {\n    res = MG_SSL_WANT_READ;\n  } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {\n    res = MG_SSL_WANT_WRITE;\n  } else if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {\n    LOG(LL_DEBUG, (\"%p TLS connection closed by peer\", nc));\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    res = MG_SSL_OK;\n  } else {\n    LOG(LL_ERROR, (\"%p mbedTLS error: -0x%04x\", nc, -ret));\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    res = MG_SSL_ERROR;\n  }\n  nc->err = ret;\n  return res;\n}\n\nstatic void mg_ssl_if_mbed_free_certs_and_keys(struct mg_ssl_if_ctx *ctx) {\n  if (ctx->cert != NULL) {\n    mbedtls_x509_crt_free(ctx->cert);\n    MG_FREE(ctx->cert);\n    ctx->cert = NULL;\n    mbedtls_pk_free(ctx->key);\n    MG_FREE(ctx->key);\n    ctx->key = NULL;\n  }\n  if (ctx->ca_cert != NULL) {\n    mbedtls_ssl_conf_ca_chain(ctx->conf, NULL, NULL);\n#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK\n    if (ctx->conf->ca_chain_file != NULL) {\n      MG_FREE((void *) ctx->conf->ca_chain_file);\n      ctx->conf->ca_chain_file = NULL;\n    }\n#endif\n    mbedtls_x509_crt_free(ctx->ca_cert);\n    MG_FREE(ctx->ca_cert);\n    ctx->ca_cert = NULL;\n  }\n}\n\nenum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  int err;\n  /* If bio is not yet set, do it now. */\n  if (ctx->ssl->p_bio == NULL) {\n    mbedtls_ssl_set_bio(ctx->ssl, nc, mg_ssl_if_mbed_send, mg_ssl_if_mbed_recv,\n                        NULL);\n  }\n  err = mbedtls_ssl_handshake(ctx->ssl);\n  if (err != 0) return mg_ssl_if_mbed_err(nc, err);\n#ifdef MG_SSL_IF_MBEDTLS_FREE_CERTS\n  /*\n   * Free the peer certificate, we don't need it after handshake.\n   * Note that this effectively disables renegotiation.\n   */\n  mbedtls_x509_crt_free(ctx->ssl->session->peer_cert);\n  mbedtls_free(ctx->ssl->session->peer_cert);\n  ctx->ssl->session->peer_cert = NULL;\n  /* On a client connection we can also free our own and CA certs. */\n  if (nc->listener == NULL) {\n    if (ctx->conf->key_cert != NULL) {\n      /* Note that this assumes one key_cert entry, which matches our init. */\n      MG_FREE(ctx->conf->key_cert);\n      ctx->conf->key_cert = NULL;\n    }\n    mbedtls_ssl_conf_ca_chain(ctx->conf, NULL, NULL);\n    mg_ssl_if_mbed_free_certs_and_keys(ctx);\n  }\n#endif\n  return MG_SSL_OK;\n}\n\nint mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  int n = mbedtls_ssl_read(ctx->ssl, (unsigned char *) buf, len);\n  DBG((\"%p %d -> %d\", nc, (int) len, n));\n  if (n < 0) return mg_ssl_if_mbed_err(nc, n);\n  if (n == 0) nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  return n;\n}\n\nint mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  /* Per mbedTLS docs, if write returns WANT_READ or WANT_WRITE, the operation\n   * should be retried with the same data and length.\n   * Here we assume that the data being pushed will remain the same but the\n   * amount may grow between calls so we save the length that was used and\n   * retry. The assumption being that the data itself won't change and won't\n   * be removed. */\n  size_t l = len;\n  if (ctx->saved_len > 0 && ctx->saved_len < l) l = ctx->saved_len;\n  int n = mbedtls_ssl_write(ctx->ssl, (const unsigned char *) buf, l);\n  DBG((\"%p %d,%d,%d -> %d\", nc, (int) len, (int) ctx->saved_len, (int) l, n));\n  if (n < 0) {\n    if (n == MBEDTLS_ERR_SSL_WANT_READ || n == MBEDTLS_ERR_SSL_WANT_WRITE) {\n      ctx->saved_len = len;\n    }\n    return mg_ssl_if_mbed_err(nc, n);\n  } else if (n > 0) {\n    ctx->saved_len = 0;\n  }\n  return n;\n}\n\nvoid mg_ssl_if_conn_close_notify(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  if (ctx == NULL) return;\n  mbedtls_ssl_close_notify(ctx->ssl);\n}\n\nvoid mg_ssl_if_conn_free(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  if (ctx == NULL) return;\n  nc->ssl_if_data = NULL;\n  if (ctx->ssl != NULL) {\n    mbedtls_ssl_free(ctx->ssl);\n    MG_FREE(ctx->ssl);\n  }\n  mg_ssl_if_mbed_free_certs_and_keys(ctx);\n  if (ctx->conf != NULL) {\n    mbedtls_ssl_config_free(ctx->conf);\n    MG_FREE(ctx->conf);\n  }\n  mbuf_free(&ctx->cipher_suites);\n  memset(ctx, 0, sizeof(*ctx));\n  MG_FREE(ctx);\n}\n\nstatic enum mg_ssl_if_result mg_use_ca_cert(struct mg_ssl_if_ctx *ctx,\n                                            const char *ca_cert) {\n  if (ca_cert == NULL || strcmp(ca_cert, \"*\") == 0) {\n    mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_NONE);\n    return MG_SSL_OK;\n  }\n  ctx->ca_cert = (mbedtls_x509_crt *) MG_CALLOC(1, sizeof(*ctx->ca_cert));\n  mbedtls_x509_crt_init(ctx->ca_cert);\n#ifdef MBEDTLS_X509_CA_CHAIN_ON_DISK\n  ca_cert = strdup(ca_cert);\n  mbedtls_ssl_conf_ca_chain_file(ctx->conf, ca_cert, NULL);\n#else\n  if (mbedtls_x509_crt_parse_file(ctx->ca_cert, ca_cert) != 0) {\n    return MG_SSL_ERROR;\n  }\n  mbedtls_ssl_conf_ca_chain(ctx->conf, ctx->ca_cert, NULL);\n#endif\n  mbedtls_ssl_conf_authmode(ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);\n  return MG_SSL_OK;\n}\n\nstatic enum mg_ssl_if_result mg_use_cert(struct mg_ssl_if_ctx *ctx,\n                                         const char *cert, const char *key,\n                                         const char **err_msg) {\n  if (key == NULL) key = cert;\n  if (cert == NULL || cert[0] == '\\0' || key == NULL || key[0] == '\\0') {\n    return MG_SSL_OK;\n  }\n  ctx->cert = (mbedtls_x509_crt *) MG_CALLOC(1, sizeof(*ctx->cert));\n  mbedtls_x509_crt_init(ctx->cert);\n  ctx->key = (mbedtls_pk_context *) MG_CALLOC(1, sizeof(*ctx->key));\n  mbedtls_pk_init(ctx->key);\n  if (mbedtls_x509_crt_parse_file(ctx->cert, cert) != 0) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL cert\");\n    return MG_SSL_ERROR;\n  }\n  if (mbedtls_pk_parse_keyfile(ctx->key, key, NULL) != 0) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL key\");\n    return MG_SSL_ERROR;\n  }\n  if (mbedtls_ssl_conf_own_cert(ctx->conf, ctx->cert, ctx->key) != 0) {\n    MG_SET_PTRPTR(err_msg, \"Invalid SSL key or cert\");\n    return MG_SSL_ERROR;\n  }\n  return MG_SSL_OK;\n}\n\nstatic const int mg_s_cipher_list[] = {\n#if CS_PLATFORM != CS_P_ESP8266\n    MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,\n    MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,\n    MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA,\n#else\n    /*\n     * ECDHE is way too slow on ESP8266 w/o cryptochip, this sometimes results\n     * in WiFi STA deauths. Use weaker but faster cipher suites. Sad but true.\n     * Disable DHE completely because it's just hopelessly slow.\n     */\n    MBEDTLS_TLS_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,\n    MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,\n    MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,\n    MBEDTLS_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,\n    MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA,\n#endif /* CS_PLATFORM != CS_P_ESP8266 */\n    0,\n};\n\n/*\n * Ciphers can be specified as a colon-separated list of cipher suite names.\n * These can be found in\n * https://github.com/ARMmbed/mbedtls/blob/development/library/ssl_ciphersuites.c#L267\n * E.g.: TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-CCM\n */\nstatic enum mg_ssl_if_result mg_set_cipher_list(struct mg_ssl_if_ctx *ctx,\n                                                const char *ciphers) {\n  if (ciphers != NULL) {\n    int l, id;\n    const char *s = ciphers, *e;\n    char tmp[50];\n    while (s != NULL) {\n      e = strchr(s, ':');\n      l = (e != NULL ? (e - s) : (int) strlen(s));\n      strncpy(tmp, s, l);\n      tmp[l] = '\\0';\n      id = mbedtls_ssl_get_ciphersuite_id(tmp);\n      DBG((\"%s -> %04x\", tmp, id));\n      if (id != 0) {\n        mbuf_append(&ctx->cipher_suites, &id, sizeof(id));\n      }\n      s = (e != NULL ? e + 1 : NULL);\n    }\n    if (ctx->cipher_suites.len == 0) return MG_SSL_ERROR;\n    id = 0;\n    mbuf_append(&ctx->cipher_suites, &id, sizeof(id));\n    mbuf_trim(&ctx->cipher_suites);\n    mbedtls_ssl_conf_ciphersuites(ctx->conf,\n                                  (const int *) ctx->cipher_suites.buf);\n  } else {\n    mbedtls_ssl_conf_ciphersuites(ctx->conf, mg_s_cipher_list);\n  }\n  return MG_SSL_OK;\n}\n\n#ifdef MBEDTLS_KEY_EXCHANGE__SOME__PSK_ENABLED\nstatic enum mg_ssl_if_result mg_ssl_if_mbed_set_psk(struct mg_ssl_if_ctx *ctx,\n                                                    const char *identity,\n                                                    const char *key_str) {\n  unsigned char key[32];\n  size_t key_len;\n  if (identity == NULL && key_str == NULL) return MG_SSL_OK;\n  if (identity == NULL || key_str == NULL) return MG_SSL_ERROR;\n  key_len = strlen(key_str);\n  if (key_len != 32 && key_len != 64) return MG_SSL_ERROR;\n  size_t i = 0;\n  memset(key, 0, sizeof(key));\n  key_len = 0;\n  for (i = 0; key_str[i] != '\\0'; i++) {\n    unsigned char c;\n    char hc = tolower((int) key_str[i]);\n    if (hc >= '0' && hc <= '9') {\n      c = hc - '0';\n    } else if (hc >= 'a' && hc <= 'f') {\n      c = hc - 'a' + 0xa;\n    } else {\n      return MG_SSL_ERROR;\n    }\n    key_len = i / 2;\n    key[key_len] <<= 4;\n    key[key_len] |= c;\n  }\n  key_len++;\n  DBG((\"identity = '%s', key = (%u)\", identity, (unsigned int) key_len));\n  /* mbedTLS makes copies of psk and identity. */\n  if (mbedtls_ssl_conf_psk(ctx->conf, (const unsigned char *) key, key_len,\n                           (const unsigned char *) identity,\n                           strlen(identity)) != 0) {\n    return MG_SSL_ERROR;\n  }\n  return MG_SSL_OK;\n}\n#endif\n\nconst char *mg_set_ssl(struct mg_connection *nc, const char *cert,\n                       const char *ca_cert) {\n  const char *err_msg = NULL;\n  struct mg_ssl_if_conn_params params;\n  memset(&params, 0, sizeof(params));\n  params.cert = cert;\n  params.ca_cert = ca_cert;\n  if (mg_ssl_if_conn_init(nc, &params, &err_msg) != MG_SSL_OK) {\n    return err_msg;\n  }\n  return NULL;\n}\n\n/* Lazy RNG. Warning: it would be a bad idea to do this in production! */\n#ifdef MG_SSL_MBED_DUMMY_RANDOM\nint mg_ssl_if_mbed_random(void *ctx, unsigned char *buf, size_t len) {\n  (void) ctx;\n  while (len--) *buf++ = rand();\n  return 0;\n}\n#endif\n\n#endif /* MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_MBEDTLS */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_uri.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_uri.h\" */\n\n/*\n * scan string until encountering one of `seps`, keeping track of component\n * boundaries in `res`.\n *\n * `p` will point to the char after the separator or it will be `end`.\n */\nstatic void parse_uri_component(const char **p, const char *end,\n                                const char *seps, struct mg_str *res) {\n  const char *q;\n  res->p = *p;\n  for (; *p < end; (*p)++) {\n    for (q = seps; *q != '\\0'; q++) {\n      if (**p == *q) break;\n    }\n    if (*q != '\\0') break;\n  }\n  res->len = (*p) - res->p;\n  if (*p < end) (*p)++;\n}\n\nint mg_parse_uri(const struct mg_str uri, struct mg_str *scheme,\n                 struct mg_str *user_info, struct mg_str *host,\n                 unsigned int *port, struct mg_str *path, struct mg_str *query,\n                 struct mg_str *fragment) {\n  struct mg_str rscheme = {0, 0}, ruser_info = {0, 0}, rhost = {0, 0},\n                rpath = {0, 0}, rquery = {0, 0}, rfragment = {0, 0};\n  unsigned int rport = 0;\n  enum {\n    P_START,\n    P_SCHEME_OR_PORT,\n    P_USER_INFO,\n    P_HOST,\n    P_PORT,\n    P_REST\n  } state = P_START;\n\n  const char *p = uri.p, *end = p + uri.len;\n  while (p < end) {\n    switch (state) {\n      case P_START:\n        /*\n         * expecting on of:\n         * - `scheme://xxxx`\n         * - `xxxx:port`\n         * - `[a:b:c]:port`\n         * - `xxxx/path`\n         */\n        if (*p == '[') {\n          state = P_HOST;\n          break;\n        }\n        for (; p < end; p++) {\n          if (*p == ':') {\n            state = P_SCHEME_OR_PORT;\n            break;\n          } else if (*p == '/') {\n            state = P_REST;\n            break;\n          }\n        }\n        if (state == P_START || state == P_REST) {\n          rhost.p = uri.p;\n          rhost.len = p - uri.p;\n        }\n        break;\n      case P_SCHEME_OR_PORT:\n        if (end - p >= 3 && strncmp(p, \"://\", 3) == 0) {\n          rscheme.p = uri.p;\n          rscheme.len = p - uri.p;\n          state = P_USER_INFO;\n          p += 3;\n        } else {\n          rhost.p = uri.p;\n          rhost.len = p - uri.p;\n          state = P_PORT;\n        }\n        break;\n      case P_USER_INFO:\n        ruser_info.p = p;\n        for (; p < end; p++) {\n          if (*p == '@' || *p == '[' || *p == '/') {\n            break;\n          }\n        }\n        if (p == end || *p == '/' || *p == '[') {\n          /* backtrack and parse as host */\n          p = ruser_info.p;\n        }\n        ruser_info.len = p - ruser_info.p;\n        state = P_HOST;\n        break;\n      case P_HOST:\n        if (*p == '@') p++;\n        rhost.p = p;\n        if (*p == '[') {\n          int found = 0;\n          for (; !found && p < end; p++) {\n            found = (*p == ']');\n          }\n          if (!found) return -1;\n        } else {\n          for (; p < end; p++) {\n            if (*p == ':' || *p == '/') break;\n          }\n        }\n        rhost.len = p - rhost.p;\n        if (p < end) {\n          if (*p == ':') {\n            state = P_PORT;\n            break;\n          } else if (*p == '/') {\n            state = P_REST;\n            break;\n          }\n        }\n        break;\n      case P_PORT:\n        p++;\n        for (; p < end; p++) {\n          if (*p == '/') {\n            state = P_REST;\n            break;\n          }\n          rport *= 10;\n          rport += *p - '0';\n        }\n        break;\n      case P_REST:\n        /* `p` points to separator. `path` includes the separator */\n        parse_uri_component(&p, end, \"?#\", &rpath);\n        if (p < end && *(p - 1) == '?') {\n          parse_uri_component(&p, end, \"#\", &rquery);\n        }\n        parse_uri_component(&p, end, \"\", &rfragment);\n        break;\n    }\n  }\n\n  if (scheme != 0) *scheme = rscheme;\n  if (user_info != 0) *user_info = ruser_info;\n  if (host != 0) *host = rhost;\n  if (port != 0) *port = rport;\n  if (path != 0) *path = rpath;\n  if (query != 0) *query = rquery;\n  if (fragment != 0) *fragment = rfragment;\n\n  return 0;\n}\n\n/* Normalize the URI path. Remove/resolve \".\" and \"..\". */\nint mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) {\n  const char *s = in->p, *se = s + in->len;\n  char *cp = (char *) out->p, *d;\n\n  if (in->len == 0 || *s != '/') {\n    out->len = 0;\n    return 0;\n  }\n\n  d = cp;\n\n  while (s < se) {\n    const char *next = s;\n    struct mg_str component;\n    parse_uri_component(&next, se, \"/\", &component);\n    if (mg_vcmp(&component, \".\") == 0) {\n      /* Yum. */\n    } else if (mg_vcmp(&component, \"..\") == 0) {\n      /* Backtrack to previous slash. */\n      if (d > cp + 1 && *(d - 1) == '/') d--;\n      while (d > cp && *(d - 1) != '/') d--;\n    } else {\n      memmove(d, s, next - s);\n      d += next - s;\n    }\n    s = next;\n  }\n  if (d == cp) *d++ = '/';\n\n  out->p = cp;\n  out->len = d - cp;\n  return 1;\n}\n\nint mg_assemble_uri(const struct mg_str *scheme, const struct mg_str *user_info,\n                    const struct mg_str *host, unsigned int port,\n                    const struct mg_str *path, const struct mg_str *query,\n                    const struct mg_str *fragment, int normalize_path,\n                    struct mg_str *uri) {\n  int result = -1;\n  struct mbuf out;\n  mbuf_init(&out, 0);\n\n  if (scheme != NULL && scheme->len > 0) {\n    mbuf_append(&out, scheme->p, scheme->len);\n    mbuf_append(&out, \"://\", 3);\n  }\n\n  if (user_info != NULL && user_info->len > 0) {\n    mbuf_append(&out, user_info->p, user_info->len);\n    mbuf_append(&out, \"@\", 1);\n  }\n\n  if (host != NULL && host->len > 0) {\n    mbuf_append(&out, host->p, host->len);\n  }\n\n  if (port != 0) {\n    char port_str[20];\n    int port_str_len = snprintf(port_str, sizeof(port_str), \":%u\", port);\n    mbuf_append(&out, port_str, port_str_len);\n  }\n\n  if (path != NULL && path->len > 0) {\n    if (normalize_path) {\n      struct mg_str npath = mg_strdup(*path);\n      if (npath.len != path->len) goto out;\n      if (!mg_normalize_uri_path(path, &npath)) {\n        free((void *) npath.p);\n        goto out;\n      }\n      mbuf_append(&out, npath.p, npath.len);\n      free((void *) npath.p);\n    } else {\n      mbuf_append(&out, path->p, path->len);\n    }\n  } else if (normalize_path) {\n    mbuf_append(&out, \"/\", 1);\n  }\n\n  if (query != NULL && query->len > 0) {\n    mbuf_append(&out, \"?\", 1);\n    mbuf_append(&out, query->p, query->len);\n  }\n\n  if (fragment != NULL && fragment->len > 0) {\n    mbuf_append(&out, \"#\", 1);\n    mbuf_append(&out, fragment->p, fragment->len);\n  }\n\n  result = 0;\n\nout:\n  if (result == 0) {\n    uri->p = out.buf;\n    uri->len = out.len;\n  } else {\n    mbuf_free(&out);\n    uri->p = NULL;\n    uri->len = 0;\n  }\n  return result;\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_HTTP\n\n/* Amalgamated: #include \"common/cs_md5.h\" */\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_util.h\" */\n\n/* altbuf {{{ */\n\n/*\n * Alternate buffer: fills the client-provided buffer with data; and if it's\n * not large enough, allocates another buffer (via mbuf), similar to asprintf.\n */\nstruct altbuf {\n  struct mbuf m;\n  char *user_buf;\n  size_t len;\n  size_t user_buf_size;\n};\n\n/*\n * Initializes altbuf; `buf`, `buf_size` is the client-provided buffer.\n */\nMG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) {\n  mbuf_init(&ab->m, 0);\n  ab->user_buf = buf;\n  ab->user_buf_size = buf_size;\n  ab->len = 0;\n}\n\n/*\n * Appends a single char to the altbuf.\n */\nMG_INTERNAL void altbuf_append(struct altbuf *ab, char c) {\n  if (ab->len < ab->user_buf_size) {\n    /* The data fits into the original buffer */\n    ab->user_buf[ab->len++] = c;\n  } else {\n    /* The data can't fit into the original buffer, so write it to mbuf.  */\n\n    /*\n     * First of all, see if that's the first byte which overflows the original\n     * buffer: if so, copy the existing data from there to a newly allocated\n     * mbuf.\n     */\n    if (ab->len > 0 && ab->m.len == 0) {\n      mbuf_append(&ab->m, ab->user_buf, ab->len);\n    }\n\n    mbuf_append(&ab->m, &c, 1);\n    ab->len = ab->m.len;\n  }\n}\n\n/*\n * Resets any data previously appended to altbuf.\n */\nMG_INTERNAL void altbuf_reset(struct altbuf *ab) {\n  mbuf_free(&ab->m);\n  ab->len = 0;\n}\n\n/*\n * Returns whether the additional buffer was allocated (and thus the data\n * is in the mbuf, not the client-provided buffer)\n */\nMG_INTERNAL int altbuf_reallocated(struct altbuf *ab) {\n  return ab->len > ab->user_buf_size;\n}\n\n/*\n * Returns the actual buffer with data, either the client-provided or a newly\n * allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first.\n */\nMG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) {\n  if (altbuf_reallocated(ab)) {\n    if (trim) {\n      mbuf_trim(&ab->m);\n    }\n    return ab->m.buf;\n  } else {\n    return ab->user_buf;\n  }\n}\n\n/* }}} */\n\nstatic const char *mg_version_header = \"Mongoose/\" MG_VERSION;\n\nenum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT };\n\nstruct mg_http_proto_data_file {\n  FILE *fp;      /* Opened file. */\n  int64_t cl;    /* Content-Length. How many bytes to send. */\n  int64_t sent;  /* How many bytes have been already sent. */\n  int keepalive; /* Keep connection open after sending. */\n  enum mg_http_proto_data_type type;\n};\n\n#if MG_ENABLE_HTTP_CGI\nstruct mg_http_proto_data_cgi {\n  struct mg_connection *cgi_nc;\n};\n#endif\n\nstruct mg_http_proto_data_chuncked {\n  int64_t body_len; /* How many bytes of chunked body was reassembled. */\n};\n\nstruct mg_http_endpoint {\n  struct mg_http_endpoint *next;\n  struct mg_str uri_pattern; /* owned */\n  char *auth_domain;         /* owned */\n  char *auth_file;           /* owned */\n\n  mg_event_handler_t handler;\n#if MG_ENABLE_CALLBACK_USERDATA\n  void *user_data;\n#endif\n};\n\nenum mg_http_multipart_stream_state {\n  MPS_BEGIN,\n  MPS_WAITING_FOR_BOUNDARY,\n  MPS_WAITING_FOR_CHUNK,\n  MPS_GOT_BOUNDARY,\n  MPS_FINALIZE,\n  MPS_FINISHED\n};\n\nstruct mg_http_multipart_stream {\n  const char *boundary;\n  int boundary_len;\n  const char *var_name;\n  const char *file_name;\n  void *user_data;\n  enum mg_http_multipart_stream_state state;\n  int processing_part;\n  int data_avail;\n};\n\nstruct mg_reverse_proxy_data {\n  struct mg_connection *linked_conn;\n};\n\nstruct mg_ws_proto_data {\n  /*\n   * Defragmented size of the frame so far.\n   *\n   * First byte of nc->recv_mbuf.buf is an op, the rest of the data is\n   * defragmented data.\n   */\n  size_t reass_len;\n};\n\nstruct mg_http_proto_data {\n#if MG_ENABLE_FILESYSTEM\n  struct mg_http_proto_data_file file;\n#endif\n#if MG_ENABLE_HTTP_CGI\n  struct mg_http_proto_data_cgi cgi;\n#endif\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n  struct mg_http_multipart_stream mp_stream;\n#endif\n#if MG_ENABLE_HTTP_WEBSOCKET\n  struct mg_ws_proto_data ws_data;\n#endif\n  struct mg_http_proto_data_chuncked chunk;\n  struct mg_http_endpoint *endpoints;\n  mg_event_handler_t endpoint_handler;\n  struct mg_reverse_proxy_data reverse_proxy_data;\n  size_t rcvd; /* How many bytes we have received. */\n};\n\nstatic void mg_http_proto_data_destructor(void *proto_data);\n\nstruct mg_connection *mg_connect_http_base(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    struct mg_connect_opts opts, const char *scheme1, const char *scheme2,\n    const char *scheme_ssl1, const char *scheme_ssl2, const char *url,\n    struct mg_str *path, struct mg_str *user_info, struct mg_str *host);\n\nMG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data(\n    struct mg_connection *c) {\n  /* If we have proto data from previous connection, flush it. */\n  if (c->proto_data != NULL) {\n    void *pd = c->proto_data;\n    c->proto_data = NULL;\n    mg_http_proto_data_destructor(pd);\n  }\n  c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data));\n  c->proto_data_destructor = mg_http_proto_data_destructor;\n  return (struct mg_http_proto_data *) c->proto_data;\n}\n\nstatic struct mg_http_proto_data *mg_http_get_proto_data(\n    struct mg_connection *c) {\n  return (struct mg_http_proto_data *) c->proto_data;\n}\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\nstatic void mg_http_free_proto_data_mp_stream(\n    struct mg_http_multipart_stream *mp) {\n  MG_FREE((void *) mp->boundary);\n  MG_FREE((void *) mp->var_name);\n  MG_FREE((void *) mp->file_name);\n  memset(mp, 0, sizeof(*mp));\n}\n#endif\n\n#if MG_ENABLE_FILESYSTEM\nstatic void mg_http_free_proto_data_file(struct mg_http_proto_data_file *d) {\n  if (d != NULL) {\n    if (d->fp != NULL) {\n      fclose(d->fp);\n    }\n    memset(d, 0, sizeof(struct mg_http_proto_data_file));\n  }\n}\n#endif\n\nstatic void mg_http_free_proto_data_endpoints(struct mg_http_endpoint **ep) {\n  struct mg_http_endpoint *current = *ep;\n\n  while (current != NULL) {\n    struct mg_http_endpoint *tmp = current->next;\n    MG_FREE((void *) current->uri_pattern.p);\n    MG_FREE((void *) current->auth_domain);\n    MG_FREE((void *) current->auth_file);\n    MG_FREE(current);\n    current = tmp;\n  }\n\n  ep = NULL;\n}\n\nstatic void mg_http_free_reverse_proxy_data(struct mg_reverse_proxy_data *rpd) {\n  if (rpd->linked_conn != NULL) {\n    /*\n     * Connection has linked one, we have to unlink & close it\n     * since _this_ connection is going to die and\n     * it doesn't make sense to keep another one\n     */\n    struct mg_http_proto_data *pd = mg_http_get_proto_data(rpd->linked_conn);\n    if (pd->reverse_proxy_data.linked_conn != NULL) {\n      pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;\n      pd->reverse_proxy_data.linked_conn = NULL;\n    }\n    rpd->linked_conn = NULL;\n  }\n}\n\nstatic void mg_http_proto_data_destructor(void *proto_data) {\n  struct mg_http_proto_data *pd = (struct mg_http_proto_data *) proto_data;\n#if MG_ENABLE_FILESYSTEM\n  mg_http_free_proto_data_file(&pd->file);\n#endif\n#if MG_ENABLE_HTTP_CGI\n  mg_http_free_proto_data_cgi(&pd->cgi);\n#endif\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n  mg_http_free_proto_data_mp_stream(&pd->mp_stream);\n#endif\n  mg_http_free_proto_data_endpoints(&pd->endpoints);\n  mg_http_free_reverse_proxy_data(&pd->reverse_proxy_data);\n  MG_FREE(proto_data);\n}\n\n#if MG_ENABLE_FILESYSTEM\n\n#define MIME_ENTRY(_ext, _type) \\\n  { _ext, sizeof(_ext) - 1, _type }\nstatic const struct {\n  const char *extension;\n  size_t ext_len;\n  const char *mime_type;\n} mg_static_builtin_mime_types[] = {\n    MIME_ENTRY(\"html\", \"text/html\"),\n    MIME_ENTRY(\"html\", \"text/html\"),\n    MIME_ENTRY(\"htm\", \"text/html\"),\n    MIME_ENTRY(\"shtm\", \"text/html\"),\n    MIME_ENTRY(\"shtml\", \"text/html\"),\n    MIME_ENTRY(\"css\", \"text/css\"),\n    MIME_ENTRY(\"js\", \"application/x-javascript\"),\n    MIME_ENTRY(\"ico\", \"image/x-icon\"),\n    MIME_ENTRY(\"gif\", \"image/gif\"),\n    MIME_ENTRY(\"jpg\", \"image/jpeg\"),\n    MIME_ENTRY(\"jpeg\", \"image/jpeg\"),\n    MIME_ENTRY(\"png\", \"image/png\"),\n    MIME_ENTRY(\"svg\", \"image/svg+xml\"),\n    MIME_ENTRY(\"txt\", \"text/plain\"),\n    MIME_ENTRY(\"torrent\", \"application/x-bittorrent\"),\n    MIME_ENTRY(\"wav\", \"audio/x-wav\"),\n    MIME_ENTRY(\"mp3\", \"audio/x-mp3\"),\n    MIME_ENTRY(\"mid\", \"audio/mid\"),\n    MIME_ENTRY(\"m3u\", \"audio/x-mpegurl\"),\n    MIME_ENTRY(\"ogg\", \"application/ogg\"),\n    MIME_ENTRY(\"ram\", \"audio/x-pn-realaudio\"),\n    MIME_ENTRY(\"xml\", \"text/xml\"),\n    MIME_ENTRY(\"ttf\", \"application/x-font-ttf\"),\n    MIME_ENTRY(\"json\", \"application/json\"),\n    MIME_ENTRY(\"xslt\", \"application/xml\"),\n    MIME_ENTRY(\"xsl\", \"application/xml\"),\n    MIME_ENTRY(\"ra\", \"audio/x-pn-realaudio\"),\n    MIME_ENTRY(\"doc\", \"application/msword\"),\n    MIME_ENTRY(\"exe\", \"application/octet-stream\"),\n    MIME_ENTRY(\"zip\", \"application/x-zip-compressed\"),\n    MIME_ENTRY(\"xls\", \"application/excel\"),\n    MIME_ENTRY(\"tgz\", \"application/x-tar-gz\"),\n    MIME_ENTRY(\"tar\", \"application/x-tar\"),\n    MIME_ENTRY(\"gz\", \"application/x-gunzip\"),\n    MIME_ENTRY(\"arj\", \"application/x-arj-compressed\"),\n    MIME_ENTRY(\"rar\", \"application/x-rar-compressed\"),\n    MIME_ENTRY(\"rtf\", \"application/rtf\"),\n    MIME_ENTRY(\"pdf\", \"application/pdf\"),\n    MIME_ENTRY(\"swf\", \"application/x-shockwave-flash\"),\n    MIME_ENTRY(\"mpg\", \"video/mpeg\"),\n    MIME_ENTRY(\"webm\", \"video/webm\"),\n    MIME_ENTRY(\"mpeg\", \"video/mpeg\"),\n    MIME_ENTRY(\"mov\", \"video/quicktime\"),\n    MIME_ENTRY(\"mp4\", \"video/mp4\"),\n    MIME_ENTRY(\"m4v\", \"video/x-m4v\"),\n    MIME_ENTRY(\"asf\", \"video/x-ms-asf\"),\n    MIME_ENTRY(\"avi\", \"video/x-msvideo\"),\n    MIME_ENTRY(\"bmp\", \"image/bmp\"),\n    {NULL, 0, NULL}};\n\nstatic struct mg_str mg_get_mime_type(const char *path, const char *dflt,\n                                      const struct mg_serve_http_opts *opts) {\n  const char *ext, *overrides;\n  size_t i, path_len;\n  struct mg_str r, k, v;\n\n  path_len = strlen(path);\n\n  overrides = opts->custom_mime_types;\n  while ((overrides = mg_next_comma_list_entry(overrides, &k, &v)) != NULL) {\n    ext = path + (path_len - k.len);\n    if (path_len > k.len && mg_vcasecmp(&k, ext) == 0) {\n      return v;\n    }\n  }\n\n  for (i = 0; mg_static_builtin_mime_types[i].extension != NULL; i++) {\n    ext = path + (path_len - mg_static_builtin_mime_types[i].ext_len);\n    if (path_len > mg_static_builtin_mime_types[i].ext_len && ext[-1] == '.' &&\n        mg_casecmp(ext, mg_static_builtin_mime_types[i].extension) == 0) {\n      r.p = mg_static_builtin_mime_types[i].mime_type;\n      r.len = strlen(r.p);\n      return r;\n    }\n  }\n\n  r.p = dflt;\n  r.len = strlen(r.p);\n  return r;\n}\n#endif\n\n/*\n * Check whether full request is buffered. Return:\n *   -1  if request is malformed\n *    0  if request is not yet fully buffered\n *   >0  actual request length, including last \\r\\n\\r\\n\n */\nstatic int mg_http_get_request_len(const char *s, int buf_len) {\n  const unsigned char *buf = (unsigned char *) s;\n  int i;\n\n  for (i = 0; i < buf_len; i++) {\n    if (!isprint(buf[i]) && buf[i] != '\\r' && buf[i] != '\\n' && buf[i] < 128) {\n      return -1;\n    } else if (buf[i] == '\\n' && i + 1 < buf_len && buf[i + 1] == '\\n') {\n      return i + 2;\n    } else if (buf[i] == '\\n' && i + 2 < buf_len && buf[i + 1] == '\\r' &&\n               buf[i + 2] == '\\n') {\n      return i + 3;\n    }\n  }\n\n  return 0;\n}\n\nstatic const char *mg_http_parse_headers(const char *s, const char *end,\n                                         int len, struct http_message *req) {\n  int i = 0;\n  while (i < (int) ARRAY_SIZE(req->header_names) - 1) {\n    struct mg_str *k = &req->header_names[i], *v = &req->header_values[i];\n\n    s = mg_skip(s, end, \": \", k);\n    s = mg_skip(s, end, \"\\r\\n\", v);\n\n    while (v->len > 0 && v->p[v->len - 1] == ' ') {\n      v->len--; /* Trim trailing spaces in header value */\n    }\n\n    /*\n     * If header value is empty - skip it and go to next (if any).\n     * NOTE: Do not add it to headers_values because such addition changes API\n     * behaviour\n     */\n    if (k->len != 0 && v->len == 0) {\n      continue;\n    }\n\n    if (k->len == 0 || v->len == 0) {\n      k->p = v->p = NULL;\n      k->len = v->len = 0;\n      break;\n    }\n\n    if (!mg_ncasecmp(k->p, \"Content-Length\", 14)) {\n      req->body.len = (size_t) to64(v->p);\n      req->message.len = len + req->body.len;\n    }\n\n    i++;\n  }\n\n  return s;\n}\n\nint mg_parse_http(const char *s, int n, struct http_message *hm, int is_req) {\n  const char *end, *qs;\n  int len = mg_http_get_request_len(s, n);\n\n  if (len <= 0) return len;\n\n  memset(hm, 0, sizeof(*hm));\n  hm->message.p = s;\n  hm->body.p = s + len;\n  hm->message.len = hm->body.len = (size_t) ~0;\n  end = s + len;\n\n  /* Request is fully buffered. Skip leading whitespaces. */\n  while (s < end && isspace(*(unsigned char *) s)) s++;\n\n  if (is_req) {\n    /* Parse request line: method, URI, proto */\n    s = mg_skip(s, end, \" \", &hm->method);\n    s = mg_skip(s, end, \" \", &hm->uri);\n    s = mg_skip(s, end, \"\\r\\n\", &hm->proto);\n    if (hm->uri.p <= hm->method.p || hm->proto.p <= hm->uri.p) return -1;\n\n    /* If URI contains '?' character, initialize query_string */\n    if ((qs = (char *) memchr(hm->uri.p, '?', hm->uri.len)) != NULL) {\n      hm->query_string.p = qs + 1;\n      hm->query_string.len = &hm->uri.p[hm->uri.len] - (qs + 1);\n      hm->uri.len = qs - hm->uri.p;\n    }\n  } else {\n    s = mg_skip(s, end, \" \", &hm->proto);\n    if (end - s < 4 || s[0] < '0' || s[0] > '9' || s[3] != ' ') return -1;\n    hm->resp_code = atoi(s);\n    if (hm->resp_code < 100 || hm->resp_code >= 600) return -1;\n    s += 4;\n    s = mg_skip(s, end, \"\\r\\n\", &hm->resp_status_msg);\n  }\n\n  s = mg_http_parse_headers(s, end, len, hm);\n\n  /*\n   * mg_parse_http() is used to parse both HTTP requests and HTTP\n   * responses. If HTTP response does not have Content-Length set, then\n   * body is read until socket is closed, i.e. body.len is infinite (~0).\n   *\n   * For HTTP requests though, according to\n   * http://tools.ietf.org/html/rfc7231#section-8.1.3,\n   * only POST and PUT methods have defined body semantics.\n   * Therefore, if Content-Length is not specified and methods are\n   * not one of PUT or POST, set body length to 0.\n   *\n   * So,\n   * if it is HTTP request, and Content-Length is not set,\n   * and method is not (PUT or POST) then reset body length to zero.\n   */\n  if (hm->body.len == (size_t) ~0 && is_req &&\n      mg_vcasecmp(&hm->method, \"PUT\") != 0 &&\n      mg_vcasecmp(&hm->method, \"POST\") != 0) {\n    hm->body.len = 0;\n    hm->message.len = len;\n  }\n\n  return len;\n}\n\nstruct mg_str *mg_get_http_header(struct http_message *hm, const char *name) {\n  size_t i, len = strlen(name);\n\n  for (i = 0; hm->header_names[i].len > 0; i++) {\n    struct mg_str *h = &hm->header_names[i], *v = &hm->header_values[i];\n    if (h->p != NULL && h->len == len && !mg_ncasecmp(h->p, name, len))\n      return v;\n  }\n\n  return NULL;\n}\n\n#if MG_ENABLE_FILESYSTEM\nstatic void mg_http_transfer_file_data(struct mg_connection *nc) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  char buf[MG_MAX_HTTP_SEND_MBUF];\n  size_t n = 0, to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent);\n\n  if (pd->file.type == DATA_FILE) {\n    struct mbuf *io = &nc->send_mbuf;\n    if (io->len >= MG_MAX_HTTP_SEND_MBUF) {\n      to_read = 0;\n    } else {\n      to_read = MG_MAX_HTTP_SEND_MBUF - io->len;\n    }\n    if (to_read > left) {\n      to_read = left;\n    }\n    if (to_read > 0) {\n      n = mg_fread(buf, 1, to_read, pd->file.fp);\n      if (n > 0) {\n        mg_send(nc, buf, n);\n        pd->file.sent += n;\n        DBG((\"%p sent %d (total %d)\", nc, (int) n, (int) pd->file.sent));\n      }\n    } else {\n      /* Rate-limited */\n    }\n    if (pd->file.sent >= pd->file.cl) {\n      LOG(LL_DEBUG, (\"%p done, %d bytes, ka %d\", nc, (int) pd->file.sent,\n                     pd->file.keepalive));\n      if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;\n      mg_http_free_proto_data_file(&pd->file);\n    }\n  } else if (pd->file.type == DATA_PUT) {\n    struct mbuf *io = &nc->recv_mbuf;\n    size_t to_write = left <= 0 ? 0 : left < io->len ? (size_t) left : io->len;\n    size_t n = mg_fwrite(io->buf, 1, to_write, pd->file.fp);\n    if (n > 0) {\n      mbuf_remove(io, n);\n      pd->file.sent += n;\n    }\n    if (n == 0 || pd->file.sent >= pd->file.cl) {\n      if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;\n      mg_http_free_proto_data_file(&pd->file);\n    }\n  }\n#if MG_ENABLE_HTTP_CGI\n  else if (pd->cgi.cgi_nc != NULL) {\n    /* This is POST data that needs to be forwarded to the CGI process */\n    if (pd->cgi.cgi_nc != NULL) {\n      mg_forward(nc, pd->cgi.cgi_nc);\n    } else {\n      nc->flags |= MG_F_SEND_AND_CLOSE;\n    }\n  }\n#endif\n}\n#endif /* MG_ENABLE_FILESYSTEM */\n\n/*\n * Parse chunked-encoded buffer. Return 0 if the buffer is not encoded, or\n * if it's incomplete. If the chunk is fully buffered, return total number of\n * bytes in a chunk, and store data in `data`, `data_len`.\n */\nstatic size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data,\n                                  size_t *chunk_len) {\n  unsigned char *s = (unsigned char *) buf;\n  size_t n = 0; /* scanned chunk length */\n  size_t i = 0; /* index in s */\n\n  /* Scan chunk length. That should be a hexadecimal number. */\n  while (i < len && isxdigit(s[i])) {\n    n *= 16;\n    n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10;\n    i++;\n    if (i > 6) {\n      /* Chunk size is unreasonable. */\n      return 0;\n    }\n  }\n\n  /* Skip new line */\n  if (i == 0 || i + 2 > len || s[i] != '\\r' || s[i + 1] != '\\n') {\n    return 0;\n  }\n  i += 2;\n\n  /* Record where the data is */\n  *chunk_data = (char *) s + i;\n  *chunk_len = n;\n\n  /* Skip data */\n  i += n;\n\n  /* Skip new line */\n  if (i == 0 || i + 2 > len || s[i] != '\\r' || s[i + 1] != '\\n') {\n    return 0;\n  }\n  return i + 2;\n}\n\nMG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,\n                                     struct http_message *hm, char *buf,\n                                     size_t blen) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  char *data;\n  size_t i, n, data_len, body_len, zero_chunk_received = 0;\n  /* Find out piece of received data that is not yet reassembled */\n  body_len = (size_t) pd->chunk.body_len;\n  assert(blen >= body_len);\n\n  /* Traverse all fully buffered chunks */\n  for (i = body_len;\n       (n = mg_http_parse_chunk(buf + i, blen - i, &data, &data_len)) > 0;\n       i += n) {\n    /* Collapse chunk data to the rest of HTTP body */\n    memmove(buf + body_len, data, data_len);\n    body_len += data_len;\n    hm->body.len = body_len;\n\n    if (data_len == 0) {\n      zero_chunk_received = 1;\n      i += n;\n      break;\n    }\n  }\n\n  if (i > body_len) {\n    /* Shift unparsed content to the parsed body */\n    assert(i <= blen);\n    memmove(buf + body_len, buf + i, blen - i);\n    memset(buf + body_len + blen - i, 0, i - body_len);\n    nc->recv_mbuf.len -= i - body_len;\n    pd->chunk.body_len = body_len;\n\n    /* Send MG_EV_HTTP_CHUNK event */\n    nc->flags &= ~MG_F_DELETE_CHUNK;\n    mg_call(nc, nc->handler, nc->user_data, MG_EV_HTTP_CHUNK, hm);\n\n    /* Delete processed data if user set MG_F_DELETE_CHUNK flag */\n    if (nc->flags & MG_F_DELETE_CHUNK) {\n      memset(buf, 0, body_len);\n      memmove(buf, buf + body_len, blen - i);\n      nc->recv_mbuf.len -= body_len;\n      hm->body.len = 0;\n      pd->chunk.body_len = 0;\n    }\n\n    if (zero_chunk_received) {\n      /* Total message size is len(body) + len(headers) */\n      hm->message.len =\n          (size_t) pd->chunk.body_len + blen - i + (hm->body.p - hm->message.p);\n    }\n  }\n\n  return body_len;\n}\n\nstruct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc,\n                                                      struct mg_str *uri_path) {\n  struct mg_http_proto_data *pd;\n  struct mg_http_endpoint *ret = NULL;\n  int matched, matched_max = 0;\n  struct mg_http_endpoint *ep;\n\n  if (nc == NULL) return NULL;\n\n  pd = mg_http_get_proto_data(nc);\n\n  if (pd == NULL) return NULL;\n\n  ep = pd->endpoints;\n  while (ep != NULL) {\n    if ((matched = mg_match_prefix_n(ep->uri_pattern, *uri_path)) > 0) {\n      if (matched > matched_max) {\n        /* Looking for the longest suitable handler */\n        ret = ep;\n        matched_max = matched;\n      }\n    }\n\n    ep = ep->next;\n  }\n\n  return ret;\n}\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\nstatic void mg_http_multipart_continue(struct mg_connection *nc);\n\nstatic void mg_http_multipart_begin(struct mg_connection *nc,\n                                    struct http_message *hm, int req_len);\n\n#endif\n\nstatic void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,\n                                          struct http_message *hm);\n\nstatic void deliver_chunk(struct mg_connection *c, struct http_message *hm,\n                          int req_len) {\n  /* Incomplete message received. Send MG_EV_HTTP_CHUNK event */\n  hm->body.len = c->recv_mbuf.len - req_len;\n  c->flags &= ~MG_F_DELETE_CHUNK;\n  mg_call(c, c->handler, c->user_data, MG_EV_HTTP_CHUNK, hm);\n  /* Delete processed data if user set MG_F_DELETE_CHUNK flag */\n  if (c->flags & MG_F_DELETE_CHUNK) c->recv_mbuf.len = req_len;\n}\n\n/*\n * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here)\n * If a big structure is declared in a big function, lx106 gcc will make it\n * even bigger (round up to 4k, from 700 bytes of actual size).\n */\n#ifdef __xtensa__\nstatic void mg_http_handler2(struct mg_connection *nc, int ev,\n                             void *ev_data MG_UD_ARG(void *user_data),\n                             struct http_message *hm) __attribute__((noinline));\n\nvoid mg_http_handler(struct mg_connection *nc, int ev,\n                     void *ev_data MG_UD_ARG(void *user_data)) {\n  struct http_message hm;\n  mg_http_handler2(nc, ev, ev_data MG_UD_ARG(user_data), &hm);\n}\n\nstatic void mg_http_handler2(struct mg_connection *nc, int ev,\n                             void *ev_data MG_UD_ARG(void *user_data),\n                             struct http_message *hm) {\n#else  /* !__XTENSA__ */\nvoid mg_http_handler(struct mg_connection *nc, int ev,\n                     void *ev_data MG_UD_ARG(void *user_data)) {\n  struct http_message shm, *hm = &shm;\n#endif /* __XTENSA__ */\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  struct mbuf *io = &nc->recv_mbuf;\n  int req_len;\n  const int is_req = (nc->listener != NULL);\n#if MG_ENABLE_HTTP_WEBSOCKET\n  struct mg_str *vec;\n#endif\n  if (ev == MG_EV_CLOSE) {\n#if MG_ENABLE_HTTP_CGI\n    /* Close associated CGI forwarder connection */\n    if (pd != NULL && pd->cgi.cgi_nc != NULL) {\n      pd->cgi.cgi_nc->user_data = NULL;\n      pd->cgi.cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    }\n#endif\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n    if (pd != NULL && pd->mp_stream.boundary != NULL) {\n      /*\n       * Multipart message is in progress, but connection is closed.\n       * Finish part and request with an error flag.\n       */\n      struct mg_http_multipart_part mp;\n      memset(&mp, 0, sizeof(mp));\n      mp.status = -1;\n      mp.user_data = pd->mp_stream.user_data;\n      mp.var_name = pd->mp_stream.var_name;\n      mp.file_name = pd->mp_stream.file_name;\n      mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),\n              nc->user_data, MG_EV_HTTP_PART_END, &mp);\n      mp.var_name = NULL;\n      mp.file_name = NULL;\n      mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),\n              nc->user_data, MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);\n    } else\n#endif\n        if (io->len > 0 &&\n            (req_len = mg_parse_http(io->buf, io->len, hm, is_req)) > 0) {\n      /*\n       * For HTTP messages without Content-Length, always send HTTP message\n       * before MG_EV_CLOSE message.\n       */\n      int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;\n      hm->message.len = io->len;\n      hm->body.len = io->buf + io->len - hm->body.p;\n      deliver_chunk(nc, hm, req_len);\n      mg_http_call_endpoint_handler(nc, ev2, hm);\n    }\n    if (pd != NULL && pd->endpoint_handler != NULL &&\n        pd->endpoint_handler != nc->handler) {\n      mg_call(nc, pd->endpoint_handler, nc->user_data, ev, NULL);\n    }\n  }\n\n#if MG_ENABLE_FILESYSTEM\n  if (pd != NULL && pd->file.fp != NULL) {\n    mg_http_transfer_file_data(nc);\n  }\n#endif\n\n  mg_call(nc, nc->handler, nc->user_data, ev, ev_data);\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n  if (pd != NULL && pd->mp_stream.boundary != NULL &&\n      (ev == MG_EV_RECV || ev == MG_EV_POLL)) {\n    if (ev == MG_EV_RECV) {\n      pd->rcvd += *(int *) ev_data;\n      mg_http_multipart_continue(nc);\n    } else if (pd->mp_stream.data_avail) {\n      /* Try re-delivering the data. */\n      mg_http_multipart_continue(nc);\n    }\n    return;\n  }\n#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */\n\n  if (ev == MG_EV_RECV) {\n    struct mg_str *s;\n\n  again:\n    req_len = mg_parse_http(io->buf, io->len, hm, is_req);\n\n    if (req_len > 0) {\n      /* New request - new proto data */\n      pd = mg_http_create_proto_data(nc);\n      pd->rcvd = io->len;\n    }\n\n    if (req_len > 0 &&\n        (s = mg_get_http_header(hm, \"Transfer-Encoding\")) != NULL &&\n        mg_vcasecmp(s, \"chunked\") == 0) {\n      mg_handle_chunked(nc, hm, io->buf + req_len, io->len - req_len);\n    }\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n    if (req_len > 0 && (s = mg_get_http_header(hm, \"Content-Type\")) != NULL &&\n        s->len >= 9 && strncmp(s->p, \"multipart\", 9) == 0) {\n      mg_http_multipart_begin(nc, hm, req_len);\n      mg_http_multipart_continue(nc);\n      return;\n    }\n#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */\n\n    /* TODO(alashkin): refactor this ifelseifelseifelseifelse */\n    if ((req_len < 0 ||\n         (req_len == 0 && io->len >= MG_MAX_HTTP_REQUEST_SIZE))) {\n      DBG((\"invalid request\"));\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    } else if (req_len == 0) {\n      /* Do nothing, request is not yet fully buffered */\n    }\n#if MG_ENABLE_HTTP_WEBSOCKET\n    else if (nc->listener == NULL && (nc->flags & MG_F_IS_WEBSOCKET)) {\n      /* We're websocket client, got handshake response from server. */\n      DBG((\"%p WebSocket upgrade code %d\", nc, hm->resp_code));\n      if (hm->resp_code == 101 &&\n          mg_get_http_header(hm, \"Sec-WebSocket-Accept\")) {\n        /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */\n        mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,\n                hm);\n        mbuf_remove(io, req_len);\n        nc->proto_handler = mg_ws_handler;\n        mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));\n      } else {\n        mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,\n                hm);\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        mbuf_remove(io, req_len);\n      }\n    } else if (nc->listener != NULL &&\n               (vec = mg_get_http_header(hm, \"Sec-WebSocket-Key\")) != NULL) {\n      struct mg_http_endpoint *ep;\n\n      /* This is a websocket request. Switch protocol handlers. */\n      mbuf_remove(io, req_len);\n      nc->proto_handler = mg_ws_handler;\n      nc->flags |= MG_F_IS_WEBSOCKET;\n\n      /*\n       * If we have a handler set up with mg_register_http_endpoint(),\n       * deliver subsequent websocket events to this handler after the\n       * protocol switch.\n       */\n      ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri);\n      if (ep != NULL) {\n        nc->handler = ep->handler;\n#if MG_ENABLE_CALLBACK_USERDATA\n        nc->user_data = ep->user_data;\n#endif\n      }\n\n      /* Send handshake */\n      mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST,\n              hm);\n      if (!(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_SEND_AND_CLOSE))) {\n        if (nc->send_mbuf.len == 0) {\n          mg_ws_handshake(nc, vec, hm);\n        }\n        mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,\n                hm);\n        mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));\n      }\n    }\n#endif /* MG_ENABLE_HTTP_WEBSOCKET */\n    else if (hm->message.len > pd->rcvd) {\n      /* Not yet received all HTTP body, deliver MG_EV_HTTP_CHUNK */\n      deliver_chunk(nc, hm, req_len);\n      if (nc->recv_mbuf_limit > 0 && nc->recv_mbuf.len >= nc->recv_mbuf_limit) {\n        LOG(LL_ERROR, (\"%p recv buffer (%lu bytes) exceeds the limit \"\n                       \"%lu bytes, and not drained, closing\",\n                       nc, (unsigned long) nc->recv_mbuf.len,\n                       (unsigned long) nc->recv_mbuf_limit));\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      }\n    } else {\n      /* We did receive all HTTP body. */\n      int request_done = 1;\n      int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;\n      char addr[32];\n      mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),\n                          MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);\n      DBG((\"%p %s %.*s %.*s\", nc, addr, (int) hm->method.len, hm->method.p,\n           (int) hm->uri.len, hm->uri.p));\n      deliver_chunk(nc, hm, req_len);\n      /* Whole HTTP message is fully buffered, call event handler */\n      mg_http_call_endpoint_handler(nc, trigger_ev, hm);\n      mbuf_remove(io, hm->message.len);\n      pd->rcvd -= hm->message.len;\n#if MG_ENABLE_FILESYSTEM\n      /* We don't have a generic mechanism of communicating that we are done\n       * responding to a request (should probably add one). But if we are\n       * serving\n       * a file, we are definitely not done. */\n      if (pd->file.fp != NULL) request_done = 0;\n#endif\n#if MG_ENABLE_HTTP_CGI\n      /* If this is a CGI request, we are not done either. */\n      if (pd->cgi.cgi_nc != NULL) request_done = 0;\n#endif\n      if (request_done && io->len > 0) goto again;\n    }\n  }\n}\n\nstatic size_t mg_get_line_len(const char *buf, size_t buf_len) {\n  size_t len = 0;\n  while (len < buf_len && buf[len] != '\\n') len++;\n  return len == buf_len ? 0 : len + 1;\n}\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\nstatic void mg_http_multipart_begin(struct mg_connection *nc,\n                                    struct http_message *hm, int req_len) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  struct mg_str *ct;\n  struct mbuf *io = &nc->recv_mbuf;\n\n  char boundary_buf[100];\n  char *boundary = boundary_buf;\n  int boundary_len;\n\n  ct = mg_get_http_header(hm, \"Content-Type\");\n  if (ct == NULL) {\n    /* We need more data - or it isn't multipart message */\n    goto exit_mp;\n  }\n\n  /* Content-type should start with \"multipart\" */\n  if (ct->len < 9 || strncmp(ct->p, \"multipart\", 9) != 0) {\n    goto exit_mp;\n  }\n\n  boundary_len =\n      mg_http_parse_header2(ct, \"boundary\", &boundary, sizeof(boundary_buf));\n  if (boundary_len == 0) {\n    /*\n     * Content type is multipart, but there is no boundary,\n     * probably malformed request\n     */\n    nc->flags = MG_F_CLOSE_IMMEDIATELY;\n    DBG((\"invalid request\"));\n    goto exit_mp;\n  }\n\n  /* If we reach this place - that is multipart request */\n\n  if (pd->mp_stream.boundary != NULL) {\n    /*\n     * Another streaming request was in progress,\n     * looks like protocol error\n     */\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  } else {\n    struct mg_http_endpoint *ep = NULL;\n    pd->mp_stream.state = MPS_BEGIN;\n    pd->mp_stream.boundary = strdup(boundary);\n    pd->mp_stream.boundary_len = strlen(boundary);\n    pd->mp_stream.var_name = pd->mp_stream.file_name = NULL;\n    pd->endpoint_handler = nc->handler;\n\n    ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri);\n    if (ep != NULL) {\n      pd->endpoint_handler = ep->handler;\n    }\n\n    mg_http_call_endpoint_handler(nc, MG_EV_HTTP_MULTIPART_REQUEST, hm);\n\n    mbuf_remove(io, req_len);\n  }\nexit_mp:\n  if (boundary != boundary_buf) MG_FREE(boundary);\n}\n\n#define CONTENT_DISPOSITION \"Content-Disposition: \"\n\nstatic size_t mg_http_multipart_call_handler(struct mg_connection *c, int ev,\n                                             const char *data,\n                                             size_t data_len) {\n  struct mg_http_multipart_part mp;\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);\n  memset(&mp, 0, sizeof(mp));\n\n  mp.var_name = pd->mp_stream.var_name;\n  mp.file_name = pd->mp_stream.file_name;\n  mp.user_data = pd->mp_stream.user_data;\n  mp.data.p = data;\n  mp.data.len = data_len;\n  mp.num_data_consumed = data_len;\n  mg_call(c, pd->endpoint_handler, c->user_data, ev, &mp);\n  pd->mp_stream.user_data = mp.user_data;\n  pd->mp_stream.data_avail = (mp.num_data_consumed != data_len);\n  return mp.num_data_consumed;\n}\n\nstatic int mg_http_multipart_finalize(struct mg_connection *c) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);\n\n  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);\n  MG_FREE((void *) pd->mp_stream.file_name);\n  pd->mp_stream.file_name = NULL;\n  MG_FREE((void *) pd->mp_stream.var_name);\n  pd->mp_stream.var_name = NULL;\n  mg_http_multipart_call_handler(c, MG_EV_HTTP_MULTIPART_REQUEST_END, NULL, 0);\n  mg_http_free_proto_data_mp_stream(&pd->mp_stream);\n  pd->mp_stream.state = MPS_FINISHED;\n\n  return 1;\n}\n\nstatic int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {\n  const char *boundary;\n  struct mbuf *io = &c->recv_mbuf;\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);\n\n  if (pd->mp_stream.boundary == NULL) {\n    pd->mp_stream.state = MPS_FINALIZE;\n    DBG((\"Invalid request: boundary not initialized\"));\n    return 0;\n  }\n\n  if ((int) io->len < pd->mp_stream.boundary_len + 2) {\n    return 0;\n  }\n\n  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);\n  if (boundary != NULL) {\n    const char *boundary_end = (boundary + pd->mp_stream.boundary_len);\n    if (io->len - (boundary_end - io->buf) < 4) {\n      return 0;\n    }\n    if (strncmp(boundary_end, \"--\\r\\n\", 4) == 0) {\n      pd->mp_stream.state = MPS_FINALIZE;\n      mbuf_remove(io, (boundary_end - io->buf) + 4);\n    } else {\n      pd->mp_stream.state = MPS_GOT_BOUNDARY;\n    }\n  } else {\n    return 0;\n  }\n\n  return 1;\n}\n\nstatic void mg_http_parse_header_internal(struct mg_str *hdr,\n                                          const char *var_name,\n                                          struct altbuf *ab);\n\nstatic int mg_http_multipart_process_boundary(struct mg_connection *c) {\n  int data_size;\n  const char *boundary, *block_begin;\n  struct mbuf *io = &c->recv_mbuf;\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);\n  struct altbuf ab_file_name, ab_var_name;\n  int line_len;\n  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);\n  block_begin = boundary + pd->mp_stream.boundary_len + 2;\n  data_size = io->len - (block_begin - io->buf);\n\n  altbuf_init(&ab_file_name, NULL, 0);\n  altbuf_init(&ab_var_name, NULL, 0);\n\n  while (data_size > 0 &&\n         (line_len = mg_get_line_len(block_begin, data_size)) != 0) {\n    if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&\n        mg_ncasecmp(block_begin, CONTENT_DISPOSITION,\n                    sizeof(CONTENT_DISPOSITION) - 1) == 0) {\n      struct mg_str header;\n\n      header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;\n      header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;\n\n      altbuf_reset(&ab_var_name);\n      mg_http_parse_header_internal(&header, \"name\", &ab_var_name);\n\n      altbuf_reset(&ab_file_name);\n      mg_http_parse_header_internal(&header, \"filename\", &ab_file_name);\n\n      block_begin += line_len;\n      data_size -= line_len;\n\n      continue;\n    }\n\n    if (line_len == 2 && mg_ncasecmp(block_begin, \"\\r\\n\", 2) == 0) {\n      mbuf_remove(io, block_begin - io->buf + 2);\n\n      if (pd->mp_stream.processing_part != 0) {\n        mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);\n      }\n\n      /* Reserve 2 bytes for \"\\r\\n\" in file_name and var_name */\n      altbuf_append(&ab_file_name, '\\0');\n      altbuf_append(&ab_file_name, '\\0');\n      altbuf_append(&ab_var_name, '\\0');\n      altbuf_append(&ab_var_name, '\\0');\n\n      MG_FREE((void *) pd->mp_stream.file_name);\n      pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */);\n      MG_FREE((void *) pd->mp_stream.var_name);\n      pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */);\n\n      mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);\n      pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;\n      pd->mp_stream.processing_part++;\n      return 1;\n    }\n\n    block_begin += line_len;\n  }\n\n  pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;\n\n  altbuf_reset(&ab_var_name);\n  altbuf_reset(&ab_file_name);\n\n  return 0;\n}\n\nstatic int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);\n  struct mbuf *io = &c->recv_mbuf;\n\n  const char *boundary;\n  if ((int) io->len < pd->mp_stream.boundary_len + 6 /* \\r\\n, --, -- */) {\n    return 0;\n  }\n\n  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);\n  if (boundary == NULL) {\n    int data_len = (io->len - (pd->mp_stream.boundary_len + 6));\n    if (data_len > 0) {\n      size_t consumed = mg_http_multipart_call_handler(\n          c, MG_EV_HTTP_PART_DATA, io->buf, (size_t) data_len);\n      mbuf_remove(io, consumed);\n    }\n    return 0;\n  } else if (boundary != NULL) {\n    size_t data_len = ((size_t)(boundary - io->buf) - 4);\n    size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA,\n                                                     io->buf, data_len);\n    mbuf_remove(io, consumed);\n    if (consumed == data_len) {\n      mbuf_remove(io, 4);\n      pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;\n      return 1;\n    } else {\n      return 0;\n    }\n  } else {\n    return 0;\n  }\n}\n\nstatic void mg_http_multipart_continue(struct mg_connection *c) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);\n  while (1) {\n    switch (pd->mp_stream.state) {\n      case MPS_BEGIN: {\n        pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;\n        break;\n      }\n      case MPS_WAITING_FOR_BOUNDARY: {\n        if (mg_http_multipart_wait_for_boundary(c) == 0) {\n          return;\n        }\n        break;\n      }\n      case MPS_GOT_BOUNDARY: {\n        if (mg_http_multipart_process_boundary(c) == 0) {\n          return;\n        }\n        break;\n      }\n      case MPS_WAITING_FOR_CHUNK: {\n        if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {\n          return;\n        }\n        break;\n      }\n      case MPS_FINALIZE: {\n        if (mg_http_multipart_finalize(c) == 0) {\n          return;\n        }\n        break;\n      }\n      case MPS_FINISHED: {\n        return;\n      }\n    }\n  }\n}\n\nstruct file_upload_state {\n  char *lfn;\n  size_t num_recd;\n  FILE *fp;\n};\n\n#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */\n\nvoid mg_set_protocol_http_websocket(struct mg_connection *nc) {\n  nc->proto_handler = mg_http_handler;\n}\n\nconst char *mg_status_message(int status_code) {\n  switch (status_code) {\n    case 206:\n      return \"Partial Content\";\n    case 301:\n      return \"Moved\";\n    case 302:\n      return \"Found\";\n    case 400:\n      return \"Bad Request\";\n    case 401:\n      return \"Unauthorized\";\n    case 403:\n      return \"Forbidden\";\n    case 404:\n      return \"Not Found\";\n    case 416:\n      return \"Requested Range Not Satisfiable\";\n    case 418:\n      return \"I'm a teapot\";\n    case 500:\n      return \"Internal Server Error\";\n    case 502:\n      return \"Bad Gateway\";\n    case 503:\n      return \"Service Unavailable\";\n\n#if MG_ENABLE_EXTRA_ERRORS_DESC\n    case 100:\n      return \"Continue\";\n    case 101:\n      return \"Switching Protocols\";\n    case 102:\n      return \"Processing\";\n    case 200:\n      return \"OK\";\n    case 201:\n      return \"Created\";\n    case 202:\n      return \"Accepted\";\n    case 203:\n      return \"Non-Authoritative Information\";\n    case 204:\n      return \"No Content\";\n    case 205:\n      return \"Reset Content\";\n    case 207:\n      return \"Multi-Status\";\n    case 208:\n      return \"Already Reported\";\n    case 226:\n      return \"IM Used\";\n    case 300:\n      return \"Multiple Choices\";\n    case 303:\n      return \"See Other\";\n    case 304:\n      return \"Not Modified\";\n    case 305:\n      return \"Use Proxy\";\n    case 306:\n      return \"Switch Proxy\";\n    case 307:\n      return \"Temporary Redirect\";\n    case 308:\n      return \"Permanent Redirect\";\n    case 402:\n      return \"Payment Required\";\n    case 405:\n      return \"Method Not Allowed\";\n    case 406:\n      return \"Not Acceptable\";\n    case 407:\n      return \"Proxy Authentication Required\";\n    case 408:\n      return \"Request Timeout\";\n    case 409:\n      return \"Conflict\";\n    case 410:\n      return \"Gone\";\n    case 411:\n      return \"Length Required\";\n    case 412:\n      return \"Precondition Failed\";\n    case 413:\n      return \"Payload Too Large\";\n    case 414:\n      return \"URI Too Long\";\n    case 415:\n      return \"Unsupported Media Type\";\n    case 417:\n      return \"Expectation Failed\";\n    case 422:\n      return \"Unprocessable Entity\";\n    case 423:\n      return \"Locked\";\n    case 424:\n      return \"Failed Dependency\";\n    case 426:\n      return \"Upgrade Required\";\n    case 428:\n      return \"Precondition Required\";\n    case 429:\n      return \"Too Many Requests\";\n    case 431:\n      return \"Request Header Fields Too Large\";\n    case 451:\n      return \"Unavailable For Legal Reasons\";\n    case 501:\n      return \"Not Implemented\";\n    case 504:\n      return \"Gateway Timeout\";\n    case 505:\n      return \"HTTP Version Not Supported\";\n    case 506:\n      return \"Variant Also Negotiates\";\n    case 507:\n      return \"Insufficient Storage\";\n    case 508:\n      return \"Loop Detected\";\n    case 510:\n      return \"Not Extended\";\n    case 511:\n      return \"Network Authentication Required\";\n#endif /* MG_ENABLE_EXTRA_ERRORS_DESC */\n\n    default:\n      return \"OK\";\n  }\n}\n\nvoid mg_send_response_line_s(struct mg_connection *nc, int status_code,\n                             const struct mg_str extra_headers) {\n  mg_printf(nc, \"HTTP/1.1 %d %s\\r\\n\", status_code,\n            mg_status_message(status_code));\n#ifndef MG_HIDE_SERVER_INFO\n  mg_printf(nc, \"Server: %s\\r\\n\", mg_version_header);\n#endif\n  if (extra_headers.len > 0) {\n    mg_printf(nc, \"%.*s\\r\\n\", (int) extra_headers.len, extra_headers.p);\n  }\n}\n\nvoid mg_send_response_line(struct mg_connection *nc, int status_code,\n                           const char *extra_headers) {\n  mg_send_response_line_s(nc, status_code, mg_mk_str(extra_headers));\n}\n\nvoid mg_http_send_redirect(struct mg_connection *nc, int status_code,\n                           const struct mg_str location,\n                           const struct mg_str extra_headers) {\n  char bbody[100], *pbody = bbody;\n  int bl = mg_asprintf(&pbody, sizeof(bbody),\n                       \"<p>Moved <a href='%.*s'>here</a>.\\r\\n\",\n                       (int) location.len, location.p);\n  char bhead[150], *phead = bhead;\n  mg_asprintf(&phead, sizeof(bhead),\n              \"Location: %.*s\\r\\n\"\n              \"Content-Type: text/html\\r\\n\"\n              \"Content-Length: %d\\r\\n\"\n              \"Cache-Control: no-cache\\r\\n\"\n              \"%.*s%s\",\n              (int) location.len, location.p, bl, (int) extra_headers.len,\n              extra_headers.p, (extra_headers.len > 0 ? \"\\r\\n\" : \"\"));\n  mg_send_response_line(nc, status_code, phead);\n  if (phead != bhead) MG_FREE(phead);\n  mg_send(nc, pbody, bl);\n  if (pbody != bbody) MG_FREE(pbody);\n}\n\nvoid mg_send_head(struct mg_connection *c, int status_code,\n                  int64_t content_length, const char *extra_headers) {\n  mg_send_response_line(c, status_code, extra_headers);\n  if (content_length < 0) {\n    mg_printf(c, \"%s\", \"Transfer-Encoding: chunked\\r\\n\");\n  } else {\n    mg_printf(c, \"Content-Length: %\" INT64_FMT \"\\r\\n\", content_length);\n  }\n  mg_send(c, \"\\r\\n\", 2);\n}\n\nvoid mg_http_send_error(struct mg_connection *nc, int code,\n                        const char *reason) {\n  if (!reason) reason = mg_status_message(code);\n  LOG(LL_DEBUG, (\"%p %d %s\", nc, code, reason));\n  mg_send_head(nc, code, strlen(reason),\n               \"Content-Type: text/plain\\r\\nConnection: close\");\n  mg_send(nc, reason, strlen(reason));\n  nc->flags |= MG_F_SEND_AND_CLOSE;\n}\n\n#if MG_ENABLE_FILESYSTEM\nstatic void mg_http_construct_etag(char *buf, size_t buf_len,\n                                   const cs_stat_t *st) {\n  snprintf(buf, buf_len, \"\\\"%lx.%\" INT64_FMT \"\\\"\", (unsigned long) st->st_mtime,\n           (int64_t) st->st_size);\n}\n\n#ifndef WINCE\nstatic void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) {\n  struct tm tm;\n  strftime(buf, buf_len, \"%a, %d %b %Y %H:%M:%S GMT\", gmtime_r(t, &tm));\n}\n#else\n/* Look wince_lib.c for WindowsCE implementation */\nstatic void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t);\n#endif\n\nstatic int mg_http_parse_range_header(const struct mg_str *header, int64_t *a,\n                                      int64_t *b) {\n  /*\n   * There is no snscanf. Headers are not guaranteed to be NUL-terminated,\n   * so we have this. Ugh.\n   */\n  int result;\n  char *p = (char *) MG_MALLOC(header->len + 1);\n  if (p == NULL) return 0;\n  memcpy(p, header->p, header->len);\n  p[header->len] = '\\0';\n  result = sscanf(p, \"bytes=%\" INT64_FMT \"-%\" INT64_FMT, a, b);\n  MG_FREE(p);\n  return result;\n}\n\nvoid mg_http_serve_file(struct mg_connection *nc, struct http_message *hm,\n                        const char *path, const struct mg_str mime_type,\n                        const struct mg_str extra_headers) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  cs_stat_t st;\n  LOG(LL_DEBUG, (\"%p [%s] %.*s\", nc, path, (int) mime_type.len, mime_type.p));\n  if (mg_stat(path, &st) != 0 || (pd->file.fp = mg_fopen(path, \"rb\")) == NULL) {\n    int code, err = mg_get_errno();\n    switch (err) {\n      case EACCES:\n        code = 403;\n        break;\n      case ENOENT:\n        code = 404;\n        break;\n      default:\n        code = 500;\n    };\n    mg_http_send_error(nc, code, \"Open failed\");\n  } else {\n    char etag[50], current_time[50], last_modified[50], range[70];\n    time_t t = (time_t) mg_time();\n    int64_t r1 = 0, r2 = 0, cl = st.st_size;\n    struct mg_str *range_hdr = mg_get_http_header(hm, \"Range\");\n    int n, status_code = 200;\n\n    /* Handle Range header */\n    range[0] = '\\0';\n    if (range_hdr != NULL &&\n        (n = mg_http_parse_range_header(range_hdr, &r1, &r2)) > 0 && r1 >= 0 &&\n        r2 >= 0) {\n      /* If range is specified like \"400-\", set second limit to content len */\n      if (n == 1) {\n        r2 = cl - 1;\n      }\n      if (r1 > r2 || r2 >= cl) {\n        status_code = 416;\n        cl = 0;\n        snprintf(range, sizeof(range),\n                 \"Content-Range: bytes */%\" INT64_FMT \"\\r\\n\",\n                 (int64_t) st.st_size);\n      } else {\n        status_code = 206;\n        cl = r2 - r1 + 1;\n        snprintf(range, sizeof(range), \"Content-Range: bytes %\" INT64_FMT\n                                       \"-%\" INT64_FMT \"/%\" INT64_FMT \"\\r\\n\",\n                 r1, r1 + cl - 1, (int64_t) st.st_size);\n#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \\\n    _XOPEN_SOURCE >= 600\n        fseeko(pd->file.fp, r1, SEEK_SET);\n#else\n        fseek(pd->file.fp, (long) r1, SEEK_SET);\n#endif\n      }\n    }\n\n#if !MG_DISABLE_HTTP_KEEP_ALIVE\n    {\n      struct mg_str *conn_hdr = mg_get_http_header(hm, \"Connection\");\n      if (conn_hdr != NULL) {\n        pd->file.keepalive = (mg_vcasecmp(conn_hdr, \"keep-alive\") == 0);\n      } else {\n        pd->file.keepalive = (mg_vcmp(&hm->proto, \"HTTP/1.1\") == 0);\n      }\n    }\n#endif\n\n    mg_http_construct_etag(etag, sizeof(etag), &st);\n    mg_gmt_time_string(current_time, sizeof(current_time), &t);\n    mg_gmt_time_string(last_modified, sizeof(last_modified), &st.st_mtime);\n    /*\n     * Content length casted to size_t because:\n     * 1) that's the maximum buffer size anyway\n     * 2) ESP8266 RTOS SDK newlib vprintf cannot contain a 64bit arg at non-last\n     *    position\n     * TODO(mkm): fix ESP8266 RTOS SDK\n     */\n    mg_send_response_line_s(nc, status_code, extra_headers);\n    mg_printf(nc,\n              \"Date: %s\\r\\n\"\n              \"Last-Modified: %s\\r\\n\"\n              \"Accept-Ranges: bytes\\r\\n\"\n              \"Content-Type: %.*s\\r\\n\"\n              \"Connection: %s\\r\\n\"\n              \"Content-Length: %\" SIZE_T_FMT\n              \"\\r\\n\"\n              \"%sEtag: %s\\r\\n\\r\\n\",\n              current_time, last_modified, (int) mime_type.len, mime_type.p,\n              (pd->file.keepalive ? \"keep-alive\" : \"close\"), (size_t) cl, range,\n              etag);\n\n    pd->file.cl = cl;\n    pd->file.type = DATA_FILE;\n    mg_http_transfer_file_data(nc);\n  }\n}\n\nstatic void mg_http_serve_file2(struct mg_connection *nc, const char *path,\n                                struct http_message *hm,\n                                struct mg_serve_http_opts *opts) {\n#if MG_ENABLE_HTTP_SSI\n  if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) > 0) {\n    mg_handle_ssi_request(nc, hm, path, opts);\n    return;\n  }\n#endif\n  mg_http_serve_file(nc, hm, path, mg_get_mime_type(path, \"text/plain\", opts),\n                     mg_mk_str(opts->extra_headers));\n}\n\n#endif\n\nint mg_url_decode(const char *src, int src_len, char *dst, int dst_len,\n                  int is_form_url_encoded) {\n  int i, j, a, b;\n#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')\n\n  for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {\n    if (src[i] == '%') {\n      if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) &&\n          isxdigit(*(const unsigned char *) (src + i + 2))) {\n        a = tolower(*(const unsigned char *) (src + i + 1));\n        b = tolower(*(const unsigned char *) (src + i + 2));\n        dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));\n        i += 2;\n      } else {\n        return -1;\n      }\n    } else if (is_form_url_encoded && src[i] == '+') {\n      dst[j] = ' ';\n    } else {\n      dst[j] = src[i];\n    }\n  }\n\n  dst[j] = '\\0'; /* Null-terminate the destination */\n\n  return i >= src_len ? j : -1;\n}\n\nint mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,\n                    size_t dst_len) {\n  const char *p, *e, *s;\n  size_t name_len;\n  int len;\n\n  /*\n   * According to the documentation function returns negative\n   * value in case of error. For debug purposes it returns:\n   * -1 - src is wrong (NUUL)\n   * -2 - dst is wrong (NULL)\n   * -3 - failed to decode url or dst is to small\n   * -4 - name does not exist\n   */\n  if (dst == NULL || dst_len == 0) {\n    len = -2;\n  } else if (buf->p == NULL || name == NULL || buf->len == 0) {\n    len = -1;\n    dst[0] = '\\0';\n  } else {\n    name_len = strlen(name);\n    e = buf->p + buf->len;\n    len = -4;\n    dst[0] = '\\0';\n\n    for (p = buf->p; p + name_len < e; p++) {\n      if ((p == buf->p || p[-1] == '&') && p[name_len] == '=' &&\n          !mg_ncasecmp(name, p, name_len)) {\n        p += name_len + 1;\n        s = (const char *) memchr(p, '&', (size_t)(e - p));\n        if (s == NULL) {\n          s = e;\n        }\n        len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);\n        /* -1 means: failed to decode or dst is too small */\n        if (len == -1) {\n          len = -3;\n        }\n        break;\n      }\n    }\n  }\n\n  return len;\n}\n\nvoid mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len) {\n  char chunk_size[50];\n  int n;\n\n  n = snprintf(chunk_size, sizeof(chunk_size), \"%lX\\r\\n\", (unsigned long) len);\n  mg_send(nc, chunk_size, n);\n  mg_send(nc, buf, len);\n  mg_send(nc, \"\\r\\n\", 2);\n}\n\nvoid mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...) {\n  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;\n  int len;\n  va_list ap;\n\n  va_start(ap, fmt);\n  len = mg_avprintf(&buf, sizeof(mem), fmt, ap);\n  va_end(ap);\n\n  if (len >= 0) {\n    mg_send_http_chunk(nc, buf, len);\n  }\n\n  /* LCOV_EXCL_START */\n  if (buf != mem && buf != NULL) {\n    MG_FREE(buf);\n  }\n  /* LCOV_EXCL_STOP */\n}\n\nvoid mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) {\n  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;\n  int i, j, len;\n  va_list ap;\n\n  va_start(ap, fmt);\n  len = mg_avprintf(&buf, sizeof(mem), fmt, ap);\n  va_end(ap);\n\n  if (len >= 0) {\n    for (i = j = 0; i < len; i++) {\n      if (buf[i] == '<' || buf[i] == '>') {\n        mg_send(nc, buf + j, i - j);\n        mg_send(nc, buf[i] == '<' ? \"&lt;\" : \"&gt;\", 4);\n        j = i + 1;\n      }\n    }\n    mg_send(nc, buf + j, i - j);\n  }\n\n  /* LCOV_EXCL_START */\n  if (buf != mem && buf != NULL) {\n    MG_FREE(buf);\n  }\n  /* LCOV_EXCL_STOP */\n}\n\nstatic void mg_http_parse_header_internal(struct mg_str *hdr,\n                                          const char *var_name,\n                                          struct altbuf *ab) {\n  int ch = ' ', ch1 = ',', ch2 = ';', n = strlen(var_name);\n  const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;\n\n  /* Find where variable starts */\n  for (s = hdr->p; s != NULL && s + n < end; s++) {\n    if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') &&\n        s[n] == '=' && !strncmp(s, var_name, n))\n      break;\n  }\n\n  if (s != NULL && &s[n + 1] < end) {\n    s += n + 1;\n    if (*s == '\"' || *s == '\\'') {\n      ch = ch1 = ch2 = *s++;\n    }\n    p = s;\n    while (p < end && p[0] != ch && p[0] != ch1 && p[0] != ch2) {\n      if (ch != ' ' && p[0] == '\\\\' && p[1] == ch) p++;\n      altbuf_append(ab, *p++);\n    }\n\n    if (ch != ' ' && *p != ch) {\n      altbuf_reset(ab);\n    }\n  }\n\n  /* If there is some data, append a NUL. */\n  if (ab->len > 0) {\n    altbuf_append(ab, '\\0');\n  }\n}\n\nint mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,\n                          size_t buf_size) {\n  struct altbuf ab;\n  altbuf_init(&ab, *buf, buf_size);\n  if (hdr == NULL) return 0;\n  if (*buf != NULL && buf_size > 0) *buf[0] = '\\0';\n\n  mg_http_parse_header_internal(hdr, var_name, &ab);\n\n  /*\n   * Get a (trimmed) buffer, and return a len without a NUL byte which might\n   * have been added.\n   */\n  *buf = altbuf_get_buf(&ab, 1 /* trim */);\n  return ab.len > 0 ? ab.len - 1 : 0;\n}\n\nint mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,\n                         size_t buf_size) {\n  char *buf2 = buf;\n\n  int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size);\n\n  if (buf2 != buf) {\n    /* Buffer was not enough and was reallocated: free it and just return 0 */\n    MG_FREE(buf2);\n    return 0;\n  }\n\n  return len;\n}\n\nint mg_get_http_basic_auth(struct http_message *hm, char *user, size_t user_len,\n                           char *pass, size_t pass_len) {\n  struct mg_str *hdr = mg_get_http_header(hm, \"Authorization\");\n  if (hdr == NULL) return -1;\n  return mg_parse_http_basic_auth(hdr, user, user_len, pass, pass_len);\n}\n\nint mg_parse_http_basic_auth(struct mg_str *hdr, char *user, size_t user_len,\n                             char *pass, size_t pass_len) {\n  char *buf = NULL;\n  char fmt[64];\n  int res = 0;\n\n  if (mg_strncmp(*hdr, mg_mk_str(\"Basic \"), 6) != 0) return -1;\n\n  buf = (char *) MG_MALLOC(hdr->len);\n  cs_base64_decode((unsigned char *) hdr->p + 6, hdr->len, buf, NULL);\n\n  /* e.g. \"%123[^:]:%321[^\\n]\" */\n  snprintf(fmt, sizeof(fmt), \"%%%\" SIZE_T_FMT \"[^:]:%%%\" SIZE_T_FMT \"[^\\n]\",\n           user_len - 1, pass_len - 1);\n  if (sscanf(buf, fmt, user, pass) == 0) {\n    res = -1;\n  }\n\n  MG_FREE(buf);\n  return res;\n}\n\n#if MG_ENABLE_FILESYSTEM\nstatic int mg_is_file_hidden(const char *path,\n                             const struct mg_serve_http_opts *opts,\n                             int exclude_specials) {\n  const char *p1 = opts->per_directory_auth_file;\n  const char *p2 = opts->hidden_file_pattern;\n\n  /* Strip directory path from the file name */\n  const char *pdir = strrchr(path, DIRSEP);\n  if (pdir != NULL) {\n    path = pdir + 1;\n  }\n\n  return (exclude_specials && (!strcmp(path, \".\") || !strcmp(path, \"..\"))) ||\n         (p1 != NULL && mg_match_prefix(p1, strlen(p1), path) == strlen(p1)) ||\n         (p2 != NULL && mg_match_prefix(p2, strlen(p2), path) > 0);\n}\n\n#if !MG_DISABLE_HTTP_DIGEST_AUTH\n\n#ifndef MG_EXT_MD5\nvoid mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],\n                   const size_t *msg_lens, uint8_t *digest) {\n  size_t i;\n  cs_md5_ctx md5_ctx;\n  cs_md5_init(&md5_ctx);\n  for (i = 0; i < num_msgs; i++) {\n    cs_md5_update(&md5_ctx, msgs[i], msg_lens[i]);\n  }\n  cs_md5_final(digest, &md5_ctx);\n}\n#else\nextern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],\n                          const size_t *msg_lens, uint8_t *digest);\n#endif\n\nvoid cs_md5(char buf[33], ...) {\n  unsigned char hash[16];\n  const uint8_t *msgs[20], *p;\n  size_t msg_lens[20];\n  size_t num_msgs = 0;\n  va_list ap;\n\n  va_start(ap, buf);\n  while ((p = va_arg(ap, const unsigned char *) ) != NULL) {\n    msgs[num_msgs] = p;\n    msg_lens[num_msgs] = va_arg(ap, size_t);\n    num_msgs++;\n  }\n  va_end(ap);\n\n  mg_hash_md5_v(num_msgs, msgs, msg_lens, hash);\n  cs_to_hex(buf, hash, sizeof(hash));\n}\n\nstatic void mg_mkmd5resp(const char *method, size_t method_len, const char *uri,\n                         size_t uri_len, const char *ha1, size_t ha1_len,\n                         const char *nonce, size_t nonce_len, const char *nc,\n                         size_t nc_len, const char *cnonce, size_t cnonce_len,\n                         const char *qop, size_t qop_len, char *resp) {\n  static const char colon[] = \":\";\n  static const size_t one = 1;\n  char ha2[33];\n  cs_md5(ha2, method, method_len, colon, one, uri, uri_len, NULL);\n  cs_md5(resp, ha1, ha1_len, colon, one, nonce, nonce_len, colon, one, nc,\n         nc_len, colon, one, cnonce, cnonce_len, colon, one, qop, qop_len,\n         colon, one, ha2, sizeof(ha2) - 1, NULL);\n}\n\nint mg_http_create_digest_auth_header(char *buf, size_t buf_len,\n                                      const char *method, const char *uri,\n                                      const char *auth_domain, const char *user,\n                                      const char *passwd, const char *nonce) {\n  static const char colon[] = \":\", qop[] = \"auth\";\n  static const size_t one = 1;\n  char ha1[33], resp[33], cnonce[40];\n\n  snprintf(cnonce, sizeof(cnonce), \"%lx\", (unsigned long) mg_time());\n  cs_md5(ha1, user, (size_t) strlen(user), colon, one, auth_domain,\n         (size_t) strlen(auth_domain), colon, one, passwd,\n         (size_t) strlen(passwd), NULL);\n  mg_mkmd5resp(method, strlen(method), uri, strlen(uri), ha1, sizeof(ha1) - 1,\n               nonce, strlen(nonce), \"1\", one, cnonce, strlen(cnonce), qop,\n               sizeof(qop) - 1, resp);\n  return snprintf(buf, buf_len,\n                  \"Authorization: Digest username=\\\"%s\\\",\"\n                  \"realm=\\\"%s\\\",uri=\\\"%s\\\",qop=%s,nc=1,cnonce=%s,\"\n                  \"nonce=%s,response=%s\\r\\n\",\n                  user, auth_domain, uri, qop, cnonce, nonce, resp);\n}\n\n/*\n * Check for authentication timeout.\n * Clients send time stamp encoded in nonce. Make sure it is not too old,\n * to prevent replay attacks.\n * Assumption: nonce is a hexadecimal number of seconds since 1970.\n */\nstatic int mg_check_nonce(const char *nonce) {\n  unsigned long now = (unsigned long) mg_time();\n  unsigned long val = (unsigned long) strtoul(nonce, NULL, 16);\n  return (now >= val) && (now - val < 60 * 60);\n}\n\nint mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,\n                              FILE *fp) {\n  int ret = 0;\n  struct mg_str *hdr;\n  char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200],\n      qop_buf[20], nc_buf[20], nonce_buf[16];\n\n  char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf,\n       *uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf;\n\n  /* Parse \"Authorization:\" header, fail fast on parse error */\n  if (hm == NULL || fp == NULL ||\n      (hdr = mg_get_http_header(hm, \"Authorization\")) == NULL ||\n      mg_http_parse_header2(hdr, \"username\", &username, sizeof(username_buf)) ==\n          0 ||\n      mg_http_parse_header2(hdr, \"cnonce\", &cnonce, sizeof(cnonce_buf)) == 0 ||\n      mg_http_parse_header2(hdr, \"response\", &response, sizeof(response_buf)) ==\n          0 ||\n      mg_http_parse_header2(hdr, \"uri\", &uri, sizeof(uri_buf)) == 0 ||\n      mg_http_parse_header2(hdr, \"qop\", &qop, sizeof(qop_buf)) == 0 ||\n      mg_http_parse_header2(hdr, \"nc\", &nc, sizeof(nc_buf)) == 0 ||\n      mg_http_parse_header2(hdr, \"nonce\", &nonce, sizeof(nonce_buf)) == 0 ||\n      mg_check_nonce(nonce) == 0) {\n    ret = 0;\n    goto clean;\n  }\n\n  /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */\n\n  ret = mg_check_digest_auth(\n      hm->method,\n      mg_mk_str_n(\n          hm->uri.p,\n          hm->uri.len + (hm->query_string.len ? hm->query_string.len + 1 : 0)),\n      mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response),\n      mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain),\n      fp);\n\nclean:\n  if (username != username_buf) MG_FREE(username);\n  if (cnonce != cnonce_buf) MG_FREE(cnonce);\n  if (response != response_buf) MG_FREE(response);\n  if (uri != uri_buf) MG_FREE(uri);\n  if (qop != qop_buf) MG_FREE(qop);\n  if (nc != nc_buf) MG_FREE(nc);\n  if (nonce != nonce_buf) MG_FREE(nonce);\n\n  return ret;\n}\n\nint mg_check_digest_auth(struct mg_str method, struct mg_str uri,\n                         struct mg_str username, struct mg_str cnonce,\n                         struct mg_str response, struct mg_str qop,\n                         struct mg_str nc, struct mg_str nonce,\n                         struct mg_str auth_domain, FILE *fp) {\n  char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];\n  char exp_resp[33];\n\n  /*\n   * Read passwords file line by line. If should have htdigest format,\n   * i.e. each line should be a colon-separated sequence:\n   * USER_NAME:DOMAIN_NAME:HA1_HASH_OF_USER_DOMAIN_AND_PASSWORD\n   */\n  while (fgets(buf, sizeof(buf), fp) != NULL) {\n    if (sscanf(buf, \"%[^:]:%[^:]:%s\", f_user, f_domain, f_ha1) == 3 &&\n        mg_vcmp(&username, f_user) == 0 &&\n        mg_vcmp(&auth_domain, f_domain) == 0) {\n      /* Username and domain matched, check the password */\n      mg_mkmd5resp(method.p, method.len, uri.p, uri.len, f_ha1, strlen(f_ha1),\n                   nonce.p, nonce.len, nc.p, nc.len, cnonce.p, cnonce.len,\n                   qop.p, qop.len, exp_resp);\n      LOG(LL_DEBUG, (\"%.*s %s %.*s %s\", (int) username.len, username.p,\n                     f_domain, (int) response.len, response.p, exp_resp));\n      return mg_ncasecmp(response.p, exp_resp, strlen(exp_resp)) == 0;\n    }\n  }\n\n  /* None of the entries in the passwords file matched - return failure */\n  return 0;\n}\n\nint mg_http_is_authorized(struct http_message *hm, struct mg_str path,\n                          const char *domain, const char *passwords_file,\n                          int flags) {\n  char buf[MG_MAX_PATH];\n  const char *p;\n  FILE *fp;\n  int authorized = 1;\n\n  if (domain != NULL && passwords_file != NULL) {\n    if (flags & MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE) {\n      fp = mg_fopen(passwords_file, \"r\");\n    } else if (flags & MG_AUTH_FLAG_IS_DIRECTORY) {\n      snprintf(buf, sizeof(buf), \"%.*s%c%s\", (int) path.len, path.p, DIRSEP,\n               passwords_file);\n      fp = mg_fopen(buf, \"r\");\n    } else {\n      p = strrchr(path.p, DIRSEP);\n      if (p == NULL) p = path.p;\n      snprintf(buf, sizeof(buf), \"%.*s%c%s\", (int) (p - path.p), path.p, DIRSEP,\n               passwords_file);\n      fp = mg_fopen(buf, \"r\");\n    }\n\n    if (fp != NULL) {\n      authorized = mg_http_check_digest_auth(hm, domain, fp);\n      fclose(fp);\n    } else if (!(flags & MG_AUTH_FLAG_ALLOW_MISSING_FILE)) {\n      authorized = 0;\n    }\n  }\n\n  LOG(LL_DEBUG, (\"%.*s %s %x %d\", (int) path.len, path.p,\n                 passwords_file ? passwords_file : \"\", flags, authorized));\n  return authorized;\n}\n#else\nint mg_http_is_authorized(struct http_message *hm, const struct mg_str path,\n                          const char *domain, const char *passwords_file,\n                          int flags) {\n  (void) hm;\n  (void) path;\n  (void) domain;\n  (void) passwords_file;\n  (void) flags;\n  return 1;\n}\n#endif\n\n#if MG_ENABLE_DIRECTORY_LISTING\nstatic void mg_escape(const char *src, char *dst, size_t dst_len) {\n  size_t n = 0;\n  while (*src != '\\0' && n + 5 < dst_len) {\n    unsigned char ch = *(unsigned char *) src++;\n    if (ch == '<') {\n      n += snprintf(dst + n, dst_len - n, \"%s\", \"&lt;\");\n    } else {\n      dst[n++] = ch;\n    }\n  }\n  dst[n] = '\\0';\n}\n\nstatic void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,\n                               cs_stat_t *stp) {\n  char size[64], mod[64], path[MG_MAX_PATH];\n  int64_t fsize = stp->st_size;\n  int is_dir = S_ISDIR(stp->st_mode);\n  const char *slash = is_dir ? \"/\" : \"\";\n  struct mg_str href;\n  struct tm tm;\n\n  if (is_dir) {\n    snprintf(size, sizeof(size), \"%s\", \"[DIRECTORY]\");\n  } else {\n    /*\n     * We use (double) cast below because MSVC 6 compiler cannot\n     * convert unsigned __int64 to double.\n     */\n    if (fsize < 1024) {\n      snprintf(size, sizeof(size), \"%d\", (int) fsize);\n    } else if (fsize < 0x100000) {\n      snprintf(size, sizeof(size), \"%.1fk\", (double) fsize / 1024.0);\n    } else if (fsize < 0x40000000) {\n      snprintf(size, sizeof(size), \"%.1fM\", (double) fsize / 1048576);\n    } else {\n      snprintf(size, sizeof(size), \"%.1fG\", (double) fsize / 1073741824);\n    }\n  }\n  strftime(mod, sizeof(mod), \"%d-%b-%Y %H:%M\", localtime_r(&stp->st_mtime, &tm));\n  mg_escape(file_name, path, sizeof(path));\n  href = mg_url_encode(mg_mk_str(file_name));\n  mg_printf_http_chunk(nc,\n                       \"<tr><td><a href=\\\"%s%s\\\">%s%s</a></td>\"\n                       \"<td>%s</td><td name=%\" INT64_FMT \">%s</td></tr>\\n\",\n                       href.p, slash, path, slash, mod, is_dir ? -1 : fsize,\n                       size);\n  free((void *) href.p);\n}\n\nstatic void mg_scan_directory(struct mg_connection *nc, const char *dir,\n                              const struct mg_serve_http_opts *opts,\n                              void (*func)(struct mg_connection *, const char *,\n                                           cs_stat_t *)) {\n  char path[MG_MAX_PATH + 1];\n  cs_stat_t st;\n  struct dirent *dp;\n  DIR *dirp;\n\n  LOG(LL_DEBUG, (\"%p [%s]\", nc, dir));\n  if ((dirp = (opendir(dir))) != NULL) {\n    while ((dp = readdir(dirp)) != NULL) {\n      /* Do not show current dir and hidden files */\n      if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {\n        continue;\n      }\n      snprintf(path, sizeof(path), \"%s/%s\", dir, dp->d_name);\n      if (mg_stat(path, &st) == 0) {\n        func(nc, (const char *) dp->d_name, &st);\n      }\n    }\n    closedir(dirp);\n  } else {\n    LOG(LL_DEBUG, (\"%p opendir(%s) -> %d\", nc, dir, mg_get_errno()));\n  }\n}\n\nstatic void mg_send_directory_listing(struct mg_connection *nc, const char *dir,\n                                      struct http_message *hm,\n                                      struct mg_serve_http_opts *opts) {\n  static const char *sort_js_code =\n      \"<script>function srt(tb, sc, so, d) {\"\n      \"var tr = Array.prototype.slice.call(tb.rows, 0),\"\n      \"tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],\"\n      \"n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), \"\n      \"t1 = a.cells[2].getAttribute('name'), \"\n      \"t2 = b.cells[2].getAttribute('name'); \"\n      \"return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : \"\n      \"n1 ? parseInt(n2) - parseInt(n1) : \"\n      \"c1.textContent.trim().localeCompare(c2.textContent.trim())); });\";\n  static const char *sort_js_code2 =\n      \"for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); \"\n      \"if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); \"\n      \"};\"\n      \"window.onload = function() {\"\n      \"var tb = document.getElementById('tb');\"\n      \"var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];\"\n      \"var sc = m[1], so = m[2]; document.onclick = function(ev) { \"\n      \"var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); \"\n      \"sc = c; ev.preventDefault();}};\"\n      \"srt(tb, sc, so, true);\"\n      \"}\"\n      \"</script>\";\n\n  mg_send_response_line(nc, 200, opts->extra_headers);\n  mg_printf(nc, \"%s: %s\\r\\n%s: %s\\r\\n\\r\\n\", \"Transfer-Encoding\", \"chunked\",\n            \"Content-Type\", \"text/html; charset=utf-8\");\n\n  mg_printf_http_chunk(\n      nc,\n      \"<html><head><title>Index of %.*s</title>%s%s\"\n      \"<style>th,td {text-align: left; padding-right: 1em; \"\n      \"font-family: monospace; }</style></head>\\n\"\n      \"<body><h1>Index of %.*s</h1>\\n<table cellpadding=0><thead>\"\n      \"<tr><th><a href=# rel=0>Name</a></th><th>\"\n      \"<a href=# rel=1>Modified</a</th>\"\n      \"<th><a href=# rel=2>Size</a></th></tr>\"\n      \"<tr><td colspan=3><hr></td></tr>\\n\"\n      \"</thead>\\n\"\n      \"<tbody id=tb>\",\n      (int) hm->uri.len, hm->uri.p, sort_js_code, sort_js_code2,\n      (int) hm->uri.len, hm->uri.p);\n  mg_scan_directory(nc, dir, opts, mg_print_dir_entry);\n  mg_printf_http_chunk(nc,\n                       \"</tbody><tr><td colspan=3><hr></td></tr>\\n\"\n                       \"</table>\\n\"\n                       \"<address>%s</address>\\n\"\n                       \"</body></html>\",\n                       mg_version_header);\n  mg_send_http_chunk(nc, \"\", 0);\n  /* TODO(rojer): Remove when cesanta/dev/issues/197 is fixed. */\n  nc->flags |= MG_F_SEND_AND_CLOSE;\n}\n#endif /* MG_ENABLE_DIRECTORY_LISTING */\n\n/*\n * Given a directory path, find one of the files specified in the\n * comma-separated list of index files `list`.\n * First found index file wins. If an index file is found, then gets\n * appended to the `path`, stat-ed, and result of `stat()` passed to `stp`.\n * If index file is not found, then `path` and `stp` remain unchanged.\n */\nMG_INTERNAL void mg_find_index_file(const char *path, const char *list,\n                                    char **index_file, cs_stat_t *stp) {\n  struct mg_str vec;\n  size_t path_len = strlen(path);\n  int found = 0;\n  *index_file = NULL;\n\n  /* Traverse index files list. For each entry, append it to the given */\n  /* path and see if the file exists. If it exists, break the loop */\n  while ((list = mg_next_comma_list_entry(list, &vec, NULL)) != NULL) {\n    cs_stat_t st;\n    size_t len = path_len + 1 + vec.len + 1;\n    *index_file = (char *) MG_REALLOC(*index_file, len);\n    if (*index_file == NULL) break;\n    snprintf(*index_file, len, \"%s%c%.*s\", path, DIRSEP, (int) vec.len, vec.p);\n\n    /* Does it exist? Is it a file? */\n    if (mg_stat(*index_file, &st) == 0 && S_ISREG(st.st_mode)) {\n      /* Yes it does, break the loop */\n      *stp = st;\n      found = 1;\n      break;\n    }\n  }\n  if (!found) {\n    MG_FREE(*index_file);\n    *index_file = NULL;\n  }\n  LOG(LL_DEBUG, (\"[%s] [%s]\", path, (*index_file ? *index_file : \"\")));\n}\n\n#if MG_ENABLE_HTTP_URL_REWRITES\nstatic int mg_http_send_port_based_redirect(\n    struct mg_connection *c, struct http_message *hm,\n    const struct mg_serve_http_opts *opts) {\n  const char *rewrites = opts->url_rewrites;\n  struct mg_str a, b;\n  char local_port[20] = {'%'};\n\n  mg_conn_addr_to_str(c, local_port + 1, sizeof(local_port) - 1,\n                      MG_SOCK_STRINGIFY_PORT);\n\n  while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {\n    if (mg_vcmp(&a, local_port) == 0) {\n      mg_send_response_line(c, 301, NULL);\n      mg_printf(c, \"Content-Length: 0\\r\\nLocation: %.*s%.*s\\r\\n\\r\\n\",\n                (int) b.len, b.p, (int) (hm->proto.p - hm->uri.p - 1),\n                hm->uri.p);\n      return 1;\n    }\n  }\n\n  return 0;\n}\n\nstatic void mg_reverse_proxy_handler(struct mg_connection *nc, int ev,\n                                     void *ev_data MG_UD_ARG(void *user_data)) {\n  struct http_message *hm = (struct http_message *) ev_data;\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n\n  if (pd == NULL || pd->reverse_proxy_data.linked_conn == NULL) {\n    DBG((\"%p: upstream closed\", nc));\n    return;\n  }\n\n  switch (ev) {\n    case MG_EV_CONNECT:\n      if (*(int *) ev_data != 0) {\n        mg_http_send_error(pd->reverse_proxy_data.linked_conn, 502, NULL);\n      }\n      break;\n    /* TODO(mkm): handle streaming */\n    case MG_EV_HTTP_REPLY:\n      mg_send(pd->reverse_proxy_data.linked_conn, hm->message.p,\n              hm->message.len);\n      pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      break;\n    case MG_EV_CLOSE:\n      pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;\n      break;\n  }\n\n#if MG_ENABLE_CALLBACK_USERDATA\n  (void) user_data;\n#endif\n}\n\nvoid mg_http_reverse_proxy(struct mg_connection *nc,\n                           const struct http_message *hm, struct mg_str mount,\n                           struct mg_str upstream) {\n  struct mg_connection *be;\n  char burl[256], *purl = burl;\n  int i;\n  const char *error;\n  struct mg_connect_opts opts;\n  struct mg_str path = MG_NULL_STR, user_info = MG_NULL_STR, host = MG_NULL_STR;\n  memset(&opts, 0, sizeof(opts));\n  opts.error_string = &error;\n\n  mg_asprintf(&purl, sizeof(burl), \"%.*s%.*s\", (int) upstream.len, upstream.p,\n              (int) (hm->uri.len - mount.len), hm->uri.p + mount.len);\n\n  be = mg_connect_http_base(nc->mgr, MG_CB(mg_reverse_proxy_handler, NULL),\n                            opts, \"http\", NULL, \"https\", NULL, purl, &path,\n                            &user_info, &host);\n  LOG(LL_DEBUG, (\"Proxying %.*s to %s (rule: %.*s)\", (int) hm->uri.len,\n                 hm->uri.p, purl, (int) mount.len, mount.p));\n\n  if (be == NULL) {\n    LOG(LL_ERROR, (\"Error connecting to %s: %s\", purl, error));\n    mg_http_send_error(nc, 502, NULL);\n    goto cleanup;\n  }\n\n  /* link connections to each other, they must live and die together */\n  mg_http_get_proto_data(be)->reverse_proxy_data.linked_conn = nc;\n  mg_http_get_proto_data(nc)->reverse_proxy_data.linked_conn = be;\n\n  /* send request upstream */\n  mg_printf(be, \"%.*s %.*s HTTP/1.1\\r\\n\", (int) hm->method.len, hm->method.p,\n            (int) path.len, path.p);\n\n  mg_printf(be, \"Host: %.*s\\r\\n\", (int) host.len, host.p);\n  for (i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {\n    struct mg_str hn = hm->header_names[i];\n    struct mg_str hv = hm->header_values[i];\n\n    /* we rewrite the host header */\n    if (mg_vcasecmp(&hn, \"Host\") == 0) continue;\n    /*\n     * Don't pass chunked transfer encoding to the client because hm->body is\n     * already dechunked when we arrive here.\n     */\n    if (mg_vcasecmp(&hn, \"Transfer-encoding\") == 0 &&\n        mg_vcasecmp(&hv, \"chunked\") == 0) {\n      mg_printf(be, \"Content-Length: %\" SIZE_T_FMT \"\\r\\n\", hm->body.len);\n      continue;\n    }\n    /* We don't support proxying Expect: 100-continue. */\n    if (mg_vcasecmp(&hn, \"Expect\") == 0 &&\n        mg_vcasecmp(&hv, \"100-continue\") == 0) {\n      continue;\n    }\n\n    mg_printf(be, \"%.*s: %.*s\\r\\n\", (int) hn.len, hn.p, (int) hv.len, hv.p);\n  }\n\n  mg_send(be, \"\\r\\n\", 2);\n  mg_send(be, hm->body.p, hm->body.len);\n\ncleanup:\n  if (purl != burl) MG_FREE(purl);\n}\n\nstatic int mg_http_handle_forwarding(struct mg_connection *nc,\n                                     struct http_message *hm,\n                                     const struct mg_serve_http_opts *opts) {\n  const char *rewrites = opts->url_rewrites;\n  struct mg_str a, b;\n  struct mg_str p1 = MG_MK_STR(\"http://\"), p2 = MG_MK_STR(\"https://\");\n\n  while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {\n    if (mg_strncmp(a, hm->uri, a.len) == 0) {\n      if (mg_strncmp(b, p1, p1.len) == 0 || mg_strncmp(b, p2, p2.len) == 0) {\n        mg_http_reverse_proxy(nc, hm, a, b);\n        return 1;\n      }\n    }\n  }\n\n  return 0;\n}\n#endif /* MG_ENABLE_FILESYSTEM */\n\nMG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,\n                                     const struct mg_serve_http_opts *opts,\n                                     char **local_path,\n                                     struct mg_str *remainder) {\n  int ok = 1;\n  const char *cp = hm->uri.p, *cp_end = hm->uri.p + hm->uri.len;\n  struct mg_str root = {NULL, 0};\n  const char *file_uri_start = cp;\n  *local_path = NULL;\n  remainder->p = NULL;\n  remainder->len = 0;\n\n  { /* 1. Determine which root to use. */\n\n#if MG_ENABLE_HTTP_URL_REWRITES\n    const char *rewrites = opts->url_rewrites;\n#else\n    const char *rewrites = \"\";\n#endif\n    struct mg_str *hh = mg_get_http_header(hm, \"Host\");\n    struct mg_str a, b;\n    /* Check rewrites first. */\n    while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {\n      if (a.len > 1 && a.p[0] == '@') {\n        /* Host rewrite. */\n        if (hh != NULL && hh->len == a.len - 1 &&\n            mg_ncasecmp(a.p + 1, hh->p, a.len - 1) == 0) {\n          root = b;\n          break;\n        }\n      } else {\n        /* Regular rewrite, URI=directory */\n        size_t match_len = mg_match_prefix_n(a, hm->uri);\n        if (match_len > 0) {\n          file_uri_start = hm->uri.p + match_len;\n          if (*file_uri_start == '/' || file_uri_start == cp_end) {\n            /* Match ended at component boundary, ok. */\n          } else if (*(file_uri_start - 1) == '/') {\n            /* Pattern ends with '/', backtrack. */\n            file_uri_start--;\n          } else {\n            /* No match: must fall on the component boundary. */\n            continue;\n          }\n          root = b;\n          break;\n        }\n      }\n    }\n    /* If no rewrite rules matched, use DAV or regular document root. */\n    if (root.p == NULL) {\n#if MG_ENABLE_HTTP_WEBDAV\n      if (opts->dav_document_root != NULL && mg_is_dav_request(&hm->method)) {\n        root.p = opts->dav_document_root;\n        root.len = strlen(opts->dav_document_root);\n      } else\n#endif\n      {\n        root.p = opts->document_root;\n        root.len = strlen(opts->document_root);\n      }\n    }\n    assert(root.p != NULL && root.len > 0);\n  }\n\n  { /* 2. Find where in the canonical URI path the local path ends. */\n    const char *u = file_uri_start + 1;\n    char *lp = (char *) MG_MALLOC(root.len + hm->uri.len + 1);\n    char *lp_end = lp + root.len + hm->uri.len + 1;\n    char *p = lp, *ps;\n    int exists = 1;\n    if (lp == NULL) {\n      ok = 0;\n      goto out;\n    }\n    memcpy(p, root.p, root.len);\n    p += root.len;\n    if (*(p - 1) == DIRSEP) p--;\n    *p = '\\0';\n    ps = p;\n\n    /* Chop off URI path components one by one and build local path. */\n    while (u <= cp_end) {\n      const char *next = u;\n      struct mg_str component;\n      if (exists) {\n        cs_stat_t st;\n        exists = (mg_stat(lp, &st) == 0);\n        if (exists && S_ISREG(st.st_mode)) {\n          /* We found the terminal, the rest of the URI (if any) is path_info.\n           */\n          if (*(u - 1) == '/') u--;\n          break;\n        }\n      }\n      if (u >= cp_end) break;\n      parse_uri_component((const char **) &next, cp_end, \"/\", &component);\n      if (component.len > 0) {\n        int len;\n        memmove(p + 1, component.p, component.len);\n        len = mg_url_decode(p + 1, component.len, p + 1, lp_end - p - 1, 0);\n        if (len <= 0) {\n          ok = 0;\n          break;\n        }\n        component.p = p + 1;\n        component.len = len;\n        if (mg_vcmp(&component, \".\") == 0) {\n          /* Yum. */\n        } else if (mg_vcmp(&component, \"..\") == 0) {\n          while (p > ps && *p != DIRSEP) p--;\n          *p = '\\0';\n        } else {\n          size_t i;\n#ifdef _WIN32\n          /* On Windows, make sure it's valid Unicode (no funny stuff). */\n          wchar_t buf[MG_MAX_PATH * 2];\n          if (to_wchar(component.p, buf, MG_MAX_PATH) == 0) {\n            DBG((\"[%.*s] smells funny\", (int) component.len, component.p));\n            ok = 0;\n            break;\n          }\n#endif\n          *p++ = DIRSEP;\n          /* No NULs and DIRSEPs in the component (percent-encoded). */\n          for (i = 0; i < component.len; i++, p++) {\n            if (*p == '\\0' || *p == DIRSEP\n#ifdef _WIN32\n                /* On Windows, \"/\" is also accepted, so check for that too. */\n                ||\n                *p == '/'\n#endif\n                ) {\n              ok = 0;\n              break;\n            }\n          }\n        }\n      }\n      u = next;\n    }\n    if (ok) {\n      *local_path = lp;\n      if (u > cp_end) u = cp_end;\n      remainder->p = u;\n      remainder->len = cp_end - u;\n    } else {\n      MG_FREE(lp);\n    }\n  }\n\nout:\n  LOG(LL_DEBUG,\n      (\"'%.*s' -> '%s' + '%.*s'\", (int) hm->uri.len, hm->uri.p,\n       *local_path ? *local_path : \"\", (int) remainder->len, remainder->p));\n  return ok;\n}\n\nstatic int mg_get_month_index(const char *s) {\n  static const char *month_names[] = {\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n                                      \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"};\n  size_t i;\n\n  for (i = 0; i < ARRAY_SIZE(month_names); i++)\n    if (!strcmp(s, month_names[i])) return (int) i;\n\n  return -1;\n}\n\nstatic int mg_num_leap_years(int year) {\n  return year / 4 - year / 100 + year / 400;\n}\n\n/* Parse UTC date-time string, and return the corresponding time_t value. */\nMG_INTERNAL time_t mg_parse_date_string(const char *datetime) {\n  static const unsigned short days_before_month[] = {\n      0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};\n  char month_str[32];\n  int second, minute, hour, day, month, year, leap_days, days;\n  time_t result = (time_t) 0;\n\n  if (((sscanf(datetime, \"%d/%3s/%d %d:%d:%d\", &day, month_str, &year, &hour,\n               &minute, &second) == 6) ||\n       (sscanf(datetime, \"%d %3s %d %d:%d:%d\", &day, month_str, &year, &hour,\n               &minute, &second) == 6) ||\n       (sscanf(datetime, \"%*3s, %d %3s %d %d:%d:%d\", &day, month_str, &year,\n               &hour, &minute, &second) == 6) ||\n       (sscanf(datetime, \"%d-%3s-%d %d:%d:%d\", &day, month_str, &year, &hour,\n               &minute, &second) == 6)) &&\n      year > 1970 && (month = mg_get_month_index(month_str)) != -1) {\n    leap_days = mg_num_leap_years(year) - mg_num_leap_years(1970);\n    year -= 1970;\n    days = year * 365 + days_before_month[month] + (day - 1) + leap_days;\n    result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;\n  }\n\n  return result;\n}\n\nMG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st) {\n  struct mg_str *hdr;\n  if ((hdr = mg_get_http_header(hm, \"If-None-Match\")) != NULL) {\n    char etag[64];\n    mg_http_construct_etag(etag, sizeof(etag), st);\n    return mg_vcasecmp(hdr, etag) == 0;\n  } else if ((hdr = mg_get_http_header(hm, \"If-Modified-Since\")) != NULL) {\n    return st->st_mtime <= mg_parse_date_string(hdr->p);\n  } else {\n    return 0;\n  }\n}\n\nvoid mg_http_send_digest_auth_request(struct mg_connection *c,\n                                      const char *domain) {\n  mg_printf(c,\n            \"HTTP/1.1 401 Unauthorized\\r\\n\"\n            \"WWW-Authenticate: Digest qop=\\\"auth\\\", \"\n            \"realm=\\\"%s\\\", nonce=\\\"%lx\\\"\\r\\n\"\n            \"Content-Length: 0\\r\\n\\r\\n\",\n            domain, (unsigned long) mg_time());\n}\n\nstatic void mg_http_send_options(struct mg_connection *nc,\n                                 struct mg_serve_http_opts *opts) {\n  mg_send_response_line(nc, 200, opts->extra_headers);\n  mg_printf(nc, \"%s\",\n            \"Allow: GET, POST, HEAD, CONNECT, OPTIONS\"\n#if MG_ENABLE_HTTP_WEBDAV\n            \", MKCOL, PUT, DELETE, PROPFIND, MOVE\\r\\nDAV: 1,2\"\n#endif\n            \"\\r\\n\\r\\n\");\n  nc->flags |= MG_F_SEND_AND_CLOSE;\n}\n\nstatic int mg_is_creation_request(const struct http_message *hm) {\n  return mg_vcmp(&hm->method, \"MKCOL\") == 0 || mg_vcmp(&hm->method, \"PUT\") == 0;\n}\n\nMG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,\n                                   const struct mg_str *path_info,\n                                   struct http_message *hm,\n                                   struct mg_serve_http_opts *opts) {\n  int exists, is_directory, is_cgi;\n#if MG_ENABLE_HTTP_WEBDAV\n  int is_dav = mg_is_dav_request(&hm->method);\n#else\n  int is_dav = 0;\n#endif\n  char *index_file = NULL;\n  cs_stat_t st;\n\n  exists = (mg_stat(path, &st) == 0);\n  is_directory = exists && S_ISDIR(st.st_mode);\n\n  if (is_directory)\n    mg_find_index_file(path, opts->index_files, &index_file, &st);\n\n  is_cgi =\n      (mg_match_prefix(opts->cgi_file_pattern, strlen(opts->cgi_file_pattern),\n                       index_file ? index_file : path) > 0);\n\n  LOG(LL_DEBUG,\n      (\"%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s\", nc,\n       (int) hm->method.len, hm->method.p, path, exists, is_directory, is_dav,\n       is_cgi, index_file ? index_file : \"\"));\n\n  if (is_directory && hm->uri.p[hm->uri.len - 1] != '/' && !is_dav) {\n    mg_printf(nc,\n              \"HTTP/1.1 301 Moved\\r\\nLocation: %.*s/\\r\\n\"\n              \"Content-Length: 0\\r\\n\\r\\n\",\n              (int) hm->uri.len, hm->uri.p);\n    MG_FREE(index_file);\n    return;\n  }\n\n  /* If we have path_info, the only way to handle it is CGI. */\n  if (path_info->len > 0 && !is_cgi) {\n    mg_http_send_error(nc, 501, NULL);\n    MG_FREE(index_file);\n    return;\n  }\n\n  if (is_dav && opts->dav_document_root == NULL) {\n    mg_http_send_error(nc, 501, NULL);\n  } else if (!mg_http_is_authorized(\n                 hm, mg_mk_str(path), opts->auth_domain, opts->global_auth_file,\n                 ((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |\n                  MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE |\n                  MG_AUTH_FLAG_ALLOW_MISSING_FILE)) ||\n             !mg_http_is_authorized(\n                 hm, mg_mk_str(path), opts->auth_domain,\n                 opts->per_directory_auth_file,\n                 ((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |\n                  MG_AUTH_FLAG_ALLOW_MISSING_FILE))) {\n    mg_http_send_digest_auth_request(nc, opts->auth_domain);\n  } else if (is_cgi) {\n#if MG_ENABLE_HTTP_CGI\n    mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts);\n#else\n    mg_http_send_error(nc, 501, NULL);\n#endif /* MG_ENABLE_HTTP_CGI */\n  } else if ((!exists ||\n              mg_is_file_hidden(path, opts, 0 /* specials are ok */)) &&\n             !mg_is_creation_request(hm)) {\n    mg_http_send_error(nc, 404, NULL);\n#if MG_ENABLE_HTTP_WEBDAV\n  } else if (!mg_vcmp(&hm->method, \"PROPFIND\")) {\n    mg_handle_propfind(nc, path, &st, hm, opts);\n#if !MG_DISABLE_DAV_AUTH\n  } else if (is_dav &&\n             (opts->dav_auth_file == NULL ||\n              (strcmp(opts->dav_auth_file, \"-\") != 0 &&\n               !mg_http_is_authorized(\n                   hm, mg_mk_str(path), opts->auth_domain, opts->dav_auth_file,\n                   ((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |\n                    MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE |\n                    MG_AUTH_FLAG_ALLOW_MISSING_FILE))))) {\n    mg_http_send_digest_auth_request(nc, opts->auth_domain);\n#endif\n  } else if (!mg_vcmp(&hm->method, \"MKCOL\")) {\n    mg_handle_mkcol(nc, path, hm);\n  } else if (!mg_vcmp(&hm->method, \"DELETE\")) {\n    mg_handle_delete(nc, opts, path);\n  } else if (!mg_vcmp(&hm->method, \"PUT\")) {\n    mg_handle_put(nc, path, hm);\n  } else if (!mg_vcmp(&hm->method, \"MOVE\")) {\n    mg_handle_move(nc, opts, path, hm);\n#if MG_ENABLE_FAKE_DAVLOCK\n  } else if (!mg_vcmp(&hm->method, \"LOCK\")) {\n    mg_handle_lock(nc, path);\n#endif\n#endif /* MG_ENABLE_HTTP_WEBDAV */\n  } else if (!mg_vcmp(&hm->method, \"OPTIONS\")) {\n    mg_http_send_options(nc, opts);\n  } else if (is_directory && index_file == NULL) {\n#if MG_ENABLE_DIRECTORY_LISTING\n    if (strcmp(opts->enable_directory_listing, \"yes\") == 0) {\n      mg_send_directory_listing(nc, path, hm, opts);\n    } else {\n      mg_http_send_error(nc, 403, NULL);\n    }\n#else\n    mg_http_send_error(nc, 501, NULL);\n#endif\n  } else if (mg_is_not_modified(hm, &st)) {\n    mg_http_send_error(nc, 304, \"Not Modified\");\n  } else {\n    mg_http_serve_file2(nc, index_file ? index_file : path, hm, opts);\n  }\n  MG_FREE(index_file);\n}\n\nvoid mg_serve_http(struct mg_connection *nc, struct http_message *hm,\n                   struct mg_serve_http_opts opts) {\n  char *path = NULL;\n  struct mg_str *hdr, path_info;\n  uint32_t remote_ip = ntohl(*(uint32_t *) &nc->sa.sin.sin_addr);\n\n  if (mg_check_ip_acl(opts.ip_acl, remote_ip) != 1) {\n    /* Not allowed to connect */\n    mg_http_send_error(nc, 403, NULL);\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n    return;\n  }\n\n#if MG_ENABLE_HTTP_URL_REWRITES\n  if (mg_http_handle_forwarding(nc, hm, &opts)) {\n    return;\n  }\n\n  if (mg_http_send_port_based_redirect(nc, hm, &opts)) {\n    return;\n  }\n#endif\n\n  if (opts.document_root == NULL) {\n    opts.document_root = \".\";\n  }\n  if (opts.per_directory_auth_file == NULL) {\n    opts.per_directory_auth_file = \".htpasswd\";\n  }\n  if (opts.enable_directory_listing == NULL) {\n    opts.enable_directory_listing = \"yes\";\n  }\n  if (opts.cgi_file_pattern == NULL) {\n    opts.cgi_file_pattern = \"**.cgi$|**.php$\";\n  }\n  if (opts.ssi_pattern == NULL) {\n    opts.ssi_pattern = \"**.shtml$|**.shtm$\";\n  }\n  if (opts.index_files == NULL) {\n    opts.index_files = \"index.html,index.htm,index.shtml,index.cgi,index.php\";\n  }\n  /* Normalize path - resolve \".\" and \"..\" (in-place). */\n  if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {\n    mg_http_send_error(nc, 400, NULL);\n    return;\n  }\n  if (mg_uri_to_local_path(hm, &opts, &path, &path_info) == 0) {\n    mg_http_send_error(nc, 404, NULL);\n    return;\n  }\n  mg_send_http_file(nc, path, &path_info, hm, &opts);\n\n  MG_FREE(path);\n  path = NULL;\n\n  /* Close connection for non-keep-alive requests */\n  if (mg_vcmp(&hm->proto, \"HTTP/1.1\") != 0 ||\n      ((hdr = mg_get_http_header(hm, \"Connection\")) != NULL &&\n       mg_vcmp(hdr, \"keep-alive\") != 0)) {\n#if 0\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n#endif\n  }\n}\n\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\nvoid mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,\n                            mg_fu_fname_fn local_name_fn\n                                MG_UD_ARG(void *user_data)) {\n  switch (ev) {\n    case MG_EV_HTTP_PART_BEGIN: {\n      struct mg_http_multipart_part *mp =\n          (struct mg_http_multipart_part *) ev_data;\n      struct file_upload_state *fus;\n      struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name));\n      mp->user_data = NULL;\n      if (lfn.p == NULL || lfn.len == 0) {\n        LOG(LL_ERROR, (\"%p Not allowed to upload %s\", nc, mp->file_name));\n        mg_printf(nc,\n                  \"HTTP/1.1 403 Not Allowed\\r\\n\"\n                  \"Content-Type: text/plain\\r\\n\"\n                  \"Connection: close\\r\\n\\r\\n\"\n                  \"Not allowed to upload %s\\r\\n\",\n                  mp->file_name);\n        nc->flags |= MG_F_SEND_AND_CLOSE;\n        return;\n      }\n      fus = (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus));\n      if (fus == NULL) {\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        return;\n      }\n      fus->lfn = (char *) MG_MALLOC(lfn.len + 1);\n      memcpy(fus->lfn, lfn.p, lfn.len);\n      fus->lfn[lfn.len] = '\\0';\n      if (lfn.p != mp->file_name) MG_FREE((char *) lfn.p);\n      LOG(LL_DEBUG,\n          (\"%p Receiving file %s -> %s\", nc, mp->file_name, fus->lfn));\n      fus->fp = mg_fopen(fus->lfn, \"wb\");\n      if (fus->fp == NULL) {\n        mg_printf(nc,\n                  \"HTTP/1.1 500 Internal Server Error\\r\\n\"\n                  \"Content-Type: text/plain\\r\\n\"\n                  \"Connection: close\\r\\n\\r\\n\");\n        LOG(LL_ERROR, (\"Failed to open %s: %d\\n\", fus->lfn, mg_get_errno()));\n        mg_printf(nc, \"Failed to open %s: %d\\n\", fus->lfn, mg_get_errno());\n        /* Do not close the connection just yet, discard remainder of the data.\n         * This is because at the time of writing some browsers (Chrome) fail to\n         * render response before all the data is sent. */\n      }\n      mp->user_data = (void *) fus;\n      break;\n    }\n    case MG_EV_HTTP_PART_DATA: {\n      struct mg_http_multipart_part *mp =\n          (struct mg_http_multipart_part *) ev_data;\n      struct file_upload_state *fus =\n          (struct file_upload_state *) mp->user_data;\n      if (fus == NULL || fus->fp == NULL) break;\n      if (mg_fwrite(mp->data.p, 1, mp->data.len, fus->fp) != mp->data.len) {\n        LOG(LL_ERROR, (\"Failed to write to %s: %d, wrote %d\", fus->lfn,\n                       mg_get_errno(), (int) fus->num_recd));\n        if (mg_get_errno() == ENOSPC\n#ifdef SPIFFS_ERR_FULL\n            || mg_get_errno() == SPIFFS_ERR_FULL\n#endif\n            ) {\n          mg_printf(nc,\n                    \"HTTP/1.1 413 Payload Too Large\\r\\n\"\n                    \"Content-Type: text/plain\\r\\n\"\n                    \"Connection: close\\r\\n\\r\\n\");\n          mg_printf(nc, \"Failed to write to %s: no space left; wrote %d\\r\\n\",\n                    fus->lfn, (int) fus->num_recd);\n        } else {\n          mg_printf(nc,\n                    \"HTTP/1.1 500 Internal Server Error\\r\\n\"\n                    \"Content-Type: text/plain\\r\\n\"\n                    \"Connection: close\\r\\n\\r\\n\");\n          mg_printf(nc, \"Failed to write to %s: %d, wrote %d\", mp->file_name,\n                    mg_get_errno(), (int) fus->num_recd);\n        }\n        fclose(fus->fp);\n        remove(fus->lfn);\n        fus->fp = NULL;\n        /* Do not close the connection just yet, discard remainder of the data.\n         * This is because at the time of writing some browsers (Chrome) fail to\n         * render response before all the data is sent. */\n        return;\n      }\n      fus->num_recd += mp->data.len;\n      LOG(LL_DEBUG, (\"%p rec'd %d bytes, %d total\", nc, (int) mp->data.len,\n                     (int) fus->num_recd));\n      break;\n    }\n    case MG_EV_HTTP_PART_END: {\n      struct mg_http_multipart_part *mp =\n          (struct mg_http_multipart_part *) ev_data;\n      struct file_upload_state *fus =\n          (struct file_upload_state *) mp->user_data;\n      if (fus == NULL) break;\n      if (mp->status >= 0 && fus->fp != NULL) {\n        LOG(LL_DEBUG, (\"%p Uploaded %s (%s), %d bytes\", nc, mp->file_name,\n                       fus->lfn, (int) fus->num_recd));\n      } else {\n        LOG(LL_ERROR, (\"Failed to store %s (%s)\", mp->file_name, fus->lfn));\n        /*\n         * mp->status < 0 means connection was terminated, so no reason to send\n         * HTTP reply\n         */\n      }\n      if (fus->fp != NULL) fclose(fus->fp);\n      MG_FREE(fus->lfn);\n      MG_FREE(fus);\n      mp->user_data = NULL;\n      /* Don't close the connection yet, there may be more files to come. */\n      break;\n    }\n    case MG_EV_HTTP_MULTIPART_REQUEST_END: {\n      mg_printf(nc,\n                \"HTTP/1.1 200 OK\\r\\n\"\n                \"Content-Type: text/plain\\r\\n\"\n                \"Connection: close\\r\\n\\r\\n\"\n                \"Ok.\\r\\n\");\n      nc->flags |= MG_F_SEND_AND_CLOSE;\n      break;\n    }\n  }\n\n#if MG_ENABLE_CALLBACK_USERDATA\n  (void) user_data;\n#endif\n}\n\n#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */\n#endif /* MG_ENABLE_FILESYSTEM */\n\nstruct mg_connection *mg_connect_http_base(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    struct mg_connect_opts opts, const char *scheme1, const char *scheme2,\n    const char *scheme_ssl1, const char *scheme_ssl2, const char *url,\n    struct mg_str *path, struct mg_str *user_info, struct mg_str *host) {\n  struct mg_connection *nc = NULL;\n  unsigned int port_i = 0;\n  int use_ssl = 0;\n  struct mg_str scheme, query, fragment;\n  char conn_addr_buf[2];\n  char *conn_addr = conn_addr_buf;\n\n  if (mg_parse_uri(mg_mk_str(url), &scheme, user_info, host, &port_i, path,\n                   &query, &fragment) != 0) {\n    MG_SET_PTRPTR(opts.error_string, \"cannot parse url\");\n    goto out;\n  }\n\n  /* If query is present, do not strip it. Pass to the caller. */\n  if (query.len > 0) path->len += query.len + 1;\n\n  if (scheme.len == 0 || mg_vcmp(&scheme, scheme1) == 0 ||\n      (scheme2 != NULL && mg_vcmp(&scheme, scheme2) == 0)) {\n    use_ssl = 0;\n    if (port_i == 0) port_i = 80;\n  } else if (mg_vcmp(&scheme, scheme_ssl1) == 0 ||\n             (scheme2 != NULL && mg_vcmp(&scheme, scheme_ssl2) == 0)) {\n    use_ssl = 1;\n    if (port_i == 0) port_i = 443;\n  } else {\n    goto out;\n  }\n\n  mg_asprintf(&conn_addr, sizeof(conn_addr_buf), \"tcp://%.*s:%u\",\n              (int) host->len, host->p, port_i);\n  if (conn_addr == NULL) goto out;\n\n  LOG(LL_DEBUG, (\"%s use_ssl? %d %s\", url, use_ssl, conn_addr));\n  if (use_ssl) {\n#if MG_ENABLE_SSL\n    /*\n     * Schema requires SSL, but no SSL parameters were provided in opts.\n     * In order to maintain backward compatibility, use a faux-SSL with no\n     * verification.\n     */\n    if (opts.ssl_ca_cert == NULL) {\n      opts.ssl_ca_cert = \"*\";\n    }\n#else\n    MG_SET_PTRPTR(opts.error_string, \"ssl is disabled\");\n    goto out;\n#endif\n  }\n\n  if ((nc = mg_connect_opt(mgr, conn_addr, MG_CB(ev_handler, user_data),\n                           opts)) != NULL) {\n    mg_set_protocol_http_websocket(nc);\n  }\n\nout:\n  if (conn_addr != NULL && conn_addr != conn_addr_buf) MG_FREE(conn_addr);\n  return nc;\n}\n\nstruct mg_connection *mg_connect_http_opt(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    struct mg_connect_opts opts, const char *url, const char *extra_headers,\n    const char *post_data) {\n  struct mg_str user = MG_NULL_STR, null_str = MG_NULL_STR;\n  struct mg_str host = MG_NULL_STR, path = MG_NULL_STR;\n  struct mbuf auth;\n  struct mg_connection *nc =\n      mg_connect_http_base(mgr, MG_CB(ev_handler, user_data), opts, \"http\",\n                           NULL, \"https\", NULL, url, &path, &user, &host);\n\n  if (nc == NULL) {\n    return NULL;\n  }\n\n  mbuf_init(&auth, 0);\n  if (user.len > 0) {\n    mg_basic_auth_header(user, null_str, &auth);\n  }\n\n  if (post_data == NULL) post_data = \"\";\n  if (extra_headers == NULL) extra_headers = \"\";\n  if (path.len == 0) path = mg_mk_str(\"/\");\n  if (host.len == 0) host = mg_mk_str(\"\");\n\n  mg_printf(nc, \"%s %.*s HTTP/1.1\\r\\nHost: %.*s\\r\\nContent-Length: %\" SIZE_T_FMT\n                \"\\r\\n%.*s%s\\r\\n%s\",\n            (post_data[0] == '\\0' ? \"GET\" : \"POST\"), (int) path.len, path.p,\n            (int) (path.p - host.p), host.p, strlen(post_data), (int) auth.len,\n            (auth.buf == NULL ? \"\" : auth.buf), extra_headers, post_data);\n\n  mbuf_free(&auth);\n  return nc;\n}\n\nstruct mg_connection *mg_connect_http(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    const char *url, const char *extra_headers, const char *post_data) {\n  struct mg_connect_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  return mg_connect_http_opt(mgr, MG_CB(ev_handler, user_data), opts, url,\n                             extra_headers, post_data);\n}\n\nsize_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,\n                          size_t var_name_len, char *file_name,\n                          size_t file_name_len, const char **data,\n                          size_t *data_len) {\n  static const char cd[] = \"Content-Disposition: \";\n  size_t hl, bl, n, ll, pos, cdl = sizeof(cd) - 1;\n  int shl;\n\n  if (buf == NULL || buf_len <= 0) return 0;\n  if ((shl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0;\n  hl = shl;\n  if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\\n') return 0;\n\n  /* Get boundary length */\n  bl = mg_get_line_len(buf, buf_len);\n\n  /* Loop through headers, fetch variable name and file name */\n  var_name[0] = file_name[0] = '\\0';\n  for (n = bl; (ll = mg_get_line_len(buf + n, hl - n)) > 0; n += ll) {\n    if (mg_ncasecmp(cd, buf + n, cdl) == 0) {\n      struct mg_str header;\n      header.p = buf + n + cdl;\n      header.len = ll - (cdl + 2);\n      {\n        char *var_name2 = var_name;\n        mg_http_parse_header2(&header, \"name\", &var_name2, var_name_len);\n        /* TODO: handle reallocated buffer correctly */\n        if (var_name2 != var_name) {\n          MG_FREE(var_name2);\n          var_name[0] = '\\0';\n        }\n      }\n      {\n        char *file_name2 = file_name;\n        mg_http_parse_header2(&header, \"filename\", &file_name2, file_name_len);\n        /* TODO: handle reallocated buffer correctly */\n        if (file_name2 != file_name) {\n          MG_FREE(file_name2);\n          file_name[0] = '\\0';\n        }\n      }\n    }\n  }\n\n  /* Scan through the body, search for terminating boundary */\n  for (pos = hl; pos + (bl - 2) < buf_len; pos++) {\n    if (buf[pos] == '-' && !strncmp(buf, &buf[pos], bl - 2)) {\n      if (data_len != NULL) *data_len = (pos - 2) - hl;\n      if (data != NULL) *data = buf + hl;\n      return pos;\n    }\n  }\n\n  return 0;\n}\n\nvoid mg_register_http_endpoint_opt(struct mg_connection *nc,\n                                   const char *uri_path,\n                                   mg_event_handler_t handler,\n                                   struct mg_http_endpoint_opts opts) {\n  struct mg_http_proto_data *pd = NULL;\n  struct mg_http_endpoint *new_ep = NULL;\n\n  if (nc == NULL) return;\n  new_ep = (struct mg_http_endpoint *) MG_CALLOC(1, sizeof(*new_ep));\n  if (new_ep == NULL) return;\n\n  pd = mg_http_get_proto_data(nc);\n  if (pd == NULL) pd = mg_http_create_proto_data(nc);\n  new_ep->uri_pattern = mg_strdup(mg_mk_str(uri_path));\n  if (opts.auth_domain != NULL && opts.auth_file != NULL) {\n    new_ep->auth_domain = strdup(opts.auth_domain);\n    new_ep->auth_file = strdup(opts.auth_file);\n  }\n  new_ep->handler = handler;\n#if MG_ENABLE_CALLBACK_USERDATA\n  new_ep->user_data = opts.user_data;\n#endif\n  new_ep->next = pd->endpoints;\n  pd->endpoints = new_ep;\n}\n\nstatic void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,\n                                          struct http_message *hm) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  void *user_data = nc->user_data;\n\n  if (ev == MG_EV_HTTP_REQUEST\n#if MG_ENABLE_HTTP_STREAMING_MULTIPART\n      || ev == MG_EV_HTTP_MULTIPART_REQUEST\n#endif\n      ) {\n    struct mg_http_endpoint *ep =\n        mg_http_get_endpoint_handler(nc->listener, &hm->uri);\n    if (ep != NULL) {\n#if MG_ENABLE_FILESYSTEM && !MG_DISABLE_HTTP_DIGEST_AUTH\n      if (!mg_http_is_authorized(hm, hm->uri, ep->auth_domain, ep->auth_file,\n                                 MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE)) {\n        mg_http_send_digest_auth_request(nc, ep->auth_domain);\n        return;\n      }\n#endif\n      pd->endpoint_handler = ep->handler;\n#if MG_ENABLE_CALLBACK_USERDATA\n      user_data = ep->user_data;\n#endif\n    }\n  }\n  mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler,\n          user_data, ev, hm);\n}\n\nvoid mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,\n                               MG_CB(mg_event_handler_t handler,\n                                     void *user_data)) {\n  struct mg_http_endpoint_opts opts;\n  memset(&opts, 0, sizeof(opts));\n#if MG_ENABLE_CALLBACK_USERDATA\n  opts.user_data = user_data;\n#endif\n  mg_register_http_endpoint_opt(nc, uri_path, handler, opts);\n}\n\n#endif /* MG_ENABLE_HTTP */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http_cgi.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#ifndef _WIN32\n#include <signal.h>\n#endif\n\n#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI\n\n#ifndef MG_MAX_CGI_ENVIR_VARS\n#define MG_MAX_CGI_ENVIR_VARS 64\n#endif\n\n#ifndef MG_ENV_EXPORT_TO_CGI\n#define MG_ENV_EXPORT_TO_CGI \"MONGOOSE_CGI\"\n#endif\n\n#define MG_F_HTTP_CGI_PARSE_HEADERS MG_F_USER_1\n\n/*\n * This structure helps to create an environment for the spawned CGI program.\n * Environment is an array of \"VARIABLE=VALUE\\0\" ASCIIZ strings,\n * last element must be NULL.\n * However, on Windows there is a requirement that all these VARIABLE=VALUE\\0\n * strings must reside in a contiguous buffer. The end of the buffer is\n * marked by two '\\0' characters.\n * We satisfy both worlds: we create an envp array (which is vars), all\n * entries are actually pointers inside buf.\n */\nstruct mg_cgi_env_block {\n  struct mg_connection *nc;\n  char buf[MG_CGI_ENVIRONMENT_SIZE];       /* Environment buffer */\n  const char *vars[MG_MAX_CGI_ENVIR_VARS]; /* char *envp[] */\n  int len;                                 /* Space taken */\n  int nvars;                               /* Number of variables in envp[] */\n};\n\n#ifdef _WIN32\nstruct mg_threadparam {\n  sock_t s;\n  HANDLE hPipe;\n};\n\nstatic int mg_wait_until_ready(sock_t sock, int for_read) {\n  fd_set set;\n  FD_ZERO(&set);\n  FD_SET(sock, &set);\n  return select(sock + 1, for_read ? &set : 0, for_read ? 0 : &set, 0, 0) == 1;\n}\n\nstatic void *mg_push_to_stdin(void *arg) {\n  struct mg_threadparam *tp = (struct mg_threadparam *) arg;\n  int n, sent, stop = 0;\n  DWORD k;\n  char buf[BUFSIZ];\n\n  while (!stop && mg_wait_until_ready(tp->s, 1) &&\n         (n = recv(tp->s, buf, sizeof(buf), 0)) > 0) {\n    if (n == -1 && GetLastError() == WSAEWOULDBLOCK) continue;\n    for (sent = 0; !stop && sent < n; sent += k) {\n      if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop = 1;\n    }\n  }\n  DBG((\"%s\", \"FORWARED EVERYTHING TO CGI\"));\n  CloseHandle(tp->hPipe);\n  MG_FREE(tp);\n  return NULL;\n}\n\nstatic void *mg_pull_from_stdout(void *arg) {\n  struct mg_threadparam *tp = (struct mg_threadparam *) arg;\n  int k = 0, stop = 0;\n  DWORD n, sent;\n  char buf[BUFSIZ];\n\n  while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {\n    for (sent = 0; !stop && sent < n; sent += k) {\n      if (mg_wait_until_ready(tp->s, 0) &&\n          (k = send(tp->s, buf + sent, n - sent, 0)) <= 0)\n        stop = 1;\n    }\n  }\n  DBG((\"%s\", \"EOF FROM CGI\"));\n  CloseHandle(tp->hPipe);\n  shutdown(tp->s, 2);  // Without this, IO thread may get truncated data\n  closesocket(tp->s);\n  MG_FREE(tp);\n  return NULL;\n}\n\nstatic void mg_spawn_stdio_thread(sock_t sock, HANDLE hPipe,\n                                  void *(*func)(void *)) {\n  struct mg_threadparam *tp = (struct mg_threadparam *) MG_MALLOC(sizeof(*tp));\n  if (tp != NULL) {\n    tp->s = sock;\n    tp->hPipe = hPipe;\n    mg_start_thread(func, tp);\n  }\n}\n\nstatic void mg_abs_path(const char *utf8_path, char *abs_path, size_t len) {\n  wchar_t buf[MG_MAX_PATH], buf2[MG_MAX_PATH];\n  to_wchar(utf8_path, buf, ARRAY_SIZE(buf));\n  GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL);\n  WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0);\n}\n\nstatic int mg_start_process(const char *interp, const char *cmd,\n                            const char *env, const char *envp[],\n                            const char *dir, sock_t sock) {\n  STARTUPINFOW si;\n  PROCESS_INFORMATION pi;\n  HANDLE a[2], b[2], me = GetCurrentProcess();\n  wchar_t wcmd[MG_MAX_PATH], full_dir[MG_MAX_PATH];\n  char buf[MG_MAX_PATH], buf2[MG_MAX_PATH], buf5[MG_MAX_PATH],\n      buf4[MG_MAX_PATH], cmdline[MG_MAX_PATH];\n  DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;\n  FILE *fp;\n\n  memset(&si, 0, sizeof(si));\n  memset(&pi, 0, sizeof(pi));\n\n  si.cb = sizeof(si);\n  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;\n  si.wShowWindow = SW_HIDE;\n  si.hStdError = GetStdHandle(STD_ERROR_HANDLE);\n\n  CreatePipe(&a[0], &a[1], NULL, 0);\n  CreatePipe(&b[0], &b[1], NULL, 0);\n  DuplicateHandle(me, a[0], me, &si.hStdInput, 0, TRUE, flags);\n  DuplicateHandle(me, b[1], me, &si.hStdOutput, 0, TRUE, flags);\n\n  if (interp == NULL && (fp = mg_fopen(cmd, \"r\")) != NULL) {\n    buf[0] = buf[1] = '\\0';\n    fgets(buf, sizeof(buf), fp);\n    buf[sizeof(buf) - 1] = '\\0';\n    if (buf[0] == '#' && buf[1] == '!') {\n      interp = buf + 2;\n      /* Trim leading spaces: https://github.com/cesanta/mongoose/issues/489 */\n      while (*interp != '\\0' && isspace(*(unsigned char *) interp)) {\n        interp++;\n      }\n    }\n    fclose(fp);\n  }\n\n  snprintf(buf, sizeof(buf), \"%s/%s\", dir, cmd);\n  mg_abs_path(buf, buf2, ARRAY_SIZE(buf2));\n\n  mg_abs_path(dir, buf5, ARRAY_SIZE(buf5));\n  to_wchar(dir, full_dir, ARRAY_SIZE(full_dir));\n\n  if (interp != NULL) {\n    mg_abs_path(interp, buf4, ARRAY_SIZE(buf4));\n    snprintf(cmdline, sizeof(cmdline), \"%s \\\"%s\\\"\", buf4, buf2);\n  } else {\n    snprintf(cmdline, sizeof(cmdline), \"\\\"%s\\\"\", buf2);\n  }\n  to_wchar(cmdline, wcmd, ARRAY_SIZE(wcmd));\n\n  if (CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP,\n                     (void *) env, full_dir, &si, &pi) != 0) {\n    mg_spawn_stdio_thread(sock, a[1], mg_push_to_stdin);\n    mg_spawn_stdio_thread(sock, b[0], mg_pull_from_stdout);\n\n    CloseHandle(si.hStdOutput);\n    CloseHandle(si.hStdInput);\n\n    CloseHandle(pi.hThread);\n    CloseHandle(pi.hProcess);\n  } else {\n    CloseHandle(a[1]);\n    CloseHandle(b[0]);\n    closesocket(sock);\n  }\n  DBG((\"CGI command: [%ls] -> %p\", wcmd, pi.hProcess));\n\n  /* Not closing a[0] and b[1] because we've used DUPLICATE_CLOSE_SOURCE */\n  (void) envp;\n  return (pi.hProcess != NULL);\n}\n#else\nstatic int mg_start_process(const char *interp, const char *cmd,\n                            const char *env, const char *envp[],\n                            const char *dir, sock_t sock) {\n  char buf[500];\n  pid_t pid = fork();\n  (void) env;\n\n  if (pid == 0) {\n    /*\n     * In Linux `chdir` declared with `warn_unused_result` attribute\n     * To shutup compiler we have yo use result in some way\n     */\n    int tmp = chdir(dir);\n    (void) tmp;\n    (void) dup2(sock, 0);\n    (void) dup2(sock, 1);\n    closesocket(sock);\n\n    /*\n     * After exec, all signal handlers are restored to their default values,\n     * with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's\n     * implementation, SIGCHLD's handler will leave unchanged after exec\n     * if it was set to be ignored. Restore it to default action.\n     */\n    signal(SIGCHLD, SIG_DFL);\n\n    if (interp == NULL) {\n      execle(cmd, cmd, (char *) 0, envp); /* (char *) 0 to squash warning */\n    } else {\n      execle(interp, interp, cmd, (char *) 0, envp);\n    }\n    snprintf(buf, sizeof(buf),\n             \"Status: 500\\r\\n\\r\\n\"\n             \"500 Server Error: %s%s%s: %s\",\n             interp == NULL ? \"\" : interp, interp == NULL ? \"\" : \" \", cmd,\n             strerror(errno));\n    send(1, buf, strlen(buf), 0);\n    _exit(EXIT_FAILURE); /* exec call failed */\n  }\n\n  return (pid != 0);\n}\n#endif /* _WIN32 */\n\n/*\n * Append VARIABLE=VALUE\\0 string to the buffer, and add a respective\n * pointer into the vars array.\n */\nstatic char *mg_addenv(struct mg_cgi_env_block *block, const char *fmt, ...) {\n  int n, space;\n  char *added = block->buf + block->len;\n  va_list ap;\n\n  /* Calculate how much space is left in the buffer */\n  space = sizeof(block->buf) - (block->len + 2);\n  if (space > 0) {\n    /* Copy VARIABLE=VALUE\\0 string into the free space */\n    va_start(ap, fmt);\n    n = vsnprintf(added, (size_t) space, fmt, ap);\n    va_end(ap);\n\n    /* Make sure we do not overflow buffer and the envp array */\n    if (n > 0 && n + 1 < space &&\n        block->nvars < (int) ARRAY_SIZE(block->vars) - 2) {\n      /* Append a pointer to the added string into the envp array */\n      block->vars[block->nvars++] = added;\n      /* Bump up used length counter. Include \\0 terminator */\n      block->len += n + 1;\n    }\n  }\n\n  return added;\n}\n\nstatic void mg_addenv2(struct mg_cgi_env_block *blk, const char *name) {\n  const char *s;\n  if ((s = getenv(name)) != NULL) mg_addenv(blk, \"%s=%s\", name, s);\n}\n\nstatic void mg_prepare_cgi_environment(struct mg_connection *nc,\n                                       const char *prog,\n                                       const struct mg_str *path_info,\n                                       const struct http_message *hm,\n                                       const struct mg_serve_http_opts *opts,\n                                       struct mg_cgi_env_block *blk) {\n  const char *s;\n  struct mg_str *h;\n  char *p;\n  size_t i;\n  char buf[100];\n  size_t path_info_len = path_info != NULL ? path_info->len : 0;\n\n  blk->len = blk->nvars = 0;\n  blk->nc = nc;\n\n  if ((s = getenv(\"SERVER_NAME\")) != NULL) {\n    mg_addenv(blk, \"SERVER_NAME=%s\", s);\n  } else {\n    mg_sock_to_str(nc->sock, buf, sizeof(buf), 3);\n    mg_addenv(blk, \"SERVER_NAME=%s\", buf);\n  }\n  mg_addenv(blk, \"SERVER_ROOT=%s\", opts->document_root);\n  mg_addenv(blk, \"DOCUMENT_ROOT=%s\", opts->document_root);\n  mg_addenv(blk, \"SERVER_SOFTWARE=%s/%s\", \"Mongoose\", MG_VERSION);\n\n  /* Prepare the environment block */\n  mg_addenv(blk, \"%s\", \"GATEWAY_INTERFACE=CGI/1.1\");\n  mg_addenv(blk, \"%s\", \"SERVER_PROTOCOL=HTTP/1.1\");\n  mg_addenv(blk, \"%s\", \"REDIRECT_STATUS=200\"); /* For PHP */\n\n  mg_addenv(blk, \"REQUEST_METHOD=%.*s\", (int) hm->method.len, hm->method.p);\n\n  mg_addenv(blk, \"REQUEST_URI=%.*s%s%.*s\", (int) hm->uri.len, hm->uri.p,\n            hm->query_string.len == 0 ? \"\" : \"?\", (int) hm->query_string.len,\n            hm->query_string.p);\n\n  mg_conn_addr_to_str(nc, buf, sizeof(buf),\n                      MG_SOCK_STRINGIFY_REMOTE | MG_SOCK_STRINGIFY_IP);\n  mg_addenv(blk, \"REMOTE_ADDR=%s\", buf);\n  mg_conn_addr_to_str(nc, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT);\n  mg_addenv(blk, \"SERVER_PORT=%s\", buf);\n\n  s = hm->uri.p + hm->uri.len - path_info_len - 1;\n  if (*s == '/') {\n    const char *base_name = strrchr(prog, DIRSEP);\n    mg_addenv(blk, \"SCRIPT_NAME=%.*s/%s\", (int) (s - hm->uri.p), hm->uri.p,\n              (base_name != NULL ? base_name + 1 : prog));\n  } else {\n    mg_addenv(blk, \"SCRIPT_NAME=%.*s\", (int) (s - hm->uri.p + 1), hm->uri.p);\n  }\n  mg_addenv(blk, \"SCRIPT_FILENAME=%s\", prog);\n\n  if (path_info != NULL && path_info->len > 0) {\n    mg_addenv(blk, \"PATH_INFO=%.*s\", (int) path_info->len, path_info->p);\n    /* Not really translated... */\n    mg_addenv(blk, \"PATH_TRANSLATED=%.*s\", (int) path_info->len, path_info->p);\n  }\n\n#if MG_ENABLE_SSL\n  mg_addenv(blk, \"HTTPS=%s\", (nc->flags & MG_F_SSL ? \"on\" : \"off\"));\n#else\n  mg_addenv(blk, \"HTTPS=off\");\n#endif\n\n  if ((h = mg_get_http_header((struct http_message *) hm, \"Content-Type\")) !=\n      NULL) {\n    mg_addenv(blk, \"CONTENT_TYPE=%.*s\", (int) h->len, h->p);\n  }\n\n  if (hm->query_string.len > 0) {\n    mg_addenv(blk, \"QUERY_STRING=%.*s\", (int) hm->query_string.len,\n              hm->query_string.p);\n  }\n\n  if ((h = mg_get_http_header((struct http_message *) hm, \"Content-Length\")) !=\n      NULL) {\n    mg_addenv(blk, \"CONTENT_LENGTH=%.*s\", (int) h->len, h->p);\n  }\n\n  mg_addenv2(blk, \"PATH\");\n  mg_addenv2(blk, \"TMP\");\n  mg_addenv2(blk, \"TEMP\");\n  mg_addenv2(blk, \"TMPDIR\");\n  mg_addenv2(blk, \"PERLLIB\");\n  mg_addenv2(blk, MG_ENV_EXPORT_TO_CGI);\n\n#ifdef _WIN32\n  mg_addenv2(blk, \"COMSPEC\");\n  mg_addenv2(blk, \"SYSTEMROOT\");\n  mg_addenv2(blk, \"SystemDrive\");\n  mg_addenv2(blk, \"ProgramFiles\");\n  mg_addenv2(blk, \"ProgramFiles(x86)\");\n  mg_addenv2(blk, \"CommonProgramFiles(x86)\");\n#else\n  mg_addenv2(blk, \"LD_LIBRARY_PATH\");\n#endif /* _WIN32 */\n\n  /* Add all headers as HTTP_* variables */\n  for (i = 0; hm->header_names[i].len > 0; i++) {\n    p = mg_addenv(blk, \"HTTP_%.*s=%.*s\", (int) hm->header_names[i].len,\n                  hm->header_names[i].p, (int) hm->header_values[i].len,\n                  hm->header_values[i].p);\n\n    /* Convert variable name into uppercase, and change - to _ */\n    for (; *p != '=' && *p != '\\0'; p++) {\n      if (*p == '-') *p = '_';\n      *p = (char) toupper(*(unsigned char *) p);\n    }\n  }\n\n  blk->vars[blk->nvars++] = NULL;\n  blk->buf[blk->len++] = '\\0';\n}\n\nstatic void mg_cgi_ev_handler(struct mg_connection *cgi_nc, int ev,\n                              void *ev_data MG_UD_ARG(void *user_data)) {\n#if !MG_ENABLE_CALLBACK_USERDATA\n  void *user_data = cgi_nc->user_data;\n#endif\n  struct mg_connection *nc = (struct mg_connection *) user_data;\n  (void) ev_data;\n\n  if (nc == NULL) {\n    /* The corresponding network connection was closed. */\n    cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    return;\n  }\n\n  switch (ev) {\n    case MG_EV_RECV:\n      /*\n       * CGI script does not output reply line, like \"HTTP/1.1 CODE XXXXX\\n\"\n       * It outputs headers, then body. Headers might include \"Status\"\n       * header, which changes CODE, and it might include \"Location\" header\n       * which changes CODE to 302.\n       *\n       * Therefore we do not send the output from the CGI script to the user\n       * until all CGI headers are received.\n       *\n       * Here we parse the output from the CGI script, and if all headers has\n       * been received, send appropriate reply line, and forward all\n       * received headers to the client.\n       */\n      if (nc->flags & MG_F_HTTP_CGI_PARSE_HEADERS) {\n        struct mbuf *io = &cgi_nc->recv_mbuf;\n        int len = mg_http_get_request_len(io->buf, io->len);\n\n        if (len == 0) break;\n        if (len < 0 || io->len > MG_MAX_HTTP_REQUEST_SIZE) {\n          cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n          mg_http_send_error(nc, 500, \"Bad headers\");\n        } else {\n          struct http_message hm;\n          struct mg_str *h;\n          mg_http_parse_headers(io->buf, io->buf + io->len, io->len, &hm);\n          if (mg_get_http_header(&hm, \"Location\") != NULL) {\n            mg_printf(nc, \"%s\", \"HTTP/1.1 302 Moved\\r\\n\");\n          } else if ((h = mg_get_http_header(&hm, \"Status\")) != NULL) {\n            mg_printf(nc, \"HTTP/1.1 %.*s\\r\\n\", (int) h->len, h->p);\n          } else {\n            mg_printf(nc, \"%s\", \"HTTP/1.1 200 OK\\r\\n\");\n          }\n        }\n        nc->flags &= ~MG_F_HTTP_CGI_PARSE_HEADERS;\n      }\n      if (!(nc->flags & MG_F_HTTP_CGI_PARSE_HEADERS)) {\n        mg_forward(cgi_nc, nc);\n      }\n      break;\n    case MG_EV_CLOSE:\n      DBG((\"%p CLOSE\", cgi_nc));\n      mg_http_free_proto_data_cgi(&mg_http_get_proto_data(nc)->cgi);\n      nc->flags |= MG_F_SEND_AND_CLOSE;\n      break;\n  }\n}\n\nMG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,\n                               const struct mg_str *path_info,\n                               const struct http_message *hm,\n                               const struct mg_serve_http_opts *opts) {\n  struct mg_cgi_env_block blk;\n  char dir[MG_MAX_PATH];\n  const char *p;\n  sock_t fds[2];\n\n  DBG((\"%p [%s]\", nc, prog));\n  mg_prepare_cgi_environment(nc, prog, path_info, hm, opts, &blk);\n  /*\n   * CGI must be executed in its own directory. 'dir' must point to the\n   * directory containing executable program, 'p' must point to the\n   * executable program name relative to 'dir'.\n   */\n  if ((p = strrchr(prog, DIRSEP)) == NULL) {\n    snprintf(dir, sizeof(dir), \"%s\", \".\");\n  } else {\n    snprintf(dir, sizeof(dir), \"%.*s\", (int) (p - prog), prog);\n    prog = p + 1;\n  }\n\n  if (!mg_socketpair(fds, SOCK_STREAM)) {\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    return;\n  }\n\n#ifndef _WIN32\n  struct sigaction sa;\n\n  sigemptyset(&sa.sa_mask);\n  sa.sa_handler = SIG_IGN;\n  sa.sa_flags = 0;\n  sigaction(SIGCHLD, &sa, NULL);\n#endif\n\n  if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir,\n                       fds[1]) != 0) {\n    struct mg_connection *cgi_nc =\n        mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler MG_UD_ARG(nc));\n    struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(nc);\n    cgi_pd->cgi.cgi_nc = cgi_nc;\n#if !MG_ENABLE_CALLBACK_USERDATA\n    cgi_pd->cgi.cgi_nc->user_data = nc;\n#endif\n    nc->flags |= MG_F_HTTP_CGI_PARSE_HEADERS;\n    /* Push POST data to the CGI */\n    if (hm->body.len > 0) {\n      mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, hm->body.len);\n    }\n    mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len);\n  } else {\n    closesocket(fds[0]);\n    mg_http_send_error(nc, 500, \"CGI failure\");\n  }\n\n#ifndef _WIN32\n  closesocket(fds[1]); /* On Windows, CGI stdio thread closes that socket */\n#endif\n}\n\nMG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) {\n  if (d == NULL) return;\n  if (d->cgi_nc != NULL) {\n    d->cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n    d->cgi_nc->user_data = NULL;\n  }\n  memset(d, 0, sizeof(*d));\n}\n\n#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http_ssi.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_SSI && MG_ENABLE_FILESYSTEM\n\nstatic void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,\n                             const char *path, FILE *fp, int include_level,\n                             const struct mg_serve_http_opts *opts);\n\nstatic void mg_send_file_data(struct mg_connection *nc, FILE *fp) {\n  char buf[BUFSIZ];\n  size_t n;\n  while ((n = mg_fread(buf, 1, sizeof(buf), fp)) > 0) {\n    mg_send(nc, buf, n);\n  }\n}\n\nstatic void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm,\n                              const char *ssi, char *tag, int include_level,\n                              const struct mg_serve_http_opts *opts) {\n  char file_name[MG_MAX_PATH], path[MG_MAX_PATH], *p;\n  FILE *fp;\n\n  /*\n   * sscanf() is safe here, since send_ssi_file() also uses buffer\n   * of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.\n   */\n  if (sscanf(tag, \" virtual=\\\"%[^\\\"]\\\"\", file_name) == 1) {\n    /* File name is relative to the webserver root */\n    snprintf(path, sizeof(path), \"%s/%s\", opts->document_root, file_name);\n  } else if (sscanf(tag, \" abspath=\\\"%[^\\\"]\\\"\", file_name) == 1) {\n    /*\n     * File name is relative to the webserver working directory\n     * or it is absolute system path\n     */\n    snprintf(path, sizeof(path), \"%s\", file_name);\n  } else if (sscanf(tag, \" file=\\\"%[^\\\"]\\\"\", file_name) == 1 ||\n             sscanf(tag, \" \\\"%[^\\\"]\\\"\", file_name) == 1) {\n    /* File name is relative to the currect document */\n    snprintf(path, sizeof(path), \"%s\", ssi);\n    if ((p = strrchr(path, DIRSEP)) != NULL) {\n      p[1] = '\\0';\n    }\n    snprintf(path + strlen(path), sizeof(path) - strlen(path), \"%s\", file_name);\n  } else {\n    mg_printf(nc, \"Bad SSI #include: [%s]\", tag);\n    return;\n  }\n\n  if ((fp = mg_fopen(path, \"rb\")) == NULL) {\n    mg_printf(nc, \"SSI include error: mg_fopen(%s): %s\", path,\n              strerror(mg_get_errno()));\n  } else {\n    mg_set_close_on_exec((sock_t) fileno(fp));\n    if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) >\n        0) {\n      mg_send_ssi_file(nc, hm, path, fp, include_level + 1, opts);\n    } else {\n      mg_send_file_data(nc, fp);\n    }\n    fclose(fp);\n  }\n}\n\n#if MG_ENABLE_HTTP_SSI_EXEC\nstatic void do_ssi_exec(struct mg_connection *nc, char *tag) {\n  char cmd[BUFSIZ];\n  FILE *fp;\n\n  if (sscanf(tag, \" \\\"%[^\\\"]\\\"\", cmd) != 1) {\n    mg_printf(nc, \"Bad SSI #exec: [%s]\", tag);\n  } else if ((fp = popen(cmd, \"r\")) == NULL) {\n    mg_printf(nc, \"Cannot SSI #exec: [%s]: %s\", cmd, strerror(mg_get_errno()));\n  } else {\n    mg_send_file_data(nc, fp);\n    pclose(fp);\n  }\n}\n#endif /* MG_ENABLE_HTTP_SSI_EXEC */\n\n/*\n * SSI directive has the following format:\n * <!--#directive parameter=value parameter=value -->\n */\nstatic void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,\n                             const char *path, FILE *fp, int include_level,\n                             const struct mg_serve_http_opts *opts) {\n  static const struct mg_str btag = MG_MK_STR(\"<!--#\");\n  static const struct mg_str d_include = MG_MK_STR(\"include\");\n  static const struct mg_str d_call = MG_MK_STR(\"call\");\n#if MG_ENABLE_HTTP_SSI_EXEC\n  static const struct mg_str d_exec = MG_MK_STR(\"exec\");\n#endif\n  char buf[BUFSIZ], *p = buf + btag.len; /* p points to SSI directive */\n  int ch, len, in_ssi_tag;\n\n  if (include_level > 10) {\n    mg_printf(nc, \"SSI #include level is too deep (%s)\", path);\n    return;\n  }\n\n  in_ssi_tag = len = 0;\n  while ((ch = fgetc(fp)) != EOF) {\n    if (in_ssi_tag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {\n      size_t i = len - 2;\n      in_ssi_tag = 0;\n\n      /* Trim closing --> */\n      buf[i--] = '\\0';\n      while (i > 0 && buf[i] == ' ') {\n        buf[i--] = '\\0';\n      }\n\n      /* Handle known SSI directives */\n      if (strncmp(p, d_include.p, d_include.len) == 0) {\n        mg_do_ssi_include(nc, hm, path, p + d_include.len + 1, include_level,\n                          opts);\n      } else if (strncmp(p, d_call.p, d_call.len) == 0) {\n        struct mg_ssi_call_ctx cctx;\n        memset(&cctx, 0, sizeof(cctx));\n        cctx.req = hm;\n        cctx.file = mg_mk_str(path);\n        cctx.arg = mg_mk_str(p + d_call.len + 1);\n        mg_call(nc, NULL, nc->user_data, MG_EV_SSI_CALL,\n                (void *) cctx.arg.p); /* NUL added above */\n        mg_call(nc, NULL, nc->user_data, MG_EV_SSI_CALL_CTX, &cctx);\n#if MG_ENABLE_HTTP_SSI_EXEC\n      } else if (strncmp(p, d_exec.p, d_exec.len) == 0) {\n        do_ssi_exec(nc, p + d_exec.len + 1);\n#endif\n      } else {\n        /* Silently ignore unknown SSI directive. */\n      }\n      len = 0;\n    } else if (ch == '<') {\n      in_ssi_tag = 1;\n      if (len > 0) {\n        mg_send(nc, buf, (size_t) len);\n      }\n      len = 0;\n      buf[len++] = ch & 0xff;\n    } else if (in_ssi_tag) {\n      if (len == (int) btag.len && strncmp(buf, btag.p, btag.len) != 0) {\n        /* Not an SSI tag */\n        in_ssi_tag = 0;\n      } else if (len == (int) sizeof(buf) - 2) {\n        mg_printf(nc, \"%s: SSI tag is too large\", path);\n        len = 0;\n      }\n      buf[len++] = ch & 0xff;\n    } else {\n      buf[len++] = ch & 0xff;\n      if (len == (int) sizeof(buf)) {\n        mg_send(nc, buf, (size_t) len);\n        len = 0;\n      }\n    }\n  }\n\n  /* Send the rest of buffered data */\n  if (len > 0) {\n    mg_send(nc, buf, (size_t) len);\n  }\n}\n\nMG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc,\n                                       struct http_message *hm,\n                                       const char *path,\n                                       const struct mg_serve_http_opts *opts) {\n  FILE *fp;\n  struct mg_str mime_type;\n  DBG((\"%p %s\", nc, path));\n\n  if ((fp = mg_fopen(path, \"rb\")) == NULL) {\n    mg_http_send_error(nc, 404, NULL);\n  } else {\n    mg_set_close_on_exec((sock_t) fileno(fp));\n\n    mime_type = mg_get_mime_type(path, \"text/plain\", opts);\n    mg_send_response_line(nc, 200, opts->extra_headers);\n    mg_printf(nc,\n              \"Content-Type: %.*s\\r\\n\"\n              \"Connection: close\\r\\n\\r\\n\",\n              (int) mime_type.len, mime_type.p);\n    mg_send_ssi_file(nc, hm, path, fp, 0, opts);\n    fclose(fp);\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n  }\n}\n\n#endif /* MG_ENABLE_HTTP_SSI && MG_ENABLE_HTTP && MG_ENABLE_FILESYSTEM */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http_webdav.c\"\n#endif\n/*\n * Copyright (c) 2014-2016 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV\n\nMG_INTERNAL int mg_is_dav_request(const struct mg_str *s) {\n  static const char *methods[] = {\n    \"PUT\",\n    \"DELETE\",\n    \"MKCOL\",\n    \"PROPFIND\",\n    \"MOVE\"\n#if MG_ENABLE_FAKE_DAVLOCK\n    ,\n    \"LOCK\",\n    \"UNLOCK\"\n#endif\n  };\n  size_t i;\n\n  for (i = 0; i < ARRAY_SIZE(methods); i++) {\n    if (mg_vcmp(s, methods[i]) == 0) {\n      return 1;\n    }\n  }\n\n  return 0;\n}\n\nstatic int mg_mkdir(const char *path, uint32_t mode) {\n#ifndef _WIN32\n  return mkdir(path, mode);\n#else\n  (void) mode;\n  return _mkdir(path);\n#endif\n}\n\nstatic void mg_print_props(struct mg_connection *nc, const char *name,\n                           cs_stat_t *stp) {\n  char mtime[64];\n  time_t t = stp->st_mtime; /* store in local variable for NDK compile */\n  struct mg_str name_esc = mg_url_encode(mg_mk_str(name));\n  mg_gmt_time_string(mtime, sizeof(mtime), &t);\n  mg_printf(nc,\n            \"<d:response>\"\n            \"<d:href>%s</d:href>\"\n            \"<d:propstat>\"\n            \"<d:prop>\"\n            \"<d:resourcetype>%s</d:resourcetype>\"\n            \"<d:getcontentlength>%\" INT64_FMT\n            \"</d:getcontentlength>\"\n            \"<d:getlastmodified>%s</d:getlastmodified>\"\n            \"</d:prop>\"\n            \"<d:status>HTTP/1.1 200 OK</d:status>\"\n            \"</d:propstat>\"\n            \"</d:response>\\n\",\n            name_esc.p, S_ISDIR(stp->st_mode) ? \"<d:collection/>\" : \"\",\n            (int64_t) stp->st_size, mtime);\n  free((void *) name_esc.p);\n}\n\nMG_INTERNAL void mg_handle_propfind(struct mg_connection *nc, const char *path,\n                                    cs_stat_t *stp, struct http_message *hm,\n                                    struct mg_serve_http_opts *opts) {\n  static const char header[] =\n      \"HTTP/1.1 207 Multi-Status\\r\\n\"\n      \"Connection: close\\r\\n\"\n      \"Content-Type: text/xml; charset=utf-8\\r\\n\\r\\n\"\n      \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\"\n      \"<d:multistatus xmlns:d='DAV:'>\\n\";\n  static const char footer[] = \"</d:multistatus>\\n\";\n  const struct mg_str *depth = mg_get_http_header(hm, \"Depth\");\n\n  /* Print properties for the requested resource itself */\n  if (S_ISDIR(stp->st_mode) &&\n      strcmp(opts->enable_directory_listing, \"yes\") != 0) {\n    mg_printf(nc, \"%s\", \"HTTP/1.1 403 Directory Listing Denied\\r\\n\\r\\n\");\n  } else {\n    char uri[MG_MAX_PATH];\n    mg_send(nc, header, sizeof(header) - 1);\n    snprintf(uri, sizeof(uri), \"%.*s\", (int) hm->uri.len, hm->uri.p);\n    mg_print_props(nc, uri, stp);\n    if (S_ISDIR(stp->st_mode) && (depth == NULL || mg_vcmp(depth, \"0\") != 0)) {\n      mg_scan_directory(nc, path, opts, mg_print_props);\n    }\n    mg_send(nc, footer, sizeof(footer) - 1);\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n  }\n}\n\n#if MG_ENABLE_FAKE_DAVLOCK\n/*\n * Windows explorer (probably there are another WebDav clients like it)\n * requires LOCK support in webdav. W/out this, it still works, but fails\n * to save file: shows error message and offers \"Save As\".\n * \"Save as\" works, but this message is very annoying.\n * This is fake lock, which doesn't lock something, just returns LOCK token,\n * UNLOCK always answers \"OK\".\n * With this fake LOCK Windows Explorer looks happy and saves file.\n * NOTE: that is not DAV LOCK imlementation, it is just a way to shut up\n * Windows native DAV client. This is why FAKE LOCK is not enabed by default\n */\nMG_INTERNAL void mg_handle_lock(struct mg_connection *nc, const char *path) {\n  static const char *reply =\n      \"HTTP/1.1 207 Multi-Status\\r\\n\"\n      \"Connection: close\\r\\n\"\n      \"Content-Type: text/xml; charset=utf-8\\r\\n\\r\\n\"\n      \"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\"?>\"\n      \"<d:multistatus xmlns:d='DAV:'>\\n\"\n      \"<D:lockdiscovery>\\n\"\n      \"<D:activelock>\\n\"\n      \"<D:locktoken>\\n\"\n      \"<D:href>\\n\"\n      \"opaquelocktoken:%s%u\"\n      \"</D:href>\"\n      \"</D:locktoken>\"\n      \"</D:activelock>\\n\"\n      \"</D:lockdiscovery>\"\n      \"</d:multistatus>\\n\";\n  mg_printf(nc, reply, path, (unsigned int) mg_time());\n  nc->flags |= MG_F_SEND_AND_CLOSE;\n}\n#endif\n\nMG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path,\n                                 struct http_message *hm) {\n  int status_code = 500;\n  if (hm->body.len != (size_t) ~0 && hm->body.len > 0) {\n    status_code = 415;\n  } else if (!mg_mkdir(path, 0755)) {\n    status_code = 201;\n  } else if (errno == EEXIST) {\n    status_code = 405;\n  } else if (errno == EACCES) {\n    status_code = 403;\n  } else if (errno == ENOENT) {\n    status_code = 409;\n  } else {\n    status_code = 500;\n  }\n  mg_http_send_error(nc, status_code, NULL);\n}\n\nstatic int mg_remove_directory(const struct mg_serve_http_opts *opts,\n                               const char *dir) {\n  char path[MG_MAX_PATH];\n  struct dirent *dp;\n  cs_stat_t st;\n  DIR *dirp;\n\n  if ((dirp = opendir(dir)) == NULL) return 0;\n\n  while ((dp = readdir(dirp)) != NULL) {\n    if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {\n      continue;\n    }\n    snprintf(path, sizeof(path), \"%s%c%s\", dir, '/', dp->d_name);\n    mg_stat(path, &st);\n    if (S_ISDIR(st.st_mode)) {\n      mg_remove_directory(opts, path);\n    } else {\n      remove(path);\n    }\n  }\n  closedir(dirp);\n  rmdir(dir);\n\n  return 1;\n}\n\nMG_INTERNAL void mg_handle_move(struct mg_connection *c,\n                                const struct mg_serve_http_opts *opts,\n                                const char *path, struct http_message *hm) {\n  const struct mg_str *dest = mg_get_http_header(hm, \"Destination\");\n  if (dest == NULL) {\n    mg_http_send_error(c, 411, NULL);\n  } else {\n    const char *p = (char *) memchr(dest->p, '/', dest->len);\n    if (p != NULL && p[1] == '/' &&\n        (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) {\n      char buf[MG_MAX_PATH];\n      snprintf(buf, sizeof(buf), \"%s%.*s\", opts->dav_document_root,\n               (int) (dest->p + dest->len - p), p);\n      if (rename(path, buf) == 0) {\n        mg_http_send_error(c, 200, NULL);\n      } else {\n        mg_http_send_error(c, 418, NULL);\n      }\n    } else {\n      mg_http_send_error(c, 500, NULL);\n    }\n  }\n}\n\nMG_INTERNAL void mg_handle_delete(struct mg_connection *nc,\n                                  const struct mg_serve_http_opts *opts,\n                                  const char *path) {\n  cs_stat_t st;\n  if (mg_stat(path, &st) != 0) {\n    mg_http_send_error(nc, 404, NULL);\n  } else if (S_ISDIR(st.st_mode)) {\n    mg_remove_directory(opts, path);\n    mg_http_send_error(nc, 204, NULL);\n  } else if (remove(path) == 0) {\n    mg_http_send_error(nc, 204, NULL);\n  } else {\n    mg_http_send_error(nc, 423, NULL);\n  }\n}\n\n/* Return -1 on error, 1 on success. */\nstatic int mg_create_itermediate_directories(const char *path) {\n  const char *s;\n\n  /* Create intermediate directories if they do not exist */\n  for (s = path + 1; *s != '\\0'; s++) {\n    if (*s == '/') {\n      char buf[MG_MAX_PATH];\n      cs_stat_t st;\n      snprintf(buf, sizeof(buf), \"%.*s\", (int) (s - path), path);\n      buf[sizeof(buf) - 1] = '\\0';\n      if (mg_stat(buf, &st) != 0 && mg_mkdir(buf, 0755) != 0) {\n        return -1;\n      }\n    }\n  }\n\n  return 1;\n}\n\nMG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,\n                               struct http_message *hm) {\n  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);\n  cs_stat_t st;\n  const struct mg_str *cl_hdr = mg_get_http_header(hm, \"Content-Length\");\n  int rc, status_code = mg_stat(path, &st) == 0 ? 200 : 201;\n\n  mg_http_free_proto_data_file(&pd->file);\n  if ((rc = mg_create_itermediate_directories(path)) == 0) {\n    mg_printf(nc, \"HTTP/1.1 %d OK\\r\\nContent-Length: 0\\r\\n\\r\\n\", status_code);\n  } else if (rc == -1) {\n    mg_http_send_error(nc, 500, NULL);\n  } else if (cl_hdr == NULL) {\n    mg_http_send_error(nc, 411, NULL);\n  } else if ((pd->file.fp = mg_fopen(path, \"w+b\")) == NULL) {\n    mg_http_send_error(nc, 500, NULL);\n  } else {\n    const struct mg_str *range_hdr = mg_get_http_header(hm, \"Content-Range\");\n    int64_t r1 = 0, r2 = 0;\n    pd->file.type = DATA_PUT;\n    mg_set_close_on_exec((sock_t) fileno(pd->file.fp));\n    pd->file.cl = to64(cl_hdr->p);\n    if (range_hdr != NULL &&\n        mg_http_parse_range_header(range_hdr, &r1, &r2) > 0) {\n      status_code = 206;\n      fseeko(pd->file.fp, r1, SEEK_SET);\n      pd->file.cl = r2 > r1 ? r2 - r1 + 1 : pd->file.cl - r1;\n    }\n    mg_printf(nc, \"HTTP/1.1 %d OK\\r\\nContent-Length: 0\\r\\n\\r\\n\", status_code);\n    /* Remove HTTP request from the mbuf, leave only payload */\n    mbuf_remove(&nc->recv_mbuf, hm->message.len - hm->body.len);\n    mg_http_transfer_file_data(nc);\n  }\n}\n\n#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_http_websocket.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET\n\n/* Amalgamated: #include \"common/cs_sha1.h\" */\n\n#ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS\n#define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5\n#endif\n\n#define FLAGS_MASK_FIN (1 << 7)\n#define FLAGS_MASK_OP 0x0f\n\nstatic int mg_is_ws_fragment(unsigned char flags) {\n  return (flags & FLAGS_MASK_FIN) == 0 ||\n         (flags & FLAGS_MASK_OP) == WEBSOCKET_OP_CONTINUE;\n}\n\nstatic int mg_is_ws_first_fragment(unsigned char flags) {\n  return (flags & FLAGS_MASK_FIN) == 0 &&\n         (flags & FLAGS_MASK_OP) != WEBSOCKET_OP_CONTINUE;\n}\n\nstatic int mg_is_ws_control_frame(unsigned char flags) {\n  unsigned char op = (flags & FLAGS_MASK_OP);\n  return op == WEBSOCKET_OP_CLOSE || op == WEBSOCKET_OP_PING ||\n         op == WEBSOCKET_OP_PONG;\n}\n\nstatic void mg_handle_incoming_websocket_frame(struct mg_connection *nc,\n                                               struct websocket_message *wsm) {\n  if (wsm->flags & 0x8) {\n    mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm);\n  } else {\n    mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_FRAME, wsm);\n  }\n}\n\nstatic struct mg_ws_proto_data *mg_ws_get_proto_data(struct mg_connection *nc) {\n  struct mg_http_proto_data *htd = mg_http_get_proto_data(nc);\n  return (htd != NULL ? &htd->ws_data : NULL);\n}\n\n/*\n * Sends a Close websocket frame with the given data, and closes the underlying\n * connection. If `len` is ~0, strlen(data) is used.\n */\nstatic void mg_ws_close(struct mg_connection *nc, const void *data,\n                        size_t len) {\n  if ((int) len == ~0) {\n    len = strlen((const char *) data);\n  }\n  mg_send_websocket_frame(nc, WEBSOCKET_OP_CLOSE, data, len);\n  nc->flags |= MG_F_SEND_AND_CLOSE;\n}\n\nstatic int mg_deliver_websocket_data(struct mg_connection *nc) {\n  /* Using unsigned char *, cause of integer arithmetic below */\n  uint64_t i, data_len = 0, frame_len = 0, new_data_len = nc->recv_mbuf.len,\n              len, mask_len = 0, header_len = 0;\n  struct mg_ws_proto_data *wsd = mg_ws_get_proto_data(nc);\n  unsigned char *new_data = (unsigned char *) nc->recv_mbuf.buf,\n                *e = (unsigned char *) nc->recv_mbuf.buf + nc->recv_mbuf.len;\n  uint8_t flags;\n  int ok, reass;\n\n  if (wsd->reass_len > 0) {\n    /*\n     * We already have some previously received data which we need to\n     * reassemble and deliver to the client code when we get the final\n     * fragment.\n     *\n     * NOTE: it doesn't mean that the current message must be a continuation:\n     * it might be a control frame (Close, Ping or Pong), which should be\n     * handled without breaking the fragmented message.\n     */\n\n    size_t existing_len = wsd->reass_len;\n    assert(new_data_len >= existing_len);\n\n    new_data += existing_len;\n    new_data_len -= existing_len;\n  }\n\n  flags = new_data[0];\n\n  reass = new_data_len > 0 && mg_is_ws_fragment(flags) &&\n          !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG);\n\n  if (reass && mg_is_ws_control_frame(flags)) {\n    /*\n     * Control frames can't be fragmented, so if we encounter fragmented\n     * control frame, close connection immediately.\n     */\n    mg_ws_close(nc, \"fragmented control frames are illegal\", ~0);\n    return 0;\n  } else if (new_data_len > 0 && !reass && !mg_is_ws_control_frame(flags) &&\n             wsd->reass_len > 0) {\n    /*\n     * When in the middle of a fragmented message, only the continuations\n     * and control frames are allowed.\n     */\n    mg_ws_close(nc, \"non-continuation in the middle of a fragmented message\",\n                ~0);\n    return 0;\n  }\n\n  if (new_data_len >= 2) {\n    len = new_data[1] & 0x7f;\n    mask_len = new_data[1] & FLAGS_MASK_FIN ? 4 : 0;\n    if (len < 126 && new_data_len >= mask_len) {\n      data_len = len;\n      header_len = 2 + mask_len;\n    } else if (len == 126 && new_data_len >= 4 + mask_len) {\n      header_len = 4 + mask_len;\n      data_len = ntohs(*(uint16_t *) &new_data[2]);\n    } else if (new_data_len >= 10 + mask_len) {\n      header_len = 10 + mask_len;\n      data_len = (((uint64_t) ntohl(*(uint32_t *) &new_data[2])) << 32) +\n                 ntohl(*(uint32_t *) &new_data[6]);\n    }\n  }\n\n  frame_len = header_len + data_len;\n  ok = (frame_len > 0 && frame_len <= new_data_len);\n\n  /* Check for overflow */\n  if (frame_len < header_len || frame_len < data_len) {\n    ok = 0;\n    mg_ws_close(nc, \"overflowed message\", ~0);\n  }\n\n  if (ok) {\n    size_t cleanup_len = 0;\n    struct websocket_message wsm;\n\n    wsm.size = (size_t) data_len;\n    wsm.data = new_data + header_len;\n    wsm.flags = flags;\n\n    /* Apply mask if necessary */\n    if (mask_len > 0) {\n      for (i = 0; i < data_len; i++) {\n        new_data[i + header_len] ^= (new_data + header_len - mask_len)[i % 4];\n      }\n    }\n\n    if (reass) {\n      /* This is a message fragment */\n\n      if (mg_is_ws_first_fragment(flags)) {\n        /*\n         * On the first fragmented frame, skip the first byte (op) and also\n         * reset size to 1 (op), it'll be incremented with the data len below.\n         */\n        new_data += 1;\n        wsd->reass_len = 1 /* op */;\n      }\n\n      /* Append this frame to the reassembled buffer */\n      memmove(new_data, wsm.data, e - wsm.data);\n      wsd->reass_len += wsm.size;\n      nc->recv_mbuf.len -= wsm.data - new_data;\n\n      if (flags & FLAGS_MASK_FIN) {\n        /* On last fragmented frame - call user handler and remove data */\n        wsm.flags = FLAGS_MASK_FIN | nc->recv_mbuf.buf[0];\n        wsm.data = (unsigned char *) nc->recv_mbuf.buf + 1 /* op */;\n        wsm.size = wsd->reass_len - 1 /* op */;\n        cleanup_len = wsd->reass_len;\n        wsd->reass_len = 0;\n\n        /* Pass reassembled message to the client code. */\n        mg_handle_incoming_websocket_frame(nc, &wsm);\n        mbuf_remove(&nc->recv_mbuf, cleanup_len); /* Cleanup frame */\n      }\n    } else {\n      /*\n       * This is a complete message, not a fragment. It might happen in between\n       * of a fragmented message (in this case, WebSocket protocol requires\n       * current message to be a control frame).\n       */\n      cleanup_len = (size_t) frame_len;\n\n      /* First of all, check if we need to react on a control frame. */\n      switch (flags & FLAGS_MASK_OP) {\n        case WEBSOCKET_OP_PING:\n          mg_send_websocket_frame(nc, WEBSOCKET_OP_PONG, wsm.data, wsm.size);\n          break;\n\n        case WEBSOCKET_OP_CLOSE:\n          mg_ws_close(nc, wsm.data, wsm.size);\n          break;\n      }\n\n      /* Pass received message to the client code. */\n      mg_handle_incoming_websocket_frame(nc, &wsm);\n\n      /* Cleanup frame */\n      memmove(nc->recv_mbuf.buf + wsd->reass_len,\n              nc->recv_mbuf.buf + wsd->reass_len + cleanup_len,\n              nc->recv_mbuf.len - wsd->reass_len - cleanup_len);\n      nc->recv_mbuf.len -= cleanup_len;\n    }\n  }\n\n  return ok;\n}\n\nstruct ws_mask_ctx {\n  size_t pos; /* zero means unmasked */\n  uint32_t mask;\n};\n\nstatic uint32_t mg_ws_random_mask(void) {\n  uint32_t mask;\n/*\n * The spec requires WS client to generate hard to\n * guess mask keys. From RFC6455, Section 5.3:\n *\n * The unpredictability of the masking key is essential to prevent\n * authors of malicious applications from selecting the bytes that appear on\n * the wire.\n *\n * Hence this feature is essential when the actual end user of this API\n * is untrusted code that wouldn't have access to a lower level net API\n * anyway (e.g. web browsers). Hence this feature is low prio for most\n * mongoose use cases and thus can be disabled, e.g. when porting to a platform\n * that lacks rand().\n */\n#if MG_DISABLE_WS_RANDOM_MASK\n  mask = 0xefbeadde; /* generated with a random number generator, I swear */\n#else\n  if (sizeof(long) >= 4) {\n    mask = (uint32_t) rand();\n  } else if (sizeof(long) == 2) {\n    mask = (uint32_t) rand() << 16 | (uint32_t) rand();\n  }\n#endif\n  return mask;\n}\n\nstatic void mg_send_ws_header(struct mg_connection *nc, int op, size_t len,\n                              struct ws_mask_ctx *ctx) {\n  int header_len;\n  unsigned char header[10];\n\n  header[0] =\n      (op & WEBSOCKET_DONT_FIN ? 0x0 : FLAGS_MASK_FIN) | (op & FLAGS_MASK_OP);\n  if (len < 126) {\n    header[1] = (unsigned char) len;\n    header_len = 2;\n  } else if (len < 65535) {\n    uint16_t tmp = htons((uint16_t) len);\n    header[1] = 126;\n    memcpy(&header[2], &tmp, sizeof(tmp));\n    header_len = 4;\n  } else {\n    uint32_t tmp;\n    header[1] = 127;\n    tmp = htonl((uint32_t)((uint64_t) len >> 32));\n    memcpy(&header[2], &tmp, sizeof(tmp));\n    tmp = htonl((uint32_t)(len & 0xffffffff));\n    memcpy(&header[6], &tmp, sizeof(tmp));\n    header_len = 10;\n  }\n\n  /* client connections enable masking */\n  if (nc->listener == NULL) {\n    header[1] |= 1 << 7; /* set masking flag */\n    mg_send(nc, header, header_len);\n    ctx->mask = mg_ws_random_mask();\n    mg_send(nc, &ctx->mask, sizeof(ctx->mask));\n    ctx->pos = nc->send_mbuf.len;\n  } else {\n    mg_send(nc, header, header_len);\n    ctx->pos = 0;\n  }\n}\n\nstatic void mg_ws_mask_frame(struct mbuf *mbuf, struct ws_mask_ctx *ctx) {\n  size_t i;\n  if (ctx->pos == 0) return;\n  for (i = 0; i < (mbuf->len - ctx->pos); i++) {\n    mbuf->buf[ctx->pos + i] ^= ((char *) &ctx->mask)[i % 4];\n  }\n}\n\nvoid mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data,\n                             size_t len) {\n  struct ws_mask_ctx ctx;\n  DBG((\"%p %d %d\", nc, op, (int) len));\n  mg_send_ws_header(nc, op, len, &ctx);\n  mg_send(nc, data, len);\n\n  mg_ws_mask_frame(&nc->send_mbuf, &ctx);\n\n  if (op == WEBSOCKET_OP_CLOSE) {\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n  }\n}\n\nvoid mg_send_websocket_framev(struct mg_connection *nc, int op,\n                              const struct mg_str *strv, int strvcnt) {\n  struct ws_mask_ctx ctx;\n  int i;\n  int len = 0;\n  for (i = 0; i < strvcnt; i++) {\n    len += strv[i].len;\n  }\n\n  mg_send_ws_header(nc, op, len, &ctx);\n\n  for (i = 0; i < strvcnt; i++) {\n    mg_send(nc, strv[i].p, strv[i].len);\n  }\n\n  mg_ws_mask_frame(&nc->send_mbuf, &ctx);\n\n  if (op == WEBSOCKET_OP_CLOSE) {\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n  }\n}\n\nvoid mg_printf_websocket_frame(struct mg_connection *nc, int op,\n                               const char *fmt, ...) {\n  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;\n  va_list ap;\n  int len;\n\n  va_start(ap, fmt);\n  if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {\n    mg_send_websocket_frame(nc, op, buf, len);\n  }\n  va_end(ap);\n\n  if (buf != mem && buf != NULL) {\n    MG_FREE(buf);\n  }\n}\n\nMG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev,\n                               void *ev_data MG_UD_ARG(void *user_data)) {\n  mg_call(nc, nc->handler, nc->user_data, ev, ev_data);\n\n  switch (ev) {\n    case MG_EV_RECV:\n      do {\n      } while (mg_deliver_websocket_data(nc));\n      break;\n    case MG_EV_POLL:\n      /* Ping idle websocket connections */\n      {\n        time_t now = *(time_t *) ev_data;\n        if (nc->flags & MG_F_IS_WEBSOCKET &&\n            now > nc->last_io_time + MG_WEBSOCKET_PING_INTERVAL_SECONDS) {\n          mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, \"\", 0);\n        }\n      }\n      break;\n    default:\n      break;\n  }\n#if MG_ENABLE_CALLBACK_USERDATA\n  (void) user_data;\n#endif\n}\n\n#ifndef MG_EXT_SHA1\nvoid mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],\n                    const size_t *msg_lens, uint8_t *digest) {\n  size_t i;\n  cs_sha1_ctx sha_ctx;\n  cs_sha1_init(&sha_ctx);\n  for (i = 0; i < num_msgs; i++) {\n    cs_sha1_update(&sha_ctx, msgs[i], msg_lens[i]);\n  }\n  cs_sha1_final(digest, &sha_ctx);\n}\n#else\nextern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],\n                           const size_t *msg_lens, uint8_t *digest);\n#endif\n\nMG_INTERNAL void mg_ws_handshake(struct mg_connection *nc,\n                                 const struct mg_str *key,\n                                 struct http_message *hm) {\n  static const char *magic = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n  const uint8_t *msgs[2] = {(const uint8_t *) key->p, (const uint8_t *) magic};\n  const size_t msg_lens[2] = {key->len, 36};\n  unsigned char sha[20];\n  char b64_sha[30];\n  struct mg_str *s;\n\n  mg_hash_sha1_v(2, msgs, msg_lens, sha);\n  mg_base64_encode(sha, sizeof(sha), b64_sha);\n  mg_printf(nc, \"%s\",\n            \"HTTP/1.1 101 Switching Protocols\\r\\n\"\n            \"Upgrade: websocket\\r\\n\"\n            \"Connection: Upgrade\\r\\n\");\n\n  s = mg_get_http_header(hm, \"Sec-WebSocket-Protocol\");\n  if (s != NULL) {\n    mg_printf(nc, \"Sec-WebSocket-Protocol: %.*s\\r\\n\", (int) s->len, s->p);\n  }\n  mg_printf(nc, \"Sec-WebSocket-Accept: %s%s\", b64_sha, \"\\r\\n\\r\\n\");\n\n  DBG((\"%p %.*s %s\", nc, (int) key->len, key->p, b64_sha));\n}\n\nvoid mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,\n                                  const char *host, const char *protocol,\n                                  const char *extra_headers) {\n  mg_send_websocket_handshake3(nc, path, host, protocol, extra_headers, NULL,\n                               NULL);\n}\n\nvoid mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,\n                                  const char *host, const char *protocol,\n                                  const char *extra_headers, const char *user,\n                                  const char *pass) {\n  mg_send_websocket_handshake3v(nc, mg_mk_str(path), mg_mk_str(host),\n                                mg_mk_str(protocol), mg_mk_str(extra_headers),\n                                mg_mk_str(user), mg_mk_str(pass));\n}\n\nvoid mg_send_websocket_handshake3v(struct mg_connection *nc,\n                                   const struct mg_str path,\n                                   const struct mg_str host,\n                                   const struct mg_str protocol,\n                                   const struct mg_str extra_headers,\n                                   const struct mg_str user,\n                                   const struct mg_str pass) {\n  struct mbuf auth;\n  char key[25];\n  uint32_t nonce[4];\n  nonce[0] = mg_ws_random_mask();\n  nonce[1] = mg_ws_random_mask();\n  nonce[2] = mg_ws_random_mask();\n  nonce[3] = mg_ws_random_mask();\n  mg_base64_encode((unsigned char *) &nonce, sizeof(nonce), key);\n\n  mbuf_init(&auth, 0);\n  if (user.len > 0) {\n    mg_basic_auth_header(user, pass, &auth);\n  }\n\n  /*\n   * NOTE: the  (auth.buf == NULL ? \"\" : auth.buf) is because cc3200 libc is\n   * broken: it doesn't like zero length to be passed to %.*s\n   * i.e. sprintf(\"f%.*so\", (int)0, NULL), yields `f\\0o`.\n   * because it handles NULL specially (and incorrectly).\n   */\n  mg_printf(nc,\n            \"GET %.*s HTTP/1.1\\r\\n\"\n            \"Upgrade: websocket\\r\\n\"\n            \"Connection: Upgrade\\r\\n\"\n            \"%.*s\"\n            \"Sec-WebSocket-Version: 13\\r\\n\"\n            \"Sec-WebSocket-Key: %s\\r\\n\",\n            (int) path.len, path.p, (int) auth.len,\n            (auth.buf == NULL ? \"\" : auth.buf), key);\n\n  /* TODO(mkm): take default hostname from http proto data if host == NULL */\n  if (host.len > 0) {\n    int host_len = (int) (path.p - host.p); /* Account for possible :PORT */\n    mg_printf(nc, \"Host: %.*s\\r\\n\", host_len, host.p);\n  }\n  if (protocol.len > 0) {\n    mg_printf(nc, \"Sec-WebSocket-Protocol: %.*s\\r\\n\", (int) protocol.len,\n              protocol.p);\n  }\n  if (extra_headers.len > 0) {\n    mg_printf(nc, \"%.*s\", (int) extra_headers.len, extra_headers.p);\n  }\n  mg_printf(nc, \"\\r\\n\");\n\n  nc->flags |= MG_F_IS_WEBSOCKET;\n\n  mbuf_free(&auth);\n}\n\nvoid mg_send_websocket_handshake(struct mg_connection *nc, const char *path,\n                                 const char *extra_headers) {\n  struct mg_str null_str = MG_NULL_STR;\n  mg_send_websocket_handshake3v(\n      nc, mg_mk_str(path), null_str /* host */, null_str /* protocol */,\n      mg_mk_str(extra_headers), null_str /* user */, null_str /* pass */);\n}\n\nstruct mg_connection *mg_connect_ws_opt(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    struct mg_connect_opts opts, const char *url, const char *protocol,\n    const char *extra_headers) {\n  struct mg_str null_str = MG_NULL_STR;\n  struct mg_str host = MG_NULL_STR, path = MG_NULL_STR, user_info = MG_NULL_STR;\n  struct mg_connection *nc =\n      mg_connect_http_base(mgr, MG_CB(ev_handler, user_data), opts, \"http\",\n                           \"ws\", \"https\", \"wss\", url, &path, &user_info, &host);\n  if (nc != NULL) {\n    mg_send_websocket_handshake3v(nc, path, host, mg_mk_str(protocol),\n                                  mg_mk_str(extra_headers), user_info,\n                                  null_str);\n  }\n  return nc;\n}\n\nstruct mg_connection *mg_connect_ws(\n    struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),\n    const char *url, const char *protocol, const char *extra_headers) {\n  struct mg_connect_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  return mg_connect_ws_opt(mgr, MG_CB(ev_handler, user_data), opts, url,\n                           protocol, extra_headers);\n}\n#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_util.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/* Amalgamated: #include \"common/cs_base64.h\" */\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_util.h\" */\n\n/* For platforms with limited libc */\n#ifndef MAX\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\n#endif\n\nconst char *mg_skip(const char *s, const char *end, const char *delims,\n                    struct mg_str *v) {\n  v->p = s;\n  while (s < end && strchr(delims, *(unsigned char *) s) == NULL) s++;\n  v->len = s - v->p;\n  while (s < end && strchr(delims, *(unsigned char *) s) != NULL) s++;\n  return s;\n}\n\n#if MG_ENABLE_FILESYSTEM && !defined(MG_USER_FILE_FUNCTIONS)\nint mg_stat(const char *path, cs_stat_t *st) {\n#ifdef _WIN32\n  wchar_t wpath[MG_MAX_PATH];\n  to_wchar(path, wpath, ARRAY_SIZE(wpath));\n  DBG((\"[%ls] -> %d\", wpath, _wstati64(wpath, st)));\n  return _wstati64(wpath, st);\n#else\n  return stat(path, st);\n#endif\n}\n\nFILE *mg_fopen(const char *path, const char *mode) {\n#ifdef _WIN32\n  wchar_t wpath[MG_MAX_PATH], wmode[10];\n  to_wchar(path, wpath, ARRAY_SIZE(wpath));\n  to_wchar(mode, wmode, ARRAY_SIZE(wmode));\n  return _wfopen(wpath, wmode);\n#else\n  return fopen(path, mode);\n#endif\n}\n\nint mg_open(const char *path, int flag, int mode) { /* LCOV_EXCL_LINE */\n#if defined(_WIN32) && !defined(WINCE)\n  wchar_t wpath[MG_MAX_PATH];\n  to_wchar(path, wpath, ARRAY_SIZE(wpath));\n  return _wopen(wpath, flag, mode);\n#else\n  return open(path, flag, mode); /* LCOV_EXCL_LINE */\n#endif\n}\n\nsize_t mg_fread(void *ptr, size_t size, size_t count, FILE *f) {\n  return fread(ptr, size, count, f);\n}\n\nsize_t mg_fwrite(const void *ptr, size_t size, size_t count, FILE *f) {\n  return fwrite(ptr, size, count, f);\n}\n#endif\n\nvoid mg_base64_encode(const unsigned char *src, int src_len, char *dst) {\n  cs_base64_encode(src, src_len, dst);\n}\n\nint mg_base64_decode(const unsigned char *s, int len, char *dst) {\n  return cs_base64_decode(s, len, dst, NULL);\n}\n\n#if MG_ENABLE_THREADS\nvoid *mg_start_thread(void *(*f)(void *), void *p) {\n#ifdef WINCE\n  return (void *) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) f, p, 0, NULL);\n#elif defined(_WIN32)\n  return (void *) _beginthread((void(__cdecl *) (void *) ) f, 0, p);\n#else\n  pthread_t thread_id = (pthread_t) 0;\n  pthread_attr_t attr;\n\n  (void) pthread_attr_init(&attr);\n  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);\n\n#if defined(MG_STACK_SIZE) && MG_STACK_SIZE > 1\n  (void) pthread_attr_setstacksize(&attr, MG_STACK_SIZE);\n#endif\n\n  pthread_create(&thread_id, &attr, f, p);\n  pthread_attr_destroy(&attr);\n\n  return (void *) thread_id;\n#endif\n}\n#endif /* MG_ENABLE_THREADS */\n\n/* Set close-on-exec bit for a given socket. */\nvoid mg_set_close_on_exec(sock_t sock) {\n#if defined(_WIN32) && !defined(WINCE)\n  (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);\n#elif defined(__unix__)\n  fcntl(sock, F_SETFD, FD_CLOEXEC);\n#else\n  (void) sock;\n#endif\n}\n\nint mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,\n                        int flags) {\n  int is_v6;\n  if (buf == NULL || len <= 0) return 0;\n  memset(buf, 0, len);\n#if MG_ENABLE_IPV6\n  is_v6 = sa->sa.sa_family == AF_INET6;\n#else\n  is_v6 = 0;\n#endif\n  if (flags & MG_SOCK_STRINGIFY_IP) {\n#if MG_ENABLE_IPV6\n    const void *addr = NULL;\n    char *start = buf;\n    socklen_t capacity = len;\n    if (!is_v6) {\n      addr = &sa->sin.sin_addr;\n    } else {\n      addr = (void *) &sa->sin6.sin6_addr;\n      if (flags & MG_SOCK_STRINGIFY_PORT) {\n        *buf = '[';\n        start++;\n        capacity--;\n      }\n    }\n    if (inet_ntop(sa->sa.sa_family, addr, start, capacity) == NULL) {\n      goto cleanup;\n    }\n#elif defined(_WIN32) || MG_LWIP || (MG_NET_IF == MG_NET_IF_PIC32)\n    /* Only Windoze Vista (and newer) have inet_ntop() */\n    char *addr_str = inet_ntoa(sa->sin.sin_addr);\n    if (addr_str != NULL) {\n      strncpy(buf, inet_ntoa(sa->sin.sin_addr), len - 1);\n    } else {\n      goto cleanup;\n    }\n#else\n    if (inet_ntop(AF_INET, (void *) &sa->sin.sin_addr, buf, len) == NULL) {\n      goto cleanup;\n    }\n#endif\n  }\n  if (flags & MG_SOCK_STRINGIFY_PORT) {\n    int port = ntohs(sa->sin.sin_port);\n    if (flags & MG_SOCK_STRINGIFY_IP) {\n      int buf_len = strlen(buf);\n      snprintf(buf + buf_len, len - (buf_len + 1), \"%s:%d\", (is_v6 ? \"]\" : \"\"),\n               port);\n    } else {\n      snprintf(buf, len, \"%d\", port);\n    }\n  }\n\n  return strlen(buf);\n\ncleanup:\n  *buf = '\\0';\n  return 0;\n}\n\nint mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,\n                        int flags) {\n  union socket_address sa;\n  memset(&sa, 0, sizeof(sa));\n  mg_if_get_conn_addr(nc, flags & MG_SOCK_STRINGIFY_REMOTE, &sa);\n  return mg_sock_addr_to_str(&sa, buf, len, flags);\n}\n\n#if MG_ENABLE_HEXDUMP\nstatic int mg_hexdump_n(const void *buf, int len, char *dst, int dst_len,\n                        int offset) {\n  const unsigned char *p = (const unsigned char *) buf;\n  char ascii[17] = \"\";\n  int i, idx, n = 0;\n\n  for (i = 0; i < len; i++) {\n    idx = i % 16;\n    if (idx == 0) {\n      if (i > 0) n += snprintf(dst + n, MAX(dst_len - n, 0), \"  %s\\n\", ascii);\n      n += snprintf(dst + n, MAX(dst_len - n, 0), \"%04x \", i + offset);\n    }\n    if (dst_len - n < 0) {\n      return n;\n    }\n    n += snprintf(dst + n, MAX(dst_len - n, 0), \" %02x\", p[i]);\n    ascii[idx] = p[i] < 0x20 || p[i] > 0x7e ? '.' : p[i];\n    ascii[idx + 1] = '\\0';\n  }\n\n  while (i++ % 16) n += snprintf(dst + n, MAX(dst_len - n, 0), \"%s\", \"   \");\n  n += snprintf(dst + n, MAX(dst_len - n, 0), \"  %s\\n\", ascii);\n\n  return n;\n}\n\nint mg_hexdump(const void *buf, int len, char *dst, int dst_len) {\n  return mg_hexdump_n(buf, len, dst, dst_len, 0);\n}\n\nvoid mg_hexdumpf(FILE *fp, const void *buf, int len) {\n  char tmp[80];\n  int offset = 0, n;\n  while (len > 0) {\n    n = (len < 16 ? len : 16);\n    mg_hexdump_n(((const char *) buf) + offset, n, tmp, sizeof(tmp), offset);\n    fputs(tmp, fp);\n    offset += n;\n    len -= n;\n  }\n}\n\nvoid mg_hexdump_connection(struct mg_connection *nc, const char *path,\n                           const void *buf, int num_bytes, int ev) {\n  FILE *fp = NULL;\n  char src[60], dst[60];\n  const char *tag = NULL;\n  switch (ev) {\n    case MG_EV_RECV:\n      tag = \"<-\";\n      break;\n    case MG_EV_SEND:\n      tag = \"->\";\n      break;\n    case MG_EV_ACCEPT:\n      tag = \"<A\";\n      break;\n    case MG_EV_CONNECT:\n      tag = \"C>\";\n      break;\n    case MG_EV_CLOSE:\n      tag = \"XX\";\n      break;\n  }\n  if (tag == NULL) return; /* Don't log MG_EV_TIMER, etc */\n\n  if (strcmp(path, \"-\") == 0) {\n    fp = stdout;\n  } else if (strcmp(path, \"--\") == 0) {\n    fp = stderr;\n#if MG_ENABLE_FILESYSTEM\n  } else {\n    fp = mg_fopen(path, \"a\");\n#endif\n  }\n  if (fp == NULL) return;\n\n  mg_conn_addr_to_str(nc, src, sizeof(src),\n                      MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);\n  mg_conn_addr_to_str(nc, dst, sizeof(dst), MG_SOCK_STRINGIFY_IP |\n                                                MG_SOCK_STRINGIFY_PORT |\n                                                MG_SOCK_STRINGIFY_REMOTE);\n  fprintf(fp, \"%lu %p %s %s %s %d\\n\", (unsigned long) mg_time(), (void *) nc,\n          src, tag, dst, (int) num_bytes);\n  if (num_bytes > 0) {\n    mg_hexdumpf(fp, buf, num_bytes);\n  }\n  if (fp != stdout && fp != stderr) fclose(fp);\n}\n#endif\n\nint mg_is_big_endian(void) {\n  static const int n = 1;\n  /* TODO(mkm) use compiletime check with 4-byte char literal */\n  return ((char *) &n)[0] == 0;\n}\n\nDO_NOT_WARN_UNUSED MG_INTERNAL int mg_get_errno(void) {\n#ifndef WINCE\n  return errno;\n#else\n  /* TODO(alashkin): translate error codes? */\n  return GetLastError();\n#endif\n}\n\nvoid mg_mbuf_append_base64_putc(char ch, void *user_data) {\n  struct mbuf *mbuf = (struct mbuf *) user_data;\n  mbuf_append(mbuf, &ch, sizeof(ch));\n}\n\nvoid mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len) {\n  struct cs_base64_ctx ctx;\n  cs_base64_init(&ctx, mg_mbuf_append_base64_putc, mbuf);\n  cs_base64_update(&ctx, (const char *) data, len);\n  cs_base64_finish(&ctx);\n}\n\nvoid mg_basic_auth_header(const struct mg_str user, const struct mg_str pass,\n                          struct mbuf *buf) {\n  const char *header_prefix = \"Authorization: Basic \";\n  const char *header_suffix = \"\\r\\n\";\n\n  struct cs_base64_ctx ctx;\n  cs_base64_init(&ctx, mg_mbuf_append_base64_putc, buf);\n\n  mbuf_append(buf, header_prefix, strlen(header_prefix));\n\n  cs_base64_update(&ctx, user.p, user.len);\n  if (pass.len > 0) {\n    cs_base64_update(&ctx, \":\", 1);\n    cs_base64_update(&ctx, pass.p, pass.len);\n  }\n  cs_base64_finish(&ctx);\n  mbuf_append(buf, header_suffix, strlen(header_suffix));\n}\n\nstruct mg_str mg_url_encode_opt(const struct mg_str src,\n                                const struct mg_str safe, unsigned int flags) {\n  const char *hex =\n      (flags & MG_URL_ENCODE_F_UPPERCASE_HEX ? \"0123456789ABCDEF\"\n                                             : \"0123456789abcdef\");\n  size_t i = 0;\n  struct mbuf mb;\n  mbuf_init(&mb, src.len);\n\n  for (i = 0; i < src.len; i++) {\n    const unsigned char c = *((const unsigned char *) src.p + i);\n    if (isalnum(c) || mg_strchr(safe, c) != NULL) {\n      mbuf_append(&mb, &c, 1);\n    } else if (c == ' ' && (flags & MG_URL_ENCODE_F_SPACE_AS_PLUS)) {\n      mbuf_append(&mb, \"+\", 1);\n    } else {\n      mbuf_append(&mb, \"%\", 1);\n      mbuf_append(&mb, &hex[c >> 4], 1);\n      mbuf_append(&mb, &hex[c & 15], 1);\n    }\n  }\n  mbuf_append(&mb, \"\", 1);\n  mbuf_trim(&mb);\n  return mg_mk_str_n(mb.buf, mb.len - 1);\n}\n\nstruct mg_str mg_url_encode(const struct mg_str src) {\n  return mg_url_encode_opt(src, mg_mk_str(\"._-$,;~()/\"), 0);\n}\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_mqtt.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_MQTT\n\n#include <string.h>\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_mqtt.h\" */\n\nstatic uint16_t getu16(const char *p) {\n  const uint8_t *up = (const uint8_t *) p;\n  return (up[0] << 8) + up[1];\n}\n\nstatic const char *scanto(const char *p, struct mg_str *s) {\n  s->len = getu16(p);\n  s->p = p + 2;\n  return s->p + s->len;\n}\n\nMG_INTERNAL int parse_mqtt(struct mbuf *io, struct mg_mqtt_message *mm) {\n  uint8_t header;\n  size_t len = 0, len_len = 0;\n  const char *p, *end, *eop = &io->buf[io->len];\n  unsigned char lc = 0;\n  int cmd;\n\n  if (io->len < 2) return MG_MQTT_ERROR_INCOMPLETE_MSG;\n  header = io->buf[0];\n  cmd = header >> 4;\n\n  /* decode mqtt variable length */\n  len = len_len = 0;\n  p = io->buf + 1;\n  while (p < eop) {\n    lc = *((const unsigned char *) p++);\n    len += (lc & 0x7f) << 7 * len_len;\n    len_len++;\n    if (!(lc & 0x80)) break;\n    if (len_len > 4) return MG_MQTT_ERROR_MALFORMED_MSG;\n  }\n\n  end = p + len;\n  if (lc & 0x80 || end > eop) return MG_MQTT_ERROR_INCOMPLETE_MSG;\n\n  mm->cmd = cmd;\n  mm->qos = MG_MQTT_GET_QOS(header);\n\n  switch (cmd) {\n    case MG_MQTT_CMD_CONNECT: {\n      p = scanto(p, &mm->protocol_name);\n      if (p > end - 4) return MG_MQTT_ERROR_MALFORMED_MSG;\n      mm->protocol_version = *(uint8_t *) p++;\n      mm->connect_flags = *(uint8_t *) p++;\n      mm->keep_alive_timer = getu16(p);\n      p += 2;\n      if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;\n      p = scanto(p, &mm->client_id);\n      if (p > end) return MG_MQTT_ERROR_MALFORMED_MSG;\n      if (mm->connect_flags & MG_MQTT_HAS_WILL) {\n        if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;\n        p = scanto(p, &mm->will_topic);\n      }\n      if (mm->connect_flags & MG_MQTT_HAS_WILL) {\n        if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;\n        p = scanto(p, &mm->will_message);\n      }\n      if (mm->connect_flags & MG_MQTT_HAS_USER_NAME) {\n        if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;\n        p = scanto(p, &mm->user_name);\n      }\n      if (mm->connect_flags & MG_MQTT_HAS_PASSWORD) {\n        if (p >= end) return MG_MQTT_ERROR_MALFORMED_MSG;\n        p = scanto(p, &mm->password);\n      }\n      if (p != end) return MG_MQTT_ERROR_MALFORMED_MSG;\n\n      LOG(LL_DEBUG,\n          (\"%d %2x %d proto [%.*s] client_id [%.*s] will_topic [%.*s] \"\n           \"will_msg [%.*s] user_name [%.*s] password [%.*s]\",\n           (int) len, (int) mm->connect_flags, (int) mm->keep_alive_timer,\n           (int) mm->protocol_name.len, mm->protocol_name.p,\n           (int) mm->client_id.len, mm->client_id.p, (int) mm->will_topic.len,\n           mm->will_topic.p, (int) mm->will_message.len, mm->will_message.p,\n           (int) mm->user_name.len, mm->user_name.p, (int) mm->password.len,\n           mm->password.p));\n      break;\n    }\n    case MG_MQTT_CMD_CONNACK:\n      if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;\n      mm->connack_ret_code = p[1];\n      break;\n    case MG_MQTT_CMD_PUBACK:\n    case MG_MQTT_CMD_PUBREC:\n    case MG_MQTT_CMD_PUBREL:\n    case MG_MQTT_CMD_PUBCOMP:\n    case MG_MQTT_CMD_SUBACK:\n      if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;\n      mm->message_id = getu16(p);\n      p += 2;\n      break;\n    case MG_MQTT_CMD_PUBLISH: {\n      p = scanto(p, &mm->topic);\n      if (p > end) return MG_MQTT_ERROR_MALFORMED_MSG;\n      if (mm->qos > 0) {\n        if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;\n        mm->message_id = getu16(p);\n        p += 2;\n      }\n      mm->payload.p = p;\n      mm->payload.len = end - p;\n      break;\n    }\n    case MG_MQTT_CMD_SUBSCRIBE:\n      if (end - p < 2) return MG_MQTT_ERROR_MALFORMED_MSG;\n      mm->message_id = getu16(p);\n      p += 2;\n      /*\n       * topic expressions are left in the payload and can be parsed with\n       * `mg_mqtt_next_subscribe_topic`\n       */\n      mm->payload.p = p;\n      mm->payload.len = end - p;\n      break;\n    default:\n      /* Unhandled command */\n      break;\n  }\n\n  mm->len = end - io->buf;\n  return mm->len;\n}\n\nstatic void mqtt_handler(struct mg_connection *nc, int ev,\n                         void *ev_data MG_UD_ARG(void *user_data)) {\n  struct mbuf *io = &nc->recv_mbuf;\n  struct mg_mqtt_message mm;\n  memset(&mm, 0, sizeof(mm));\n\n  nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));\n\n  switch (ev) {\n    case MG_EV_ACCEPT:\n      if (nc->proto_data == NULL) mg_set_protocol_mqtt(nc);\n      break;\n    case MG_EV_RECV: {\n      /* There can be multiple messages in the buffer, process them all. */\n      while (1) {\n        int len = parse_mqtt(io, &mm);\n        if (len < 0) {\n          if (len == MG_MQTT_ERROR_MALFORMED_MSG) {\n            /* Protocol error. */\n            nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n          } else if (len == MG_MQTT_ERROR_INCOMPLETE_MSG) {\n            /* Not fully buffered, let's check if we have a chance to get more\n             * data later */\n            if (nc->recv_mbuf_limit > 0 &&\n                nc->recv_mbuf.len >= nc->recv_mbuf_limit) {\n              LOG(LL_ERROR, (\"%p recv buffer (%lu bytes) exceeds the limit \"\n                             \"%lu bytes, and not drained, closing\",\n                             nc, (unsigned long) nc->recv_mbuf.len,\n                             (unsigned long) nc->recv_mbuf_limit));\n              nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n            }\n          } else {\n            /* Should never be here */\n            LOG(LL_ERROR, (\"%p invalid len: %d, closing\", nc, len));\n            nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n          }\n          break;\n        }\n\n        nc->handler(nc, MG_MQTT_EVENT_BASE + mm.cmd, &mm MG_UD_ARG(user_data));\n        mbuf_remove(io, len);\n      }\n      break;\n    }\n    case MG_EV_POLL: {\n      struct mg_mqtt_proto_data *pd =\n          (struct mg_mqtt_proto_data *) nc->proto_data;\n      double now = mg_time();\n      if (pd->keep_alive > 0 && pd->last_control_time > 0 &&\n          (now - pd->last_control_time) > pd->keep_alive) {\n        LOG(LL_DEBUG, (\"Send PINGREQ\"));\n        mg_mqtt_ping(nc);\n      }\n      break;\n    }\n  }\n}\n\nstatic void mg_mqtt_proto_data_destructor(void *proto_data) {\n  MG_FREE(proto_data);\n}\n\nstatic struct mg_str mg_mqtt_next_topic_component(struct mg_str *topic) {\n  struct mg_str res = *topic;\n  const char *c = mg_strchr(*topic, '/');\n  if (c != NULL) {\n    res.len = (c - topic->p);\n    topic->len -= (res.len + 1);\n    topic->p += (res.len + 1);\n  } else {\n    topic->len = 0;\n  }\n  return res;\n}\n\n/* Reference: https://mosquitto.org/man/mqtt-7.html */\nint mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) {\n  struct mg_str ec, tc;\n  if (exp.len == 0) return 0;\n  while (1) {\n    ec = mg_mqtt_next_topic_component(&exp);\n    tc = mg_mqtt_next_topic_component(&topic);\n    if (ec.len == 0) {\n      if (tc.len != 0) return 0;\n      if (exp.len == 0) break;\n      continue;\n    }\n    if (mg_vcmp(&ec, \"+\") == 0) {\n      if (tc.len == 0 && topic.len == 0) return 0;\n      continue;\n    }\n    if (mg_vcmp(&ec, \"#\") == 0) {\n      /* Must be the last component in the expression or it's invalid. */\n      return (exp.len == 0);\n    }\n    if (mg_strcmp(ec, tc) != 0) {\n      return 0;\n    }\n  }\n  return (tc.len == 0 && topic.len == 0);\n}\n\nint mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) {\n  return mg_mqtt_match_topic_expression(mg_mk_str(exp), topic);\n}\n\nvoid mg_set_protocol_mqtt(struct mg_connection *nc) {\n  nc->proto_handler = mqtt_handler;\n  nc->proto_data = MG_CALLOC(1, sizeof(struct mg_mqtt_proto_data));\n  nc->proto_data_destructor = mg_mqtt_proto_data_destructor;\n}\n\nstatic void mg_send_mqtt_header(struct mg_connection *nc, uint8_t cmd,\n                                uint8_t flags, size_t len) {\n  struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data;\n  uint8_t buf[1 + sizeof(size_t)];\n  uint8_t *vlen = &buf[1];\n\n  buf[0] = (cmd << 4) | flags;\n\n  /* mqtt variable length encoding */\n  do {\n    *vlen = len % 0x80;\n    len /= 0x80;\n    if (len > 0) *vlen |= 0x80;\n    vlen++;\n  } while (len > 0);\n\n  mg_send(nc, buf, vlen - buf);\n  pd->last_control_time = mg_time();\n}\n\nvoid mg_send_mqtt_handshake(struct mg_connection *nc, const char *client_id) {\n  static struct mg_send_mqtt_handshake_opts opts;\n  mg_send_mqtt_handshake_opt(nc, client_id, opts);\n}\n\nvoid mg_send_mqtt_handshake_opt(struct mg_connection *nc, const char *client_id,\n                                struct mg_send_mqtt_handshake_opts opts) {\n  struct mg_mqtt_proto_data *pd = (struct mg_mqtt_proto_data *) nc->proto_data;\n  uint16_t id_len = 0, wt_len = 0, wm_len = 0, user_len = 0, pw_len = 0;\n  uint16_t netbytes;\n  size_t total_len;\n\n  if (client_id != NULL) {\n    id_len = strlen(client_id);\n  }\n\n  total_len = 7 + 1 + 2 + 2 + id_len;\n\n  if (opts.user_name != NULL) {\n    opts.flags |= MG_MQTT_HAS_USER_NAME;\n  }\n  if (opts.password != NULL) {\n    opts.flags |= MG_MQTT_HAS_PASSWORD;\n  }\n  if (opts.will_topic != NULL && opts.will_message != NULL) {\n    wt_len = strlen(opts.will_topic);\n    wm_len = strlen(opts.will_message);\n    opts.flags |= MG_MQTT_HAS_WILL;\n  }\n  if (opts.keep_alive == 0) {\n    opts.keep_alive = 60;\n  }\n\n  if (opts.flags & MG_MQTT_HAS_WILL) {\n    total_len += 2 + wt_len + 2 + wm_len;\n  }\n  if (opts.flags & MG_MQTT_HAS_USER_NAME) {\n    user_len = strlen(opts.user_name);\n    total_len += 2 + user_len;\n  }\n  if (opts.flags & MG_MQTT_HAS_PASSWORD) {\n    pw_len = strlen(opts.password);\n    total_len += 2 + pw_len;\n  }\n\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_CONNECT, 0, total_len);\n  mg_send(nc, \"\\00\\04MQTT\\04\", 7);\n  mg_send(nc, &opts.flags, 1);\n\n  netbytes = htons(opts.keep_alive);\n  mg_send(nc, &netbytes, 2);\n\n  netbytes = htons(id_len);\n  mg_send(nc, &netbytes, 2);\n  mg_send(nc, client_id, id_len);\n\n  if (opts.flags & MG_MQTT_HAS_WILL) {\n    netbytes = htons(wt_len);\n    mg_send(nc, &netbytes, 2);\n    mg_send(nc, opts.will_topic, wt_len);\n\n    netbytes = htons(wm_len);\n    mg_send(nc, &netbytes, 2);\n    mg_send(nc, opts.will_message, wm_len);\n  }\n\n  if (opts.flags & MG_MQTT_HAS_USER_NAME) {\n    netbytes = htons(user_len);\n    mg_send(nc, &netbytes, 2);\n    mg_send(nc, opts.user_name, user_len);\n  }\n  if (opts.flags & MG_MQTT_HAS_PASSWORD) {\n    netbytes = htons(pw_len);\n    mg_send(nc, &netbytes, 2);\n    mg_send(nc, opts.password, pw_len);\n  }\n\n  if (pd != NULL) {\n    pd->keep_alive = opts.keep_alive;\n  }\n}\n\nvoid mg_mqtt_publish(struct mg_connection *nc, const char *topic,\n                     uint16_t message_id, int flags, const void *data,\n                     size_t len) {\n  uint16_t netbytes;\n  uint16_t topic_len = strlen(topic);\n\n  size_t total_len = 2 + topic_len + len;\n  if (MG_MQTT_GET_QOS(flags) > 0) {\n    total_len += 2;\n  }\n\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_PUBLISH, flags, total_len);\n\n  netbytes = htons(topic_len);\n  mg_send(nc, &netbytes, 2);\n  mg_send(nc, topic, topic_len);\n\n  if (MG_MQTT_GET_QOS(flags) > 0) {\n    netbytes = htons(message_id);\n    mg_send(nc, &netbytes, 2);\n  }\n\n  mg_send(nc, data, len);\n}\n\nvoid mg_mqtt_subscribe(struct mg_connection *nc,\n                       const struct mg_mqtt_topic_expression *topics,\n                       size_t topics_len, uint16_t message_id) {\n  uint16_t netbytes;\n  size_t i;\n  uint16_t topic_len;\n  size_t total_len = 2;\n\n  for (i = 0; i < topics_len; i++) {\n    total_len += 2 + strlen(topics[i].topic) + 1;\n  }\n\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_SUBSCRIBE, MG_MQTT_QOS(1), total_len);\n\n  netbytes = htons(message_id);\n  mg_send(nc, (char *) &netbytes, 2);\n\n  for (i = 0; i < topics_len; i++) {\n    topic_len = strlen(topics[i].topic);\n    netbytes = htons(topic_len);\n    mg_send(nc, &netbytes, 2);\n    mg_send(nc, topics[i].topic, topic_len);\n    mg_send(nc, &topics[i].qos, 1);\n  }\n}\n\nint mg_mqtt_next_subscribe_topic(struct mg_mqtt_message *msg,\n                                 struct mg_str *topic, uint8_t *qos, int pos) {\n  unsigned char *buf = (unsigned char *) msg->payload.p + pos;\n  int new_pos;\n\n  if ((size_t) pos >= msg->payload.len) return -1;\n\n  topic->len = buf[0] << 8 | buf[1];\n  topic->p = (char *) buf + 2;\n  new_pos = pos + 2 + topic->len + 1;\n  if ((size_t) new_pos > msg->payload.len) return -1;\n  *qos = buf[2 + topic->len];\n  return new_pos;\n}\n\nvoid mg_mqtt_unsubscribe(struct mg_connection *nc, char **topics,\n                         size_t topics_len, uint16_t message_id) {\n  uint16_t netbytes;\n  size_t i;\n  uint16_t topic_len;\n  size_t total_len = 2;\n\n  for (i = 0; i < topics_len; i++) {\n    total_len += 2 + strlen(topics[i]);\n  }\n\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_UNSUBSCRIBE, MG_MQTT_QOS(1), total_len);\n\n  netbytes = htons(message_id);\n  mg_send(nc, (char *) &netbytes, 2);\n\n  for (i = 0; i < topics_len; i++) {\n    topic_len = strlen(topics[i]);\n    netbytes = htons(topic_len);\n    mg_send(nc, &netbytes, 2);\n    mg_send(nc, topics[i], topic_len);\n  }\n}\n\nvoid mg_mqtt_connack(struct mg_connection *nc, uint8_t return_code) {\n  uint8_t unused = 0;\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_CONNACK, 0, 2);\n  mg_send(nc, &unused, 1);\n  mg_send(nc, &return_code, 1);\n}\n\n/*\n * Sends a command which contains only a `message_id` and a QoS level of 1.\n *\n * Helper function.\n */\nstatic void mg_send_mqtt_short_command(struct mg_connection *nc, uint8_t cmd,\n                                       uint16_t message_id) {\n  uint16_t netbytes;\n  uint8_t flags = (cmd == MG_MQTT_CMD_PUBREL ? 2 : 0);\n\n  mg_send_mqtt_header(nc, cmd, flags, 2 /* len */);\n\n  netbytes = htons(message_id);\n  mg_send(nc, &netbytes, 2);\n}\n\nvoid mg_mqtt_puback(struct mg_connection *nc, uint16_t message_id) {\n  mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBACK, message_id);\n}\n\nvoid mg_mqtt_pubrec(struct mg_connection *nc, uint16_t message_id) {\n  mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBREC, message_id);\n}\n\nvoid mg_mqtt_pubrel(struct mg_connection *nc, uint16_t message_id) {\n  mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBREL, message_id);\n}\n\nvoid mg_mqtt_pubcomp(struct mg_connection *nc, uint16_t message_id) {\n  mg_send_mqtt_short_command(nc, MG_MQTT_CMD_PUBCOMP, message_id);\n}\n\nvoid mg_mqtt_suback(struct mg_connection *nc, uint8_t *qoss, size_t qoss_len,\n                    uint16_t message_id) {\n  size_t i;\n  uint16_t netbytes;\n\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_SUBACK, MG_MQTT_QOS(1), 2 + qoss_len);\n\n  netbytes = htons(message_id);\n  mg_send(nc, &netbytes, 2);\n\n  for (i = 0; i < qoss_len; i++) {\n    mg_send(nc, &qoss[i], 1);\n  }\n}\n\nvoid mg_mqtt_unsuback(struct mg_connection *nc, uint16_t message_id) {\n  mg_send_mqtt_short_command(nc, MG_MQTT_CMD_UNSUBACK, message_id);\n}\n\nvoid mg_mqtt_ping(struct mg_connection *nc) {\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_PINGREQ, 0, 0);\n}\n\nvoid mg_mqtt_pong(struct mg_connection *nc) {\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_PINGRESP, 0, 0);\n}\n\nvoid mg_mqtt_disconnect(struct mg_connection *nc) {\n  mg_send_mqtt_header(nc, MG_MQTT_CMD_DISCONNECT, 0, 0);\n}\n\n#endif /* MG_ENABLE_MQTT */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_mqtt_server.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_mqtt_server.h\" */\n\n#if MG_ENABLE_MQTT_BROKER\n\nstatic void mg_mqtt_session_init(struct mg_mqtt_broker *brk,\n                                 struct mg_mqtt_session *s,\n                                 struct mg_connection *nc) {\n  s->brk = brk;\n  s->subscriptions = NULL;\n  s->num_subscriptions = 0;\n  s->nc = nc;\n}\n\nstatic void mg_mqtt_add_session(struct mg_mqtt_session *s) {\n  LIST_INSERT_HEAD(&s->brk->sessions, s, link);\n}\n\nstatic void mg_mqtt_remove_session(struct mg_mqtt_session *s) {\n  LIST_REMOVE(s, link);\n}\n\nstatic void mg_mqtt_destroy_session(struct mg_mqtt_session *s) {\n  size_t i;\n  for (i = 0; i < s->num_subscriptions; i++) {\n    MG_FREE((void *) s->subscriptions[i].topic);\n  }\n  MG_FREE(s->subscriptions);\n  MG_FREE(s);\n}\n\nstatic void mg_mqtt_close_session(struct mg_mqtt_session *s) {\n  mg_mqtt_remove_session(s);\n  mg_mqtt_destroy_session(s);\n}\n\nvoid mg_mqtt_broker_init(struct mg_mqtt_broker *brk, void *user_data) {\n  LIST_INIT(&brk->sessions);\n  brk->user_data = user_data;\n}\n\nstatic void mg_mqtt_broker_handle_connect(struct mg_mqtt_broker *brk,\n                                          struct mg_connection *nc) {\n  struct mg_mqtt_session *s =\n      (struct mg_mqtt_session *) MG_CALLOC(1, sizeof *s);\n  if (s == NULL) {\n    /* LCOV_EXCL_START */\n    mg_mqtt_connack(nc, MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE);\n    return;\n    /* LCOV_EXCL_STOP */\n  }\n\n  /* TODO(mkm): check header (magic and version) */\n\n  mg_mqtt_session_init(brk, s, nc);\n  nc->priv_2 = s;\n  mg_mqtt_add_session(s);\n\n  mg_mqtt_connack(nc, MG_EV_MQTT_CONNACK_ACCEPTED);\n}\n\nstatic void mg_mqtt_broker_handle_subscribe(struct mg_connection *nc,\n                                            struct mg_mqtt_message *msg) {\n  struct mg_mqtt_session *ss = (struct mg_mqtt_session *) nc->priv_2;\n  uint8_t qoss[MG_MQTT_MAX_SESSION_SUBSCRIPTIONS];\n  size_t num_subs = 0;\n  struct mg_str topic;\n  uint8_t qos;\n  int pos;\n  struct mg_mqtt_topic_expression *te;\n\n  for (pos = 0;\n       (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;) {\n    if (num_subs >= sizeof(MG_MQTT_MAX_SESSION_SUBSCRIPTIONS) ||\n        (ss->num_subscriptions + num_subs >=\n         MG_MQTT_MAX_SESSION_SUBSCRIPTIONS)) {\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      return;\n    }\n    qoss[num_subs++] = qos;\n  }\n\n  if (num_subs > 0) {\n    te = (struct mg_mqtt_topic_expression *) MG_REALLOC(\n        ss->subscriptions,\n        sizeof(*ss->subscriptions) * (ss->num_subscriptions + num_subs));\n    if (te == NULL) {\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      return;\n    }\n    ss->subscriptions = te;\n    for (pos = 0;\n         pos < (int) msg->payload.len &&\n             (pos = mg_mqtt_next_subscribe_topic(msg, &topic, &qos, pos)) != -1;\n         ss->num_subscriptions++) {\n      te = &ss->subscriptions[ss->num_subscriptions];\n      te->topic = (char *) MG_MALLOC(topic.len + 1);\n      te->qos = qos;\n      memcpy((char *) te->topic, topic.p, topic.len);\n      ((char *) te->topic)[topic.len] = '\\0';\n    }\n  }\n\n  if (pos == (int) msg->payload.len) {\n    mg_mqtt_suback(nc, qoss, num_subs, msg->message_id);\n  } else {\n    /* We did not fully parse the payload, something must be wrong. */\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  }\n}\n\nstatic void mg_mqtt_broker_handle_publish(struct mg_mqtt_broker *brk,\n                                          struct mg_mqtt_message *msg) {\n  struct mg_mqtt_session *s;\n  size_t i;\n\n  for (s = mg_mqtt_next(brk, NULL); s != NULL; s = mg_mqtt_next(brk, s)) {\n    for (i = 0; i < s->num_subscriptions; i++) {\n      if (mg_mqtt_vmatch_topic_expression(s->subscriptions[i].topic,\n                                          msg->topic)) {\n        char buf[100], *p = buf;\n        mg_asprintf(&p, sizeof(buf), \"%.*s\", (int) msg->topic.len,\n                    msg->topic.p);\n        if (p == NULL) {\n          return;\n        }\n        mg_mqtt_publish(s->nc, p, 0, 0, msg->payload.p, msg->payload.len);\n        if (p != buf) {\n          MG_FREE(p);\n        }\n        break;\n      }\n    }\n  }\n}\n\nvoid mg_mqtt_broker(struct mg_connection *nc, int ev, void *data) {\n  struct mg_mqtt_message *msg = (struct mg_mqtt_message *) data;\n  struct mg_mqtt_broker *brk;\n\n  if (nc->listener) {\n    brk = (struct mg_mqtt_broker *) nc->listener->priv_2;\n  } else {\n    brk = (struct mg_mqtt_broker *) nc->priv_2;\n  }\n\n  switch (ev) {\n    case MG_EV_ACCEPT:\n      if (nc->proto_data == NULL) mg_set_protocol_mqtt(nc);\n      nc->priv_2 = NULL; /* Clear up the inherited pointer to broker */\n      break;\n    case MG_EV_MQTT_CONNECT:\n      if (nc->priv_2 == NULL) {\n        mg_mqtt_broker_handle_connect(brk, nc);\n      } else {\n        /* Repeated CONNECT */\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      }\n      break;\n    case MG_EV_MQTT_SUBSCRIBE:\n      if (nc->priv_2 != NULL) {\n        mg_mqtt_broker_handle_subscribe(nc, msg);\n      } else {\n        /* Subscribe before CONNECT */\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      }\n      break;\n    case MG_EV_MQTT_PUBLISH:\n      if (nc->priv_2 != NULL) {\n        mg_mqtt_broker_handle_publish(brk, msg);\n      } else {\n        /* Publish before CONNECT */\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      }\n      break;\n    case MG_EV_CLOSE:\n      if (nc->listener && nc->priv_2 != NULL) {\n        mg_mqtt_close_session((struct mg_mqtt_session *) nc->priv_2);\n      }\n      break;\n  }\n}\n\nstruct mg_mqtt_session *mg_mqtt_next(struct mg_mqtt_broker *brk,\n                                     struct mg_mqtt_session *s) {\n  return s == NULL ? LIST_FIRST(&brk->sessions) : LIST_NEXT(s, link);\n}\n\n#endif /* MG_ENABLE_MQTT_BROKER */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_dns.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_DNS\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_dns.h\" */\n\nstatic int mg_dns_tid = 0xa0;\n\nstruct mg_dns_header {\n  uint16_t transaction_id;\n  uint16_t flags;\n  uint16_t num_questions;\n  uint16_t num_answers;\n  uint16_t num_authority_prs;\n  uint16_t num_other_prs;\n};\n\nstruct mg_dns_resource_record *mg_dns_next_record(\n    struct mg_dns_message *msg, int query,\n    struct mg_dns_resource_record *prev) {\n  struct mg_dns_resource_record *rr;\n\n  for (rr = (prev == NULL ? msg->answers : prev + 1);\n       rr - msg->answers < msg->num_answers; rr++) {\n    if (rr->rtype == query) {\n      return rr;\n    }\n  }\n  return NULL;\n}\n\nint mg_dns_parse_record_data(struct mg_dns_message *msg,\n                             struct mg_dns_resource_record *rr, void *data,\n                             size_t data_len) {\n  switch (rr->rtype) {\n    case MG_DNS_A_RECORD:\n      if (data_len < sizeof(struct in_addr)) {\n        return -1;\n      }\n      if (rr->rdata.p + data_len > msg->pkt.p + msg->pkt.len) {\n        return -1;\n      }\n      memcpy(data, rr->rdata.p, data_len);\n      return 0;\n#if MG_ENABLE_IPV6\n    case MG_DNS_AAAA_RECORD:\n      if (data_len < sizeof(struct in6_addr)) {\n        return -1; /* LCOV_EXCL_LINE */\n      }\n      memcpy(data, rr->rdata.p, data_len);\n      return 0;\n#endif\n    case MG_DNS_CNAME_RECORD:\n      mg_dns_uncompress_name(msg, &rr->rdata, (char *) data, data_len);\n      return 0;\n  }\n\n  return -1;\n}\n\nint mg_dns_insert_header(struct mbuf *io, size_t pos,\n                         struct mg_dns_message *msg) {\n  struct mg_dns_header header;\n\n  memset(&header, 0, sizeof(header));\n  header.transaction_id = msg->transaction_id;\n  header.flags = htons(msg->flags);\n  header.num_questions = htons(msg->num_questions);\n  header.num_answers = htons(msg->num_answers);\n\n  return mbuf_insert(io, pos, &header, sizeof(header));\n}\n\nint mg_dns_copy_questions(struct mbuf *io, struct mg_dns_message *msg) {\n  unsigned char *begin, *end;\n  struct mg_dns_resource_record *last_q;\n  if (msg->num_questions <= 0) return 0;\n  begin = (unsigned char *) msg->pkt.p + sizeof(struct mg_dns_header);\n  last_q = &msg->questions[msg->num_questions - 1];\n  end = (unsigned char *) last_q->name.p + last_q->name.len + 4;\n  return mbuf_append(io, begin, end - begin);\n}\n\nint mg_dns_encode_name(struct mbuf *io, const char *name, size_t len) {\n  const char *s;\n  unsigned char n;\n  size_t pos = io->len;\n\n  do {\n    if ((s = strchr(name, '.')) == NULL) {\n      s = name + len;\n    }\n\n    if (s - name > 127) {\n      return -1; /* TODO(mkm) cover */\n    }\n    n = s - name;           /* chunk length */\n    mbuf_append(io, &n, 1); /* send length */\n    mbuf_append(io, name, n);\n\n    if (*s == '.') {\n      n++;\n    }\n\n    name += n;\n    len -= n;\n  } while (*s != '\\0');\n  mbuf_append(io, \"\\0\", 1); /* Mark end of host name */\n\n  return io->len - pos;\n}\n\nint mg_dns_encode_record(struct mbuf *io, struct mg_dns_resource_record *rr,\n                         const char *name, size_t nlen, const void *rdata,\n                         size_t rlen) {\n  size_t pos = io->len;\n  uint16_t u16;\n  uint32_t u32;\n\n  if (rr->kind == MG_DNS_INVALID_RECORD) {\n    return -1; /* LCOV_EXCL_LINE */\n  }\n\n  if (mg_dns_encode_name(io, name, nlen) == -1) {\n    return -1;\n  }\n\n  u16 = htons(rr->rtype);\n  mbuf_append(io, &u16, 2);\n  u16 = htons(rr->rclass);\n  mbuf_append(io, &u16, 2);\n\n  if (rr->kind == MG_DNS_ANSWER) {\n    u32 = htonl(rr->ttl);\n    mbuf_append(io, &u32, 4);\n\n    if (rr->rtype == MG_DNS_CNAME_RECORD) {\n      int clen;\n      /* fill size after encoding */\n      size_t off = io->len;\n      mbuf_append(io, &u16, 2);\n      if ((clen = mg_dns_encode_name(io, (const char *) rdata, rlen)) == -1) {\n        return -1;\n      }\n      u16 = clen;\n      io->buf[off] = u16 >> 8;\n      io->buf[off + 1] = u16 & 0xff;\n    } else {\n      u16 = htons((uint16_t) rlen);\n      mbuf_append(io, &u16, 2);\n      mbuf_append(io, rdata, rlen);\n    }\n  }\n\n  return io->len - pos;\n}\n\nvoid mg_send_dns_query(struct mg_connection *nc, const char *name,\n                       int query_type) {\n  struct mg_dns_message *msg =\n      (struct mg_dns_message *) MG_CALLOC(1, sizeof(*msg));\n  struct mbuf pkt;\n  struct mg_dns_resource_record *rr = &msg->questions[0];\n\n  DBG((\"%s %d\", name, query_type));\n\n  mbuf_init(&pkt, 64 /* Start small, it'll grow as needed. */);\n\n  msg->transaction_id = ++mg_dns_tid;\n  msg->flags = 0x100;\n  msg->num_questions = 1;\n\n  mg_dns_insert_header(&pkt, 0, msg);\n\n  rr->rtype = query_type;\n  rr->rclass = 1; /* Class: inet */\n  rr->kind = MG_DNS_QUESTION;\n\n  if (mg_dns_encode_record(&pkt, rr, name, strlen(name), NULL, 0) == -1) {\n    /* TODO(mkm): return an error code */\n    goto cleanup; /* LCOV_EXCL_LINE */\n  }\n\n  /* TCP DNS requires messages to be prefixed with len */\n  if (!(nc->flags & MG_F_UDP)) {\n    uint16_t len = htons((uint16_t) pkt.len);\n    mbuf_insert(&pkt, 0, &len, 2);\n  }\n\n  mg_send(nc, pkt.buf, pkt.len);\n  mbuf_free(&pkt);\n\ncleanup:\n  MG_FREE(msg);\n}\n\nstatic unsigned char *mg_parse_dns_resource_record(\n    unsigned char *data, unsigned char *end, struct mg_dns_resource_record *rr,\n    int reply) {\n  unsigned char *name = data;\n  int chunk_len, data_len;\n\n  while (data < end && (chunk_len = *data)) {\n    if (((unsigned char *) data)[0] & 0xc0) {\n      data += 1;\n      break;\n    }\n    data += chunk_len + 1;\n  }\n\n  if (data > end - 5) {\n    return NULL;\n  }\n\n  rr->name.p = (char *) name;\n  rr->name.len = data - name + 1;\n  data++;\n\n  rr->rtype = data[0] << 8 | data[1];\n  data += 2;\n\n  rr->rclass = data[0] << 8 | data[1];\n  data += 2;\n\n  rr->kind = reply ? MG_DNS_ANSWER : MG_DNS_QUESTION;\n  if (reply) {\n    if (data >= end - 6) {\n      return NULL;\n    }\n\n    rr->ttl = (uint32_t) data[0] << 24 | (uint32_t) data[1] << 16 |\n              data[2] << 8 | data[3];\n    data += 4;\n\n    data_len = *data << 8 | *(data + 1);\n    data += 2;\n\n    rr->rdata.p = (char *) data;\n    rr->rdata.len = data_len;\n    data += data_len;\n  }\n  return data;\n}\n\nint mg_parse_dns(const char *buf, int len, struct mg_dns_message *msg) {\n  struct mg_dns_header *header = (struct mg_dns_header *) buf;\n  unsigned char *data = (unsigned char *) buf + sizeof(*header);\n  unsigned char *end = (unsigned char *) buf + len;\n  int i;\n\n  memset(msg, 0, sizeof(*msg));\n  msg->pkt.p = buf;\n  msg->pkt.len = len;\n\n  if (len < (int) sizeof(*header)) return -1;\n\n  msg->transaction_id = header->transaction_id;\n  msg->flags = ntohs(header->flags);\n  msg->num_questions = ntohs(header->num_questions);\n  if (msg->num_questions > (int) ARRAY_SIZE(msg->questions)) {\n    msg->num_questions = (int) ARRAY_SIZE(msg->questions);\n  }\n  msg->num_answers = ntohs(header->num_answers);\n  if (msg->num_answers > (int) ARRAY_SIZE(msg->answers)) {\n    msg->num_answers = (int) ARRAY_SIZE(msg->answers);\n  }\n\n  for (i = 0; i < msg->num_questions; i++) {\n    data = mg_parse_dns_resource_record(data, end, &msg->questions[i], 0);\n    if (data == NULL) return -1;\n  }\n\n  for (i = 0; i < msg->num_answers; i++) {\n    data = mg_parse_dns_resource_record(data, end, &msg->answers[i], 1);\n    if (data == NULL) return -1;\n  }\n\n  return 0;\n}\n\nsize_t mg_dns_uncompress_name(struct mg_dns_message *msg, struct mg_str *name,\n                              char *dst, int dst_len) {\n  int chunk_len, num_ptrs = 0;\n  char *old_dst = dst;\n  const unsigned char *data = (unsigned char *) name->p;\n  const unsigned char *end = (unsigned char *) msg->pkt.p + msg->pkt.len;\n\n  if (data >= end) {\n    return 0;\n  }\n\n  while ((chunk_len = *data++)) {\n    int leeway = dst_len - (dst - old_dst);\n    if (data >= end) {\n      return 0;\n    }\n\n    if ((chunk_len & 0xc0) == 0xc0) {\n      uint16_t off = (data[-1] & (~0xc0)) << 8 | data[0];\n      if (off >= msg->pkt.len) {\n        return 0;\n      }\n      /* Basic circular loop avoidance: allow up to 16 pointer hops. */\n      if (++num_ptrs > 15) {\n        return 0;\n      }\n      data = (unsigned char *) msg->pkt.p + off;\n      continue;\n    }\n    if (chunk_len > 63) {\n      return 0;\n    }\n    if (chunk_len > leeway) {\n      chunk_len = leeway;\n    }\n\n    if (data + chunk_len >= end) {\n      return 0;\n    }\n\n    memcpy(dst, data, chunk_len);\n    data += chunk_len;\n    dst += chunk_len;\n    leeway -= chunk_len;\n    if (leeway == 0) {\n      return dst - old_dst;\n    }\n    *dst++ = '.';\n  }\n\n  if (dst != old_dst) {\n    *--dst = 0;\n  }\n  return dst - old_dst;\n}\n\nstatic void dns_handler(struct mg_connection *nc, int ev,\n                        void *ev_data MG_UD_ARG(void *user_data)) {\n  struct mbuf *io = &nc->recv_mbuf;\n  struct mg_dns_message msg;\n\n  /* Pass low-level events to the user handler */\n  nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));\n\n  switch (ev) {\n    case MG_EV_RECV:\n      if (!(nc->flags & MG_F_UDP)) {\n        mbuf_remove(&nc->recv_mbuf, 2);\n      }\n      if (mg_parse_dns(nc->recv_mbuf.buf, nc->recv_mbuf.len, &msg) == -1) {\n        /* reply + recursion allowed + format error */\n        memset(&msg, 0, sizeof(msg));\n        msg.flags = 0x8081;\n        mg_dns_insert_header(io, 0, &msg);\n        if (!(nc->flags & MG_F_UDP)) {\n          uint16_t len = htons((uint16_t) io->len);\n          mbuf_insert(io, 0, &len, 2);\n        }\n        mg_send(nc, io->buf, io->len);\n      } else {\n        /* Call user handler with parsed message */\n        nc->handler(nc, MG_DNS_MESSAGE, &msg MG_UD_ARG(user_data));\n      }\n      mbuf_remove(io, io->len);\n      break;\n  }\n}\n\nvoid mg_set_protocol_dns(struct mg_connection *nc) {\n  nc->proto_handler = dns_handler;\n}\n\n#endif /* MG_ENABLE_DNS */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_dns_server.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_DNS_SERVER\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"dns-server.h\" */\n\nstruct mg_dns_reply mg_dns_create_reply(struct mbuf *io,\n                                        struct mg_dns_message *msg) {\n  struct mg_dns_reply rep;\n  rep.msg = msg;\n  rep.io = io;\n  rep.start = io->len;\n\n  /* reply + recursion allowed */\n  msg->flags |= 0x8080;\n  mg_dns_copy_questions(io, msg);\n\n  msg->num_answers = 0;\n  return rep;\n}\n\nvoid mg_dns_send_reply(struct mg_connection *nc, struct mg_dns_reply *r) {\n  size_t sent = r->io->len - r->start;\n  mg_dns_insert_header(r->io, r->start, r->msg);\n  if (!(nc->flags & MG_F_UDP)) {\n    uint16_t len = htons((uint16_t) sent);\n    mbuf_insert(r->io, r->start, &len, 2);\n  }\n\n  if (&nc->send_mbuf != r->io) {\n    mg_send(nc, r->io->buf + r->start, r->io->len - r->start);\n    r->io->len = r->start;\n  }\n}\n\nint mg_dns_reply_record(struct mg_dns_reply *reply,\n                        struct mg_dns_resource_record *question,\n                        const char *name, int rtype, int ttl, const void *rdata,\n                        size_t rdata_len) {\n  struct mg_dns_message *msg = (struct mg_dns_message *) reply->msg;\n  char rname[512];\n  struct mg_dns_resource_record *ans = &msg->answers[msg->num_answers];\n  if (msg->num_answers >= MG_MAX_DNS_ANSWERS) {\n    return -1; /* LCOV_EXCL_LINE */\n  }\n\n  if (name == NULL) {\n    name = rname;\n    rname[511] = 0;\n    mg_dns_uncompress_name(msg, &question->name, rname, sizeof(rname) - 1);\n  }\n\n  *ans = *question;\n  ans->kind = MG_DNS_ANSWER;\n  ans->rtype = rtype;\n  ans->ttl = ttl;\n\n  if (mg_dns_encode_record(reply->io, ans, name, strlen(name), rdata,\n                           rdata_len) == -1) {\n    return -1; /* LCOV_EXCL_LINE */\n  };\n\n  msg->num_answers++;\n  return 0;\n}\n\n#endif /* MG_ENABLE_DNS_SERVER */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_resolv.c\"\n#endif\n/*\n * Copyright (c) 2014 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_ASYNC_RESOLVER\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_resolv.h\" */\n\n#ifndef MG_DEFAULT_NAMESERVER\n#define MG_DEFAULT_NAMESERVER \"8.8.8.8\"\n#endif\n\nstruct mg_resolve_async_request {\n  char name[1024];\n  int query;\n  mg_resolve_callback_t callback;\n  void *data;\n  time_t timeout;\n  int max_retries;\n  enum mg_resolve_err err;\n\n  /* state */\n  time_t last_time;\n  int retries;\n};\n\n/*\n * Find what nameserver to use.\n *\n * Return 0 if OK, -1 if error\n */\nstatic int mg_get_ip_address_of_nameserver(char *name, size_t name_len) {\n  int ret = -1;\n\n#ifdef _WIN32\n  int i;\n  LONG err;\n  HKEY hKey, hSub;\n  wchar_t subkey[512], value[128],\n      *key = L\"SYSTEM\\\\ControlSet001\\\\Services\\\\Tcpip\\\\Parameters\\\\Interfaces\";\n\n  if ((err = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key, 0, KEY_READ, &hKey)) !=\n      ERROR_SUCCESS) {\n    fprintf(stderr, \"cannot open reg key %S: %ld\\n\", key, err);\n    ret = -1;\n  } else {\n    for (ret = -1, i = 0; 1; i++) {\n      DWORD subkey_size = sizeof(subkey), type, len = sizeof(value);\n      if (RegEnumKeyExW(hKey, i, subkey, &subkey_size, NULL, NULL, NULL,\n                        NULL) != ERROR_SUCCESS) {\n        break;\n      }\n      if (RegOpenKeyExW(hKey, subkey, 0, KEY_READ, &hSub) == ERROR_SUCCESS &&\n          ((RegQueryValueExW(hSub, L\"NameServer\", 0, &type, (void *) value,\n                             &len) == ERROR_SUCCESS &&\n            value[0] != '\\0') ||\n           (RegQueryValueExW(hSub, L\"DhcpNameServer\", 0, &type, (void *) value,\n                             &len) == ERROR_SUCCESS &&\n            value[0] != '\\0'))) {\n        /*\n         * See https://github.com/cesanta/mongoose/issues/176\n         * The value taken from the registry can be empty, a single\n         * IP address, or multiple IP addresses separated by comma.\n         * If it's empty, check the next interface.\n         * If it's multiple IP addresses, take the first one.\n         */\n        wchar_t *comma = wcschr(value, ',');\n        if (comma != NULL) {\n          *comma = '\\0';\n        }\n        /* %S will convert wchar_t -> char */\n        snprintf(name, name_len, \"%S\", value);\n        ret = 0;\n        RegCloseKey(hSub);\n        break;\n      }\n    }\n    RegCloseKey(hKey);\n  }\n#elif MG_ENABLE_FILESYSTEM && defined(MG_RESOLV_CONF_FILE_NAME)\n  FILE *fp;\n  char line[512];\n\n  if ((fp = mg_fopen(MG_RESOLV_CONF_FILE_NAME, \"r\")) == NULL) {\n    ret = -1;\n  } else {\n    /* Try to figure out what nameserver to use */\n    for (ret = -1; fgets(line, sizeof(line), fp) != NULL;) {\n      unsigned int a, b, c, d;\n      if (sscanf(line, \"nameserver %u.%u.%u.%u\", &a, &b, &c, &d) == 4) {\n        snprintf(name, name_len, \"%u.%u.%u.%u\", a, b, c, d);\n        ret = 0;\n        break;\n      }\n    }\n    (void) fclose(fp);\n  }\n#else\n  snprintf(name, name_len, \"%s\", MG_DEFAULT_NAMESERVER);\n#endif /* _WIN32 */\n\n  return ret;\n}\n\nint mg_resolve_from_hosts_file(const char *name, union socket_address *usa) {\n#if MG_ENABLE_FILESYSTEM && defined(MG_HOSTS_FILE_NAME)\n  /* TODO(mkm) cache /etc/hosts */\n  FILE *fp;\n  char line[1024];\n  char *p;\n  char alias[256];\n  unsigned int a, b, c, d;\n  int len = 0;\n\n  if ((fp = mg_fopen(MG_HOSTS_FILE_NAME, \"r\")) == NULL) {\n    return -1;\n  }\n\n  for (; fgets(line, sizeof(line), fp) != NULL;) {\n    if (line[0] == '#') continue;\n\n    if (sscanf(line, \"%u.%u.%u.%u%n\", &a, &b, &c, &d, &len) == 0) {\n      /* TODO(mkm): handle ipv6 */\n      continue;\n    }\n    for (p = line + len; sscanf(p, \"%s%n\", alias, &len) == 1; p += len) {\n      if (strcmp(alias, name) == 0) {\n        usa->sin.sin_addr.s_addr = htonl(a << 24 | b << 16 | c << 8 | d);\n        fclose(fp);\n        return 0;\n      }\n    }\n  }\n\n  fclose(fp);\n#else\n  (void) name;\n  (void) usa;\n#endif\n\n  return -1;\n}\n\nstatic void mg_resolve_async_eh(struct mg_connection *nc, int ev,\n                                void *data MG_UD_ARG(void *user_data)) {\n  time_t now = (time_t) mg_time();\n  struct mg_resolve_async_request *req;\n  struct mg_dns_message *msg;\n#if !MG_ENABLE_CALLBACK_USERDATA\n  void *user_data = nc->user_data;\n#endif\n\n  if (ev != MG_EV_POLL) {\n    DBG((\"ev=%d user_data=%p\", ev, user_data));\n  }\n\n  req = (struct mg_resolve_async_request *) user_data;\n\n  if (req == NULL) {\n    return;\n  }\n\n  switch (ev) {\n    case MG_EV_POLL:\n      if (req->retries > req->max_retries) {\n        req->err = MG_RESOLVE_EXCEEDED_RETRY_COUNT;\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        break;\n      }\n      if (nc->flags & MG_F_CONNECTING) break;\n    /* fallthrough */\n    case MG_EV_CONNECT:\n      if (req->retries == 0 || now - req->last_time >= req->timeout) {\n        mg_send_dns_query(nc, req->name, req->query);\n        req->last_time = now;\n        req->retries++;\n      }\n      break;\n    case MG_EV_RECV:\n      msg = (struct mg_dns_message *) MG_MALLOC(sizeof(*msg));\n      if (mg_parse_dns(nc->recv_mbuf.buf, *(int *) data, msg) == 0 &&\n          msg->num_answers > 0) {\n        req->callback(msg, req->data, MG_RESOLVE_OK);\n        nc->user_data = NULL;\n        MG_FREE(req);\n      } else {\n        req->err = MG_RESOLVE_NO_ANSWERS;\n      }\n      MG_FREE(msg);\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      break;\n    case MG_EV_SEND:\n      /*\n       * If a send error occurs, prevent closing of the connection by the core.\n       * We will retry after timeout.\n       */\n      nc->flags &= ~MG_F_CLOSE_IMMEDIATELY;\n      mbuf_remove(&nc->send_mbuf, nc->send_mbuf.len);\n      break;\n    case MG_EV_TIMER:\n      req->err = MG_RESOLVE_TIMEOUT;\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      break;\n    case MG_EV_CLOSE:\n      /* If we got here with request still not done, fire an error callback. */\n      if (req != NULL) {\n        char addr[32];\n        mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP);\n#ifdef MG_LOG_DNS_FAILURES\n        LOG(LL_ERROR, (\"Failed to resolve '%s', server %s\", req->name, addr));\n#endif\n        req->callback(NULL, req->data, req->err);\n        nc->user_data = NULL;\n        MG_FREE(req);\n      }\n      break;\n  }\n}\n\nint mg_resolve_async(struct mg_mgr *mgr, const char *name, int query,\n                     mg_resolve_callback_t cb, void *data) {\n  struct mg_resolve_async_opts opts;\n  memset(&opts, 0, sizeof(opts));\n  return mg_resolve_async_opt(mgr, name, query, cb, data, opts);\n}\n\nint mg_resolve_async_opt(struct mg_mgr *mgr, const char *name, int query,\n                         mg_resolve_callback_t cb, void *data,\n                         struct mg_resolve_async_opts opts) {\n  struct mg_resolve_async_request *req;\n  struct mg_connection *dns_nc;\n  const char *nameserver = opts.nameserver;\n  char dns_server_buff[17], nameserver_url[26];\n\n  if (nameserver == NULL) {\n    nameserver = mgr->nameserver;\n  }\n\n  DBG((\"%s %d %p\", name, query, opts.dns_conn));\n\n  /* resolve with DNS */\n  req = (struct mg_resolve_async_request *) MG_CALLOC(1, sizeof(*req));\n  if (req == NULL) {\n    return -1;\n  }\n\n  strncpy(req->name, name, sizeof(req->name));\n  req->name[sizeof(req->name) - 1] = '\\0';\n\n  req->query = query;\n  req->callback = cb;\n  req->data = data;\n  /* TODO(mkm): parse defaults out of resolve.conf */\n  req->max_retries = opts.max_retries ? opts.max_retries : 2;\n  req->timeout = opts.timeout ? opts.timeout : 5;\n\n  /* Lazily initialize dns server */\n  if (nameserver == NULL) {\n    if (mg_get_ip_address_of_nameserver(dns_server_buff,\n                                        sizeof(dns_server_buff)) != -1) {\n      nameserver = dns_server_buff;\n    } else {\n      nameserver = MG_DEFAULT_NAMESERVER;\n    }\n  }\n\n  snprintf(nameserver_url, sizeof(nameserver_url), \"udp://%s:53\", nameserver);\n\n  dns_nc = mg_connect(mgr, nameserver_url, MG_CB(mg_resolve_async_eh, NULL));\n  if (dns_nc == NULL) {\n    MG_FREE(req);\n    return -1;\n  }\n  dns_nc->user_data = req;\n  if (opts.dns_conn != NULL) {\n    *opts.dns_conn = dns_nc;\n  }\n\n  return 0;\n}\n\nvoid mg_set_nameserver(struct mg_mgr *mgr, const char *nameserver) {\n  MG_FREE((char *) mgr->nameserver);\n  mgr->nameserver = NULL;\n  if (nameserver != NULL) {\n    mgr->nameserver = strdup(nameserver);\n  }\n}\n\n#endif /* MG_ENABLE_ASYNC_RESOLVER */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_coap.c\"\n#endif\n/*\n * Copyright (c) 2015 Cesanta Software Limited\n * All rights reserved\n * This software is dual-licensed: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License version 2 as\n * published by the Free Software Foundation. For the terms of this\n * license, see <http://www.gnu.org/licenses/>.\n *\n * You are free to use this software under the terms of the GNU General\n * Public License, but WITHOUT ANY WARRANTY; without even the implied\n * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n * See the GNU General Public License for more details.\n *\n * Alternatively, you can license this software under a commercial\n * license, as set out in <https://www.cesanta.com/license>.\n */\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_coap.h\" */\n\n#if MG_ENABLE_COAP\n\nvoid mg_coap_free_options(struct mg_coap_message *cm) {\n  while (cm->options != NULL) {\n    struct mg_coap_option *next = cm->options->next;\n    MG_FREE(cm->options);\n    cm->options = next;\n  }\n}\n\nstruct mg_coap_option *mg_coap_add_option(struct mg_coap_message *cm,\n                                          uint32_t number, char *value,\n                                          size_t len) {\n  struct mg_coap_option *new_option =\n      (struct mg_coap_option *) MG_CALLOC(1, sizeof(*new_option));\n\n  new_option->number = number;\n  new_option->value.p = value;\n  new_option->value.len = len;\n\n  if (cm->options == NULL) {\n    cm->options = cm->optiomg_tail = new_option;\n  } else {\n    /*\n     * A very simple attention to help clients to compose options:\n     * CoAP wants to see options ASC ordered.\n     * Could be change by using sort in coap_compose\n     */\n    if (cm->optiomg_tail->number <= new_option->number) {\n      /* if option is already ordered just add it */\n      cm->optiomg_tail = cm->optiomg_tail->next = new_option;\n    } else {\n      /* looking for appropriate position */\n      struct mg_coap_option *current_opt = cm->options;\n      struct mg_coap_option *prev_opt = 0;\n\n      while (current_opt != NULL) {\n        if (current_opt->number > new_option->number) {\n          break;\n        }\n        prev_opt = current_opt;\n        current_opt = current_opt->next;\n      }\n\n      if (prev_opt != NULL) {\n        prev_opt->next = new_option;\n        new_option->next = current_opt;\n      } else {\n        /* insert new_option to the beginning */\n        new_option->next = cm->options;\n        cm->options = new_option;\n      }\n    }\n  }\n\n  return new_option;\n}\n\n/*\n * Fills CoAP header in mg_coap_message.\n *\n * Helper function.\n */\nstatic char *coap_parse_header(char *ptr, struct mbuf *io,\n                               struct mg_coap_message *cm) {\n  if (io->len < sizeof(uint32_t)) {\n    cm->flags |= MG_COAP_NOT_ENOUGH_DATA;\n    return NULL;\n  }\n\n  /*\n   * Version (Ver):  2-bit unsigned integer.  Indicates the CoAP version\n   * number.  Implementations of this specification MUST set this field\n   * to 1 (01 binary).  Other values are reserved for future versions.\n   * Messages with unknown version numbers MUST be silently ignored.\n   */\n  if (((uint8_t) *ptr >> 6) != 1) {\n    cm->flags |= MG_COAP_IGNORE;\n    return NULL;\n  }\n\n  /*\n   * Type (T):  2-bit unsigned integer.  Indicates if this message is of\n   * type Confirmable (0), Non-confirmable (1), Acknowledgement (2), or\n   * Reset (3).\n   */\n  cm->msg_type = ((uint8_t) *ptr & 0x30) >> 4;\n  cm->flags |= MG_COAP_MSG_TYPE_FIELD;\n\n  /*\n   * Token Length (TKL):  4-bit unsigned integer.  Indicates the length of\n   * the variable-length Token field (0-8 bytes).  Lengths 9-15 are\n   * reserved, MUST NOT be sent, and MUST be processed as a message\n   * format error.\n   */\n  cm->token.len = *ptr & 0x0F;\n  if (cm->token.len > 8) {\n    cm->flags |= MG_COAP_FORMAT_ERROR;\n    return NULL;\n  }\n\n  ptr++;\n\n  /*\n   * Code:  8-bit unsigned integer, split into a 3-bit class (most\n   * significant bits) and a 5-bit detail (least significant bits)\n   */\n  cm->code_class = (uint8_t) *ptr >> 5;\n  cm->code_detail = *ptr & 0x1F;\n  cm->flags |= (MG_COAP_CODE_CLASS_FIELD | MG_COAP_CODE_DETAIL_FIELD);\n\n  ptr++;\n\n  /* Message ID:  16-bit unsigned integer in network byte order. */\n  cm->msg_id = (uint8_t) *ptr << 8 | (uint8_t) * (ptr + 1);\n  cm->flags |= MG_COAP_MSG_ID_FIELD;\n\n  ptr += 2;\n\n  return ptr;\n}\n\n/*\n * Fills token information in mg_coap_message.\n *\n * Helper function.\n */\nstatic char *coap_get_token(char *ptr, struct mbuf *io,\n                            struct mg_coap_message *cm) {\n  if (cm->token.len != 0) {\n    if (ptr + cm->token.len > io->buf + io->len) {\n      cm->flags |= MG_COAP_NOT_ENOUGH_DATA;\n      return NULL;\n    } else {\n      cm->token.p = ptr;\n      ptr += cm->token.len;\n      cm->flags |= MG_COAP_TOKEN_FIELD;\n    }\n  }\n\n  return ptr;\n}\n\n/*\n * Returns Option Delta or Length.\n *\n * Helper function.\n */\nstatic int coap_get_ext_opt(char *ptr, struct mbuf *io, uint16_t *opt_info) {\n  int ret = 0;\n\n  if (*opt_info == 13) {\n    /*\n     * 13:  An 8-bit unsigned integer follows the initial byte and\n     * indicates the Option Delta/Length minus 13.\n     */\n    if (ptr < io->buf + io->len) {\n      *opt_info = (uint8_t) *ptr + 13;\n      ret = sizeof(uint8_t);\n    } else {\n      ret = -1; /* LCOV_EXCL_LINE */\n    }\n  } else if (*opt_info == 14) {\n    /*\n     * 14:  A 16-bit unsigned integer in network byte order follows the\n     * initial byte and indicates the Option Delta/Length minus 269.\n     */\n    if (ptr + sizeof(uint8_t) < io->buf + io->len) {\n      *opt_info = ((uint8_t) *ptr << 8 | (uint8_t) * (ptr + 1)) + 269;\n      ret = sizeof(uint16_t);\n    } else {\n      ret = -1; /* LCOV_EXCL_LINE */\n    }\n  }\n\n  return ret;\n}\n\n/*\n * Fills options in mg_coap_message.\n *\n * Helper function.\n *\n * General options format:\n * +---------------+---------------+\n * | Option Delta  | Option Length |  1 byte\n * +---------------+---------------+\n * \\    Option Delta (extended)    \\  0-2 bytes\n * +-------------------------------+\n * / Option Length  (extended)     \\  0-2 bytes\n * +-------------------------------+\n * \\         Option Value          \\  0 or more bytes\n * +-------------------------------+\n */\nstatic char *coap_get_options(char *ptr, struct mbuf *io,\n                              struct mg_coap_message *cm) {\n  uint16_t prev_opt = 0;\n\n  if (ptr == io->buf + io->len) {\n    /* end of packet, ok */\n    return NULL;\n  }\n\n  /* 0xFF is payload marker */\n  while (ptr < io->buf + io->len && (uint8_t) *ptr != 0xFF) {\n    uint16_t option_delta, option_lenght;\n    int optinfo_len;\n\n    /* Option Delta:  4-bit unsigned integer */\n    option_delta = ((uint8_t) *ptr & 0xF0) >> 4;\n    /* Option Length:  4-bit unsigned integer */\n    option_lenght = *ptr & 0x0F;\n\n    if (option_delta == 15 || option_lenght == 15) {\n      /*\n       * 15:  Reserved for future use.  If the field is set to this value,\n       * it MUST be processed as a message format error\n       */\n      cm->flags |= MG_COAP_FORMAT_ERROR;\n      break;\n    }\n\n    ptr++;\n\n    /* check for extended option delta */\n    optinfo_len = coap_get_ext_opt(ptr, io, &option_delta);\n    if (optinfo_len == -1) {\n      cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */\n      break;                                /* LCOV_EXCL_LINE */\n    }\n\n    ptr += optinfo_len;\n\n    /* check or extended option lenght */\n    optinfo_len = coap_get_ext_opt(ptr, io, &option_lenght);\n    if (optinfo_len == -1) {\n      cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */\n      break;                                /* LCOV_EXCL_LINE */\n    }\n\n    ptr += optinfo_len;\n\n    /*\n     * Instead of specifying the Option Number directly, the instances MUST\n     * appear in order of their Option Numbers and a delta encoding is used\n     * between them.\n     */\n    option_delta += prev_opt;\n\n    mg_coap_add_option(cm, option_delta, ptr, option_lenght);\n\n    prev_opt = option_delta;\n\n    if (ptr + option_lenght > io->buf + io->len) {\n      cm->flags |= MG_COAP_NOT_ENOUGH_DATA; /* LCOV_EXCL_LINE */\n      break;                                /* LCOV_EXCL_LINE */\n    }\n\n    ptr += option_lenght;\n  }\n\n  if ((cm->flags & MG_COAP_ERROR) != 0) {\n    mg_coap_free_options(cm);\n    return NULL;\n  }\n\n  cm->flags |= MG_COAP_OPTIOMG_FIELD;\n\n  if (ptr == io->buf + io->len) {\n    /* end of packet, ok */\n    return NULL;\n  }\n\n  ptr++;\n\n  return ptr;\n}\n\nuint32_t mg_coap_parse(struct mbuf *io, struct mg_coap_message *cm) {\n  char *ptr;\n\n  memset(cm, 0, sizeof(*cm));\n\n  if ((ptr = coap_parse_header(io->buf, io, cm)) == NULL) {\n    return cm->flags;\n  }\n\n  if ((ptr = coap_get_token(ptr, io, cm)) == NULL) {\n    return cm->flags;\n  }\n\n  if ((ptr = coap_get_options(ptr, io, cm)) == NULL) {\n    return cm->flags;\n  }\n\n  /* the rest is payload */\n  cm->payload.len = io->len - (ptr - io->buf);\n  if (cm->payload.len != 0) {\n    cm->payload.p = ptr;\n    cm->flags |= MG_COAP_PAYLOAD_FIELD;\n  }\n\n  return cm->flags;\n}\n\n/*\n * Calculates extended size of given Opt Number/Length in coap message.\n *\n * Helper function.\n */\nstatic size_t coap_get_ext_opt_size(uint32_t value) {\n  int ret = 0;\n\n  if (value >= 13 && value <= 0xFF + 13) {\n    ret = sizeof(uint8_t);\n  } else if (value > 0xFF + 13 && value <= 0xFFFF + 269) {\n    ret = sizeof(uint16_t);\n  }\n\n  return ret;\n}\n\n/*\n * Splits given Opt Number/Length into base and ext values.\n *\n * Helper function.\n */\nstatic int coap_split_opt(uint32_t value, uint8_t *base, uint16_t *ext) {\n  int ret = 0;\n\n  if (value < 13) {\n    *base = value;\n  } else if (value >= 13 && value <= 0xFF + 13) {\n    *base = 13;\n    *ext = value - 13;\n    ret = sizeof(uint8_t);\n  } else if (value > 0xFF + 13 && value <= 0xFFFF + 269) {\n    *base = 14;\n    *ext = value - 269;\n    ret = sizeof(uint16_t);\n  }\n\n  return ret;\n}\n\n/*\n * Puts uint16_t (in network order) into given char stream.\n *\n * Helper function.\n */\nstatic char *coap_add_uint16(char *ptr, uint16_t val) {\n  *ptr = val >> 8;\n  ptr++;\n  *ptr = val & 0x00FF;\n  ptr++;\n  return ptr;\n}\n\n/*\n * Puts extended value of Opt Number/Length into given char stream.\n *\n * Helper function.\n */\nstatic char *coap_add_opt_info(char *ptr, uint16_t val, size_t len) {\n  if (len == sizeof(uint8_t)) {\n    *ptr = (char) val;\n    ptr++;\n  } else if (len == sizeof(uint16_t)) {\n    ptr = coap_add_uint16(ptr, val);\n  }\n\n  return ptr;\n}\n\n/*\n * Verifies given mg_coap_message and calculates message size for it.\n *\n * Helper function.\n */\nstatic uint32_t coap_calculate_packet_size(struct mg_coap_message *cm,\n                                           size_t *len) {\n  struct mg_coap_option *opt;\n  uint32_t prev_opt_number;\n\n  *len = 4; /* header */\n  if (cm->msg_type > MG_COAP_MSG_MAX) {\n    return MG_COAP_ERROR | MG_COAP_MSG_TYPE_FIELD;\n  }\n  if (cm->token.len > 8) {\n    return MG_COAP_ERROR | MG_COAP_TOKEN_FIELD;\n  }\n  if (cm->code_class > 7) {\n    return MG_COAP_ERROR | MG_COAP_CODE_CLASS_FIELD;\n  }\n  if (cm->code_detail > 31) {\n    return MG_COAP_ERROR | MG_COAP_CODE_DETAIL_FIELD;\n  }\n\n  *len += cm->token.len;\n  if (cm->payload.len != 0) {\n    *len += cm->payload.len + 1; /* ... + 1; add payload marker */\n  }\n\n  opt = cm->options;\n  prev_opt_number = 0;\n  while (opt != NULL) {\n    *len += 1; /* basic delta/length */\n    *len += coap_get_ext_opt_size(opt->number - prev_opt_number);\n    *len += coap_get_ext_opt_size((uint32_t) opt->value.len);\n    /*\n     * Current implementation performs check if\n     * option_number > previous option_number and produces an error\n     * TODO(alashkin): write design doc with limitations\n     * May be resorting is more suitable solution.\n     */\n    if ((opt->next != NULL && opt->number > opt->next->number) ||\n        opt->value.len > 0xFFFF + 269 ||\n        opt->number - prev_opt_number > 0xFFFF + 269) {\n      return MG_COAP_ERROR | MG_COAP_OPTIOMG_FIELD;\n    }\n    *len += opt->value.len;\n    prev_opt_number = opt->number;\n    opt = opt->next;\n  }\n\n  return 0;\n}\n\nuint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io) {\n  struct mg_coap_option *opt;\n  uint32_t res, prev_opt_number;\n  size_t prev_io_len, packet_size;\n  char *ptr;\n\n  res = coap_calculate_packet_size(cm, &packet_size);\n  if (res != 0) {\n    return res;\n  }\n\n  /* saving previous lenght to handle non-empty mbuf */\n  prev_io_len = io->len;\n  if (mbuf_append(io, NULL, packet_size) == 0) return MG_COAP_ERROR;\n  ptr = io->buf + prev_io_len;\n\n  /*\n   * since cm is verified, it is possible to use bits shift operator\n   * without additional zeroing of unused bits\n   */\n\n  /* ver: 2 bits, msg_type: 2 bits, toklen: 4 bits */\n  *ptr = (1 << 6) | (cm->msg_type << 4) | (uint8_t)(cm->token.len);\n  ptr++;\n\n  /* code class: 3 bits, code detail: 5 bits */\n  *ptr = (cm->code_class << 5) | (cm->code_detail);\n  ptr++;\n\n  ptr = coap_add_uint16(ptr, cm->msg_id);\n\n  if (cm->token.len != 0) {\n    memcpy(ptr, cm->token.p, cm->token.len);\n    ptr += cm->token.len;\n  }\n\n  opt = cm->options;\n  prev_opt_number = 0;\n  while (opt != NULL) {\n    uint8_t delta_base = 0, length_base = 0;\n    uint16_t delta_ext = 0, length_ext = 0;\n\n    size_t opt_delta_len =\n        coap_split_opt(opt->number - prev_opt_number, &delta_base, &delta_ext);\n    size_t opt_lenght_len =\n        coap_split_opt((uint32_t) opt->value.len, &length_base, &length_ext);\n\n    *ptr = (delta_base << 4) | length_base;\n    ptr++;\n\n    ptr = coap_add_opt_info(ptr, delta_ext, opt_delta_len);\n    ptr = coap_add_opt_info(ptr, length_ext, opt_lenght_len);\n\n    if (opt->value.len != 0) {\n      memcpy(ptr, opt->value.p, opt->value.len);\n      ptr += opt->value.len;\n    }\n\n    prev_opt_number = opt->number;\n    opt = opt->next;\n  }\n\n  if (cm->payload.len != 0) {\n    *ptr = (char) -1;\n    ptr++;\n    memcpy(ptr, cm->payload.p, cm->payload.len);\n  }\n\n  return 0;\n}\n\nuint32_t mg_coap_send_message(struct mg_connection *nc,\n                              struct mg_coap_message *cm) {\n  struct mbuf packet_out;\n  uint32_t compose_res;\n\n  mbuf_init(&packet_out, 0);\n  compose_res = mg_coap_compose(cm, &packet_out);\n  if (compose_res != 0) {\n    return compose_res; /* LCOV_EXCL_LINE */\n  }\n\n  mg_send(nc, packet_out.buf, (int) packet_out.len);\n  mbuf_free(&packet_out);\n\n  return 0;\n}\n\nuint32_t mg_coap_send_ack(struct mg_connection *nc, uint16_t msg_id) {\n  struct mg_coap_message cm;\n  memset(&cm, 0, sizeof(cm));\n  cm.msg_type = MG_COAP_MSG_ACK;\n  cm.msg_id = msg_id;\n\n  return mg_coap_send_message(nc, &cm);\n}\n\nstatic void coap_handler(struct mg_connection *nc, int ev,\n                         void *ev_data MG_UD_ARG(void *user_data)) {\n  struct mbuf *io = &nc->recv_mbuf;\n  struct mg_coap_message cm;\n  uint32_t parse_res;\n\n  memset(&cm, 0, sizeof(cm));\n\n  nc->handler(nc, ev, ev_data MG_UD_ARG(user_data));\n\n  switch (ev) {\n    case MG_EV_RECV:\n      parse_res = mg_coap_parse(io, &cm);\n      if ((parse_res & MG_COAP_IGNORE) == 0) {\n        if ((cm.flags & MG_COAP_NOT_ENOUGH_DATA) != 0) {\n          /*\n           * Since we support UDP only\n           * MG_COAP_NOT_ENOUGH_DATA == MG_COAP_FORMAT_ERROR\n           */\n          cm.flags |= MG_COAP_FORMAT_ERROR; /* LCOV_EXCL_LINE */\n        }                                   /* LCOV_EXCL_LINE */\n        nc->handler(nc, MG_COAP_EVENT_BASE + cm.msg_type,\n                    &cm MG_UD_ARG(user_data));\n      }\n\n      mg_coap_free_options(&cm);\n      mbuf_remove(io, io->len);\n      break;\n  }\n}\n/*\n * Attach built-in CoAP event handler to the given connection.\n *\n * The user-defined event handler will receive following extra events:\n *\n * - MG_EV_COAP_CON\n * - MG_EV_COAP_NOC\n * - MG_EV_COAP_ACK\n * - MG_EV_COAP_RST\n */\nint mg_set_protocol_coap(struct mg_connection *nc) {\n  /* supports UDP only */\n  if ((nc->flags & MG_F_UDP) == 0) {\n    return -1;\n  }\n\n  nc->proto_handler = coap_handler;\n\n  return 0;\n}\n\n#endif /* MG_ENABLE_COAP */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_sntp.c\"\n#endif\n/*\n * Copyright (c) 2016 Cesanta Software Limited\n * All rights reserved\n */\n\n/* Amalgamated: #include \"mg_internal.h\" */\n/* Amalgamated: #include \"mg_sntp.h\" */\n/* Amalgamated: #include \"mg_util.h\" */\n\n#if MG_ENABLE_SNTP\n\n#define SNTP_TIME_OFFSET 2208988800\n\n#ifndef SNTP_TIMEOUT\n#define SNTP_TIMEOUT 10\n#endif\n\n#ifndef SNTP_ATTEMPTS\n#define SNTP_ATTEMPTS 3\n#endif\n\nstatic uint64_t mg_get_sec(uint64_t val) {\n  return (val & 0xFFFFFFFF00000000) >> 32;\n}\n\nstatic uint64_t mg_get_usec(uint64_t val) {\n  uint64_t tmp = (val & 0x00000000FFFFFFFF);\n  tmp *= 1000000;\n  tmp >>= 32;\n  return tmp;\n}\n\nstatic void mg_ntp_to_tv(uint64_t val, struct timeval *tv) {\n  uint64_t tmp;\n  tmp = mg_get_sec(val);\n  tmp -= SNTP_TIME_OFFSET;\n  tv->tv_sec = tmp;\n  tv->tv_usec = mg_get_usec(val);\n}\n\nstatic void mg_get_ntp_ts(const char *ntp, uint64_t *val) {\n  uint32_t tmp;\n  memcpy(&tmp, ntp, sizeof(tmp));\n  tmp = ntohl(tmp);\n  *val = (uint64_t) tmp << 32;\n  memcpy(&tmp, ntp + 4, sizeof(tmp));\n  tmp = ntohl(tmp);\n  *val |= tmp;\n}\n\nvoid mg_sntp_send_request(struct mg_connection *c) {\n  uint8_t buf[48] = {0};\n  /*\n   * header - 8 bit:\n   * LI (2 bit) - 3 (not in sync), VN (3 bit) - 4 (version),\n   * mode (3 bit) - 3 (client)\n   */\n  buf[0] = (3 << 6) | (4 << 3) | 3;\n\n/*\n * Next fields should be empty in client request\n * stratum, 8 bit\n * poll interval, 8 bit\n * rrecision, 8 bit\n * root delay, 32 bit\n * root dispersion, 32 bit\n * ref id, 32 bit\n * ref timestamp, 64 bit\n * originate Timestamp, 64 bit\n * receive Timestamp, 64 bit\n*/\n\n/*\n * convert time to sntp format (sntp starts from 00:00:00 01.01.1900)\n * according to rfc868 it is 2208988800L sec\n * this information is used to correct roundtrip delay\n * but if local clock is absolutely broken (and doesn't work even\n * as simple timer), it is better to disable it\n*/\n#ifndef MG_SNTP_NO_DELAY_CORRECTION\n  uint32_t sec;\n  sec = htonl((uint32_t)(mg_time() + SNTP_TIME_OFFSET));\n  memcpy(&buf[40], &sec, sizeof(sec));\n#endif\n\n  mg_send(c, buf, sizeof(buf));\n}\n\n#ifndef MG_SNTP_NO_DELAY_CORRECTION\nstatic uint64_t mg_calculate_delay(uint64_t t1, uint64_t t2, uint64_t t3) {\n  /* roundloop delay = (T4 - T1) - (T3 - T2) */\n  uint64_t d1 = ((mg_time() + SNTP_TIME_OFFSET) * 1000000) -\n                (mg_get_sec(t1) * 1000000 + mg_get_usec(t1));\n  uint64_t d2 = (mg_get_sec(t3) * 1000000 + mg_get_usec(t3)) -\n                (mg_get_sec(t2) * 1000000 + mg_get_usec(t2));\n\n  return (d1 > d2) ? d1 - d2 : 0;\n}\n#endif\n\nMG_INTERNAL int mg_sntp_parse_reply(const char *buf, int len,\n                                    struct mg_sntp_message *msg) {\n  uint8_t hdr;\n  uint64_t trsm_ts_T3, delay = 0;\n  int mode;\n  struct timeval tv;\n\n  if (len < 48) {\n    return -1;\n  }\n\n  hdr = buf[0];\n\n  if ((hdr & 0x38) >> 3 != 4) {\n    /* Wrong version */\n    return -1;\n  }\n\n  mode = hdr & 0x7;\n  if (mode != 4 && mode != 5) {\n    /* Not a server reply */\n    return -1;\n  }\n\n  memset(msg, 0, sizeof(*msg));\n\n  msg->kiss_of_death = (buf[1] == 0); /* Server asks to not send requests */\n\n  mg_get_ntp_ts(&buf[40], &trsm_ts_T3);\n\n#ifndef MG_SNTP_NO_DELAY_CORRECTION\n  {\n    uint64_t orig_ts_T1, recv_ts_T2;\n    mg_get_ntp_ts(&buf[24], &orig_ts_T1);\n    mg_get_ntp_ts(&buf[32], &recv_ts_T2);\n    delay = mg_calculate_delay(orig_ts_T1, recv_ts_T2, trsm_ts_T3);\n  }\n#endif\n\n  mg_ntp_to_tv(trsm_ts_T3, &tv);\n\n  msg->time = (double) tv.tv_sec + (((double) tv.tv_usec + delay) / 1000000.0);\n\n  return 0;\n}\n\nstatic void mg_sntp_handler(struct mg_connection *c, int ev,\n                            void *ev_data MG_UD_ARG(void *user_data)) {\n  struct mbuf *io = &c->recv_mbuf;\n  struct mg_sntp_message msg;\n\n  c->handler(c, ev, ev_data MG_UD_ARG(user_data));\n\n  switch (ev) {\n    case MG_EV_RECV: {\n      if (mg_sntp_parse_reply(io->buf, io->len, &msg) < 0) {\n        DBG((\"Invalid SNTP packet received (%d)\", (int) io->len));\n        c->handler(c, MG_SNTP_MALFORMED_REPLY, NULL MG_UD_ARG(user_data));\n      } else {\n        c->handler(c, MG_SNTP_REPLY, (void *) &msg MG_UD_ARG(user_data));\n      }\n\n      mbuf_remove(io, io->len);\n      break;\n    }\n  }\n}\n\nint mg_set_protocol_sntp(struct mg_connection *c) {\n  if ((c->flags & MG_F_UDP) == 0) {\n    return -1;\n  }\n\n  c->proto_handler = mg_sntp_handler;\n\n  return 0;\n}\n\nstruct mg_connection *mg_sntp_connect(struct mg_mgr *mgr,\n                                      MG_CB(mg_event_handler_t event_handler,\n                                            void *user_data),\n                                      const char *sntp_server_name) {\n  struct mg_connection *c = NULL;\n  char url[100], *p_url = url;\n  const char *proto = \"\", *port = \"\", *tmp;\n\n  /* If port is not specified, use default (123) */\n  tmp = strchr(sntp_server_name, ':');\n  if (tmp != NULL && *(tmp + 1) == '/') {\n    tmp = strchr(tmp + 1, ':');\n  }\n\n  if (tmp == NULL) {\n    port = \":123\";\n  }\n\n  /* Add udp:// if needed */\n  if (strncmp(sntp_server_name, \"udp://\", 6) != 0) {\n    proto = \"udp://\";\n  }\n\n  mg_asprintf(&p_url, sizeof(url), \"%s%s%s\", proto, sntp_server_name, port);\n\n  c = mg_connect(mgr, p_url, event_handler MG_UD_ARG(user_data));\n\n  if (c == NULL) {\n    goto cleanup;\n  }\n\n  mg_set_protocol_sntp(c);\n\ncleanup:\n  if (p_url != url) {\n    MG_FREE(p_url);\n  }\n\n  return c;\n}\n\nstruct sntp_data {\n  mg_event_handler_t hander;\n  int count;\n};\n\nstatic void mg_sntp_util_ev_handler(struct mg_connection *c, int ev,\n                                    void *ev_data MG_UD_ARG(void *user_data)) {\n#if !MG_ENABLE_CALLBACK_USERDATA\n  void *user_data = c->user_data;\n#endif\n  struct sntp_data *sd = (struct sntp_data *) user_data;\n\n  switch (ev) {\n    case MG_EV_CONNECT:\n      if (*(int *) ev_data != 0) {\n        mg_call(c, sd->hander, c->user_data, MG_SNTP_FAILED, NULL);\n        break;\n      }\n    /* fallthrough */\n    case MG_EV_TIMER:\n      if (sd->count <= SNTP_ATTEMPTS) {\n        mg_sntp_send_request(c);\n        mg_set_timer(c, mg_time() + 10);\n        sd->count++;\n      } else {\n        mg_call(c, sd->hander, c->user_data, MG_SNTP_FAILED, NULL);\n        c->flags |= MG_F_CLOSE_IMMEDIATELY;\n      }\n      break;\n    case MG_SNTP_MALFORMED_REPLY:\n      mg_call(c, sd->hander, c->user_data, MG_SNTP_FAILED, NULL);\n      c->flags |= MG_F_CLOSE_IMMEDIATELY;\n      break;\n    case MG_SNTP_REPLY:\n      mg_call(c, sd->hander, c->user_data, MG_SNTP_REPLY, ev_data);\n      c->flags |= MG_F_CLOSE_IMMEDIATELY;\n      break;\n    case MG_EV_CLOSE:\n      MG_FREE(user_data);\n      c->user_data = NULL;\n      break;\n  }\n}\n\nstruct mg_connection *mg_sntp_get_time(struct mg_mgr *mgr,\n                                       mg_event_handler_t event_handler,\n                                       const char *sntp_server_name) {\n  struct mg_connection *c;\n  struct sntp_data *sd = (struct sntp_data *) MG_CALLOC(1, sizeof(*sd));\n  if (sd == NULL) {\n    return NULL;\n  }\n\n  c = mg_sntp_connect(mgr, MG_CB(mg_sntp_util_ev_handler, sd),\n                      sntp_server_name);\n  if (c == NULL) {\n    MG_FREE(sd);\n    return NULL;\n  }\n\n  sd->hander = event_handler;\n#if !MG_ENABLE_CALLBACK_USERDATA\n  c->user_data = sd;\n#endif\n\n  return c;\n}\n\n#endif /* MG_ENABLE_SNTP */\n#ifdef MG_MODULE_LINES\n#line 1 \"mongoose/src/mg_socks.c\"\n#endif\n/*\n * Copyright (c) 2017 Cesanta Software Limited\n * All rights reserved\n */\n\n#if MG_ENABLE_SOCKS\n\n/* Amalgamated: #include \"mg_socks.h\" */\n/* Amalgamated: #include \"mg_internal.h\" */\n\n/*\n *  https://www.ietf.org/rfc/rfc1928.txt paragraph 3, handle client handshake\n *\n *  +----+----------+----------+\n *  |VER | NMETHODS | METHODS  |\n *  +----+----------+----------+\n *  | 1  |    1     | 1 to 255 |\n *  +----+----------+----------+\n */\nstatic void mg_socks5_handshake(struct mg_connection *c) {\n  struct mbuf *r = &c->recv_mbuf;\n  if (r->buf[0] != MG_SOCKS_VERSION) {\n    c->flags |= MG_F_CLOSE_IMMEDIATELY;\n  } else if (r->len > 2 && (size_t) r->buf[1] + 2 <= r->len) {\n    /* https://www.ietf.org/rfc/rfc1928.txt paragraph 3 */\n    unsigned char reply[2] = {MG_SOCKS_VERSION, MG_SOCKS_HANDSHAKE_FAILURE};\n    int i;\n    for (i = 2; i < r->buf[1] + 2; i++) {\n      /* TODO(lsm): support other auth methods */\n      if (r->buf[i] == MG_SOCKS_HANDSHAKE_NOAUTH) reply[1] = r->buf[i];\n    }\n    mbuf_remove(r, 2 + r->buf[1]);\n    mg_send(c, reply, sizeof(reply));\n    c->flags |= MG_SOCKS_HANDSHAKE_DONE; /* Mark handshake done */\n  }\n}\n\nstatic void disband(struct mg_connection *c) {\n  struct mg_connection *c2 = (struct mg_connection *) c->user_data;\n  if (c2 != NULL) {\n    c2->flags |= MG_F_SEND_AND_CLOSE;\n    c2->user_data = NULL;\n  }\n  c->flags |= MG_F_SEND_AND_CLOSE;\n  c->user_data = NULL;\n}\n\nstatic void relay_data(struct mg_connection *c) {\n  struct mg_connection *c2 = (struct mg_connection *) c->user_data;\n  if (c2 != NULL) {\n    mg_send(c2, c->recv_mbuf.buf, c->recv_mbuf.len);\n    mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);\n  } else {\n    c->flags |= MG_F_SEND_AND_CLOSE;\n  }\n}\n\nstatic void serv_ev_handler(struct mg_connection *c, int ev, void *ev_data) {\n  if (ev == MG_EV_CLOSE) {\n    disband(c);\n  } else if (ev == MG_EV_RECV) {\n    relay_data(c);\n  } else if (ev == MG_EV_CONNECT) {\n    int res = *(int *) ev_data;\n    if (res != 0) LOG(LL_ERROR, (\"connect error: %d\", res));\n  }\n}\n\nstatic void mg_socks5_connect(struct mg_connection *c, const char *addr) {\n  struct mg_connection *serv = mg_connect(c->mgr, addr, serv_ev_handler);\n  serv->user_data = c;\n  c->user_data = serv;\n}\n\n/*\n *  Request, https://www.ietf.org/rfc/rfc1928.txt paragraph 4\n *\n *  +----+-----+-------+------+----------+----------+\n *  |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |\n *  +----+-----+-------+------+----------+----------+\n *  | 1  |  1  | X'00' |  1   | Variable |    2     |\n *  +----+-----+-------+------+----------+----------+\n */\nstatic void mg_socks5_handle_request(struct mg_connection *c) {\n  struct mbuf *r = &c->recv_mbuf;\n  unsigned char *p = (unsigned char *) r->buf;\n  unsigned char addr_len = 4, reply = MG_SOCKS_SUCCESS;\n  int ver, cmd, atyp;\n  char addr[300];\n\n  if (r->len < 8) return; /* return if not fully buffered. min DST.ADDR is 2 */\n  ver = p[0];\n  cmd = p[1];\n  atyp = p[3];\n\n  /* TODO(lsm): support other commands */\n  if (ver != MG_SOCKS_VERSION || cmd != MG_SOCKS_CMD_CONNECT) {\n    reply = MG_SOCKS_CMD_NOT_SUPPORTED;\n  } else if (atyp == MG_SOCKS_ADDR_IPV4) {\n    addr_len = 4;\n    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */\n    snprintf(addr, sizeof(addr), \"%d.%d.%d.%d:%d\", p[4], p[5], p[6], p[7],\n             p[8] << 8 | p[9]);\n    mg_socks5_connect(c, addr);\n  } else if (atyp == MG_SOCKS_ADDR_IPV6) {\n    addr_len = 16;\n    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */\n    snprintf(addr, sizeof(addr), \"[%x:%x:%x:%x:%x:%x:%x:%x]:%d\",\n             p[4] << 8 | p[5], p[6] << 8 | p[7], p[8] << 8 | p[9],\n             p[10] << 8 | p[11], p[12] << 8 | p[13], p[14] << 8 | p[15],\n             p[16] << 8 | p[17], p[18] << 8 | p[19], p[20] << 8 | p[21]);\n    mg_socks5_connect(c, addr);\n  } else if (atyp == MG_SOCKS_ADDR_DOMAIN) {\n    addr_len = p[4] + 1;\n    if (r->len < (size_t) addr_len + 6) return; /* return if not buffered */\n    snprintf(addr, sizeof(addr), \"%.*s:%d\", p[4], p + 5,\n             p[4 + addr_len] << 8 | p[4 + addr_len + 1]);\n    mg_socks5_connect(c, addr);\n  } else {\n    reply = MG_SOCKS_ADDR_NOT_SUPPORTED;\n  }\n\n  /*\n   *  Reply, https://www.ietf.org/rfc/rfc1928.txt paragraph 5\n   *\n   *  +----+-----+-------+------+----------+----------+\n   *  |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |\n   *  +----+-----+-------+------+----------+----------+\n   *  | 1  |  1  | X'00' |  1   | Variable |    2     |\n   *  +----+-----+-------+------+----------+----------+\n   */\n  {\n    unsigned char buf[] = {MG_SOCKS_VERSION, reply, 0};\n    mg_send(c, buf, sizeof(buf));\n  }\n  mg_send(c, r->buf + 3, addr_len + 1 + 2);\n\n  mbuf_remove(r, 6 + addr_len);      /* Remove request from the input stream */\n  c->flags |= MG_SOCKS_CONNECT_DONE; /* Mark ourselves as connected */\n}\n\nstatic void socks_handler(struct mg_connection *c, int ev, void *ev_data) {\n  if (ev == MG_EV_RECV) {\n    if (!(c->flags & MG_SOCKS_HANDSHAKE_DONE)) mg_socks5_handshake(c);\n    if (c->flags & MG_SOCKS_HANDSHAKE_DONE &&\n        !(c->flags & MG_SOCKS_CONNECT_DONE)) {\n      mg_socks5_handle_request(c);\n    }\n    if (c->flags & MG_SOCKS_CONNECT_DONE) relay_data(c);\n  } else if (ev == MG_EV_CLOSE) {\n    disband(c);\n  }\n  (void) ev_data;\n}\n\nvoid mg_set_protocol_socks(struct mg_connection *c) {\n  c->proto_handler = socks_handler;\n}\n#endif\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/cc3200/cc3200_libc.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if CS_PLATFORM == CS_P_CC3200\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n#include <stdio.h>\n#include <string.h>\n\n#ifndef __TI_COMPILER_VERSION__\n#include <reent.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <unistd.h>\n#endif\n\n#include <inc/hw_types.h>\n#include <inc/hw_memmap.h>\n#include <driverlib/prcm.h>\n#include <driverlib/rom.h>\n#include <driverlib/rom_map.h>\n#include <driverlib/uart.h>\n#include <driverlib/utils.h>\n\n#define CONSOLE_UART UARTA0_BASE\n\n#ifdef __TI_COMPILER_VERSION__\nint asprintf(char **strp, const char *fmt, ...) {\n  va_list ap;\n  int len;\n\n  *strp = MG_MALLOC(BUFSIZ);\n  if (*strp == NULL) return -1;\n\n  va_start(ap, fmt);\n  len = vsnprintf(*strp, BUFSIZ, fmt, ap);\n  va_end(ap);\n\n  if (len > 0) {\n    *strp = MG_REALLOC(*strp, len + 1);\n    if (*strp == NULL) return -1;\n  }\n\n  if (len >= BUFSIZ) {\n    va_start(ap, fmt);\n    len = vsnprintf(*strp, len + 1, fmt, ap);\n    va_end(ap);\n  }\n\n  return len;\n}\n\n#if MG_TI_NO_HOST_INTERFACE\ntime_t HOSTtime() {\n  struct timeval tp;\n  gettimeofday(&tp, NULL);\n  return tp.tv_sec;\n}\n#endif\n\n#endif /* __TI_COMPILER_VERSION__ */\n\nvoid fprint_str(FILE *fp, const char *str) {\n  while (*str != '\\0') {\n    if (*str == '\\n') MAP_UARTCharPut(CONSOLE_UART, '\\r');\n    MAP_UARTCharPut(CONSOLE_UART, *str++);\n  }\n}\n\nvoid _exit(int status) {\n  fprint_str(stderr, \"_exit\\n\");\n  /* cause an unaligned access exception, that will drop you into gdb */\n  *(int *) 1 = status;\n  while (1)\n    ; /* avoid gcc warning because stdlib abort() has noreturn attribute */\n}\n\nvoid _not_implemented(const char *what) {\n  fprint_str(stderr, what);\n  fprint_str(stderr, \" is not implemented\\n\");\n  _exit(42);\n}\n\nint _kill(int pid, int sig) {\n  (void) pid;\n  (void) sig;\n  _not_implemented(\"_kill\");\n  return -1;\n}\n\nint _getpid() {\n  fprint_str(stderr, \"_getpid is not implemented\\n\");\n  return 42;\n}\n\nint _isatty(int fd) {\n  /* 0, 1 and 2 are TTYs. */\n  return fd < 2;\n}\n\n#endif /* CS_PLATFORM == CS_P_CC3200 */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/msp432/msp432_libc.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if CS_PLATFORM == CS_P_MSP432\n\n#include <ti/sysbios/BIOS.h>\n#include <ti/sysbios/knl/Clock.h>\n\nint gettimeofday(struct timeval *tp, void *tzp) {\n  uint32_t ticks = Clock_getTicks();\n  tp->tv_sec = ticks / 1000;\n  tp->tv_usec = (ticks % 1000) * 1000;\n  return 0;\n}\n\n#endif /* CS_PLATFORM == CS_P_MSP432 */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/nrf5/nrf5_libc.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if (CS_PLATFORM == CS_P_NRF51 || CS_PLATFORM == CS_P_NRF52) && \\\n    defined(__ARMCC_VERSION)\nint gettimeofday(struct timeval *tp, void *tzp) {\n  /* TODO */\n  tp->tv_sec = 0;\n  tp->tv_usec = 0;\n  return 0;\n}\n#endif\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_fs_slfs.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_\n#define CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_\n\n#if defined(MG_FS_SLFS)\n\n#include <stdio.h>\n#ifndef __TI_COMPILER_VERSION__\n#include <unistd.h>\n#include <sys/stat.h>\n#endif\n\n#define MAX_OPEN_SLFS_FILES 8\n\n/* Indirect libc interface - same functions, different names. */\nint fs_slfs_open(const char *pathname, int flags, mode_t mode);\nint fs_slfs_close(int fd);\nssize_t fs_slfs_read(int fd, void *buf, size_t count);\nssize_t fs_slfs_write(int fd, const void *buf, size_t count);\nint fs_slfs_stat(const char *pathname, struct stat *s);\nint fs_slfs_fstat(int fd, struct stat *s);\noff_t fs_slfs_lseek(int fd, off_t offset, int whence);\nint fs_slfs_unlink(const char *filename);\nint fs_slfs_rename(const char *from, const char *to);\n\nvoid fs_slfs_set_file_size(const char *name, size_t size);\nvoid fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token);\nvoid fs_slfs_unset_file_flags(const char *name);\n\n#endif /* defined(MG_FS_SLFS) */\n\n#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_SL_FS_SLFS_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_fs_slfs.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Standard libc interface to TI SimpleLink FS. */\n\n#if defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS)\n\n/* Amalgamated: #include \"common/platforms/simplelink/sl_fs_slfs.h\" */\n\n#include <errno.h>\n\n#if CS_PLATFORM == CS_P_CC3200\n#include <inc/hw_types.h>\n#endif\n\n/* Amalgamated: #include \"common/cs_dbg.h\" */\n/* Amalgamated: #include \"common/mg_mem.h\" */\n\n#if SL_MAJOR_VERSION_NUM < 2\nint slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) {\n  _i32 fh;\n  _i32 r = sl_FsOpen(fname, flags, (unsigned long *) token, &fh);\n  return (r < 0 ? r : fh);\n}\n#else /* SL_MAJOR_VERSION_NUM >= 2 */\nint slfs_open(const unsigned char *fname, uint32_t flags, uint32_t *token) {\n  return sl_FsOpen(fname, flags, (unsigned long *) token);\n}\n#endif\n\n/* From sl_fs.c */\nint set_errno(int e);\nconst char *drop_dir(const char *fname, bool *is_slfs);\n\n/*\n * With SLFS, you have to pre-declare max file size. Yes. Really.\n * 64K should be enough for everyone. Right?\n */\n#ifndef FS_SLFS_MAX_FILE_SIZE\n#define FS_SLFS_MAX_FILE_SIZE (64 * 1024)\n#endif\n\nstruct sl_file_open_info {\n  char *name;\n  size_t size;\n  uint32_t flags;\n  uint32_t *token;\n};\n\nstruct sl_fd_info {\n  _i32 fh;\n  _off_t pos;\n  size_t size;\n};\n\nstatic struct sl_fd_info s_sl_fds[MAX_OPEN_SLFS_FILES];\nstatic struct sl_file_open_info s_sl_file_open_infos[MAX_OPEN_SLFS_FILES];\n\nstatic struct sl_file_open_info *fs_slfs_find_foi(const char *name,\n                                                  bool create);\n\nstatic int sl_fs_to_errno(_i32 r) {\n  DBG((\"SL error: %d\", (int) r));\n  switch (r) {\n    case SL_FS_OK:\n      return 0;\n    case SL_ERROR_FS_FILE_NAME_EXIST:\n      return EEXIST;\n    case SL_ERROR_FS_WRONG_FILE_NAME:\n      return EINVAL;\n    case SL_ERROR_FS_NO_AVAILABLE_NV_INDEX:\n    case SL_ERROR_FS_NOT_ENOUGH_STORAGE_SPACE:\n      return ENOSPC;\n    case SL_ERROR_FS_FAILED_TO_ALLOCATE_MEM:\n      return ENOMEM;\n    case SL_ERROR_FS_FILE_NOT_EXISTS:\n      return ENOENT;\n    case SL_ERROR_FS_NOT_SUPPORTED:\n      return ENOTSUP;\n  }\n  return ENXIO;\n}\n\nint fs_slfs_open(const char *pathname, int flags, mode_t mode) {\n  int fd;\n  for (fd = 0; fd < MAX_OPEN_SLFS_FILES; fd++) {\n    if (s_sl_fds[fd].fh <= 0) break;\n  }\n  if (fd >= MAX_OPEN_SLFS_FILES) return set_errno(ENOMEM);\n  struct sl_fd_info *fi = &s_sl_fds[fd];\n\n  /*\n   * Apply path manipulations again, in case we got here directly\n   * (via TI libc's \"add_device\").\n   */\n  pathname = drop_dir(pathname, NULL);\n\n  _u32 am = 0;\n  fi->size = (size_t) -1;\n  int rw = (flags & 3);\n  size_t new_size = 0;\n  struct sl_file_open_info *foi =\n      fs_slfs_find_foi(pathname, false /* create */);\n  if (foi != NULL) {\n    LOG(LL_DEBUG, (\"FOI for %s: %d 0x%x %p\", pathname, (int) foi->size,\n                   (unsigned int) foi->flags, foi->token));\n  }\n  if (rw == O_RDONLY) {\n    SlFsFileInfo_t sl_fi;\n    _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi);\n    if (r == SL_FS_OK) {\n      fi->size = SL_FI_FILE_SIZE(sl_fi);\n    }\n    am = SL_FS_READ;\n  } else {\n    if (!(flags & O_TRUNC) || (flags & O_APPEND)) {\n      // FailFS files cannot be opened for append and will be truncated\n      // when opened for write.\n      return set_errno(ENOTSUP);\n    }\n    if (flags & O_CREAT) {\n      if (foi->size > 0) {\n        new_size = foi->size;\n      } else {\n        new_size = FS_SLFS_MAX_FILE_SIZE;\n      }\n      am = FS_MODE_OPEN_CREATE(new_size, 0);\n    } else {\n      am = SL_FS_WRITE;\n    }\n#if SL_MAJOR_VERSION_NUM >= 2\n    am |= SL_FS_OVERWRITE;\n#endif\n  }\n  uint32_t *token = NULL;\n  if (foi != NULL) {\n    am |= foi->flags;\n    token = foi->token;\n  }\n  fi->fh = slfs_open((_u8 *) pathname, am, token);\n  LOG(LL_DEBUG, (\"sl_FsOpen(%s, 0x%x, %p) sz %u = %d\", pathname, (int) am,\n                 token, (unsigned int) new_size, (int) fi->fh));\n  int r;\n  if (fi->fh >= 0) {\n    fi->pos = 0;\n    r = fd;\n  } else {\n    r = set_errno(sl_fs_to_errno(fi->fh));\n  }\n  return r;\n}\n\nint fs_slfs_close(int fd) {\n  struct sl_fd_info *fi = &s_sl_fds[fd];\n  if (fi->fh <= 0) return set_errno(EBADF);\n  _i32 r = sl_FsClose(fi->fh, NULL, NULL, 0);\n  LOG(LL_DEBUG, (\"sl_FsClose(%d) = %d\", (int) fi->fh, (int) r));\n  s_sl_fds[fd].fh = -1;\n  return set_errno(sl_fs_to_errno(r));\n}\n\nssize_t fs_slfs_read(int fd, void *buf, size_t count) {\n  struct sl_fd_info *fi = &s_sl_fds[fd];\n  if (fi->fh <= 0) return set_errno(EBADF);\n  /* Simulate EOF. sl_FsRead @ file_size return SL_FS_ERR_OFFSET_OUT_OF_RANGE.\n   */\n  if (fi->pos == fi->size) return 0;\n  _i32 r = sl_FsRead(fi->fh, fi->pos, buf, count);\n  DBG((\"sl_FsRead(%d, %d, %d) = %d\", (int) fi->fh, (int) fi->pos, (int) count,\n       (int) r));\n  if (r >= 0) {\n    fi->pos += r;\n    return r;\n  }\n  return set_errno(sl_fs_to_errno(r));\n}\n\nssize_t fs_slfs_write(int fd, const void *buf, size_t count) {\n  struct sl_fd_info *fi = &s_sl_fds[fd];\n  if (fi->fh <= 0) return set_errno(EBADF);\n  _i32 r = sl_FsWrite(fi->fh, fi->pos, (_u8 *) buf, count);\n  DBG((\"sl_FsWrite(%d, %d, %d) = %d\", (int) fi->fh, (int) fi->pos, (int) count,\n       (int) r));\n  if (r >= 0) {\n    fi->pos += r;\n    return r;\n  }\n  return set_errno(sl_fs_to_errno(r));\n}\n\nint fs_slfs_stat(const char *pathname, struct stat *s) {\n  SlFsFileInfo_t sl_fi;\n  /*\n   * Apply path manipulations again, in case we got here directly\n   * (via TI libc's \"add_device\").\n   */\n  pathname = drop_dir(pathname, NULL);\n  _i32 r = sl_FsGetInfo((const _u8 *) pathname, 0, &sl_fi);\n  if (r == SL_FS_OK) {\n    s->st_mode = S_IFREG | 0666;\n    s->st_nlink = 1;\n    s->st_size = SL_FI_FILE_SIZE(sl_fi);\n    return 0;\n  }\n  return set_errno(sl_fs_to_errno(r));\n}\n\nint fs_slfs_fstat(int fd, struct stat *s) {\n  struct sl_fd_info *fi = &s_sl_fds[fd];\n  if (fi->fh <= 0) return set_errno(EBADF);\n  s->st_mode = 0666;\n  s->st_mode = S_IFREG | 0666;\n  s->st_nlink = 1;\n  s->st_size = fi->size;\n  return 0;\n}\n\noff_t fs_slfs_lseek(int fd, off_t offset, int whence) {\n  if (s_sl_fds[fd].fh <= 0) return set_errno(EBADF);\n  switch (whence) {\n    case SEEK_SET:\n      s_sl_fds[fd].pos = offset;\n      break;\n    case SEEK_CUR:\n      s_sl_fds[fd].pos += offset;\n      break;\n    case SEEK_END:\n      return set_errno(ENOTSUP);\n  }\n  return 0;\n}\n\nint fs_slfs_unlink(const char *pathname) {\n  /*\n   * Apply path manipulations again, in case we got here directly\n   * (via TI libc's \"add_device\").\n   */\n  pathname = drop_dir(pathname, NULL);\n  return set_errno(sl_fs_to_errno(sl_FsDel((const _u8 *) pathname, 0)));\n}\n\nint fs_slfs_rename(const char *from, const char *to) {\n  return set_errno(ENOTSUP);\n}\n\nstatic struct sl_file_open_info *fs_slfs_find_foi(const char *name,\n                                                  bool create) {\n  int i = 0;\n  for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {\n    if (s_sl_file_open_infos[i].name != NULL &&\n        strcmp(drop_dir(s_sl_file_open_infos[i].name, NULL), name) == 0) {\n      break;\n    }\n  }\n  if (i != MAX_OPEN_SLFS_FILES) return &s_sl_file_open_infos[i];\n  if (!create) return NULL;\n  for (i = 0; i < MAX_OPEN_SLFS_FILES; i++) {\n    if (s_sl_file_open_infos[i].name == NULL) break;\n  }\n  if (i == MAX_OPEN_SLFS_FILES) {\n    i = 0; /* Evict a random slot. */\n  }\n  if (s_sl_file_open_infos[i].name != NULL) {\n    free(s_sl_file_open_infos[i].name);\n  }\n  s_sl_file_open_infos[i].name = strdup(name);\n  return &s_sl_file_open_infos[i];\n}\n\nvoid fs_slfs_set_file_size(const char *name, size_t size) {\n  struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */);\n  foi->size = size;\n}\n\nvoid fs_slfs_set_file_flags(const char *name, uint32_t flags, uint32_t *token) {\n  struct sl_file_open_info *foi = fs_slfs_find_foi(name, true /* create */);\n  foi->flags = flags;\n  foi->token = token;\n}\n\nvoid fs_slfs_unset_file_flags(const char *name) {\n  struct sl_file_open_info *foi = fs_slfs_find_foi(name, false /* create */);\n  if (foi == NULL) return;\n  free(foi->name);\n  memset(foi, 0, sizeof(*foi));\n}\n\n#endif /* defined(MG_FS_SLFS) || defined(CC3200_FS_SLFS) */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_fs.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if MG_NET_IF == MG_NET_IF_SIMPLELINK && \\\n    (defined(MG_FS_SLFS) || defined(MG_FS_SPIFFS))\n\nint set_errno(int e) {\n  errno = e;\n  return (e == 0 ? 0 : -1);\n}\n\nconst char *drop_dir(const char *fname, bool *is_slfs) {\n  if (is_slfs != NULL) {\n    *is_slfs = (strncmp(fname, \"SL:\", 3) == 0);\n    if (*is_slfs) fname += 3;\n  }\n  /* Drop \"./\", if any */\n  if (fname[0] == '.' && fname[1] == '/') {\n    fname += 2;\n  }\n  /*\n   * Drop / if it is the only one in the path.\n   * This allows use of /pretend/directories but serves /file.txt as normal.\n   */\n  if (fname[0] == '/' && strchr(fname + 1, '/') == NULL) {\n    fname++;\n  }\n  return fname;\n}\n\n#if !defined(MG_FS_NO_VFS)\n\n#include <errno.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#ifdef __TI_COMPILER_VERSION__\n#include <file.h>\n#endif\n\n/* Amalgamated: #include \"common/cs_dbg.h\" */\n/* Amalgamated: #include \"common/platform.h\" */\n\n#ifdef CC3200_FS_SPIFFS\n/* Amalgamated: #include \"cc3200_fs_spiffs.h\" */\n#endif\n\n#ifdef MG_FS_SLFS\n/* Amalgamated: #include \"sl_fs_slfs.h\" */\n#endif\n\n#define NUM_SYS_FDS 3\n#define SPIFFS_FD_BASE 10\n#define SLFS_FD_BASE 100\n\n#if !defined(MG_UART_CHAR_PUT) && !defined(MG_UART_WRITE)\n#if CS_PLATFORM == CS_P_CC3200\n#include <inc/hw_types.h>\n#include <inc/hw_memmap.h>\n#include <driverlib/rom.h>\n#include <driverlib/rom_map.h>\n#include <driverlib/uart.h>\n#define MG_UART_CHAR_PUT(fd, c) MAP_UARTCharPut(UARTA0_BASE, c);\n#else\n#define MG_UART_WRITE(fd, buf, len)\n#endif /* CS_PLATFORM == CS_P_CC3200 */\n#endif /* !MG_UART_CHAR_PUT */\n\nenum fd_type {\n  FD_INVALID,\n  FD_SYS,\n#ifdef CC3200_FS_SPIFFS\n  FD_SPIFFS,\n#endif\n#ifdef MG_FS_SLFS\n  FD_SLFS\n#endif\n};\nstatic int fd_type(int fd) {\n  if (fd >= 0 && fd < NUM_SYS_FDS) return FD_SYS;\n#ifdef CC3200_FS_SPIFFS\n  if (fd >= SPIFFS_FD_BASE && fd < SPIFFS_FD_BASE + MAX_OPEN_SPIFFS_FILES) {\n    return FD_SPIFFS;\n  }\n#endif\n#ifdef MG_FS_SLFS\n  if (fd >= SLFS_FD_BASE && fd < SLFS_FD_BASE + MAX_OPEN_SLFS_FILES) {\n    return FD_SLFS;\n  }\n#endif\n  return FD_INVALID;\n}\n\n#if MG_TI_NO_HOST_INTERFACE\nint open(const char *pathname, unsigned flags, int mode) {\n#else\nint _open(const char *pathname, int flags, mode_t mode) {\n#endif\n  int fd = -1;\n  bool is_sl;\n  const char *fname = drop_dir(pathname, &is_sl);\n  if (is_sl) {\n#ifdef MG_FS_SLFS\n    fd = fs_slfs_open(fname, flags, mode);\n    if (fd >= 0) fd += SLFS_FD_BASE;\n#endif\n  } else {\n#ifdef CC3200_FS_SPIFFS\n    fd = fs_spiffs_open(fname, flags, mode);\n    if (fd >= 0) fd += SPIFFS_FD_BASE;\n#endif\n  }\n  LOG(LL_DEBUG,\n      (\"open(%s, 0x%x) = %d, fname = %s\", pathname, flags, fd, fname));\n  return fd;\n}\n\nint _stat(const char *pathname, struct stat *st) {\n  int res = -1;\n  bool is_sl;\n  const char *fname = drop_dir(pathname, &is_sl);\n  memset(st, 0, sizeof(*st));\n  /* Simulate statting the root directory. */\n  if (fname[0] == '\\0' || strcmp(fname, \".\") == 0) {\n    st->st_ino = 0;\n    st->st_mode = S_IFDIR | 0777;\n    st->st_nlink = 1;\n    st->st_size = 0;\n    return 0;\n  }\n  if (is_sl) {\n#ifdef MG_FS_SLFS\n    res = fs_slfs_stat(fname, st);\n#endif\n  } else {\n#ifdef CC3200_FS_SPIFFS\n    res = fs_spiffs_stat(fname, st);\n#endif\n  }\n  LOG(LL_DEBUG, (\"stat(%s) = %d; fname = %s\", pathname, res, fname));\n  return res;\n}\n\n#if MG_TI_NO_HOST_INTERFACE\nint close(int fd) {\n#else\nint _close(int fd) {\n#endif\n  int r = -1;\n  switch (fd_type(fd)) {\n    case FD_INVALID:\n      r = set_errno(EBADF);\n      break;\n    case FD_SYS:\n      r = set_errno(EACCES);\n      break;\n#ifdef CC3200_FS_SPIFFS\n    case FD_SPIFFS:\n      r = fs_spiffs_close(fd - SPIFFS_FD_BASE);\n      break;\n#endif\n#ifdef MG_FS_SLFS\n    case FD_SLFS:\n      r = fs_slfs_close(fd - SLFS_FD_BASE);\n      break;\n#endif\n  }\n  DBG((\"close(%d) = %d\", fd, r));\n  return r;\n}\n\n#if MG_TI_NO_HOST_INTERFACE\noff_t lseek(int fd, off_t offset, int whence) {\n#else\noff_t _lseek(int fd, off_t offset, int whence) {\n#endif\n  int r = -1;\n  switch (fd_type(fd)) {\n    case FD_INVALID:\n      r = set_errno(EBADF);\n      break;\n    case FD_SYS:\n      r = set_errno(ESPIPE);\n      break;\n#ifdef CC3200_FS_SPIFFS\n    case FD_SPIFFS:\n      r = fs_spiffs_lseek(fd - SPIFFS_FD_BASE, offset, whence);\n      break;\n#endif\n#ifdef MG_FS_SLFS\n    case FD_SLFS:\n      r = fs_slfs_lseek(fd - SLFS_FD_BASE, offset, whence);\n      break;\n#endif\n  }\n  DBG((\"lseek(%d, %d, %d) = %d\", fd, (int) offset, whence, r));\n  return r;\n}\n\nint _fstat(int fd, struct stat *s) {\n  int r = -1;\n  memset(s, 0, sizeof(*s));\n  switch (fd_type(fd)) {\n    case FD_INVALID:\n      r = set_errno(EBADF);\n      break;\n    case FD_SYS: {\n      /* Create barely passable stats for STD{IN,OUT,ERR}. */\n      memset(s, 0, sizeof(*s));\n      s->st_ino = fd;\n      s->st_mode = S_IFCHR | 0666;\n      r = 0;\n      break;\n    }\n#ifdef CC3200_FS_SPIFFS\n    case FD_SPIFFS:\n      r = fs_spiffs_fstat(fd - SPIFFS_FD_BASE, s);\n      break;\n#endif\n#ifdef MG_FS_SLFS\n    case FD_SLFS:\n      r = fs_slfs_fstat(fd - SLFS_FD_BASE, s);\n      break;\n#endif\n  }\n  DBG((\"fstat(%d) = %d\", fd, r));\n  return r;\n}\n\n#if MG_TI_NO_HOST_INTERFACE\nint read(int fd, char *buf, unsigned count) {\n#else\nssize_t _read(int fd, void *buf, size_t count) {\n#endif\n  int r = -1;\n  switch (fd_type(fd)) {\n    case FD_INVALID:\n      r = set_errno(EBADF);\n      break;\n    case FD_SYS: {\n      if (fd != 0) {\n        r = set_errno(EACCES);\n        break;\n      }\n      /* Should we allow reading from stdin = uart? */\n      r = set_errno(ENOTSUP);\n      break;\n    }\n#ifdef CC3200_FS_SPIFFS\n    case FD_SPIFFS:\n      r = fs_spiffs_read(fd - SPIFFS_FD_BASE, buf, count);\n      break;\n#endif\n#ifdef MG_FS_SLFS\n    case FD_SLFS:\n      r = fs_slfs_read(fd - SLFS_FD_BASE, buf, count);\n      break;\n#endif\n  }\n  DBG((\"read(%d, %u) = %d\", fd, count, r));\n  return r;\n}\n\n#if MG_TI_NO_HOST_INTERFACE\nint write(int fd, const char *buf, unsigned count) {\n#else\nssize_t _write(int fd, const void *buf, size_t count) {\n#endif\n  int r = -1;\n  switch (fd_type(fd)) {\n    case FD_INVALID:\n      r = set_errno(EBADF);\n      break;\n    case FD_SYS: {\n      if (fd == 0) {\n        r = set_errno(EACCES);\n        break;\n      }\n#ifdef MG_UART_WRITE\n      MG_UART_WRITE(fd, buf, count);\n#elif defined(MG_UART_CHAR_PUT)\n      {\n        size_t i;\n        for (i = 0; i < count; i++) {\n          const char c = ((const char *) buf)[i];\n          if (c == '\\n') MG_UART_CHAR_PUT(fd, '\\r');\n          MG_UART_CHAR_PUT(fd, c);\n        }\n      }\n#endif\n      r = count;\n      break;\n    }\n#ifdef CC3200_FS_SPIFFS\n    case FD_SPIFFS:\n      r = fs_spiffs_write(fd - SPIFFS_FD_BASE, buf, count);\n      break;\n#endif\n#ifdef MG_FS_SLFS\n    case FD_SLFS:\n      r = fs_slfs_write(fd - SLFS_FD_BASE, buf, count);\n      break;\n#endif\n  }\n  return r;\n}\n\n/*\n * On Newlib we override rename directly too, because the default\n * implementation using _link and _unlink doesn't work for us.\n */\n#if MG_TI_NO_HOST_INTERFACE || defined(_NEWLIB_VERSION)\nint rename(const char *frompath, const char *topath) {\n  int r = -1;\n  bool is_sl_from, is_sl_to;\n  const char *from = drop_dir(frompath, &is_sl_from);\n  const char *to = drop_dir(topath, &is_sl_to);\n  if (is_sl_from || is_sl_to) {\n    set_errno(ENOTSUP);\n  } else {\n#ifdef CC3200_FS_SPIFFS\n    r = fs_spiffs_rename(from, to);\n#endif\n  }\n  DBG((\"rename(%s, %s) = %d\", from, to, r));\n  return r;\n}\n#endif /* MG_TI_NO_HOST_INTERFACE || defined(_NEWLIB_VERSION) */\n\n#if MG_TI_NO_HOST_INTERFACE\nint unlink(const char *pathname) {\n#else\nint _unlink(const char *pathname) {\n#endif\n  int r = -1;\n  bool is_sl;\n  const char *fname = drop_dir(pathname, &is_sl);\n  if (is_sl) {\n#ifdef MG_FS_SLFS\n    r = fs_slfs_unlink(fname);\n#endif\n  } else {\n#ifdef CC3200_FS_SPIFFS\n    r = fs_spiffs_unlink(fname);\n#endif\n  }\n  DBG((\"unlink(%s) = %d, fname = %s\", pathname, r, fname));\n  return r;\n}\n\n#ifdef CC3200_FS_SPIFFS /* FailFS does not support listing files. */\nDIR *opendir(const char *dir_name) {\n  DIR *r = NULL;\n  bool is_sl;\n  drop_dir(dir_name, &is_sl);\n  if (is_sl) {\n    r = NULL;\n    set_errno(ENOTSUP);\n  } else {\n    r = fs_spiffs_opendir(dir_name);\n  }\n  DBG((\"opendir(%s) = %p\", dir_name, r));\n  return r;\n}\n\nstruct dirent *readdir(DIR *dir) {\n  struct dirent *res = fs_spiffs_readdir(dir);\n  DBG((\"readdir(%p) = %p\", dir, res));\n  return res;\n}\n\nint closedir(DIR *dir) {\n  int res = fs_spiffs_closedir(dir);\n  DBG((\"closedir(%p) = %d\", dir, res));\n  return res;\n}\n\nint rmdir(const char *path) {\n  return fs_spiffs_rmdir(path);\n}\n\nint mkdir(const char *path, mode_t mode) {\n  (void) path;\n  (void) mode;\n  /* for spiffs supports only root dir, which comes from mongoose as '.' */\n  return (strlen(path) == 1 && *path == '.') ? 0 : ENOTDIR;\n}\n#endif\n\nint sl_fs_init(void) {\n  int ret = 1;\n#ifdef __TI_COMPILER_VERSION__\n#ifdef MG_FS_SLFS\n#pragma diag_push\n#pragma diag_suppress 169 /* Nothing we can do about the prototype mismatch. \\\n                             */\n  ret = (add_device(\"SL\", _MSA, fs_slfs_open, fs_slfs_close, fs_slfs_read,\n                    fs_slfs_write, fs_slfs_lseek, fs_slfs_unlink,\n                    fs_slfs_rename) == 0);\n#pragma diag_pop\n#endif\n#endif\n  return ret;\n}\n\n#endif /* !defined(MG_FS_NO_VFS) */\n#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK && (defined(MG_FS_SLFS) || \\\n          defined(MG_FS_SPIFFS)) */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_socket.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if MG_NET_IF == MG_NET_IF_SIMPLELINK\n\n#include <errno.h>\n#include <stdio.h>\n\n/* Amalgamated: #include \"common/platform.h\" */\n\nconst char *inet_ntop(int af, const void *src, char *dst, socklen_t size) {\n  int res;\n  struct in_addr *in = (struct in_addr *) src;\n  if (af != AF_INET) {\n    errno = ENOTSUP;\n    return NULL;\n  }\n  res = snprintf(dst, size, \"%lu.%lu.%lu.%lu\", SL_IPV4_BYTE(in->s_addr, 0),\n                 SL_IPV4_BYTE(in->s_addr, 1), SL_IPV4_BYTE(in->s_addr, 2),\n                 SL_IPV4_BYTE(in->s_addr, 3));\n  return res > 0 ? dst : NULL;\n}\n\nchar *inet_ntoa(struct in_addr n) {\n  static char a[16];\n  return (char *) inet_ntop(AF_INET, &n, a, sizeof(a));\n}\n\nint inet_pton(int af, const char *src, void *dst) {\n  uint32_t a0, a1, a2, a3;\n  uint8_t *db = (uint8_t *) dst;\n  if (af != AF_INET) {\n    errno = ENOTSUP;\n    return 0;\n  }\n  if (sscanf(src, \"%lu.%lu.%lu.%lu\", &a0, &a1, &a2, &a3) != 4) {\n    return 0;\n  }\n  *db = a3;\n  *(db + 1) = a2;\n  *(db + 2) = a1;\n  *(db + 3) = a0;\n  return 1;\n}\n\n#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_mg_task.c\"\n#endif\n#if MG_NET_IF == MG_NET_IF_SIMPLELINK && !defined(MG_SIMPLELINK_NO_OSI)\n\n/* Amalgamated: #include \"mg_task.h\" */\n\n#include <oslib/osi.h>\n\nenum mg_q_msg_type {\n  MG_Q_MSG_CB,\n};\nstruct mg_q_msg {\n  enum mg_q_msg_type type;\n  void (*cb)(struct mg_mgr *mgr, void *arg);\n  void *arg;\n};\nstatic OsiMsgQ_t s_mg_q;\nstatic void mg_task(void *arg);\n\nbool mg_start_task(int priority, int stack_size, mg_init_cb mg_init) {\n  if (osi_MsgQCreate(&s_mg_q, \"MG\", sizeof(struct mg_q_msg), 16) != OSI_OK) {\n    return false;\n  }\n  if (osi_TaskCreate(mg_task, (const signed char *) \"MG\", stack_size,\n                     (void *) mg_init, priority, NULL) != OSI_OK) {\n    return false;\n  }\n  return true;\n}\n\nstatic void mg_task(void *arg) {\n  struct mg_mgr mgr;\n  mg_init_cb mg_init = (mg_init_cb) arg;\n  mg_mgr_init(&mgr, NULL);\n  mg_init(&mgr);\n  while (1) {\n    struct mg_q_msg msg;\n    mg_mgr_poll(&mgr, 1);\n    if (osi_MsgQRead(&s_mg_q, &msg, 1) != OSI_OK) continue;\n    switch (msg.type) {\n      case MG_Q_MSG_CB: {\n        msg.cb(&mgr, msg.arg);\n      }\n    }\n  }\n}\n\nvoid mg_run_in_task(void (*cb)(struct mg_mgr *mgr, void *arg), void *cb_arg) {\n  struct mg_q_msg msg = {MG_Q_MSG_CB, cb, cb_arg};\n  osi_MsgQWrite(&s_mg_q, &msg, OSI_NO_WAIT);\n}\n\n#endif /* MG_NET_IF == MG_NET_IF_SIMPLELINK && !defined(MG_SIMPLELINK_NO_OSI) \\\n          */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_net_if.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_SIMPLELINK_SL_NET_IF_H_\n#define CS_COMMON_PLATFORMS_SIMPLELINK_SL_NET_IF_H_\n\n/* Amalgamated: #include \"mongoose/src/net_if.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifndef MG_ENABLE_NET_IF_SIMPLELINK\n#define MG_ENABLE_NET_IF_SIMPLELINK MG_NET_IF == MG_NET_IF_SIMPLELINK\n#endif\n\nextern const struct mg_iface_vtable mg_simplelink_iface_vtable;\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_PLATFORMS_SIMPLELINK_SL_NET_IF_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_net_if.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* Amalgamated: #include \"common/platforms/simplelink/sl_net_if.h\" */\n\n#if MG_ENABLE_NET_IF_SIMPLELINK\n\n/* Amalgamated: #include \"mongoose/src/internal.h\" */\n/* Amalgamated: #include \"mongoose/src/util.h\" */\n\n#define MG_TCP_RECV_BUFFER_SIZE 1024\n#define MG_UDP_RECV_BUFFER_SIZE 1500\n\nstatic sock_t mg_open_listening_socket(struct mg_connection *nc,\n                                       union socket_address *sa, int type,\n                                       int proto);\n\nstatic void mg_set_non_blocking_mode(sock_t sock) {\n  SlSockNonblocking_t opt;\n#if SL_MAJOR_VERSION_NUM < 2\n  opt.NonblockingEnabled = 1;\n#else\n  opt.NonBlockingEnabled = 1;\n#endif\n  sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_NONBLOCKING, &opt, sizeof(opt));\n}\n\nstatic int mg_is_error(int n) {\n  return (n < 0 && n != SL_ERROR_BSD_EALREADY && n != SL_ERROR_BSD_EAGAIN);\n}\n\nstatic void mg_sl_if_connect_tcp(struct mg_connection *nc,\n                                 const union socket_address *sa) {\n  int proto = 0;\n#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK\n  if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;\n#endif\n  sock_t sock = sl_Socket(AF_INET, SOCK_STREAM, proto);\n  if (sock < 0) {\n    nc->err = sock;\n    goto out;\n  }\n  mg_sock_set(nc, sock);\n#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK\n  nc->err = sl_set_ssl_opts(sock, nc);\n  if (nc->err != 0) goto out;\n#endif\n  nc->err = sl_Connect(sock, &sa->sa, sizeof(sa->sin));\nout:\n  DBG((\"%p to %s:%d sock %d %d err %d\", nc, inet_ntoa(sa->sin.sin_addr),\n       ntohs(sa->sin.sin_port), nc->sock, proto, nc->err));\n}\n\nstatic void mg_sl_if_connect_udp(struct mg_connection *nc) {\n  sock_t sock = sl_Socket(AF_INET, SOCK_DGRAM, 0);\n  if (sock < 0) {\n    nc->err = sock;\n    return;\n  }\n  mg_sock_set(nc, sock);\n  nc->err = 0;\n}\n\nstatic int mg_sl_if_listen_tcp(struct mg_connection *nc,\n                               union socket_address *sa) {\n  int proto = 0;\n  if (nc->flags & MG_F_SSL) proto = SL_SEC_SOCKET;\n  sock_t sock = mg_open_listening_socket(nc, sa, SOCK_STREAM, proto);\n  if (sock < 0) return sock;\n  mg_sock_set(nc, sock);\n  return 0;\n}\n\nstatic int mg_sl_if_listen_udp(struct mg_connection *nc,\n                               union socket_address *sa) {\n  sock_t sock = mg_open_listening_socket(nc, sa, SOCK_DGRAM, 0);\n  if (sock == INVALID_SOCKET) return (errno ? errno : 1);\n  mg_sock_set(nc, sock);\n  return 0;\n}\n\nstatic int mg_sl_if_tcp_send(struct mg_connection *nc, const void *buf,\n                             size_t len) {\n  int n = (int) sl_Send(nc->sock, buf, len, 0);\n  if (n < 0 && !mg_is_error(n)) n = 0;\n  return n;\n}\n\nstatic int mg_sl_if_udp_send(struct mg_connection *nc, const void *buf,\n                             size_t len) {\n  int n = sl_SendTo(nc->sock, buf, len, 0, &nc->sa.sa, sizeof(nc->sa.sin));\n  if (n < 0 && !mg_is_error(n)) n = 0;\n  return n;\n}\n\nstatic int mg_sl_if_tcp_recv(struct mg_connection *nc, void *buf, size_t len) {\n  int n = sl_Recv(nc->sock, buf, len, 0);\n  if (n == 0) {\n    /* Orderly shutdown of the socket, try flushing output. */\n    nc->flags |= MG_F_SEND_AND_CLOSE;\n  } else if (n < 0 && !mg_is_error(n)) {\n    n = 0;\n  }\n  return n;\n}\n\nstatic int mg_sl_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,\n                             union socket_address *sa, size_t *sa_len) {\n  SlSocklen_t sa_len_t = *sa_len;\n  int n = sl_RecvFrom(nc->sock, buf, MG_UDP_RECV_BUFFER_SIZE, 0,\n                      (SlSockAddr_t *) sa, &sa_len_t);\n  *sa_len = sa_len_t;\n  if (n < 0 && !mg_is_error(n)) n = 0;\n  return n;\n}\n\nstatic int mg_sl_if_create_conn(struct mg_connection *nc) {\n  (void) nc;\n  return 1;\n}\n\nvoid mg_sl_if_destroy_conn(struct mg_connection *nc) {\n  if (nc->sock == INVALID_SOCKET) return;\n  /* For UDP, only close outgoing sockets or listeners. */\n  if (!(nc->flags & MG_F_UDP) || nc->listener == NULL) {\n    sl_Close(nc->sock);\n  }\n  nc->sock = INVALID_SOCKET;\n}\n\nstatic int mg_accept_conn(struct mg_connection *lc) {\n  struct mg_connection *nc;\n  union socket_address sa;\n  socklen_t sa_len = sizeof(sa);\n  sock_t sock = sl_Accept(lc->sock, &sa.sa, &sa_len);\n  if (sock < 0) {\n    DBG((\"%p: failed to accept: %d\", lc, sock));\n    return 0;\n  }\n  nc = mg_if_accept_new_conn(lc);\n  if (nc == NULL) {\n    sl_Close(sock);\n    return 0;\n  }\n  DBG((\"%p conn from %s:%d\", nc, inet_ntoa(sa.sin.sin_addr),\n       ntohs(sa.sin.sin_port)));\n  mg_sock_set(nc, sock);\n  mg_if_accept_tcp_cb(nc, &sa, sa_len);\n  return 1;\n}\n\n/* 'sa' must be an initialized address to bind to */\nstatic sock_t mg_open_listening_socket(struct mg_connection *nc,\n                                       union socket_address *sa, int type,\n                                       int proto) {\n  int r;\n  socklen_t sa_len =\n      (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);\n  sock_t sock = sl_Socket(sa->sa.sa_family, type, proto);\n  if (sock < 0) return sock;\n#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK\n  if ((r = sl_set_ssl_opts(sock, nc)) < 0) goto clean;\n#endif\n  if ((r = sl_Bind(sock, &sa->sa, sa_len)) < 0) goto clean;\n  if (type != SOCK_DGRAM) {\n    if ((r = sl_Listen(sock, SOMAXCONN)) < 0) goto clean;\n  }\n  mg_set_non_blocking_mode(sock);\nclean:\n  if (r < 0) {\n    sl_Close(sock);\n    sock = r;\n  }\n  return sock;\n}\n\n#define _MG_F_FD_CAN_READ 1\n#define _MG_F_FD_CAN_WRITE 1 << 1\n#define _MG_F_FD_ERROR 1 << 2\n\nvoid mg_mgr_handle_conn(struct mg_connection *nc, int fd_flags, double now) {\n  DBG((\"%p fd=%d fd_flags=%d nc_flags=0x%lx rmbl=%d smbl=%d\", nc, nc->sock,\n       fd_flags, nc->flags, (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));\n\n  if (!mg_if_poll(nc, now)) return;\n\n  if (nc->flags & MG_F_CONNECTING) {\n    if ((nc->flags & MG_F_UDP) || nc->err != SL_ERROR_BSD_EALREADY) {\n      mg_if_connect_cb(nc, nc->err);\n    } else {\n      /* In SimpleLink, to get status of non-blocking connect() we need to wait\n       * until socket is writable and repeat the call to sl_Connect again,\n       * which will now return the real status. */\n      if (fd_flags & _MG_F_FD_CAN_WRITE) {\n        nc->err = sl_Connect(nc->sock, &nc->sa.sa, sizeof(nc->sa.sin));\n        DBG((\"%p conn res=%d\", nc, nc->err));\n        if (nc->err == SL_ERROR_BSD_ESECSNOVERIFY ||\n            /* TODO(rojer): Provide API to set the date for verification. */\n            nc->err == SL_ERROR_BSD_ESECDATEERROR\n#if SL_MAJOR_VERSION_NUM >= 2\n            /* Per SWRU455, this error does not mean verification failed,\n             * it only means that the cert used is not present in the trusted\n             * root CA catalog. Which is perfectly fine. */\n            ||\n            nc->err == SL_ERROR_BSD_ESECUNKNOWNROOTCA\n#endif\n            ) {\n          nc->err = 0;\n        }\n        mg_if_connect_cb(nc, nc->err);\n      }\n    }\n    /* Ignore read/write in further processing, we've handled it. */\n    fd_flags &= ~(_MG_F_FD_CAN_READ | _MG_F_FD_CAN_WRITE);\n  }\n\n  if (fd_flags & _MG_F_FD_CAN_READ) {\n    if (nc->flags & MG_F_UDP) {\n      mg_if_can_recv_cb(nc);\n    } else {\n      if (nc->flags & MG_F_LISTENING) {\n        mg_accept_conn(nc);\n      } else {\n        mg_if_can_recv_cb(nc);\n      }\n    }\n  }\n\n  if (fd_flags & _MG_F_FD_CAN_WRITE) {\n    mg_if_can_send_cb(nc);\n  }\n\n  DBG((\"%p after fd=%d nc_flags=0x%lx rmbl=%d smbl=%d\", nc, nc->sock, nc->flags,\n       (int) nc->recv_mbuf.len, (int) nc->send_mbuf.len));\n}\n\n/* Associate a socket to a connection. */\nvoid mg_sl_if_sock_set(struct mg_connection *nc, sock_t sock) {\n  mg_set_non_blocking_mode(sock);\n  nc->sock = sock;\n  DBG((\"%p %d\", nc, sock));\n}\n\nvoid mg_sl_if_init(struct mg_iface *iface) {\n  (void) iface;\n  DBG((\"%p using sl_Select()\", iface->mgr));\n}\n\nvoid mg_sl_if_free(struct mg_iface *iface) {\n  (void) iface;\n}\n\nvoid mg_sl_if_add_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\nvoid mg_sl_if_remove_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\ntime_t mg_sl_if_poll(struct mg_iface *iface, int timeout_ms) {\n  struct mg_mgr *mgr = iface->mgr;\n  double now = mg_time();\n  double min_timer;\n  struct mg_connection *nc, *tmp;\n  struct SlTimeval_t tv;\n  SlFdSet_t read_set, write_set, err_set;\n  sock_t max_fd = INVALID_SOCKET;\n  int num_fds, num_ev = 0, num_timers = 0;\n\n  SL_SOCKET_FD_ZERO(&read_set);\n  SL_SOCKET_FD_ZERO(&write_set);\n  SL_SOCKET_FD_ZERO(&err_set);\n\n  /*\n   * Note: it is ok to have connections with sock == INVALID_SOCKET in the list,\n   * e.g. timer-only \"connections\".\n   */\n  min_timer = 0;\n  for (nc = mgr->active_connections, num_fds = 0; nc != NULL; nc = tmp) {\n    tmp = nc->next;\n\n    if (nc->sock != INVALID_SOCKET) {\n      num_fds++;\n\n      if (!(nc->flags & MG_F_WANT_WRITE) &&\n          nc->recv_mbuf.len < nc->recv_mbuf_limit &&\n          (!(nc->flags & MG_F_UDP) || nc->listener == NULL)) {\n        SL_SOCKET_FD_SET(nc->sock, &read_set);\n        if (max_fd == INVALID_SOCKET || nc->sock > max_fd) max_fd = nc->sock;\n      }\n\n      if (((nc->flags & MG_F_CONNECTING) && !(nc->flags & MG_F_WANT_READ)) ||\n          (nc->send_mbuf.len > 0 && !(nc->flags & MG_F_CONNECTING))) {\n        SL_SOCKET_FD_SET(nc->sock, &write_set);\n        SL_SOCKET_FD_SET(nc->sock, &err_set);\n        if (max_fd == INVALID_SOCKET || nc->sock > max_fd) max_fd = nc->sock;\n      }\n    }\n\n    if (nc->ev_timer_time > 0) {\n      if (num_timers == 0 || nc->ev_timer_time < min_timer) {\n        min_timer = nc->ev_timer_time;\n      }\n      num_timers++;\n    }\n  }\n\n  /*\n   * If there is a timer to be fired earlier than the requested timeout,\n   * adjust the timeout.\n   */\n  if (num_timers > 0) {\n    double timer_timeout_ms = (min_timer - mg_time()) * 1000 + 1 /* rounding */;\n    if (timer_timeout_ms < timeout_ms) {\n      timeout_ms = timer_timeout_ms;\n    }\n  }\n  if (timeout_ms < 0) timeout_ms = 0;\n\n  tv.tv_sec = timeout_ms / 1000;\n  tv.tv_usec = (timeout_ms % 1000) * 1000;\n\n  if (num_fds > 0) {\n    num_ev = sl_Select((int) max_fd + 1, &read_set, &write_set, &err_set, &tv);\n  }\n\n  now = mg_time();\n  DBG((\"sl_Select @ %ld num_ev=%d of %d, timeout=%d\", (long) now, num_ev,\n       num_fds, timeout_ms));\n\n  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {\n    int fd_flags = 0;\n    if (nc->sock != INVALID_SOCKET) {\n      if (num_ev > 0) {\n        fd_flags =\n            (SL_SOCKET_FD_ISSET(nc->sock, &read_set) &&\n                     (!(nc->flags & MG_F_UDP) || nc->listener == NULL)\n                 ? _MG_F_FD_CAN_READ\n                 : 0) |\n            (SL_SOCKET_FD_ISSET(nc->sock, &write_set) ? _MG_F_FD_CAN_WRITE\n                                                      : 0) |\n            (SL_SOCKET_FD_ISSET(nc->sock, &err_set) ? _MG_F_FD_ERROR : 0);\n      }\n      /* SimpleLink does not report UDP sockets as writable. */\n      if (nc->flags & MG_F_UDP && nc->send_mbuf.len > 0) {\n        fd_flags |= _MG_F_FD_CAN_WRITE;\n      }\n    }\n    tmp = nc->next;\n    mg_mgr_handle_conn(nc, fd_flags, now);\n  }\n\n  return now;\n}\n\nvoid mg_sl_if_get_conn_addr(struct mg_connection *nc, int remote,\n                            union socket_address *sa) {\n  /* SimpleLink does not provide a way to get socket's peer address after\n   * accept or connect. Address should have been preserved in the connection,\n   * so we do our best here by using it. */\n  if (remote) memcpy(sa, &nc->sa, sizeof(*sa));\n}\n\nvoid sl_restart_cb(struct mg_mgr *mgr) {\n  /*\n   * SimpleLink has been restarted, meaning all sockets have been invalidated.\n   * We try our best - we'll restart the listeners, but for outgoing\n   * connections we have no option but to terminate.\n   */\n  struct mg_connection *nc;\n  for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {\n    if (nc->sock == INVALID_SOCKET) continue; /* Could be a timer */\n    if (nc->flags & MG_F_LISTENING) {\n      DBG((\"restarting %p %s:%d\", nc, inet_ntoa(nc->sa.sin.sin_addr),\n           ntohs(nc->sa.sin.sin_port)));\n      int res = (nc->flags & MG_F_UDP ? mg_sl_if_listen_udp(nc, &nc->sa)\n                                      : mg_sl_if_listen_tcp(nc, &nc->sa));\n      if (res == 0) continue;\n      /* Well, we tried and failed. Fall through to closing. */\n    }\n    nc->sock = INVALID_SOCKET;\n    DBG((\"terminating %p %s:%d\", nc, inet_ntoa(nc->sa.sin.sin_addr),\n         ntohs(nc->sa.sin.sin_port)));\n    /* TODO(rojer): Outgoing UDP? */\n    nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n  }\n}\n\n/* clang-format off */\n#define MG_SL_IFACE_VTABLE                                              \\\n  {                                                                     \\\n    mg_sl_if_init,                                                      \\\n    mg_sl_if_free,                                                      \\\n    mg_sl_if_add_conn,                                                  \\\n    mg_sl_if_remove_conn,                                               \\\n    mg_sl_if_poll,                                                      \\\n    mg_sl_if_listen_tcp,                                                \\\n    mg_sl_if_listen_udp,                                                \\\n    mg_sl_if_connect_tcp,                                               \\\n    mg_sl_if_connect_udp,                                               \\\n    mg_sl_if_tcp_send,                                                  \\\n    mg_sl_if_udp_send,                                                  \\\n    mg_sl_if_tcp_recv,                                                  \\\n    mg_sl_if_udp_recv,                                                  \\\n    mg_sl_if_create_conn,                                               \\\n    mg_sl_if_destroy_conn,                                              \\\n    mg_sl_if_sock_set,                                                  \\\n    mg_sl_if_get_conn_addr,                                             \\\n  }\n/* clang-format on */\n\nconst struct mg_iface_vtable mg_simplelink_iface_vtable = MG_SL_IFACE_VTABLE;\n#if MG_NET_IF == MG_NET_IF_SIMPLELINK\nconst struct mg_iface_vtable mg_default_iface_vtable = MG_SL_IFACE_VTABLE;\n#endif\n\n#endif /* MG_ENABLE_NET_IF_SIMPLELINK */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/simplelink/sl_ssl_if.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n\n#ifndef MG_SSL_IF_SIMPLELINK_SLFS_PREFIX\n#define MG_SSL_IF_SIMPLELINK_SLFS_PREFIX \"SL:\"\n#endif\n\n#define MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN \\\n  (sizeof(MG_SSL_IF_SIMPLELINK_SLFS_PREFIX) - 1)\n\nstruct mg_ssl_if_ctx {\n  char *ssl_cert;\n  char *ssl_key;\n  char *ssl_ca_cert;\n  char *ssl_server_name;\n};\n\nvoid mg_ssl_if_init() {\n}\n\nenum mg_ssl_if_result mg_ssl_if_conn_init(\n    struct mg_connection *nc, const struct mg_ssl_if_conn_params *params,\n    const char **err_msg) {\n  struct mg_ssl_if_ctx *ctx =\n      (struct mg_ssl_if_ctx *) MG_CALLOC(1, sizeof(*ctx));\n  if (ctx == NULL) {\n    MG_SET_PTRPTR(err_msg, \"Out of memory\");\n    return MG_SSL_ERROR;\n  }\n  nc->ssl_if_data = ctx;\n\n  if (params->cert != NULL || params->key != NULL) {\n    if (params->cert != NULL && params->key != NULL) {\n      ctx->ssl_cert = strdup(params->cert);\n      ctx->ssl_key = strdup(params->key);\n    } else {\n      MG_SET_PTRPTR(err_msg, \"Both cert and key are required.\");\n      return MG_SSL_ERROR;\n    }\n  }\n  if (params->ca_cert != NULL && strcmp(params->ca_cert, \"*\") != 0) {\n    ctx->ssl_ca_cert = strdup(params->ca_cert);\n  }\n  /* TODO(rojer): cipher_suites. */\n  if (params->server_name != NULL) {\n    ctx->ssl_server_name = strdup(params->server_name);\n  }\n  return MG_SSL_OK;\n}\n\nenum mg_ssl_if_result mg_ssl_if_conn_accept(struct mg_connection *nc,\n                                            struct mg_connection *lc) {\n  /* SimpleLink does everything for us, nothing for us to do. */\n  (void) nc;\n  (void) lc;\n  return MG_SSL_OK;\n}\n\nenum mg_ssl_if_result mg_ssl_if_handshake(struct mg_connection *nc) {\n  /* SimpleLink has already performed the handshake, nothing to do. */\n  return MG_SSL_OK;\n}\n\nint mg_ssl_if_read(struct mg_connection *nc, void *buf, size_t len) {\n  /* SimpelLink handles TLS, so this is just a pass-through. */\n  int n = nc->iface->vtable->tcp_recv(nc, buf, len);\n  if (n == 0) nc->flags |= MG_F_WANT_READ;\n  return n;\n}\n\nint mg_ssl_if_write(struct mg_connection *nc, const void *buf, size_t len) {\n  /* SimpelLink handles TLS, so this is just a pass-through. */\n  return nc->iface->vtable->tcp_send(nc, buf, len);\n}\n\nvoid mg_ssl_if_conn_close_notify(struct mg_connection *nc) {\n  /* Nothing to do */\n  (void) nc;\n}\n\nvoid mg_ssl_if_conn_free(struct mg_connection *nc) {\n  struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  if (ctx == NULL) return;\n  nc->ssl_if_data = NULL;\n  MG_FREE(ctx->ssl_cert);\n  MG_FREE(ctx->ssl_key);\n  MG_FREE(ctx->ssl_ca_cert);\n  MG_FREE(ctx->ssl_server_name);\n  memset(ctx, 0, sizeof(*ctx));\n  MG_FREE(ctx);\n}\n\nbool pem_to_der(const char *pem_file, const char *der_file) {\n  bool ret = false;\n  FILE *pf = NULL, *df = NULL;\n  bool writing = false;\n  pf = fopen(pem_file, \"r\");\n  if (pf == NULL) goto clean;\n  remove(der_file);\n  fs_slfs_set_file_size(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN, 2048);\n  df = fopen(der_file, \"w\");\n  fs_slfs_unset_file_flags(der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN);\n  if (df == NULL) goto clean;\n  while (1) {\n    char pem_buf[70];\n    char der_buf[48];\n    if (!fgets(pem_buf, sizeof(pem_buf), pf)) break;\n    if (writing) {\n      if (strstr(pem_buf, \"-----END \") != NULL) {\n        ret = true;\n        break;\n      }\n      int l = 0;\n      while (!isspace((unsigned int) pem_buf[l])) l++;\n      int der_len = 0;\n      cs_base64_decode((const unsigned char *) pem_buf, sizeof(pem_buf),\n                       der_buf, &der_len);\n      if (der_len <= 0) break;\n      if (fwrite(der_buf, 1, der_len, df) != der_len) break;\n    } else if (strstr(pem_buf, \"-----BEGIN \") != NULL) {\n      writing = true;\n    }\n  }\n\nclean:\n  if (pf != NULL) fclose(pf);\n  if (df != NULL) {\n    fclose(df);\n    if (!ret) remove(der_file);\n  }\n  return ret;\n}\n\n#if MG_ENABLE_FILESYSTEM && defined(MG_FS_SLFS)\n/* If the file's extension is .pem, convert it to DER format and put on SLFS. */\nstatic char *sl_pem2der(const char *pem_file) {\n  const char *pem_ext = strstr(pem_file, \".pem\");\n  if (pem_ext == NULL || *(pem_ext + 4) != '\\0') {\n    return strdup(pem_file);\n  }\n  char *der_file = NULL;\n  /* DER file must be located on SLFS, add prefix. */\n  int l = mg_asprintf(&der_file, 0, MG_SSL_IF_SIMPLELINK_SLFS_PREFIX \"%.*s.der\",\n                      (int) (pem_ext - pem_file), pem_file);\n  if (der_file == NULL) return NULL;\n  bool result = false;\n  cs_stat_t st;\n  if (mg_stat(der_file, &st) != 0) {\n    result = pem_to_der(pem_file, der_file);\n    LOG(LL_DEBUG, (\"%s -> %s = %d\", pem_file, der_file, result));\n  } else {\n    /* File exists, assume it's already been converted. */\n    result = true;\n  }\n  if (result) {\n    /* Strip the SL: prefix we added since NWP does not expect it. */\n    memmove(der_file, der_file + MG_SSL_IF_SIMPLELINK_SLFS_PREFIX_LEN,\n            l - 2 /* including \\0 */);\n  } else {\n    MG_FREE(der_file);\n    der_file = NULL;\n  }\n  return der_file;\n}\n#else\nstatic char *sl_pem2der(const char *pem_file) {\n  return strdup(pem_file);\n}\n#endif\n\nint sl_set_ssl_opts(int sock, struct mg_connection *nc) {\n  int err;\n  const struct mg_ssl_if_ctx *ctx = (struct mg_ssl_if_ctx *) nc->ssl_if_data;\n  DBG((\"%p ssl ctx: %p\", nc, ctx));\n\n  if (ctx == NULL) return 0;\n  DBG((\"%p %s,%s,%s,%s\", nc, (ctx->ssl_cert ? ctx->ssl_cert : \"-\"),\n       (ctx->ssl_key ? ctx->ssl_cert : \"-\"),\n       (ctx->ssl_ca_cert ? ctx->ssl_ca_cert : \"-\"),\n       (ctx->ssl_server_name ? ctx->ssl_server_name : \"-\")));\n  if (ctx->ssl_cert != NULL && ctx->ssl_key != NULL) {\n    char *ssl_cert = sl_pem2der(ctx->ssl_cert), *ssl_key = NULL;\n    if (ssl_cert != NULL) {\n      err = sl_SetSockOpt(sock, SL_SOL_SOCKET,\n                          SL_SO_SECURE_FILES_CERTIFICATE_FILE_NAME, ssl_cert,\n                          strlen(ssl_cert));\n      MG_FREE(ssl_cert);\n      LOG(LL_DEBUG, (\"CERTIFICATE_FILE_NAME %s -> %d\", ssl_cert, err));\n      ssl_key = sl_pem2der(ctx->ssl_key);\n      if (ssl_key != NULL) {\n        err = sl_SetSockOpt(sock, SL_SOL_SOCKET,\n                            SL_SO_SECURE_FILES_PRIVATE_KEY_FILE_NAME, ssl_key,\n                            strlen(ssl_key));\n        MG_FREE(ssl_key);\n        LOG(LL_DEBUG, (\"PRIVATE_KEY_FILE_NAME %s -> %d\", ssl_key, err));\n      } else {\n        err = -1;\n      }\n    } else {\n      err = -1;\n    }\n    if (err != 0) return err;\n  }\n  if (ctx->ssl_ca_cert != NULL) {\n    if (ctx->ssl_ca_cert[0] != '\\0') {\n      char *ssl_ca_cert = sl_pem2der(ctx->ssl_ca_cert);\n      if (ssl_ca_cert != NULL) {\n        err =\n            sl_SetSockOpt(sock, SL_SOL_SOCKET, SL_SO_SECURE_FILES_CA_FILE_NAME,\n                          ssl_ca_cert, strlen(ssl_ca_cert));\n        LOG(LL_DEBUG, (\"CA_FILE_NAME %s -> %d\", ssl_ca_cert, err));\n      } else {\n        err = -1;\n      }\n      MG_FREE(ssl_ca_cert);\n      if (err != 0) return err;\n    }\n  }\n  if (ctx->ssl_server_name != NULL) {\n    err = sl_SetSockOpt(sock, SL_SOL_SOCKET,\n                        SL_SO_SECURE_DOMAIN_NAME_VERIFICATION,\n                        ctx->ssl_server_name, strlen(ctx->ssl_server_name));\n    DBG((\"DOMAIN_NAME_VERIFICATION %s -> %d\", ctx->ssl_server_name, err));\n    /* Domain name verificationw as added in a NWP service pack, older\n     * versions return SL_ERROR_BSD_ENOPROTOOPT. There isn't much we can do\n     * about it,\n     * so we ignore the error. */\n    if (err != 0 && err != SL_ERROR_BSD_ENOPROTOOPT) return err;\n  }\n  return 0;\n}\n\n#endif /* MG_ENABLE_SSL && MG_SSL_IF == MG_SSL_IF_SIMPLELINK */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/lwip/mg_lwip_net_if.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_\n#define CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_\n\n#ifndef MG_ENABLE_NET_IF_LWIP_LOW_LEVEL\n#define MG_ENABLE_NET_IF_LWIP_LOW_LEVEL MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL\n#endif\n\n#if MG_ENABLE_NET_IF_LWIP_LOW_LEVEL\n\n#include <stdint.h>\n\nextern const struct mg_iface_vtable mg_lwip_iface_vtable;\n\nstruct mg_lwip_conn_state {\n  struct mg_connection *nc;\n  struct mg_connection *lc;\n  union {\n    struct tcp_pcb *tcp;\n    struct udp_pcb *udp;\n  } pcb;\n  err_t err;\n  size_t num_sent; /* Number of acknowledged bytes to be reported to the core */\n  struct pbuf *rx_chain; /* Chain of incoming data segments. */\n  size_t rx_offset; /* Offset within the first pbuf (if partially consumed) */\n  /* Last SSL write size, for retries. */\n  int last_ssl_write_size;\n  /* Whether MG_SIG_RECV is already pending for this connection */\n  int recv_pending;\n  /* Whether the connection is about to close, just `rx_chain` needs to drain */\n  int draining_rx_chain;\n};\n\nenum mg_sig_type {\n  MG_SIG_CONNECT_RESULT = 1,\n  MG_SIG_RECV = 2,\n  MG_SIG_CLOSE_CONN = 3,\n  MG_SIG_TOMBSTONE = 4,\n  MG_SIG_ACCEPT = 5,\n};\n\nvoid mg_lwip_post_signal(enum mg_sig_type sig, struct mg_connection *nc);\n\n/* To be implemented by the platform. */\nvoid mg_lwip_mgr_schedule_poll(struct mg_mgr *mgr);\n\n#endif /* MG_ENABLE_NET_IF_LWIP_LOW_LEVEL */\n\n#endif /* CS_COMMON_PLATFORMS_LWIP_MG_NET_IF_LWIP_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/lwip/mg_lwip_net_if.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if MG_ENABLE_NET_IF_LWIP_LOW_LEVEL\n\n/* Amalgamated: #include \"common/mg_mem.h\" */\n\n#include <lwip/init.h>\n#include <lwip/pbuf.h>\n#include <lwip/tcp.h>\n#include <lwip/tcpip.h>\n#if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105\n#include <lwip/priv/tcp_priv.h>   /* For tcp_seg */\n#include <lwip/priv/tcpip_priv.h> /* For tcpip_api_call */\n#else\n#include <lwip/tcp_impl.h>\n#endif\n#include <lwip/udp.h>\n\n/* Amalgamated: #include \"common/cs_dbg.h\" */\n\n/*\n * Newest versions of LWIP have ip_2_ip4, older have ipX_2_ip,\n * even older have nothing.\n */\n#ifndef ip_2_ip4\n#ifdef ipX_2_ip\n#define ip_2_ip4(addr) ipX_2_ip(addr)\n#else\n#define ip_2_ip4(addr) (addr)\n#endif\n#endif\n\n/*\n * Depending on whether Mongoose is compiled with ipv6 support, use right\n * lwip functions\n */\n#if MG_ENABLE_IPV6\n#define TCP_NEW tcp_new_ip6\n#define TCP_BIND tcp_bind_ip6\n#define UDP_BIND udp_bind_ip6\n#define IPADDR_NTOA(x) ip6addr_ntoa((const ip6_addr_t *)(x))\n#define SET_ADDR(dst, src)                               \\\n  memcpy((dst)->sin6.sin6_addr.s6_addr, (src)->ip6.addr, \\\n         sizeof((dst)->sin6.sin6_addr.s6_addr))\n#else\n#define TCP_NEW tcp_new\n#define TCP_BIND tcp_bind\n#define UDP_BIND udp_bind\n#define IPADDR_NTOA ipaddr_ntoa\n#define SET_ADDR(dst, src) (dst)->sin.sin_addr.s_addr = ip_2_ip4(src)->addr\n#endif\n\n#if !NO_SYS\n#if LWIP_TCPIP_CORE_LOCKING\n/* With locking tcpip_api_call is just a function call wrapped in lock/unlock,\n * so we can get away with just casting. */\nvoid mg_lwip_netif_run_on_tcpip(void (*fn)(void *), void *arg) {\n  tcpip_api_call((tcpip_api_call_fn) fn, (struct tcpip_api_call_data *) arg);\n}\n#else\nstatic sys_sem_t s_tcpip_call_lock_sem = NULL;\nstatic sys_sem_t s_tcpip_call_sync_sem = NULL;\nstruct mg_lwip_netif_tcpip_call_ctx {\n  void (*fn)(void *);\n  void *arg;\n};\nstatic void xxx_tcpip(void *arg) {\n  struct mg_lwip_netif_tcpip_call_ctx *ctx =\n      (struct mg_lwip_netif_tcpip_call_ctx *) arg;\n  ctx->fn(ctx->arg);\n  sys_sem_signal(&s_tcpip_call_sync_sem);\n}\nvoid mg_lwip_netif_run_on_tcpip(void (*fn)(void *), void *arg) {\n  struct mg_lwip_netif_tcpip_call_ctx ctx = {.fn = fn, .arg = arg};\n  sys_arch_sem_wait(&s_tcpip_call_lock_sem, 0);\n  tcpip_send_msg_wait_sem(xxx_tcpip, &ctx, &s_tcpip_call_sync_sem);\n  sys_sem_signal(&s_tcpip_call_lock_sem);\n}\n#endif\n#else\n#define mg_lwip_netif_run_on_tcpip(fn, arg) (fn)(arg)\n#endif\n\nvoid mg_lwip_if_init(struct mg_iface *iface);\nvoid mg_lwip_if_free(struct mg_iface *iface);\nvoid mg_lwip_if_add_conn(struct mg_connection *nc);\nvoid mg_lwip_if_remove_conn(struct mg_connection *nc);\ntime_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms);\n\n// If compiling for Mongoose OS.\n#ifdef MGOS\nextern void mgos_lock();\nextern void mgos_unlock();\n#else\n#define mgos_lock()\n#define mgos_unlock()\n#endif\n\nstatic void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p);\n\n#if LWIP_TCP_KEEPALIVE\nvoid mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle,\n                                  int interval, int count) {\n  if (nc->sock == INVALID_SOCKET || nc->flags & MG_F_UDP) {\n    return;\n  }\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  struct tcp_pcb *tpcb = cs->pcb.tcp;\n  if (idle > 0 && interval > 0 && count > 0) {\n    tpcb->keep_idle = idle * 1000;\n    tpcb->keep_intvl = interval * 1000;\n    tpcb->keep_cnt = count;\n    tpcb->so_options |= SOF_KEEPALIVE;\n  } else {\n    tpcb->so_options &= ~SOF_KEEPALIVE;\n  }\n}\n#elif !defined(MG_NO_LWIP_TCP_KEEPALIVE)\n#warning LWIP TCP keepalive is disabled. Please consider enabling it.\n#endif /* LWIP_TCP_KEEPALIVE */\n\nstatic err_t mg_lwip_tcp_conn_cb(void *arg, struct tcp_pcb *tpcb, err_t err) {\n  struct mg_connection *nc = (struct mg_connection *) arg;\n  DBG((\"%p connect to %s:%u = %d\", nc, IPADDR_NTOA(ipX_2_ip(&tpcb->remote_ip)),\n       tpcb->remote_port, err));\n  if (nc == NULL) {\n    tcp_abort(tpcb);\n    return ERR_ARG;\n  }\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  cs->err = err;\n#if LWIP_TCP_KEEPALIVE\n  if (err == 0) mg_lwip_set_keepalive_params(nc, 60, 10, 6);\n#endif\n  mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);\n  return ERR_OK;\n}\n\nstatic void mg_lwip_tcp_error_cb(void *arg, err_t err) {\n  struct mg_connection *nc = (struct mg_connection *) arg;\n  DBG((\"%p conn error %d\", nc, err));\n  if (nc == NULL || (nc->flags & MG_F_CLOSE_IMMEDIATELY)) return;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  cs->pcb.tcp = NULL; /* Has already been deallocated */\n  if (nc->flags & MG_F_CONNECTING) {\n    cs->err = err;\n    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);\n  } else {\n    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);\n  }\n}\n\nstatic err_t mg_lwip_tcp_recv_cb(void *arg, struct tcp_pcb *tpcb,\n                                 struct pbuf *p, err_t err) {\n  struct mg_connection *nc = (struct mg_connection *) arg;\n  struct mg_lwip_conn_state *cs =\n      (nc ? (struct mg_lwip_conn_state *) nc->sock : NULL);\n  DBG((\"%p %p %p %p %u %d\", nc, cs, tpcb, p, (p != NULL ? p->tot_len : 0),\n       err));\n  if (p == NULL) {\n    if (nc != NULL && !(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {\n      if (cs->rx_chain != NULL) {\n        /*\n         * rx_chain still contains non-consumed data, don't close the\n         * connection\n         */\n        cs->draining_rx_chain = 1;\n      } else {\n        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);\n      }\n    } else {\n      /* Tombstoned connection, do nothing. */\n    }\n    return ERR_OK;\n  } else if (nc == NULL) {\n    tcp_abort(tpcb);\n    return ERR_ARG;\n  }\n  /*\n   * If we get a chain of more than one segment at once, we need to bump\n   * refcount on the subsequent bufs to make them independent.\n   */\n  if (p->next != NULL) {\n    struct pbuf *q = p->next;\n    for (; q != NULL; q = q->next) pbuf_ref(q);\n  }\n  mgos_lock();\n  if (cs->rx_chain == NULL) {\n    cs->rx_offset = 0;\n  } else if (pbuf_clen(cs->rx_chain) >= 4) {\n    /* ESP SDK has a limited pool of 5 pbufs. We must not hog them all or RX\n     * will be completely blocked. We already have at least 4 in the chain,\n     * this one is the last, so we have to make a copy and release this one. */\n    struct pbuf *np = pbuf_alloc(PBUF_RAW, p->tot_len, PBUF_RAM);\n    if (np != NULL) {\n      pbuf_copy(np, p);\n      pbuf_free(p);\n      p = np;\n    }\n  }\n  mg_lwip_recv_common(nc, p);\n  mgos_unlock();\n  (void) err;\n  return ERR_OK;\n}\n\nstatic err_t mg_lwip_tcp_sent_cb(void *arg, struct tcp_pcb *tpcb,\n                                 u16_t num_sent) {\n  struct mg_connection *nc = (struct mg_connection *) arg;\n  DBG((\"%p %p %u %p %p\", nc, tpcb, num_sent, tpcb->unsent, tpcb->unacked));\n  if (nc == NULL) return ERR_OK;\n  if ((nc->flags & MG_F_SEND_AND_CLOSE) && !(nc->flags & MG_F_WANT_WRITE) &&\n      nc->send_mbuf.len == 0 && tpcb->unsent == NULL && tpcb->unacked == NULL) {\n    mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);\n  }\n  if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) {\n    mg_lwip_mgr_schedule_poll(nc->mgr);\n  }\n  (void) num_sent;\n  return ERR_OK;\n}\n\nstruct mg_lwip_if_connect_tcp_ctx {\n  struct mg_connection *nc;\n  const union socket_address *sa;\n};\n\nstatic void mg_lwip_if_connect_tcp_tcpip(void *arg) {\n  struct mg_lwip_if_connect_tcp_ctx *ctx =\n      (struct mg_lwip_if_connect_tcp_ctx *) arg;\n  struct mg_connection *nc = ctx->nc;\n  const union socket_address *sa = ctx->sa;\n\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  struct tcp_pcb *tpcb = TCP_NEW();\n  cs->pcb.tcp = tpcb;\n  ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;\n  u16_t port = ntohs(sa->sin.sin_port);\n  tcp_arg(tpcb, nc);\n  tcp_err(tpcb, mg_lwip_tcp_error_cb);\n  tcp_sent(tpcb, mg_lwip_tcp_sent_cb);\n  tcp_recv(tpcb, mg_lwip_tcp_recv_cb);\n  cs->err = TCP_BIND(tpcb, IP_ADDR_ANY, 0 /* any port */);\n  DBG((\"%p tcp_bind = %d\", nc, cs->err));\n  if (cs->err != ERR_OK) {\n    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);\n    return;\n  }\n  cs->err = tcp_connect(tpcb, ip, port, mg_lwip_tcp_conn_cb);\n  DBG((\"%p tcp_connect %p = %d\", nc, tpcb, cs->err));\n  if (cs->err != ERR_OK) {\n    mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);\n    return;\n  }\n}\n\nvoid mg_lwip_if_connect_tcp(struct mg_connection *nc,\n                            const union socket_address *sa) {\n  struct mg_lwip_if_connect_tcp_ctx ctx = {.nc = nc, .sa = sa};\n  mg_lwip_netif_run_on_tcpip(mg_lwip_if_connect_tcp_tcpip, &ctx);\n}\n\n/*\n * Lwip included in the SDKs for nRF5x chips has different type for the\n * callback of `udp_recv()`\n */\n#if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105\nstatic void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,\n                                const ip_addr_t *addr, u16_t port)\n#else\nstatic void mg_lwip_udp_recv_cb(void *arg, struct udp_pcb *pcb, struct pbuf *p,\n                                ip_addr_t *addr, u16_t port)\n#endif\n{\n  struct mg_connection *nc = (struct mg_connection *) arg;\n  DBG((\"%p %s:%u %p %u %u\", nc, IPADDR_NTOA(addr), port, p, p->ref, p->len));\n  /* Put address in a separate pbuf and tack it onto the packet. */\n  struct pbuf *sap =\n      pbuf_alloc(PBUF_RAW, sizeof(union socket_address), PBUF_RAM);\n  if (sap == NULL) {\n    pbuf_free(p);\n    return;\n  }\n  union socket_address *sa = (union socket_address *) sap->payload;\n#if ((LWIP_VERSION_MAJOR << 8) | LWIP_VERSION_MINOR) >= 0x0105\n  sa->sin.sin_addr.s_addr = ip_2_ip4(addr)->addr;\n#else\n  sa->sin.sin_addr.s_addr = addr->addr;\n#endif\n  sa->sin.sin_port = htons(port);\n  /* Logic in the recv handler requires that there be exactly one data pbuf. */\n  p = pbuf_coalesce(p, PBUF_RAW);\n  pbuf_chain(sap, p);\n  mgos_lock();\n  mg_lwip_recv_common(nc, sap);\n  mgos_unlock();\n  (void) pcb;\n}\n\nstatic void mg_lwip_recv_common(struct mg_connection *nc, struct pbuf *p) {\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (cs->rx_chain == NULL) {\n    cs->rx_chain = p;\n  } else {\n    pbuf_chain(cs->rx_chain, p);\n  }\n  if (!cs->recv_pending) {\n    cs->recv_pending = 1;\n    mg_lwip_post_signal(MG_SIG_RECV, nc);\n  }\n}\n\nstatic int mg_lwip_if_udp_recv(struct mg_connection *nc, void *buf, size_t len,\n                               union socket_address *sa, size_t *sa_len) {\n  /*\n   * For UDP, RX chain consists of interleaved address and packet bufs:\n   * Address pbuf followed by exactly one data pbuf (recv_cb took care of that).\n   */\n  int res = 0;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (nc->sock == INVALID_SOCKET) return -1;\n  mgos_lock();\n  if (cs->rx_chain != NULL) {\n    struct pbuf *ap = cs->rx_chain;\n    struct pbuf *dp = ap->next;\n    cs->rx_chain = pbuf_dechain(dp);\n    res = MIN(dp->len, len);\n    pbuf_copy_partial(dp, buf, res, 0);\n    pbuf_free(dp);\n    pbuf_copy_partial(ap, sa, MIN(*sa_len, ap->len), 0);\n    pbuf_free(ap);\n  }\n  mgos_unlock();\n  return res;\n}\n\nstatic void mg_lwip_if_connect_udp_tcpip(void *arg) {\n  struct mg_connection *nc = (struct mg_connection *) arg;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  struct udp_pcb *upcb = udp_new();\n  cs->err = UDP_BIND(upcb, IP_ADDR_ANY, 0 /* any port */);\n  DBG((\"%p udp_bind %p = %d\", nc, upcb, cs->err));\n  if (cs->err == ERR_OK) {\n    udp_recv(upcb, mg_lwip_udp_recv_cb, nc);\n    cs->pcb.udp = upcb;\n  } else {\n    udp_remove(upcb);\n  }\n  mg_lwip_post_signal(MG_SIG_CONNECT_RESULT, nc);\n}\n\nvoid mg_lwip_if_connect_udp(struct mg_connection *nc) {\n  mg_lwip_netif_run_on_tcpip(mg_lwip_if_connect_udp_tcpip, nc);\n}\n\nstatic void tcp_close_tcpip(void *arg) {\n  tcp_close((struct tcp_pcb *) arg);\n}\n\nvoid mg_lwip_handle_accept(struct mg_connection *nc) {\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (cs->pcb.tcp == NULL) return;\n  union socket_address sa;\n  struct tcp_pcb *tpcb = cs->pcb.tcp;\n  SET_ADDR(&sa, &tpcb->remote_ip);\n  sa.sin.sin_port = htons(tpcb->remote_port);\n  mg_if_accept_tcp_cb(nc, &sa, sizeof(sa.sin));\n}\n\nstatic err_t mg_lwip_accept_cb(void *arg, struct tcp_pcb *newtpcb, err_t err) {\n  struct mg_connection *lc = (struct mg_connection *) arg, *nc;\n  struct mg_lwip_conn_state *lcs, *cs;\n  struct tcp_pcb_listen *lpcb;\n  LOG(LL_DEBUG,\n      (\"%p conn %p from %s:%u\", lc, newtpcb,\n       IPADDR_NTOA(ipX_2_ip(&newtpcb->remote_ip)), newtpcb->remote_port));\n  if (lc == NULL) {\n    tcp_abort(newtpcb);\n    return ERR_ABRT;\n  }\n  lcs = (struct mg_lwip_conn_state *) lc->sock;\n  lpcb = (struct tcp_pcb_listen *) lcs->pcb.tcp;\n#if TCP_LISTEN_BACKLOG\n  tcp_accepted(lpcb);\n#endif\n  nc = mg_if_accept_new_conn(lc);\n  if (nc == NULL) {\n    tcp_abort(newtpcb);\n    return ERR_ABRT;\n  }\n  cs = (struct mg_lwip_conn_state *) nc->sock;\n  cs->lc = lc;\n  cs->pcb.tcp = newtpcb;\n  /* We need to set up callbacks before returning because data may start\n   * arriving immediately. */\n  tcp_arg(newtpcb, nc);\n  tcp_err(newtpcb, mg_lwip_tcp_error_cb);\n  tcp_sent(newtpcb, mg_lwip_tcp_sent_cb);\n  tcp_recv(newtpcb, mg_lwip_tcp_recv_cb);\n#if LWIP_TCP_KEEPALIVE\n  mg_lwip_set_keepalive_params(nc, 60, 10, 6);\n#endif\n  mg_lwip_post_signal(MG_SIG_ACCEPT, nc);\n  (void) err;\n  (void) lpcb;\n  return ERR_OK;\n}\n\nstruct mg_lwip_if_listen_ctx {\n  struct mg_connection *nc;\n  union socket_address *sa;\n  int ret;\n};\n\nstatic void mg_lwip_if_listen_tcp_tcpip(void *arg) {\n  struct mg_lwip_if_listen_ctx *ctx = (struct mg_lwip_if_listen_ctx *) arg;\n  struct mg_connection *nc = ctx->nc;\n  union socket_address *sa = ctx->sa;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  struct tcp_pcb *tpcb = TCP_NEW();\n  ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;\n  u16_t port = ntohs(sa->sin.sin_port);\n  cs->err = TCP_BIND(tpcb, ip, port);\n  DBG((\"%p tcp_bind(%s:%u) = %d\", nc, IPADDR_NTOA(ip), port, cs->err));\n  if (cs->err != ERR_OK) {\n    tcp_close(tpcb);\n    ctx->ret = -1;\n    return;\n  }\n  tcp_arg(tpcb, nc);\n  tpcb = tcp_listen(tpcb);\n  cs->pcb.tcp = tpcb;\n  tcp_accept(tpcb, mg_lwip_accept_cb);\n  ctx->ret = 0;\n}\n\nint mg_lwip_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {\n  struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa};\n  mg_lwip_netif_run_on_tcpip(mg_lwip_if_listen_tcp_tcpip, &ctx);\n  return ctx.ret;\n}\n\nstatic void mg_lwip_if_listen_udp_tcpip(void *arg) {\n  struct mg_lwip_if_listen_ctx *ctx = (struct mg_lwip_if_listen_ctx *) arg;\n  struct mg_connection *nc = ctx->nc;\n  union socket_address *sa = ctx->sa;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  struct udp_pcb *upcb = udp_new();\n  ip_addr_t *ip = (ip_addr_t *) &sa->sin.sin_addr.s_addr;\n  u16_t port = ntohs(sa->sin.sin_port);\n  cs->err = UDP_BIND(upcb, ip, port);\n  DBG((\"%p udb_bind(%s:%u) = %d\", nc, IPADDR_NTOA(ip), port, cs->err));\n  if (cs->err != ERR_OK) {\n    udp_remove(upcb);\n    ctx->ret = -1;\n  } else {\n    udp_recv(upcb, mg_lwip_udp_recv_cb, nc);\n    cs->pcb.udp = upcb;\n    ctx->ret = 0;\n  }\n}\n\nint mg_lwip_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {\n  struct mg_lwip_if_listen_ctx ctx = {.nc = nc, .sa = sa};\n  mg_lwip_netif_run_on_tcpip(mg_lwip_if_listen_udp_tcpip, &ctx);\n  return ctx.ret;\n}\n\nstruct mg_lwip_tcp_write_ctx {\n  struct mg_connection *nc;\n  const void *data;\n  uint16_t len;\n  int ret;\n};\n\nstatic void tcp_output_tcpip(void *arg) {\n  tcp_output((struct tcp_pcb *) arg);\n}\n\nstatic void mg_lwip_tcp_write_tcpip(void *arg) {\n  struct mg_lwip_tcp_write_ctx *ctx = (struct mg_lwip_tcp_write_ctx *) arg;\n  struct mg_connection *nc = ctx->nc;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  struct tcp_pcb *tpcb = cs->pcb.tcp;\n  size_t len = MIN(tpcb->mss, MIN(ctx->len, tpcb->snd_buf));\n  size_t unsent, unacked;\n  if (len == 0) {\n    DBG((\"%p no buf avail %u %u %p %p\", tpcb, tpcb->snd_buf, tpcb->snd_queuelen,\n         tpcb->unsent, tpcb->unacked));\n    mg_lwip_netif_run_on_tcpip(tcp_output_tcpip, tpcb);\n    ctx->ret = 0;\n    return;\n  }\n  unsent = (tpcb->unsent != NULL ? tpcb->unsent->len : 0);\n  unacked = (tpcb->unacked != NULL ? tpcb->unacked->len : 0);\n/*\n * On ESP8266 we only allow one TCP segment in flight at any given time.\n * This may increase latency and reduce efficiency of tcp windowing,\n * but memory is scarce and precious on that platform so we do this to\n * reduce footprint.\n */\n#if CS_PLATFORM == CS_P_ESP8266\n  if (unacked > 0) {\n    ctx->ret = 0;\n    return;\n  }\n  len = MIN(len, (TCP_MSS - unsent));\n#endif\n  cs->err = tcp_write(tpcb, ctx->data, len, TCP_WRITE_FLAG_COPY);\n  unsent = (tpcb->unsent != NULL ? tpcb->unsent->len : 0);\n  unacked = (tpcb->unacked != NULL ? tpcb->unacked->len : 0);\n  DBG((\"%p tcp_write %u = %d, %u %u\", tpcb, len, cs->err, unsent, unacked));\n  if (cs->err != ERR_OK) {\n    /*\n     * We ignore ERR_MEM because memory will be freed up when the data is sent\n     * and we'll retry.\n     */\n    ctx->ret = (cs->err == ERR_MEM ? 0 : -1);\n    return;\n  }\n  ctx->ret = len;\n  (void) unsent;\n  (void) unacked;\n}\n\nint mg_lwip_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) {\n  struct mg_lwip_tcp_write_ctx ctx = {.nc = nc, .data = buf, .len = len};\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (nc->sock == INVALID_SOCKET) return -1;\n  struct tcp_pcb *tpcb = cs->pcb.tcp;\n  if (tpcb == NULL) return -1;\n  if (tpcb->snd_buf <= 0) return 0;\n  mg_lwip_netif_run_on_tcpip(mg_lwip_tcp_write_tcpip, &ctx);\n  return ctx.ret;\n}\n\nstruct udp_sendto_ctx {\n  struct udp_pcb *upcb;\n  struct pbuf *p;\n  ip_addr_t *ip;\n  uint16_t port;\n  int ret;\n};\n\nstatic void udp_sendto_tcpip(void *arg) {\n  struct udp_sendto_ctx *ctx = (struct udp_sendto_ctx *) arg;\n  ctx->ret = udp_sendto(ctx->upcb, ctx->p, ctx->ip, ctx->port);\n}\n\nstatic int mg_lwip_if_udp_send(struct mg_connection *nc, const void *data,\n                               size_t len) {\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (nc->sock == INVALID_SOCKET || cs->pcb.udp == NULL) return -1;\n  struct udp_pcb *upcb = cs->pcb.udp;\n  struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);\n#if defined(LWIP_IPV4) && LWIP_IPV4 && defined(LWIP_IPV6) && LWIP_IPV6\n  ip_addr_t ip = {.u_addr.ip4.addr = nc->sa.sin.sin_addr.s_addr, .type = 0};\n#else\n  ip_addr_t ip = {.addr = nc->sa.sin.sin_addr.s_addr};\n#endif\n  u16_t port = ntohs(nc->sa.sin.sin_port);\n  if (p == NULL) return 0;\n  memcpy(p->payload, data, len);\n  struct udp_sendto_ctx ctx = {.upcb = upcb, .p = p, .ip = &ip, .port = port};\n  mg_lwip_netif_run_on_tcpip(udp_sendto_tcpip, &ctx);\n  cs->err = ctx.ret;\n  pbuf_free(p);\n  return (cs->err == ERR_OK ? (int) len : -2);\n}\n\nstatic int mg_lwip_if_can_send(struct mg_connection *nc,\n                               struct mg_lwip_conn_state *cs) {\n  int can_send = 0;\n  if (nc->send_mbuf.len > 0 || (nc->flags & MG_F_WANT_WRITE)) {\n    /* We have stuff to send, but can we? */\n    if (nc->flags & MG_F_UDP) {\n      /* UDP is always ready for sending. */\n      can_send = (cs->pcb.udp != NULL);\n    } else {\n      can_send = (cs->pcb.tcp != NULL && cs->pcb.tcp->snd_buf > 0);\n/* See comment above. */\n#if CS_PLATFORM == CS_P_ESP8266\n      if (cs->pcb.tcp->unacked != NULL) can_send = 0;\n#endif\n    }\n  }\n  return can_send;\n}\n\nstruct tcp_recved_ctx {\n  struct tcp_pcb *tpcb;\n  size_t len;\n};\n\nvoid tcp_recved_tcpip(void *arg) {\n  struct tcp_recved_ctx *ctx = (struct tcp_recved_ctx *) arg;\n  if (ctx->tpcb != NULL) tcp_recved(ctx->tpcb, ctx->len);\n}\n\nstatic int mg_lwip_if_tcp_recv(struct mg_connection *nc, void *buf,\n                               size_t len) {\n  int res = 0;\n  char *bufp = buf;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (nc->sock == INVALID_SOCKET) return -1;\n  mgos_lock();\n  while (cs->rx_chain != NULL && len > 0) {\n    struct pbuf *seg = cs->rx_chain;\n    size_t seg_len = (seg->len - cs->rx_offset);\n    size_t copy_len = MIN(len, seg_len);\n\n    pbuf_copy_partial(seg, bufp, copy_len, cs->rx_offset);\n    len -= copy_len;\n    res += copy_len;\n    bufp += copy_len;\n    cs->rx_offset += copy_len;\n    if (cs->rx_offset == cs->rx_chain->len) {\n      cs->rx_chain = pbuf_dechain(cs->rx_chain);\n      pbuf_free(seg);\n      cs->rx_offset = 0;\n    }\n  }\n  mgos_unlock();\n  if (res > 0) {\n    struct tcp_recved_ctx ctx = {.tpcb = cs->pcb.tcp, .len = res};\n    mg_lwip_netif_run_on_tcpip(tcp_recved_tcpip, &ctx);\n  }\n  return res;\n}\n\nint mg_lwip_if_create_conn(struct mg_connection *nc) {\n  struct mg_lwip_conn_state *cs =\n      (struct mg_lwip_conn_state *) MG_CALLOC(1, sizeof(*cs));\n  if (cs == NULL) return 0;\n  cs->nc = nc;\n  nc->sock = (intptr_t) cs;\n  return 1;\n}\n\nstatic void udp_remove_tcpip(void *arg) {\n  udp_remove((struct udp_pcb *) arg);\n}\n\nvoid mg_lwip_if_destroy_conn(struct mg_connection *nc) {\n  if (nc->sock == INVALID_SOCKET) return;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (!(nc->flags & MG_F_UDP)) {\n    struct tcp_pcb *tpcb = cs->pcb.tcp;\n    if (tpcb != NULL) {\n      tcp_arg(tpcb, NULL);\n      DBG((\"%p tcp_close %p\", nc, tpcb));\n      tcp_arg(tpcb, NULL);\n      mg_lwip_netif_run_on_tcpip(tcp_close_tcpip, tpcb);\n    }\n    while (cs->rx_chain != NULL) {\n      struct pbuf *seg = cs->rx_chain;\n      cs->rx_chain = pbuf_dechain(cs->rx_chain);\n      pbuf_free(seg);\n    }\n    memset(cs, 0, sizeof(*cs));\n    MG_FREE(cs);\n  } else if (nc->listener == NULL) {\n    /* Only close outgoing UDP pcb or listeners. */\n    struct udp_pcb *upcb = cs->pcb.udp;\n    if (upcb != NULL) {\n      DBG((\"%p udp_remove %p\", nc, upcb));\n      mg_lwip_netif_run_on_tcpip(udp_remove_tcpip, upcb);\n    }\n    memset(cs, 0, sizeof(*cs));\n    MG_FREE(cs);\n  }\n  nc->sock = INVALID_SOCKET;\n}\n\nvoid mg_lwip_if_get_conn_addr(struct mg_connection *nc, int remote,\n                              union socket_address *sa) {\n  memset(sa, 0, sizeof(*sa));\n  if (nc == NULL || nc->sock == INVALID_SOCKET) return;\n  struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n  if (nc->flags & MG_F_UDP) {\n    struct udp_pcb *upcb = cs->pcb.udp;\n    if (remote) {\n      memcpy(sa, &nc->sa, sizeof(*sa));\n    } else if (upcb != NULL) {\n      sa->sin.sin_port = htons(upcb->local_port);\n      SET_ADDR(sa, &upcb->local_ip);\n    }\n  } else {\n    struct tcp_pcb *tpcb = cs->pcb.tcp;\n    if (remote) {\n      memcpy(sa, &nc->sa, sizeof(*sa));\n    } else if (tpcb != NULL) {\n      sa->sin.sin_port = htons(tpcb->local_port);\n      SET_ADDR(sa, &tpcb->local_ip);\n    }\n  }\n}\n\nvoid mg_lwip_if_sock_set(struct mg_connection *nc, sock_t sock) {\n  nc->sock = sock;\n}\n\n/* clang-format off */\n#define MG_LWIP_IFACE_VTABLE                                          \\\n  {                                                                   \\\n    mg_lwip_if_init,                                                  \\\n    mg_lwip_if_free,                                                  \\\n    mg_lwip_if_add_conn,                                              \\\n    mg_lwip_if_remove_conn,                                           \\\n    mg_lwip_if_poll,                                                  \\\n    mg_lwip_if_listen_tcp,                                            \\\n    mg_lwip_if_listen_udp,                                            \\\n    mg_lwip_if_connect_tcp,                                           \\\n    mg_lwip_if_connect_udp,                                           \\\n    mg_lwip_if_tcp_send,                                              \\\n    mg_lwip_if_udp_send,                                              \\\n    mg_lwip_if_tcp_recv,                                              \\\n    mg_lwip_if_udp_recv,                                              \\\n    mg_lwip_if_create_conn,                                           \\\n    mg_lwip_if_destroy_conn,                                          \\\n    mg_lwip_if_sock_set,                                              \\\n    mg_lwip_if_get_conn_addr,                                         \\\n  }\n/* clang-format on */\n\nconst struct mg_iface_vtable mg_lwip_iface_vtable = MG_LWIP_IFACE_VTABLE;\n#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL\nconst struct mg_iface_vtable mg_default_iface_vtable = MG_LWIP_IFACE_VTABLE;\n#endif\n\n#endif /* MG_ENABLE_NET_IF_LWIP_LOW_LEVEL */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/lwip/mg_lwip_ev_mgr.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL\n\n#ifndef MG_SIG_QUEUE_LEN\n#define MG_SIG_QUEUE_LEN 32\n#endif\n\nstruct mg_ev_mgr_lwip_signal {\n  int sig;\n  struct mg_connection *nc;\n};\n\nstruct mg_ev_mgr_lwip_data {\n  struct mg_ev_mgr_lwip_signal sig_queue[MG_SIG_QUEUE_LEN];\n  int sig_queue_len;\n  int start_index;\n};\n\nvoid mg_lwip_post_signal(enum mg_sig_type sig, struct mg_connection *nc) {\n  struct mg_ev_mgr_lwip_data *md =\n      (struct mg_ev_mgr_lwip_data *) nc->iface->data;\n  mgos_lock();\n  if (md->sig_queue_len >= MG_SIG_QUEUE_LEN) {\n    mgos_unlock();\n    return;\n  }\n  int end_index = (md->start_index + md->sig_queue_len) % MG_SIG_QUEUE_LEN;\n  md->sig_queue[end_index].sig = sig;\n  md->sig_queue[end_index].nc = nc;\n  md->sig_queue_len++;\n  mg_lwip_mgr_schedule_poll(nc->mgr);\n  mgos_unlock();\n}\n\nvoid mg_ev_mgr_lwip_process_signals(struct mg_mgr *mgr) {\n  struct mg_ev_mgr_lwip_data *md =\n      (struct mg_ev_mgr_lwip_data *) mgr->ifaces[MG_MAIN_IFACE]->data;\n  while (md->sig_queue_len > 0) {\n    mgos_lock();\n    int i = md->start_index;\n    int sig = md->sig_queue[i].sig;\n    struct mg_connection *nc = md->sig_queue[i].nc;\n    struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n    md->start_index = (i + 1) % MG_SIG_QUEUE_LEN;\n    md->sig_queue_len--;\n    mgos_unlock();\n    if (nc->iface == NULL || nc->mgr == NULL) continue;\n    switch (sig) {\n      case MG_SIG_CONNECT_RESULT: {\n        mg_if_connect_cb(nc, cs->err);\n        break;\n      }\n      case MG_SIG_CLOSE_CONN: {\n        mg_close_conn(nc);\n        break;\n      }\n      case MG_SIG_RECV: {\n        cs->recv_pending = 0;\n        mg_if_can_recv_cb(nc);\n        mbuf_trim(&nc->recv_mbuf);\n        break;\n      }\n      case MG_SIG_TOMBSTONE: {\n        break;\n      }\n      case MG_SIG_ACCEPT: {\n        mg_lwip_handle_accept(nc);\n        break;\n      }\n    }\n  }\n}\n\nvoid mg_lwip_if_init(struct mg_iface *iface) {\n  LOG(LL_INFO, (\"Mongoose %s, LwIP %u.%u.%u\", MG_VERSION, LWIP_VERSION_MAJOR,\n                LWIP_VERSION_MINOR, LWIP_VERSION_REVISION));\n  iface->data = MG_CALLOC(1, sizeof(struct mg_ev_mgr_lwip_data));\n#if !NO_SYS && !LWIP_TCPIP_CORE_LOCKING\n  sys_sem_new(&s_tcpip_call_lock_sem, 1);\n  sys_sem_new(&s_tcpip_call_sync_sem, 0);\n#endif\n}\n\nvoid mg_lwip_if_free(struct mg_iface *iface) {\n  MG_FREE(iface->data);\n  iface->data = NULL;\n}\n\nvoid mg_lwip_if_add_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\nvoid mg_lwip_if_remove_conn(struct mg_connection *nc) {\n  struct mg_ev_mgr_lwip_data *md =\n      (struct mg_ev_mgr_lwip_data *) nc->iface->data;\n  /* Walk the queue and null-out further signals for this conn. */\n  for (int i = 0; i < MG_SIG_QUEUE_LEN; i++) {\n    if (md->sig_queue[i].nc == nc) {\n      md->sig_queue[i].sig = MG_SIG_TOMBSTONE;\n    }\n  }\n}\n\ntime_t mg_lwip_if_poll(struct mg_iface *iface, int timeout_ms) {\n  struct mg_mgr *mgr = iface->mgr;\n  int n = 0;\n  double now = mg_time();\n  struct mg_connection *nc, *tmp;\n  double min_timer = 0;\n  int num_timers = 0;\n#if 0\n  DBG((\"begin poll @%u\", (unsigned int) (now * 1000)));\n#endif\n  mg_ev_mgr_lwip_process_signals(mgr);\n  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {\n    struct mg_lwip_conn_state *cs = (struct mg_lwip_conn_state *) nc->sock;\n    tmp = nc->next;\n    n++;\n    if (!mg_if_poll(nc, now)) continue;\n    if (nc->sock != INVALID_SOCKET &&\n        !(nc->flags & (MG_F_UDP | MG_F_LISTENING)) && cs->pcb.tcp != NULL &&\n        cs->pcb.tcp->unsent != NULL) {\n      mg_lwip_netif_run_on_tcpip(tcp_output_tcpip, cs->pcb.tcp);\n    }\n    if (nc->ev_timer_time > 0) {\n      if (num_timers == 0 || nc->ev_timer_time < min_timer) {\n        min_timer = nc->ev_timer_time;\n      }\n      num_timers++;\n    }\n\n    if (nc->sock != INVALID_SOCKET) {\n      if (mg_lwip_if_can_send(nc, cs)) {\n        mg_if_can_send_cb(nc);\n        mbuf_trim(&nc->send_mbuf);\n      }\n      if (cs->rx_chain != NULL) {\n        mg_if_can_recv_cb(nc);\n      } else if (cs->draining_rx_chain) {\n        /*\n         * If the connection is about to close, and rx_chain is finally empty,\n         * send the MG_SIG_CLOSE_CONN signal\n         */\n        mg_lwip_post_signal(MG_SIG_CLOSE_CONN, nc);\n      }\n    }\n  }\n#if 0\n  DBG((\"end poll @%u, %d conns, %d timers (min %u), next in %d ms\",\n       (unsigned int) (now * 1000), n, num_timers,\n       (unsigned int) (min_timer * 1000), timeout_ms));\n#endif\n  (void) timeout_ms;\n  return now;\n}\n\n#endif /* MG_NET_IF == MG_NET_IF_LWIP_LOW_LEVEL */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/wince/wince_libc.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifdef WINCE\n\nconst char *strerror(int err) {\n  /*\n   * TODO(alashkin): there is no strerror on WinCE;\n   * look for similar wce_xxxx function\n   */\n  static char buf[10];\n  snprintf(buf, sizeof(buf), \"%d\", err);\n  return buf;\n}\n\nint open(const char *filename, int oflag, int pmode) {\n  /*\n   * TODO(alashkin): mg_open function is not used in mongoose\n   * but exists in documentation as utility function\n   * Shall we delete it at all or implement for WinCE as well?\n   */\n  DebugBreak();\n  return 0; /* for compiler */\n}\n\nint _wstati64(const wchar_t *path, cs_stat_t *st) {\n  DWORD fa = GetFileAttributesW(path);\n  if (fa == INVALID_FILE_ATTRIBUTES) {\n    return -1;\n  }\n  memset(st, 0, sizeof(*st));\n  if ((fa & FILE_ATTRIBUTE_DIRECTORY) == 0) {\n    HANDLE h;\n    FILETIME ftime;\n    st->st_mode |= _S_IFREG;\n    h = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING,\n                    FILE_ATTRIBUTE_NORMAL, NULL);\n    if (h == INVALID_HANDLE_VALUE) {\n      return -1;\n    }\n    st->st_size = GetFileSize(h, NULL);\n    GetFileTime(h, NULL, NULL, &ftime);\n    st->st_mtime = (uint32_t)((((uint64_t) ftime.dwLowDateTime +\n                                ((uint64_t) ftime.dwHighDateTime << 32)) /\n                               10000000.0) -\n                              11644473600);\n    CloseHandle(h);\n  } else {\n    st->st_mode |= _S_IFDIR;\n  }\n  return 0;\n}\n\n/* Windows CE doesn't have neither gmtime nor strftime */\nstatic void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) {\n  FILETIME ft;\n  SYSTEMTIME systime;\n  if (t != NULL) {\n    uint64_t filetime = (*t + 11644473600) * 10000000;\n    ft.dwLowDateTime = filetime & 0xFFFFFFFF;\n    ft.dwHighDateTime = (filetime & 0xFFFFFFFF00000000) >> 32;\n    FileTimeToSystemTime(&ft, &systime);\n  } else {\n    GetSystemTime(&systime);\n  }\n  /* There is no PRIu16 in WinCE SDK */\n  snprintf(buf, buf_len, \"%d.%d.%d %d:%d:%d GMT\", (int) systime.wYear,\n           (int) systime.wMonth, (int) systime.wDay, (int) systime.wHour,\n           (int) systime.wMinute, (int) systime.wSecond);\n}\n\n#endif\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/pic32/pic32_net_if.h\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifndef CS_COMMON_PLATFORMS_PIC32_NET_IF_H_\n#define CS_COMMON_PLATFORMS_PIC32_NET_IF_H_\n\n/* Amalgamated: #include \"mongoose/src/net_if.h\" */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#ifndef MG_ENABLE_NET_IF_PIC32\n#define MG_ENABLE_NET_IF_PIC32 MG_NET_IF == MG_NET_IF_PIC32\n#endif\n\nextern const struct mg_iface_vtable mg_pic32_iface_vtable;\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* CS_COMMON_PLATFORMS_PIC32_NET_IF_H_ */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/pic32/pic32_net_if.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#if MG_ENABLE_NET_IF_PIC32\n\nint mg_pic32_if_create_conn(struct mg_connection *nc) {\n  (void) nc;\n  return 1;\n}\n\nvoid mg_pic32_if_recved(struct mg_connection *nc, size_t len) {\n  (void) nc;\n  (void) len;\n}\n\nvoid mg_pic32_if_add_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\nvoid mg_pic32_if_init(struct mg_iface *iface) {\n  (void) iface;\n  (void) mg_get_errno(); /* Shutup compiler */\n}\n\nvoid mg_pic32_if_free(struct mg_iface *iface) {\n  (void) iface;\n}\n\nvoid mg_pic32_if_remove_conn(struct mg_connection *nc) {\n  (void) nc;\n}\n\nvoid mg_pic32_if_destroy_conn(struct mg_connection *nc) {\n  if (nc->sock == INVALID_SOCKET) return;\n  /* For UDP, only close outgoing sockets or listeners. */\n  if (!(nc->flags & MG_F_UDP)) {\n    /* Close TCP */\n    TCPIP_TCP_Close((TCP_SOCKET) nc->sock);\n  } else if (nc->listener == NULL) {\n    /* Only close outgoing UDP or listeners. */\n    TCPIP_UDP_Close((UDP_SOCKET) nc->sock);\n  }\n\n  nc->sock = INVALID_SOCKET;\n}\n\nint mg_pic32_if_listen_udp(struct mg_connection *nc, union socket_address *sa) {\n  nc->sock = TCPIP_UDP_ServerOpen(\n      sa->sin.sin_family == AF_INET ? IP_ADDRESS_TYPE_IPV4\n                                    : IP_ADDRESS_TYPE_IPV6,\n      ntohs(sa->sin.sin_port),\n      sa->sin.sin_addr.s_addr == 0 ? 0 : (IP_MULTI_ADDRESS *) &sa->sin);\n  if (nc->sock == INVALID_SOCKET) {\n    return -1;\n  }\n  return 0;\n}\n\nvoid mg_pic32_if_udp_send(struct mg_connection *nc, const void *buf,\n                          size_t len) {\n  mbuf_append(&nc->send_mbuf, buf, len);\n}\n\nvoid mg_pic32_if_tcp_send(struct mg_connection *nc, const void *buf,\n                          size_t len) {\n  mbuf_append(&nc->send_mbuf, buf, len);\n}\n\nint mg_pic32_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) {\n  nc->sock = TCPIP_TCP_ServerOpen(\n      sa->sin.sin_family == AF_INET ? IP_ADDRESS_TYPE_IPV4\n                                    : IP_ADDRESS_TYPE_IPV6,\n      ntohs(sa->sin.sin_port),\n      sa->sin.sin_addr.s_addr == 0 ? 0 : (IP_MULTI_ADDRESS *) &sa->sin);\n  memcpy(&nc->sa, sa, sizeof(*sa));\n  if (nc->sock == INVALID_SOCKET) {\n    return -1;\n  }\n  return 0;\n}\n\nstatic int mg_accept_conn(struct mg_connection *lc) {\n  struct mg_connection *nc;\n  TCP_SOCKET_INFO si;\n  union socket_address sa;\n\n  nc = mg_if_accept_new_conn(lc);\n\n  if (nc == NULL) {\n    return 0;\n  }\n\n  nc->sock = lc->sock;\n  nc->flags &= ~MG_F_LISTENING;\n\n  if (!TCPIP_TCP_SocketInfoGet((TCP_SOCKET) nc->sock, &si)) {\n    return 0;\n  }\n\n  if (si.addressType == IP_ADDRESS_TYPE_IPV4) {\n    sa.sin.sin_family = AF_INET;\n    sa.sin.sin_port = htons(si.remotePort);\n    sa.sin.sin_addr.s_addr = si.remoteIPaddress.v4Add.Val;\n  } else {\n    /* TODO(alashkin): do something with _potential_ IPv6 */\n    memset(&sa, 0, sizeof(sa));\n  }\n\n  mg_if_accept_tcp_cb(nc, (union socket_address *) &sa, sizeof(sa));\n\n  return mg_pic32_if_listen_tcp(lc, &lc->sa) >= 0;\n}\n\nchar *inet_ntoa(struct in_addr in) {\n  static char addr[17];\n  snprintf(addr, sizeof(addr), \"%d.%d.%d.%d\", (int) in.S_un.S_un_b.s_b1,\n           (int) in.S_un.S_un_b.s_b2, (int) in.S_un.S_un_b.s_b3,\n           (int) in.S_un.S_un_b.s_b4);\n  return addr;\n}\n\nstatic void mg_handle_send(struct mg_connection *nc) {\n  uint16_t bytes_written = 0;\n  if (nc->flags & MG_F_UDP) {\n    if (!TCPIP_UDP_RemoteBind(\n            (UDP_SOCKET) nc->sock,\n            nc->sa.sin.sin_family == AF_INET ? IP_ADDRESS_TYPE_IPV4\n                                             : IP_ADDRESS_TYPE_IPV6,\n            ntohs(nc->sa.sin.sin_port), (IP_MULTI_ADDRESS *) &nc->sa.sin)) {\n      nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n      return;\n    }\n    bytes_written = TCPIP_UDP_TxPutIsReady((UDP_SOCKET) nc->sock, 0);\n    if (bytes_written >= nc->send_mbuf.len) {\n      if (TCPIP_UDP_ArrayPut((UDP_SOCKET) nc->sock,\n                             (uint8_t *) nc->send_mbuf.buf,\n                             nc->send_mbuf.len) != nc->send_mbuf.len) {\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        bytes_written = 0;\n      }\n    }\n  } else {\n    bytes_written = TCPIP_TCP_FifoTxFreeGet((TCP_SOCKET) nc->sock);\n    if (bytes_written != 0) {\n      if (bytes_written > nc->send_mbuf.len) {\n        bytes_written = nc->send_mbuf.len;\n      }\n      if (TCPIP_TCP_ArrayPut((TCP_SOCKET) nc->sock,\n                             (uint8_t *) nc->send_mbuf.buf,\n                             bytes_written) != bytes_written) {\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        bytes_written = 0;\n      }\n    }\n  }\n\n  mg_if_sent_cb(nc, bytes_written);\n}\n\nstatic void mg_handle_recv(struct mg_connection *nc) {\n  uint16_t bytes_read = 0;\n  uint8_t *buf = NULL;\n  if (nc->flags & MG_F_UDP) {\n    bytes_read = TCPIP_UDP_GetIsReady((UDP_SOCKET) nc->sock);\n    if (bytes_read != 0 &&\n        (nc->recv_mbuf_limit == -1 ||\n         nc->recv_mbuf.len + bytes_read < nc->recv_mbuf_limit)) {\n      buf = (uint8_t *) MG_MALLOC(bytes_read);\n      if (TCPIP_UDP_ArrayGet((UDP_SOCKET) nc->sock, buf, bytes_read) !=\n          bytes_read) {\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        bytes_read = 0;\n        MG_FREE(buf);\n      }\n    }\n  } else {\n    bytes_read = TCPIP_TCP_GetIsReady((TCP_SOCKET) nc->sock);\n    if (bytes_read != 0) {\n      if (nc->recv_mbuf_limit != -1 &&\n          nc->recv_mbuf_limit - nc->recv_mbuf.len > bytes_read) {\n        bytes_read = nc->recv_mbuf_limit - nc->recv_mbuf.len;\n      }\n      buf = (uint8_t *) MG_MALLOC(bytes_read);\n      if (TCPIP_TCP_ArrayGet((TCP_SOCKET) nc->sock, buf, bytes_read) !=\n          bytes_read) {\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        MG_FREE(buf);\n        bytes_read = 0;\n      }\n    }\n  }\n\n  if (bytes_read != 0) {\n    mg_if_recv_tcp_cb(nc, buf, bytes_read, 1 /* own */);\n  }\n}\n\ntime_t mg_pic32_if_poll(struct mg_iface *iface, int timeout_ms) {\n  struct mg_mgr *mgr = iface->mgr;\n  double now = mg_time();\n  struct mg_connection *nc, *tmp;\n\n  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {\n    tmp = nc->next;\n\n    if (nc->flags & MG_F_CONNECTING) {\n      /* processing connections */\n      if (nc->flags & MG_F_UDP ||\n          TCPIP_TCP_IsConnected((TCP_SOCKET) nc->sock)) {\n        mg_if_connect_cb(nc, 0);\n      }\n    } else if (nc->flags & MG_F_LISTENING) {\n      if (TCPIP_TCP_IsConnected((TCP_SOCKET) nc->sock)) {\n        /* accept new connections */\n        mg_accept_conn(nc);\n      }\n    } else {\n      if (nc->send_mbuf.len != 0) {\n        mg_handle_send(nc);\n      }\n\n      if (nc->recv_mbuf_limit == -1 ||\n          nc->recv_mbuf.len < nc->recv_mbuf_limit) {\n        mg_handle_recv(nc);\n      }\n    }\n  }\n\n  for (nc = mgr->active_connections; nc != NULL; nc = tmp) {\n    tmp = nc->next;\n    if ((nc->flags & MG_F_CLOSE_IMMEDIATELY) ||\n        (nc->send_mbuf.len == 0 && (nc->flags & MG_F_SEND_AND_CLOSE))) {\n      mg_close_conn(nc);\n    }\n  }\n\n  return now;\n}\n\nvoid mg_pic32_if_sock_set(struct mg_connection *nc, sock_t sock) {\n  nc->sock = sock;\n}\n\nvoid mg_pic32_if_get_conn_addr(struct mg_connection *nc, int remote,\n                               union socket_address *sa) {\n  /* TODO(alaskin): not implemented yet */\n}\n\nvoid mg_pic32_if_connect_tcp(struct mg_connection *nc,\n                             const union socket_address *sa) {\n  nc->sock = TCPIP_TCP_ClientOpen(\n      sa->sin.sin_family == AF_INET ? IP_ADDRESS_TYPE_IPV4\n                                    : IP_ADDRESS_TYPE_IPV6,\n      ntohs(sa->sin.sin_port), (IP_MULTI_ADDRESS *) &sa->sin);\n  nc->err = (nc->sock == INVALID_SOCKET) ? -1 : 0;\n}\n\nvoid mg_pic32_if_connect_udp(struct mg_connection *nc) {\n  nc->sock = TCPIP_UDP_ClientOpen(IP_ADDRESS_TYPE_ANY, 0, NULL);\n  nc->err = (nc->sock == INVALID_SOCKET) ? -1 : 0;\n}\n\n/* clang-format off */\n#define MG_PIC32_IFACE_VTABLE                                   \\\n  {                                                             \\\n    mg_pic32_if_init,                                           \\\n    mg_pic32_if_free,                                           \\\n    mg_pic32_if_add_conn,                                       \\\n    mg_pic32_if_remove_conn,                                    \\\n    mg_pic32_if_poll,                                           \\\n    mg_pic32_if_listen_tcp,                                     \\\n    mg_pic32_if_listen_udp,                                     \\\n    mg_pic32_if_connect_tcp,                                    \\\n    mg_pic32_if_connect_udp,                                    \\\n    mg_pic32_if_tcp_send,                                       \\\n    mg_pic32_if_udp_send,                                       \\\n    mg_pic32_if_recved,                                         \\\n    mg_pic32_if_create_conn,                                    \\\n    mg_pic32_if_destroy_conn,                                   \\\n    mg_pic32_if_sock_set,                                       \\\n    mg_pic32_if_get_conn_addr,                                  \\\n  }\n/* clang-format on */\n\nconst struct mg_iface_vtable mg_pic32_iface_vtable = MG_PIC32_IFACE_VTABLE;\n#if MG_NET_IF == MG_NET_IF_PIC32\nconst struct mg_iface_vtable mg_default_iface_vtable = MG_PIC32_IFACE_VTABLE;\n#endif\n\n#endif /* MG_ENABLE_NET_IF_PIC32 */\n#ifdef MG_MODULE_LINES\n#line 1 \"common/platforms/windows/windows_direct.c\"\n#endif\n/*\n * Copyright (c) 2014-2018 Cesanta Software Limited\n * All rights reserved\n *\n * Licensed under the Apache License, Version 2.0 (the \"\"License\"\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"\"AS IS\"\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n#ifdef _WIN32\n\nunsigned int sleep(unsigned int seconds) {\n  Sleep(seconds * 1000);\n  return 0;\n}\n\n#endif /* _WIN32 */\n"
  },
  {
    "path": "src/mongoose_bool.patch",
    "content": "diff --git a/include/mongoose.h b/include/mongoose.h\nindex b885270..5166d1a 100644\n--- a/include/mongoose.h\n+++ b/include/mongoose.h\n@@ -231,7 +231,7 @@\n #include <windows.h>\n #include <process.h>\n \n-#if _MSC_VER < 1700\n+#if defined(_MSC_VER) && (_MSC_VER < 1700)\n typedef int bool;\n #else\n #include <stdbool.h>\n"
  },
  {
    "path": "src/mongoose_broadcast.patch",
    "content": "diff --git a/src/mongoose.c b/src/mongoose.c\nindex c9c40863..55bd01e1 100644\n--- a/src/mongoose.c\n+++ b/src/mongoose.c\n@@ -3459,14 +3459,17 @@ void mg_broadcast(struct mg_mgr *mgr, mg_event_handler_t cb, void *data,\n    */\n   if (mgr->ctl[0] != INVALID_SOCKET && data != NULL &&\n       len < sizeof(ctl_msg.message)) {\n-    size_t dummy;\n+    size_t ret;\n \n     ctl_msg.callback = cb;\n     memcpy(ctl_msg.message, data, len);\n-    dummy = MG_SEND_FUNC(mgr->ctl[0], (char *) &ctl_msg,\n+    ret = MG_SEND_FUNC(mgr->ctl[0], (char *) &ctl_msg,\n                          offsetof(struct ctl_msg, message) + len, 0);\n-    dummy = MG_RECV_FUNC(mgr->ctl[0], (char *) &len, 1, 0);\n-    (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */\n+    if (ret < 0)\n+      perror(\"mg_broadcast() send failed, check UDP loopback device\");\n+    ret = MG_RECV_FUNC(mgr->ctl[0], (char *) &len, 1, 0);\n+    if (ret < 0)\n+      perror(\"mg_broadcast() recv failed, check firewall UDP loopback rules\");\n   }\n }\n #endif /* MG_ENABLE_BROADCAST */\n@@ -4116,9 +4119,12 @@ static void mg_mgr_handle_ctl_sock(struct mg_mgr *mgr) {\n   struct ctl_msg ctl_msg;\n   int len =\n       (int) MG_RECV_FUNC(mgr->ctl[1], (char *) &ctl_msg, sizeof(ctl_msg), 0);\n-  size_t dummy = MG_SEND_FUNC(mgr->ctl[1], ctl_msg.message, 1, 0);\n+  if (len < 0)\n+    perror(\"mg_mgr_handle_ctl_sock() recv failed, check firewall UDP loopback rules\");\n+  size_t ret = MG_SEND_FUNC(mgr->ctl[1], ctl_msg.message, 1, 0);\n+  if (ret < 0)\n+    perror(\"mg_mgr_handle_ctl_sock() send failed, check UDP loopback device\");\n   DBG((\"read %d from ctl socket\", len));\n-  (void) dummy; /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509 */\n   if (len >= (int) sizeof(ctl_msg.callback) && ctl_msg.callback != NULL) {\n     struct mg_connection *nc;\n     for (nc = mg_next(mgr, NULL); nc != NULL; nc = mg_next(mgr, nc)) {\n"
  },
  {
    "path": "src/mongoose_ipv6.patch",
    "content": "From 16eac315358e79e919c82cdaf3bb7be6ccf35e83 Mon Sep 17 00:00:00 2001\nFrom: \"Christian W. Zuckschwerdt\" <christian@zuckschwerdt.org>\nDate: Fri, 22 Mar 2019 17:51:30 +0100\nSubject: [PATCH] Fix IPv6 connect in mg_socket_if_connect_tcp\n\n---\n src/mg_net_if_socket.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/src/mg_net_if_socket.c b/src/mg_net_if_socket.c\nindex 607e2b53..51c0788f 100644\n--- a/src/mg_net_if_socket.c\n+++ b/src/mg_net_if_socket.c\n@@ -37,7 +37,7 @@ static int mg_is_error(void) {\n void mg_socket_if_connect_tcp(struct mg_connection *nc,\n                               const union socket_address *sa) {\n   int rc, proto = 0;\n-  nc->sock = socket(AF_INET, SOCK_STREAM, proto);\n+  nc->sock = socket(sa->sa.sa_family, SOCK_STREAM, proto);\n   if (nc->sock == INVALID_SOCKET) {\n     nc->err = mg_get_errno() ? mg_get_errno() : 1;\n     return;\n@@ -45,7 +45,9 @@ void mg_socket_if_connect_tcp(struct mg_connection *nc,\n #if !defined(MG_ESP8266)\n   mg_set_non_blocking_mode(nc->sock);\n #endif\n-  rc = connect(nc->sock, &sa->sa, sizeof(sa->sin));\n+  socklen_t sa_len =\n+      (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);\n+  rc = connect(nc->sock, &sa->sa, sa_len);\n   nc->err = rc < 0 && mg_is_error() ? mg_get_errno() : 0;\n   DBG((\"%p sock %d rc %d errno %d err %d\", nc, nc->sock, rc, mg_get_errno(),\n        nc->err));\n"
  },
  {
    "path": "src/mongoose_lenchk.patch",
    "content": "diff --git a/src/common/str_util.c b/src/common/str_util.c\nindex cc825cb..5a0b8b1 100644\n--- a/src/common/str_util.c\n+++ b/src/common/str_util.c\n@@ -507,7 +507,7 @@ size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {\n         const struct mg_str pstr = {pattern.p + i, pattern.len - i};\n         const struct mg_str sstr = {str.p + j + len, str.len - j - len};\n         res = mg_match_prefix_n(pstr, sstr);\n-      } while (res == 0 && len != 0 && len-- > 0);\n+      } while (res == 0 && len != 0 && --len > 0);\n       return res == 0 ? 0 : j + res + len;\n     } else if (str_util_lowercase(&pattern.p[i]) !=\n                str_util_lowercase(&str.p[j])) {\n"
  },
  {
    "path": "src/mongoose_libressl.patch",
    "content": "diff --git a/src/mongoose.c b/src/mongoose.c\nindex d3d2ef99..c9c40863 100644\n--- a/src/mongoose.c\n+++ b/src/mongoose.c\n@@ -5042,8 +5042,7 @@ static enum mg_ssl_if_result mg_ssl_if_ossl_set_psk(struct mg_ssl_if_ctx *ctx,\n                                                     const char *identity,\n                                                     const char *key_str) {\n   (void) ctx;\n-  (void) identity;\n-  (void) key_str;\n+  if (identity == NULL && key_str == NULL) return MG_SSL_OK;\n   /* Krypton / LibreSSL does not support PSK. */\n   return MG_SSL_ERROR;\n }\n"
  },
  {
    "path": "src/mongoose_msvcdbg.patch",
    "content": "From e6f124779d1c1edd050a6fc42389fcfdbd03915a Mon Sep 17 00:00:00 2001\nFrom: Gisle Vanem <gvanem@yahoo.no>\nDate: Mon, 9 Sep 2019 10:27:08 +0200\nSubject: [PATCH] Fixes in Mongoose for MSVC debug-mode (-MDd) (#1146)\n\nIn debug-mode (_DEBUG is defined), 'strdup()' is already defined to '_strdmp_dbg()'.\nAnd 'free()' is defined to '_free_dbg()'.\n---\n include/mongoose.h | 4 ++--\n src/mongoose.c     | 4 ++--\n 2 files changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/include/mongoose.h b/include/mongoose.h\nindex 57c12af..f2b8891 100644\n--- a/include/mongoose.h\n+++ b/include/mongoose.h\n@@ -227,7 +227,7 @@ typedef int bool;\n #include <stdbool.h>\n #endif\n \n-#if defined(_MSC_VER) && _MSC_VER >= 1800\n+#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_DEBUG)\n #define strdup _strdup\n #endif\n \n@@ -3634,7 +3634,7 @@ struct mg_iface {\n \n struct mg_iface_vtable {\n   void (*init)(struct mg_iface *iface);\n-  void (*free)(struct mg_iface *iface);\n+  void (*_free)(struct mg_iface *iface);\n   void (*add_conn)(struct mg_connection *nc);\n   void (*remove_conn)(struct mg_connection *nc);\n   time_t (*poll)(struct mg_iface *iface, int timeout_ms);\ndiff --git a/src/mongoose.c b/src/mongoose.c\nindex 593f8c6..c68a627 100644\n--- a/src/mongoose.c\n+++ b/src/mongoose.c\n@@ -2577,7 +2577,7 @@ void mg_mgr_free(struct mg_mgr *m) {\n   {\n     int i;\n     for (i = 0; i < m->num_ifaces; i++) {\n-      m->ifaces[i]->vtable->free(m->ifaces[i]);\n+      m->ifaces[i]->vtable->_free(m->ifaces[i]);\n       MG_FREE(m->ifaces[i]);\n     }\n     MG_FREE(m->ifaces);\n@@ -4494,7 +4494,7 @@ static int mg_socks_if_create_conn(struct mg_connection *c) {\n }\n \n static void mg_socks_if_destroy_conn(struct mg_connection *c) {\n-  c->iface->vtable->free(c->iface);\n+  c->iface->vtable->_free(c->iface);\n   MG_FREE(c->iface);\n   c->iface = NULL;\n   LOG(LL_DEBUG, (\"%p\", c));\n\n"
  },
  {
    "path": "src/mongoose_pedantic.patch",
    "content": "diff --git a/include/mongoose.h b/include/mongoose.h\nindex 03cc04e..b885270 100644\n--- a/include/mongoose.h\n+++ b/include/mongoose.h\n@@ -1,3 +1,6 @@\n+#ifndef INCLUDE_MONGOOSE_H_\n+#define INCLUDE_MONGOOSE_H_\n+\n #ifdef MG_MODULE_LINES\n #line 1 \"mongoose/src/mg_common.h\"\n #endif\n@@ -6750,3 +6753,5 @@ struct mg_iface *mg_socks_mk_iface(struct mg_mgr *, const char *proxy_addr);\n \n #endif\n #endif\n+\n+#endif /* INCLUDE_MONGOOSE_H_ */\ndiff --git a/include/mongoose.h b/include/mongoose.h\nindex 316f5f9..03cc04e 100644\n--- a/include/mongoose.h\n+++ b/include/mongoose.h\n@@ -352,10 +352,8 @@ unsigned int sleep(unsigned int seconds);\n /* https://stackoverflow.com/questions/16647819/timegm-cross-platform */\n #define timegm _mkgmtime\n \n-#define gmtime_r(a, b) \\\n-  do {                 \\\n-    *(b) = *gmtime(a); \\\n-  } while (0)\n+#define gmtime_r(a, b) (gmtime_s((b), (a)), (b))\n+#define localtime_r(a, b) (localtime_s((b), (a)), (b))\n \n #endif /* CS_PLATFORM == CS_P_WINDOWS */\n #endif /* CS_COMMON_PLATFORMS_PLATFORM_WINDOWS_H_ */\ndiff --git a/src/mongoose.c b/src/mongoose.c\nindex fe0df63..160df87 100644\n--- a/src/mongoose.c\n+++ b/src/mongoose.c\n@@ -4330,19 +4330,27 @@ int mg_socketpair(sock_t sp[2], int sock_type) {\n   sa2 = sa;\n \n   if ((sock = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {\n+    // abort\n   } else if (bind(sock, &sa.sa, len) != 0) {\n+    // abort\n   } else if (sock_type == SOCK_STREAM && listen(sock, 1) != 0) {\n+    // abort\n   } else if (getsockname(sock, &sa.sa, &len) != 0) {\n+    // abort\n   } else if ((sp[0] = socket(AF_INET, sock_type, 0)) == INVALID_SOCKET) {\n+    // abort\n   } else if (sock_type == SOCK_STREAM && connect(sp[0], &sa.sa, len) != 0) {\n+    // abort\n   } else if (sock_type == SOCK_DGRAM &&\n              (bind(sp[0], &sa2.sa, len) != 0 ||\n               getsockname(sp[0], &sa2.sa, &len) != 0 ||\n               connect(sp[0], &sa.sa, len) != 0 ||\n               connect(sock, &sa2.sa, len) != 0)) {\n+    // abort\n   } else if ((sp[1] = (sock_type == SOCK_DGRAM ? sock : mg_socketpair_accept(\n                                                             sock, &sa, len))) ==\n              INVALID_SOCKET) {\n+    // abort\n   } else {\n     mg_set_close_on_exec(sp[0]);\n     mg_set_close_on_exec(sp[1]);\n@@ -7271,7 +7279,8 @@ static void mg_http_construct_etag(char *buf, size_t buf_len,\n \n #ifndef WINCE\n static void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) {\n-  strftime(buf, buf_len, \"%a, %d %b %Y %H:%M:%S GMT\", gmtime(t));\n+  struct tm tm;\n+  strftime(buf, buf_len, \"%a, %d %b %Y %H:%M:%S GMT\", gmtime_r(t, &tm));\n }\n #else\n /* Look wince_lib.c for WindowsCE implementation */\n@@ -7886,6 +7895,7 @@ static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,\n   int is_dir = S_ISDIR(stp->st_mode);\n   const char *slash = is_dir ? \"/\" : \"\";\n   struct mg_str href;\n+  struct tm tm;\n \n   if (is_dir) {\n     snprintf(size, sizeof(size), \"%s\", \"[DIRECTORY]\");\n@@ -7904,7 +7914,7 @@ static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,\n       snprintf(size, sizeof(size), \"%.1fG\", (double) fsize / 1073741824);\n     }\n   }\n-  strftime(mod, sizeof(mod), \"%d-%b-%Y %H:%M\", localtime(&stp->st_mtime));\n+  strftime(mod, sizeof(mod), \"%d-%b-%Y %H:%M\", localtime_r(&stp->st_mtime, &tm));\n   mg_escape(file_name, path, sizeof(path));\n   href = mg_url_encode(mg_mk_str(file_name));\n   mg_printf_http_chunk(nc,\n"
  },
  {
    "path": "src/mongoose_sigpipe.patch",
    "content": "diff --git a/src/mongoose.c b/src/mongoose.c\nindex 049539e8..7f05c0b3 100644\n--- a/src/mongoose.c\n+++ b/src/mongoose.c\n@@ -1,4 +1,8 @@\n #include \"mongoose.h\"\n+/* MSG_NOSIGNAL is Linux and most BSDs only, not macOS or Windows */\n+#ifndef MSG_NOSIGNAL\n+#define MSG_NOSIGNAL 0\n+#endif\n #ifdef MG_MODULE_LINES\n #line 1 \"mongoose/src/mg_internal.h\"\n #endif\n@@ -3874,7 +3878,12 @@ void mg_socket_if_connect_tcp(struct mg_connection *nc,\n                               const union socket_address *sa) {\n   int rc, proto = 0;\n   nc->sock = socket(sa->sa.sa_family, SOCK_STREAM, proto);\n-  if (nc->sock == INVALID_SOCKET) {\n+  if (nc->sock == INVALID_SOCKET\n+#ifdef SO_NOSIGPIPE\n+      /* Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs */\n+      || setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nopipe, sizeof(nopipe))\n+#endif\n+  ) {\n     nc->err = mg_get_errno() ? mg_get_errno() : 1;\n     return;\n   }\n@@ -3891,7 +3900,12 @@ void mg_socket_if_connect_tcp(struct mg_connection *nc,\n \n void mg_socket_if_connect_udp(struct mg_connection *nc) {\n   nc->sock = socket(AF_INET, SOCK_DGRAM, 0);\n-  if (nc->sock == INVALID_SOCKET) {\n+  if (nc->sock == INVALID_SOCKET\n+#ifdef SO_NOSIGPIPE\n+      /* Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs */\n+      || setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nopipe, sizeof(nopipe))\n+#endif\n+  ) {\n     nc->err = mg_get_errno() ? mg_get_errno() : 1;\n     return;\n   }\n@@ -3927,7 +3941,7 @@ static int mg_socket_if_listen_udp(struct mg_connection *nc,\n \n static int mg_socket_if_tcp_send(struct mg_connection *nc, const void *buf,\n                                  size_t len) {\n-  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, 0);\n+  int n = (int) MG_SEND_FUNC(nc->sock, buf, len, MSG_NOSIGNAL);\n   if (n < 0 && !mg_is_error()) n = 0;\n   return n;\n }\n@@ -4007,11 +4021,17 @@ static sock_t mg_open_listening_socket(union socket_address *sa, int type,\n   socklen_t sa_len =\n       (sa->sa.sa_family == AF_INET) ? sizeof(sa->sin) : sizeof(sa->sin6);\n   sock_t sock = INVALID_SOCKET;\n+  int nopipe = 1;\n #if !MG_LWIP\n   int on = 1;\n #endif\n \n   if ((sock = socket(sa->sa.sa_family, type, proto)) != INVALID_SOCKET &&\n+#ifdef SO_NOSIGPIPE\n+      /* Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs */\n+      !setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, (void *)&nopipe, sizeof(nopipe)) &&\n+#endif\n+\n #if !MG_LWIP /* LWIP doesn't support either */\n #if defined(_WIN32) && defined(SO_EXCLUSIVEADDRUSE) && !defined(WINCE)\n       /* \"Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE\" http://goo.gl/RmrFTm */\n"
  },
  {
    "path": "src/mongoose_warn.patch",
    "content": "diff --git a/src/mongoose.c b/src/mongoose.c\nindex 6db561bc..2a284ee8 100644\n--- a/src/mongoose.c\n+++ b/src/mongoose.c\n@@ -1221,7 +1221,7 @@ void cs_md5_update(cs_md5_ctx *ctx, const unsigned char *buf, size_t len) {\n   memcpy(ctx->in, buf, len);\n }\n \n-void cs_md5_final(unsigned char digest[16], cs_md5_ctx *ctx) {\n+void cs_md5_final(unsigned char *digest, cs_md5_ctx *ctx) {\n   unsigned count;\n   unsigned char *p;\n   uint32_t *a;\n@@ -4965,7 +4965,7 @@ static enum mg_ssl_if_result mg_use_cert(SSL_CTX *ctx, const char *cert,\n       DH_free(dh);\n     }\n #if OPENSSL_VERSION_NUMBER > 0x10002000L\n-    SSL_CTX_set_ecdh_auto(ctx, 1);\n+    (void) SSL_CTX_set_ecdh_auto(ctx, 1);\n #endif\n #endif\n   }\n"
  },
  {
    "path": "src/optparse.c",
    "content": "/** @file\n    Option parsing functions to complement getopt.\n\n    Copyright (C) 2017 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"optparse.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <limits.h>\n#include <string.h>\n\nint tls_param(tls_opts_t *tls_opts, char const *key, char const *val)\n{\n    if (!tls_opts || !key || !*key)\n        return 1;\n    else if (!strcasecmp(key, \"tls_cert\"))\n        tls_opts->tls_cert = val;\n    else if (!strcasecmp(key, \"tls_key\"))\n        tls_opts->tls_key = val;\n    else if (!strcasecmp(key, \"tls_ca_cert\"))\n        tls_opts->tls_ca_cert = val;\n    else if (!strcasecmp(key, \"tls_cipher_suites\"))\n        tls_opts->tls_cipher_suites = val;\n    else if (!strcasecmp(key, \"tls_server_name\"))\n        tls_opts->tls_server_name = val;\n    else if (!strcasecmp(key, \"tls_psk_identity\"))\n        tls_opts->tls_psk_identity = val;\n    else if (!strcasecmp(key, \"tls_psk_key\"))\n        tls_opts->tls_psk_key = val;\n    else\n        return 1;\n    return 0;\n}\n\nint atobv(char const *arg, int def)\n{\n    if (!arg)\n        return def;\n    if (!strcasecmp(arg, \"true\") || !strcasecmp(arg, \"yes\") || !strcasecmp(arg, \"on\") || !strcasecmp(arg, \"enable\"))\n        return 1;\n    return atoi(arg);\n}\n\nint atoiv(char const *arg, int def)\n{\n    if (!arg)\n        return def;\n    char *endptr;\n    int val = strtol(arg, &endptr, 10);\n    if (arg == endptr)\n        return def;\n    return val;\n}\n\nchar *arg_param(char const *arg)\n{\n    if (!arg)\n        return NULL;\n    char *p = strchr(arg, ':');\n    char *c = strchr(arg, ',');\n    if (p && (!c || p < c))\n        return ++p;\n    else if (c)\n        return c;\n    else\n        return p;\n}\n\ndouble arg_float(char const *str, char const *error_hint)\n{\n    if (!str) {\n        fprintf(stderr, \"%smissing number argument\\n\", error_hint);\n        exit(1);\n    }\n\n    if (!*str) {\n        fprintf(stderr, \"%sempty number argument\\n\", error_hint);\n        exit(1);\n    }\n\n    // allow whitespace and equals char\n    while (*str == ' ' || *str == '=')\n        ++str;\n\n    char *endptr;\n    double val = strtod(str, &endptr);\n\n    if (str == endptr) {\n        fprintf(stderr, \"%sinvalid number argument (%s)\\n\", error_hint, str);\n        exit(1);\n    }\n\n    return val;\n}\n\nchar *hostport_param(char *param, char const **host, char const **port)\n{\n    if (param && *param) {\n        if (param[0] == '/' && param[1] == '/') {\n            param += 2;\n        }\n        if (*param != ':' && *param != ',') {\n            *host = param;\n            if (*param == '[') {\n                (*host)++;\n                param = strchr(param, ']');\n                if (param) {\n                    *param++ = '\\0';\n                }\n                else {\n                    fprintf(stderr, \"Malformed Ipv6 address!\\n\");\n                    exit(1);\n                }\n            }\n        }\n        char *colon = strchr(param, ':');\n        char *comma = strchr(param, ',');\n        if (colon && (!comma || colon < comma)) {\n            *colon++ = '\\0';\n            *port    = colon;\n        }\n        if (comma) {\n            *comma++ = '\\0';\n            return comma;\n        }\n    }\n    return NULL;\n}\n\nuint32_t atouint32_metric(char const *str, char const *error_hint)\n{\n    if (!str) {\n        fprintf(stderr, \"%smissing number argument\\n\", error_hint);\n        exit(1);\n    }\n\n    if (!*str) {\n        fprintf(stderr, \"%sempty number argument\\n\", error_hint);\n        exit(1);\n    }\n\n    char *endptr;\n    double val = strtod(str, &endptr);\n\n    if (str == endptr) {\n        fprintf(stderr, \"%sinvalid number argument (%s)\\n\", error_hint, str);\n        exit(1);\n    }\n\n    if (val < 0.0) {\n        fprintf(stderr, \"%snon-negative number argument expected (%f)\\n\", error_hint, val);\n        exit(1);\n    }\n\n    // allow whitespace before suffix\n    while (*endptr == ' ' || *endptr == '\\t')\n        ++endptr;\n\n    switch (*endptr) {\n        case '\\0':\n            break;\n        case 'k':\n        case 'K':\n            val *= 1e3;\n            break;\n        case 'M':\n        case 'm':\n            val *= 1e6;\n            break;\n        case 'G':\n        case 'g':\n            val *= 1e9;\n            break;\n        default:\n            fprintf(stderr, \"%sunknown number suffix (%s)\\n\", error_hint, endptr);\n            exit(1);\n    }\n\n    if (val > UINT32_MAX) {\n        fprintf(stderr, \"%snumber argument too big (%f)\\n\", error_hint, val);\n        exit(1);\n    }\n\n    val += 1e-5; // rounding (e.g. 4123456789.99999)\n    if (val - (uint32_t)val > 2e-5) {\n        fprintf(stderr, \"%sdecimal fraction (%f) did you forget k, M, or G suffix?\\n\", error_hint, val - (uint32_t)val);\n    }\n\n    return (uint32_t)val;\n}\n\nint atoi_time(char const *str, char const *error_hint)\n{\n    if (!str) {\n        fprintf(stderr, \"%smissing time argument\\n\", error_hint);\n        exit(1);\n    }\n\n    if (!*str) {\n        fprintf(stderr, \"%sempty time argument\\n\", error_hint);\n        exit(1);\n    }\n\n    char *endptr    = NULL;\n    double val      = 0.0;\n    unsigned colons = 0;\n\n    do {\n        double num = strtod(str, &endptr);\n\n        if (!endptr || str == endptr) {\n            fprintf(stderr, \"%sinvalid time argument (%s)\\n\", error_hint, str);\n            exit(1);\n        }\n\n        // allow whitespace before suffix\n        while (*endptr == ' ' || *endptr == '\\t')\n            ++endptr;\n\n        switch (*endptr) {\n        case '\\0':\n            if (colons == 0) {\n                // assume seconds\n                val += num;\n                break;\n            }\n            // intentional fallthrough\n#if defined __has_attribute\n#if __has_attribute(fallthrough)\n            __attribute__((fallthrough));\n#endif\n#endif\n        case ':':\n            ++colons;\n            if (colons == 1)\n                val += num * 60 * 60;\n            else if (colons == 2)\n                val += num * 60;\n            else if (colons == 3)\n                val += num;\n            else {\n                fprintf(stderr, \"%stoo many colons (use HH:MM[:SS]))\\n\", error_hint);\n                exit(1);\n            }\n            if (*endptr)\n                ++endptr;\n            break;\n        case 's':\n        case 'S':\n            val += num;\n            ++endptr;\n            break;\n        case 'm':\n        case 'M':\n            val += num * 60;\n            ++endptr;\n            break;\n        case 'h':\n        case 'H':\n            val += num * 60 * 60;\n            ++endptr;\n            break;\n        case 'd':\n        case 'D':\n            val += num * 60 * 60 * 24;\n            ++endptr;\n            break;\n        default:\n            fprintf(stderr, \"%sunknown time suffix (%s)\\n\", error_hint, endptr);\n            exit(1);\n        }\n\n        // chew up any remaining whitespace\n        while (*endptr == ' ' || *endptr == '\\t')\n            ++endptr;\n        str = endptr;\n\n    } while (*endptr);\n\n    if (val > INT_MAX || val < INT_MIN) {\n        fprintf(stderr, \"%stime argument too big (%f)\\n\", error_hint, val);\n        exit(1);\n    }\n\n    if (val < 0) {\n        val -= 1e-5; // rounding (e.g. -4123456789.99999)\n    }\n    else {\n        val += 1e-5; // rounding (e.g. 4123456789.99999)\n    }\n    if (val - (int)(val) > 2e-5) {\n        fprintf(stderr, \"%sdecimal fraction (%f) did you forget m, or h suffix?\\n\", error_hint, val - (uint32_t)val);\n    }\n\n    return (int)val;\n}\n\nchar *asepc(char **stringp, char delim)\n{\n    if (!stringp || !*stringp) return NULL;\n    char *s = strchr(*stringp, delim);\n    if (s) *s++ = '\\0';\n    char *p = *stringp;\n    *stringp = s;\n    return p;\n}\n\nstatic char *achrb(char *s, int c, int b)\n{\n    for (; s && *s && *s != b; ++s)\n        if (*s == c) return (char *)s;\n    return NULL;\n}\n\nchar *asepcb(char **stringp, char delim, char stop)\n{\n    if (!stringp || !*stringp) return NULL;\n    char *s = achrb(*stringp, delim, stop);\n    if (s) *s++ = '\\0';\n    char *p = *stringp;\n    *stringp = s;\n    return p;\n}\n\nint kwargs_match(char const *s, char const *key, char const **val)\n{\n    if (!key || !*key) {\n        return 0; // no match\n    }\n    size_t len = strlen(key);\n    // check prefix match\n    if (strncmp(s, key, len)) {\n        return 0; // no match\n    }\n    s += len;\n    // skip whitespace after match\n    while (*s == ' ' || *s == '\\t')\n        ++s;\n    if (*s == '\\0' || * s == ',') {\n        if (val)\n            *val = NULL;\n        return 1; // match with no arg\n    }\n    if (*s == '=') {\n        if (val)\n            *val = s + 1;\n        return 1; // match with arg\n    }\n    return 0; // no exact match\n}\n\nchar const *kwargs_skip(char const *s)\n{\n    // skip to the next comma if possible\n    while (s && *s && *s != ',')\n        ++s;\n    // skip comma and whitespace if possible\n    while (s && *s && (*s == ',' || *s == ' ' || *s == '\\t'))\n        ++s;\n    return s;\n}\n\nchar *getkwargs(char **s, char **key, char **val)\n{\n    char *v = asepc(s, ',');\n    char *k = asepc(&v, '=');\n    if (key) *key = k;\n    if (val) *val = v;\n    return k;\n}\n\nchar *trim_ws(char *str)\n{\n    if (!str || !*str)\n        return str;\n    while (*str == ' ' || *str == '\\t' || *str == '\\r' || *str == '\\n')\n        ++str;\n    char *e = str; // end pointer (last non ws)\n    char *p = str; // scanning pointer\n    while (*p) {\n        while (*p == ' ' || *p == '\\t' || *p == '\\r' || *p == '\\n')\n            ++p;\n        if (*p)\n            e = p++;\n    }\n    *++e = '\\0';\n    return str;\n}\n\nchar *remove_ws(char *str)\n{\n    if (!str)\n        return str;\n    char *d = str; // dst pointer\n    char *s = str; // src pointer\n    while (*s) {\n        while (*s == ' ' || *s == '\\t' || *s == '\\r' || *s == '\\n')\n            ++s;\n        if (*s)\n            *d++ = *s++;\n    }\n    *d++ = '\\0';\n    return str;\n}\n\n// Unit testing\n#ifdef _TEST\n#define ASSERT_EQUALS(a,b)                                  \\\n    do {                                                    \\\n        if ((a) == (b))                                     \\\n            ++passed;                                       \\\n        else {                                              \\\n            ++failed;                                       \\\n            fprintf(stderr, \"FAIL: %d <> %d\\n\", (a), (b));  \\\n        }                                                   \\\n    } while (0)\n\nint main(void)\n{\n    unsigned passed = 0;\n    unsigned failed = 0;\n\n    fprintf(stderr, \"optparse:: atouint32_metric\\n\");\n    ASSERT_EQUALS(atouint32_metric(\"0\", \"\"), 0);\n    ASSERT_EQUALS(atouint32_metric(\"1\", \"\"), 1);\n    ASSERT_EQUALS(atouint32_metric(\"0.0\", \"\"), 0);\n    ASSERT_EQUALS(atouint32_metric(\"1.0\", \"\"), 1);\n    ASSERT_EQUALS(atouint32_metric(\"1.024k\", \"\"), 1024);\n    ASSERT_EQUALS(atouint32_metric(\"433.92M\", \"\"), 433920000);\n    ASSERT_EQUALS(atouint32_metric(\"433.94M\", \"\"), 433940000);\n    ASSERT_EQUALS(atouint32_metric(\" +1 G \", \"\"), 1000000000);\n\n    fprintf(stderr, \"optparse:: atoi_time\\n\");\n    ASSERT_EQUALS(atoi_time(\"0\", \"\"), 0);\n    ASSERT_EQUALS(atoi_time(\"1\", \"\"), 1);\n    ASSERT_EQUALS(atoi_time(\"0.0\", \"\"), 0);\n    ASSERT_EQUALS(atoi_time(\"1.0\", \"\"), 1);\n    ASSERT_EQUALS(atoi_time(\"1s\", \"\"), 1);\n    ASSERT_EQUALS(atoi_time(\"2d\", \"\"), 2 * 60 * 60 * 24);\n    ASSERT_EQUALS(atoi_time(\"2h\", \"\"), 2 * 60 * 60);\n    ASSERT_EQUALS(atoi_time(\"2m\", \"\"), 2 * 60);\n    ASSERT_EQUALS(atoi_time(\"2s\", \"\"), 2);\n    ASSERT_EQUALS(atoi_time(\"2D\", \"\"), 2 * 60 * 60 * 24);\n    ASSERT_EQUALS(atoi_time(\"2H\", \"\"), 2 * 60 * 60);\n    ASSERT_EQUALS(atoi_time(\"2M\", \"\"), 2 * 60);\n    ASSERT_EQUALS(atoi_time(\"2S\", \"\"), 2);\n    ASSERT_EQUALS(atoi_time(\"2h3m4s\", \"\"), 2 * 60 * 60 + 3 * 60 + 4);\n    ASSERT_EQUALS(atoi_time(\"2h 3m 4s\", \"\"), 2 * 60 * 60 + 3 * 60 + 4);\n    ASSERT_EQUALS(atoi_time(\"2h3h 3m 4s 5\", \"\"), 5 * 60 * 60 + 3 * 60 + 9);\n    ASSERT_EQUALS(atoi_time(\" 2m \", \"\"), 2 * 60);\n    ASSERT_EQUALS(atoi_time(\"2 m\", \"\"), 2 * 60);\n    ASSERT_EQUALS(atoi_time(\"  2  m  \", \"\"), 2 * 60);\n    ASSERT_EQUALS(atoi_time(\"-1m\", \"\"), -60);\n    ASSERT_EQUALS(atoi_time(\"1h-15m\", \"\"), 45 * 60);\n\n    ASSERT_EQUALS(atoi_time(\"2:3\", \"\"), 2 * 60 * 60 + 3 * 60);\n    ASSERT_EQUALS(atoi_time(\"2:3:4\", \"\"), 2 * 60 * 60 + 3 * 60 + 4);\n    ASSERT_EQUALS(atoi_time(\"02:03\", \"\"), 2 * 60 * 60 + 3 * 60);\n    ASSERT_EQUALS(atoi_time(\"02:03:04\", \"\"), 2 * 60 * 60 + 3 * 60 + 4);\n    ASSERT_EQUALS(atoi_time(\" 2 : 3 \", \"\"), 2 * 60 * 60 + 3 * 60);\n    ASSERT_EQUALS(atoi_time(\" 2 : 3 : 4 \", \"\"), 2 * 60 * 60 + 3 * 60 + 4);\n\n    fprintf(stderr, \"optparse:: test (%u/%u) passed, (%u) failed.\\n\", passed, passed + failed, failed);\n\n    return failed;\n}\n#endif /* _TEST */\n"
  },
  {
    "path": "src/output_file.c",
    "content": "/** @file\n    File outputs for rtl_433 events.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"output_file.h\"\n\n#include \"data.h\"\n#include \"term_ctl.h\"\n#include \"r_util.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n\n/* JSON printer */\n\ntypedef struct {\n    struct data_output output;\n    FILE *file;\n} data_output_json_t;\n\nstatic void R_API_CALLCONV print_json_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    data_output_json_t *json = (data_output_json_t *)output;\n\n    fprintf(json->file, \"[\");\n    for (int c = 0; c < array->num_values; ++c) {\n        if (c)\n            fprintf(json->file, \", \");\n        print_array_value(output, array, format, c);\n    }\n    fprintf(json->file, \"]\");\n}\n\nstatic void R_API_CALLCONV print_json_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_output_json_t *json = (data_output_json_t *)output;\n\n    bool separator = false;\n    fputc('{', json->file);\n    while (data) {\n        if (separator)\n            fprintf(json->file, \", \");\n        output->print_string(output, data->key, NULL);\n        fprintf(json->file, \" : \");\n        print_value(output, data->type, data->value, data->format);\n        separator = true;\n        data = data->next;\n    }\n    fputc('}', json->file);\n}\n\nstatic void R_API_CALLCONV print_json_string(data_output_t *output, const char *str, char const *format)\n{\n    UNUSED(format);\n    data_output_json_t *json = (data_output_json_t *)output;\n\n    size_t str_len = strlen(str);\n    if (str[0] == '{' && str[str_len - 1] == '}') {\n        // Print embedded JSON object verbatim\n        fprintf(json->file, \"%s\", str);\n        return;\n    }\n\n    fprintf(json->file, \"\\\"\");\n    for (; *str; ++str) {\n        if (*str == '\\r') {\n            fprintf(json->file, \"\\\\r\");\n        }\n        else if (*str == '\\n') {\n            fprintf(json->file, \"\\\\n\");\n        }\n        else if (*str == '\\t') {\n            fprintf(json->file, \"\\\\t\");\n        }\n        else if (*str == '\"' || *str == '\\\\') {\n            fputc('\\\\', json->file);\n            fputc(*str, json->file);\n        }\n        else {\n            fputc(*str, json->file);\n        }\n    }\n    fprintf(json->file, \"\\\"\");\n}\n\nstatic void R_API_CALLCONV print_json_double(data_output_t *output, double data, char const *format)\n{\n    UNUSED(format);\n    data_output_json_t *json = (data_output_json_t *)output;\n\n    fprintf(json->file, \"%.3f\", data);\n}\n\nstatic void R_API_CALLCONV print_json_int(data_output_t *output, int data, char const *format)\n{\n    UNUSED(format);\n    data_output_json_t *json = (data_output_json_t *)output;\n\n    fprintf(json->file, \"%d\", data);\n}\n\nstatic void R_API_CALLCONV data_output_json_print(data_output_t *output, data_t *data)\n{\n    data_output_json_t *json = (data_output_json_t *)output;\n\n    if (json && json->file) {\n        json->output.print_data(output, data, NULL);\n        fputc('\\n', json->file);\n        fflush(json->file);\n    }\n}\n\nstatic void R_API_CALLCONV data_output_json_free(data_output_t *output)\n{\n    if (!output)\n        return;\n\n    free(output);\n}\n\nstruct data_output *data_output_json_create(int log_level, FILE *file)\n{\n    data_output_json_t *json = calloc(1, sizeof(data_output_json_t));\n    if (!json) {\n        WARN_CALLOC(\"data_output_json_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    json->output.log_level    = log_level;\n    json->output.print_data   = print_json_data;\n    json->output.print_array  = print_json_array;\n    json->output.print_string = print_json_string;\n    json->output.print_double = print_json_double;\n    json->output.print_int    = print_json_int;\n    json->output.output_print = data_output_json_print;\n    json->output.output_free  = data_output_json_free;\n    json->file                = file;\n\n    return (struct data_output *)json;\n}\n\n/* Pretty Key-Value printer */\n\nstatic int kv_color_for_key(char const *key)\n{\n    if (!key || !*key)\n        return TERM_COLOR_RESET;\n    if (!strcmp(key, \"tag\") || !strcmp(key, \"time\"))\n        return TERM_COLOR_BLUE;\n    if (!strcmp(key, \"model\") || !strcmp(key, \"type\") || !strcmp(key, \"id\"))\n        return TERM_COLOR_RED;\n    if (!strcmp(key, \"mic\"))\n        return TERM_COLOR_CYAN;\n    if (!strcmp(key, \"mod\") || !strcmp(key, \"freq\") || !strcmp(key, \"freq1\") || !strcmp(key, \"freq2\"))\n        return TERM_COLOR_MAGENTA;\n    if (!strcmp(key, \"rssi\") || !strcmp(key, \"snr\") || !strcmp(key, \"noise\"))\n        return TERM_COLOR_YELLOW;\n    return TERM_COLOR_GREEN;\n}\n\nstatic int kv_break_before_key(char const *key)\n{\n    if (!key || !*key)\n        return 0;\n    if (!strcmp(key, \"model\") || !strcmp(key, \"mod\") || !strcmp(key, \"rssi\") || !strcmp(key, \"codes\"))\n        return 1;\n    return 0;\n}\n\nstatic int kv_break_after_key(char const *key)\n{\n    if (!key || !*key)\n        return 0;\n    if (!strcmp(key, \"id\") || !strcmp(key, \"mic\"))\n        return 1;\n    return 0;\n}\n\ntypedef struct {\n    struct data_output output;\n    FILE *file;\n    void *term;\n    int color;\n    int ring_bell;\n    int term_width;\n    int data_recursion;\n    int column;\n} data_output_kv_t;\n\n#define KV_SEP \"_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \"\n\nstatic void R_API_CALLCONV print_kv_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    int color = kv->color;\n    int ring_bell = kv->ring_bell;\n    int is_log = 0;\n\n    // top-level: update width and print separator\n    if (!kv->data_recursion) {\n        // collect well-known top level keys\n        data_t *data_src = NULL;\n        data_t *data_lvl  = NULL;\n        data_t *data_msg  = NULL;\n        for (data_t *d = data; d; d = d->next) {\n            if (!strcmp(d->key, \"src\"))\n                data_src = d;\n            else if (!strcmp(d->key, \"lvl\"))\n                data_lvl = d;\n            else if (!strcmp(d->key, \"msg\"))\n                data_msg = d;\n        }\n        is_log = data_src && data_lvl && data_msg;\n\n        kv->term_width = term_get_columns(kv->term); // update current term width\n        if (!is_log) {\n        if (color)\n            term_set_fg(kv->term, TERM_COLOR_BLACK);\n        if (ring_bell)\n            term_ring_bell(kv->term);\n        char sep[] = KV_SEP KV_SEP KV_SEP KV_SEP;\n        if (kv->term_width < (int)sizeof(sep))\n            sep[kv->term_width > 0 ? kv->term_width - 1 : 40] = '\\0';\n        fprintf(kv->file, \"%s\\n\", sep);\n        if (color)\n            term_set_fg(kv->term, TERM_COLOR_RESET);\n        }\n\n        // print special log format\n        if (is_log) {\n            int level = 0;\n            if (data_lvl->type == DATA_INT) {\n                level = data_lvl->value.v_int;\n            }\n            term_color_t src_bg = TERM_COLOR_RESET;\n            term_color_t src_fg = TERM_COLOR_RESET;\n            if (level == LOG_FATAL) {\n                src_bg = TERM_COLOR_BRIGHT_BLACK;\n                src_fg = TERM_COLOR_WHITE;\n            } else if (level == LOG_CRITICAL) {\n                src_bg = TERM_COLOR_BRIGHT_GREEN;\n                src_fg = TERM_COLOR_BLACK;\n            } else if (level == LOG_ERROR) {\n                src_bg = TERM_COLOR_BRIGHT_RED;\n                src_fg = TERM_COLOR_WHITE;\n            } else if (level == LOG_WARNING) {\n                src_bg = TERM_COLOR_BRIGHT_YELLOW;\n                src_fg = TERM_COLOR_BLACK;\n            } else if (level == LOG_NOTICE) {\n                src_bg = TERM_COLOR_BRIGHT_CYAN;\n                src_fg = TERM_COLOR_BLACK;\n            } else if (level == LOG_INFO) {\n                src_bg = TERM_COLOR_BRIGHT_BLUE;\n                src_fg = TERM_COLOR_WHITE;\n            } else if (level == LOG_DEBUG) {\n                src_bg = TERM_COLOR_BRIGHT_MAGENTA;\n                src_fg = TERM_COLOR_WHITE;\n            } else if (level == LOG_TRACE) {\n                src_bg = TERM_COLOR_BRIGHT_BLACK;\n                src_fg = TERM_COLOR_WHITE;\n            }\n            term_set_bg(kv->term, src_bg, src_bg); // hides the brackets\n            fprintf(kv->file, \"[\");\n            term_set_bg(kv->term, 0, src_fg);\n            print_value(output, data_src->type, data_src->value, data_src->format);\n            term_set_bg(kv->term, 0, src_bg); // hides the brackets\n            fprintf(kv->file, \"]\");\n            term_set_fg(kv->term, TERM_COLOR_RESET);\n            // fprintf(kv->file, \" (\");\n            // print_value(output, data_lvl->type, data_lvl->value, data_lvl->format);\n            // fprintf(kv->file, \") \");\n            fprintf(kv->file, \" \");\n            print_value(output, data_msg->type, data_msg->value, data_msg->format);\n            // force break on next key\n            kv->column = kv->term_width;\n        }\n    }\n    // nested data object: break before\n    else {\n        if (color)\n            term_set_fg(kv->term, TERM_COLOR_RESET);\n        fprintf(kv->file, \"\\n\");\n        kv->column = 0;\n    }\n\n    ++kv->data_recursion;\n    for (; data; data = data->next) {\n        // skip logging keys\n        if (is_log && (!strcmp(data->key, \"time\") || !strcmp(data->key, \"src\") || !strcmp(data->key, \"lvl\")\n                || !strcmp(data->key, \"msg\") || !strcmp(data->key, \"num_rows\"))) {\n            continue;\n        }\n\n        // break before some known keys\n        if (kv->column > 0 && kv_break_before_key(data->key)) {\n            fprintf(kv->file, \"\\n\");\n            kv->column = 0;\n        }\n        // break if not enough width left\n        else if (kv->column >= kv->term_width - 26) {\n            fprintf(kv->file, \"\\n\");\n            kv->column = 0;\n        }\n        // pad to next alignment if there is enough width left\n        else if (kv->column > 0 && kv->column < kv->term_width - 26) {\n            kv->column += fprintf(kv->file, \"%*s\", 25 - kv->column % 26, \" \");\n        }\n\n        // print key\n        char *key = *data->pretty_key ? data->pretty_key : data->key;\n        kv->column += fprintf(kv->file, \"%-10s: \", key);\n        // print value\n        if (color)\n            term_set_fg(kv->term, kv_color_for_key(data->key));\n        print_value(output, data->type, data->value, data->format);\n        if (color)\n            term_set_fg(kv->term, TERM_COLOR_RESET);\n\n        // force break after some known keys\n        if (kv->column > 0 && kv_break_after_key(data->key)) {\n            kv->column = kv->term_width; // force break;\n        }\n    }\n    --kv->data_recursion;\n\n    // top-level: always end with newline\n    if (!kv->data_recursion && kv->column > 0) {\n        //fprintf(kv->file, \"\\n\"); // data_output_print() already adds a newline\n        kv->column = 0;\n    }\n}\n\nstatic void R_API_CALLCONV print_kv_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    //fprintf(kv->file, \"[ \");\n    for (int c = 0; c < array->num_values; ++c) {\n        if (c)\n            fprintf(kv->file, \", \");\n        print_array_value(output, array, format, c);\n    }\n    //fprintf(kv->file, \" ]\");\n}\n\nstatic void R_API_CALLCONV print_kv_double(data_output_t *output, double data, char const *format)\n{\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    kv->column += fprintf(kv->file, format ? format : \"%.3f\", data);\n}\n\nstatic void R_API_CALLCONV print_kv_int(data_output_t *output, int data, char const *format)\n{\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    kv->column += fprintf(kv->file, format ? format : \"%d\", data);\n}\n\nstatic void R_API_CALLCONV print_kv_string(data_output_t *output, const char *data, char const *format)\n{\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    kv->column += fprintf(kv->file, format ? format : \"%s\", data);\n}\n\nstatic void R_API_CALLCONV data_output_kv_print(data_output_t *output, data_t *data)\n{\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    if (kv && kv->file) {\n        kv->output.print_data(output, data, NULL);\n        fputc('\\n', kv->file);\n        fflush(kv->file);\n    }\n}\n\nstatic void R_API_CALLCONV data_output_kv_free(data_output_t *output)\n{\n    data_output_kv_t *kv = (data_output_kv_t *)output;\n\n    if (!output)\n        return;\n\n    if (kv->color)\n        term_free(kv->term);\n\n    free(output);\n}\nstruct data_output *data_output_kv_create(int log_level, FILE *file)\n{\n    data_output_kv_t *kv = calloc(1, sizeof(data_output_kv_t));\n    if (!kv) {\n        WARN_CALLOC(\"data_output_kv_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    kv->output.log_level    = log_level;\n    kv->output.print_data   = print_kv_data;\n    kv->output.print_array  = print_kv_array;\n    kv->output.print_string = print_kv_string;\n    kv->output.print_double = print_kv_double;\n    kv->output.print_int    = print_kv_int;\n    kv->output.output_print = data_output_kv_print;\n    kv->output.output_free  = data_output_kv_free;\n    kv->file                = file;\n\n    kv->term = term_init(file);\n    kv->color = term_has_color(kv->term);\n\n    kv->ring_bell = 0; // TODO: enable if requested...\n\n    return (struct data_output *)kv;\n}\n\n/* CSV printer */\n\ntypedef struct {\n    struct data_output output;\n    FILE *file;\n    const char **fields;\n    const char *separator;\n} data_output_csv_t;\n\nstatic void R_API_CALLCONV print_csv_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    fputc('{', csv->file);\n    for (bool separator = false; data; data = data->next) {\n        if (separator)\n            fprintf(csv->file, \"; \"); // NOTE: distinct from csv->separator\n        output->print_string(output, data->key, NULL);\n        fprintf(csv->file, \": \");\n        print_value(output, data->type, data->value, data->format);\n        separator = true;\n    }\n    fputc('}', csv->file);\n}\n\nstatic void R_API_CALLCONV print_csv_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    for (int c = 0; c < array->num_values; ++c) {\n        if (c)\n            fprintf(csv->file, \";\");\n        print_array_value(output, array, format, c);\n    }\n}\n\nstatic void R_API_CALLCONV print_csv_string(data_output_t *output, const char *str, char const *format)\n{\n    UNUSED(format);\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    while (str && *str) {\n        if (strncmp(str, csv->separator, strlen(csv->separator)) == 0)\n            fputc('\\\\', csv->file);\n        fputc(*str, csv->file);\n        ++str;\n    }\n}\n\nstatic int compare_strings(const void *a, const void *b)\n{\n    return strcmp(*(char **)a, *(char **)b);\n}\n\nstatic void R_API_CALLCONV data_output_csv_start(struct data_output *output, char const *const *fields, int num_fields)\n{\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    int csv_fields = 0;\n    int i, j;\n    const char **allowed = NULL;\n    int *use_count = NULL;\n    int num_unique_fields;\n    if (!csv)\n        goto alloc_error;\n\n    csv->separator = \",\";\n\n    allowed = calloc(num_fields, sizeof(const char *));\n    if (!allowed) {\n        WARN_CALLOC(\"data_output_csv_start()\");\n        goto alloc_error;\n    }\n    memcpy((void *)allowed, fields, sizeof(const char *) * num_fields);\n\n    qsort((void *)allowed, num_fields, sizeof(char *), compare_strings);\n\n    // overwrite duplicates\n    i = 0;\n    j = 0;\n    while (j < num_fields) {\n        while (j > 0 && j < num_fields &&\n                strcmp(allowed[j - 1], allowed[j]) == 0)\n            ++j;\n\n        if (j < num_fields) {\n            allowed[i] = allowed[j];\n            ++i;\n            ++j;\n        }\n    }\n    num_unique_fields = i;\n\n    csv->fields = calloc(num_unique_fields + 1, sizeof(const char *));\n    if (!csv->fields) {\n        WARN_CALLOC(\"data_output_csv_start()\");\n        goto alloc_error;\n    }\n\n    use_count = calloc(num_unique_fields + 1, sizeof(*use_count)); // '+ 1' so we never alloc size 0\n    if (!use_count) {\n        WARN_CALLOC(\"data_output_csv_start()\");\n        goto alloc_error;\n    }\n\n    for (i = 0; i < num_fields; ++i) {\n        const char **field = bsearch(&fields[i], allowed, num_unique_fields, sizeof(const char *),\n                compare_strings);\n        int *field_use_count = use_count + (field - allowed);\n        if (field && !*field_use_count) {\n            csv->fields[csv_fields] = fields[i];\n            ++csv_fields;\n            ++*field_use_count;\n        }\n    }\n    csv->fields[csv_fields] = NULL;\n    free((void *)allowed);\n    free(use_count);\n\n    // Output the CSV header\n    for (i = 0; csv->fields[i]; ++i) {\n        fprintf(csv->file, \"%s%s\", i > 0 ? csv->separator : \"\", csv->fields[i]);\n    }\n    fprintf(csv->file, \"\\n\");\n    return;\n\nalloc_error:\n    free(use_count);\n    free((void *)allowed);\n    if (csv)\n        free((void *)csv->fields);\n    free(csv);\n}\n\nstatic void R_API_CALLCONV print_csv_double(data_output_t *output, double data, char const *format)\n{\n    UNUSED(format);\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    fprintf(csv->file, \"%.3f\", data);\n}\n\nstatic void R_API_CALLCONV print_csv_int(data_output_t *output, int data, char const *format)\n{\n    UNUSED(format);\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    fprintf(csv->file, \"%d\", data);\n}\n\nstatic void R_API_CALLCONV data_output_csv_print(data_output_t *output, data_t *data)\n{\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    const char **fields = csv->fields;\n\n    int regular = 0; // skip \"states\" output\n    for (data_t *d = data; d; d = d->next) {\n        if (!strcmp(d->key, \"msg\") || !strcmp(d->key, \"codes\") || !strcmp(d->key, \"model\")) {\n            regular = 1;\n            break;\n        }\n    }\n    if (!regular)\n        return;\n\n    for (int i = 0; fields[i]; ++i) {\n        const char *key = fields[i];\n        data_t *found   = NULL;\n        if (i)\n            fprintf(csv->file, \"%s\", csv->separator);\n        for (data_t *iter = data; !found && iter; iter = iter->next)\n            if (strcmp(iter->key, key) == 0)\n                found = iter;\n\n        if (found)\n            print_value(output, found->type, found->value, found->format);\n    }\n\n    fputc('\\n', csv->file);\n    fflush(csv->file);\n}\n\nstatic void R_API_CALLCONV data_output_csv_free(data_output_t *output)\n{\n    data_output_csv_t *csv = (data_output_csv_t *)output;\n\n    free((void *)csv->fields);\n    free(csv);\n}\n\nstruct data_output *data_output_csv_create(int log_level, FILE *file)\n{\n    data_output_csv_t *csv = calloc(1, sizeof(data_output_csv_t));\n    if (!csv) {\n        WARN_CALLOC(\"data_output_csv_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    csv->output.log_level    = log_level;\n    csv->output.print_data   = print_csv_data;\n    csv->output.print_array  = print_csv_array;\n    csv->output.print_string = print_csv_string;\n    csv->output.print_double = print_csv_double;\n    csv->output.print_int    = print_csv_int;\n    csv->output.output_start = data_output_csv_start;\n    csv->output.output_print = data_output_csv_print;\n    csv->output.output_free  = data_output_csv_free;\n    csv->file                = file;\n\n    return (struct data_output *)csv;\n}\n"
  },
  {
    "path": "src/output_influx.c",
    "content": "/** @file\n    InfluxDB output for rtl_433 events.\n\n    Copyright (C) 2019 Daniel Krueger\n    based on output_mqtt.c\n    Copyright (C) 2019 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n// note: our unit header includes unistd.h for gethostname() via data.h\n#include \"output_influx.h\"\n#include \"optparse.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include \"r_util.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdbool.h>\n#include <string.h>\n\n#include \"mongoose.h\"\n\n/* InfluxDB client abstraction / printer */\n\ntypedef struct {\n    struct data_output output;\n    struct mg_mgr *mgr;\n    struct mg_connection *conn;\n    struct mg_connection *timer;\n    int reconnect_delay;\n    int prev_status;\n    int prev_resp_code;\n    char hostname[64];\n    char url[400];\n    char extra_headers[150];\n    tls_opts_t tls_opts;\n    int databufidxfill;\n    struct mbuf databufs[2];\n\n} influx_client_t;\n\nstatic void influx_client_send(influx_client_t *ctx);\n\nstatic void influx_client_event(struct mg_connection *nc, int ev, void *ev_data)\n{\n    // note that while shutting down the ctx is NULL\n    influx_client_t *ctx = (influx_client_t *)nc->user_data;\n    struct http_message *hm = (struct http_message *)ev_data;\n\n    switch (ev) {\n    case MG_EV_CONNECT: {\n        int connect_status = *(int *)ev_data;\n        if (connect_status == 0) {\n            // Success\n            if (ctx) {\n                ctx->reconnect_delay = 0;\n            }\n        } else {\n            // Error, print only once\n            if (ctx) {\n                if (ctx->prev_status != connect_status)\n                    print_logf(LOG_WARNING, \"InfluxDB\", \"InfluxDB connect error: %s\", strerror(connect_status));\n                ctx->conn = NULL;\n            }\n        }\n        if (ctx) {\n            ctx->prev_status = connect_status;\n        }\n        break;\n    }\n    case MG_EV_HTTP_CHUNK: // response is normally empty (so mongoose thinks we received a chunk only)\n    case MG_EV_HTTP_REPLY:\n        nc->flags |= MG_F_CLOSE_IMMEDIATELY;\n        if (hm->resp_code == 204) {\n            // mark influx data as sent\n        }\n        else {\n            if (ctx && ctx->prev_resp_code != hm->resp_code)\n                print_logf(LOG_WARNING, \"InfluxDB\", \"InfluxDB replied HTTP code: %d with message:\\n%s\", hm->resp_code, hm->body.p);\n        }\n        if (ctx) {\n            ctx->prev_resp_code = hm->resp_code;\n        }\n        break;\n    case MG_EV_CLOSE:\n        if (!ctx) {\n            break; // shutting down\n        }\n        ctx->conn = NULL;\n        if (!ctx->timer) {\n            break; // shutting down\n        }\n        // Timer for next connect attempt, sends us MG_EV_TIMER event\n        mg_set_timer(ctx->timer, mg_time() + ctx->reconnect_delay);\n        if (ctx->reconnect_delay < 60) {\n            // 0, 1, 3, 6, 10, 16, 25, 39, 60\n            ctx->reconnect_delay = (ctx->reconnect_delay + 1) * 3 / 2;\n        }\n        break;\n    }\n}\n\nstatic void influx_client_timer(struct mg_connection *nc, int ev, void *ev_data)\n{\n    // note that while shutting down the ctx is NULL\n    influx_client_t *ctx = (influx_client_t *)nc->user_data;\n    (void)ev_data;\n\n    switch (ev) {\n    case MG_EV_TIMER: {\n        // Try to reconnect, ends if no data to send\n        influx_client_send(ctx);\n        break;\n    }\n    }\n}\n\nstatic influx_client_t *influx_client_init(influx_client_t *ctx, char const *url, char const *token)\n{\n    snprintf(ctx->url, sizeof(ctx->url), \"%s\", url);\n    snprintf(ctx->extra_headers, sizeof (ctx->extra_headers), \"Authorization: Token %s\\r\\n\", token);\n\n    return ctx;\n}\n\nstatic void influx_client_send(influx_client_t *ctx)\n{\n    struct mbuf *buf = &ctx->databufs[ctx->databufidxfill];\n\n    /*fprintf(stderr, \"Influx %p msg: \\\"%s\\\" with %lu/%lu %s\\n\",\n            (void*)ctx, buf->buf, buf->len, buf->size,\n            ctx->conn ? \"buffering\" : \"to be sent\");*/\n\n    if (ctx->conn || !buf->len)\n        return;\n\n    char const *error_string = NULL;\n    struct mg_connect_opts opts = {.user_data = ctx, .error_string = &error_string};\n    if (ctx->tls_opts.tls_ca_cert) {\n        print_logf(LOG_INFO, \"InfluxDB\", \"influxs (TLS) parameters are: \"\n                                       \"tls_cert=%s \"\n                                       \"tls_key=%s \"\n                                       \"tls_ca_cert=%s \"\n                                       \"tls_cipher_suites=%s \"\n                                       \"tls_server_name=%s \"\n                                       \"tls_psk_identity=%s \"\n                                       \"tls_psk_key=%s \",\n                ctx->tls_opts.tls_cert,\n                ctx->tls_opts.tls_key,\n                ctx->tls_opts.tls_ca_cert,\n                ctx->tls_opts.tls_cipher_suites,\n                ctx->tls_opts.tls_server_name,\n                ctx->tls_opts.tls_psk_identity,\n                ctx->tls_opts.tls_psk_key);\n\n#if MG_ENABLE_SSL\n        opts.ssl_cert          = ctx->tls_opts.tls_cert;\n        opts.ssl_key           = ctx->tls_opts.tls_key;\n        opts.ssl_ca_cert       = ctx->tls_opts.tls_ca_cert;\n        opts.ssl_cipher_suites = ctx->tls_opts.tls_cipher_suites;\n        opts.ssl_server_name   = ctx->tls_opts.tls_server_name;\n        opts.ssl_psk_identity  = ctx->tls_opts.tls_psk_identity;\n        opts.ssl_psk_key       = ctx->tls_opts.tls_psk_key;\n#else\n        print_log(LOG_FATAL, __func__, \"influxs (TLS) not available\");\n        exit(1);\n#endif\n    }\n    if ((ctx->conn = mg_connect_http_opt(ctx->mgr, influx_client_event, opts, ctx->url, ctx->extra_headers, buf->buf)) == NULL) {\n        print_logf(LOG_WARNING, \"InfluxDB\", \"Connect to InfluxDB (%s) failed (%s)\", ctx->url, error_string);\n    }\n    else {\n        ctx->databufidxfill ^= 1;\n        buf->len = 0;\n        *buf->buf = '\\0';\n    }\n}\n\n/* Helper */\n\n/// clean the tag/identifier inplace to [-.A-Za-z0-9], esp. not whitespace, =, comma and replace any leading _ by x\nstatic char *influx_sanitize_tag(char *tag, char *end)\n{\n    for (char *p = tag; *p && p != end; ++p)\n        if (*p != '-' && *p != '.' && (*p < 'A' || *p > 'Z') && (*p < 'a' || *p > 'z') && (*p < '0' || *p > '9'))\n            *p = '_';\n\n    for (char *p = tag; *p && p != end; ++p)\n        if (*p == '_')\n            *p = 'x';\n        else\n            break;\n\n    return tag;\n}\n\n/// reserve additional space of size len at end of mbuf a; returns actual free space\nstatic size_t mbuf_reserve(struct mbuf *a, size_t len)\n{\n    // insert undefined values at end of current buffer (it will increase the buffer if necessary)\n    len = mbuf_insert(a, a->len, NULL, len);\n    // reduce the buffer length again by actual inserted number of bytes\n    a->len -= len;\n    len = a->size - a->len;\n\n    if (len)\n        a->buf[a->len] = '\\0';\n\n    return len;\n}\n\nstatic char *mbuf_snprintf(struct mbuf *a, char const *format, ...)\n#if defined(__GNUC__) || defined(__clang__)\n        __attribute__((format(printf, 2, 3)))\n#endif\n;\nstatic char *mbuf_snprintf(struct mbuf *a, char const *format, ...)\n{\n    char *str = &a->buf[a->len];\n    int size;\n    va_list ap;\n    va_start(ap, format);\n    size = vsnprintf(str, a->size - a->len, format, ap);\n    va_end(ap);\n    if (size > 0) {\n        // vsnprintf might return size larger than actually filled\n        size_t len = strlen(str);\n        a->len += len;\n    }\n    return str;\n}\n\nstatic void mbuf_remove_part(struct mbuf *a, char *pos, size_t len)\n{\n    if (pos >= a->buf && pos < &a->buf[a->len] && &pos[len] <= &a->buf[a->len]) {\n        memmove(pos, &pos[len], a->len - (pos - a->buf) - len);\n        a->len -= len;\n    }\n}\n\nstatic void R_API_CALLCONV print_influx_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    UNUSED(array);\n    UNUSED(format);\n    influx_client_t *influx = (influx_client_t *)output;\n    struct mbuf *buf = &influx->databufs[influx->databufidxfill];\n    mbuf_snprintf(buf, \"\\\"array\\\"\"); // TODO\n}\n\nstatic void R_API_CALLCONV print_influx_data_escaped(data_output_t *output, data_t *data, char const *format)\n{\n    char str[1000];\n    data_print_jsons(data, str, sizeof (str));\n    output->print_string(output, str, format);\n}\n\nstatic void R_API_CALLCONV print_influx_string_escaped(data_output_t *output, char const *str, char const *format)\n{\n    UNUSED(format);\n    influx_client_t *influx = (influx_client_t *)output;\n    struct mbuf *databuf = &influx->databufs[influx->databufidxfill];\n    size_t size = databuf->size - databuf->len;\n    char *buf = &databuf->buf[databuf->len];\n\n    if (size < strlen(str) + 3) {\n        return;\n    }\n\n    *buf++ = '\"';\n    size--;\n    for (; *str && size >= 3; ++str) {\n        if (*str == '\\r') {\n            *buf++ = '\\\\';\n            size--;\n            *buf++ = 'r';\n            size--;\n            continue;\n        }\n        if (*str == '\\n') {\n            *buf++ = '\\\\';\n            size--;\n            *buf++ = 'n';\n            size--;\n            continue;\n        }\n        if (*str == '\\t') {\n            *buf++ = '\\\\';\n            size--;\n            *buf++ = 't';\n            size--;\n            continue;\n        }\n        if (*str == '\"' || *str == '\\\\') {\n            *buf++ = '\\\\';\n            size--;\n        }\n        *buf++ = *str;\n        size--;\n    }\n    if (size >= 2) {\n        *buf++ = '\"';\n        size--;\n    }\n    *buf = '\\0';\n\n    databuf->len = databuf->size - size;\n}\n\nstatic void R_API_CALLCONV print_influx_string(data_output_t *output, char const *str, char const *format)\n{\n    UNUSED(format);\n    influx_client_t *influx = (influx_client_t *)output;\n    struct mbuf *buf = &influx->databufs[influx->databufidxfill];\n    mbuf_snprintf(buf, \"%s\", str);\n}\n\n// Generate InfluxDB line protocol\nstatic void R_API_CALLCONV print_influx_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    influx_client_t *influx = (influx_client_t *)output;\n    char *str;\n    char *end;\n    struct mbuf *buf = &influx->databufs[influx->databufidxfill];\n    bool comma = false;\n\n    data_t *data_org = data;\n    data_t *data_model = NULL;\n    data_t *data_time = NULL;\n    for (data_t *d = data; d; d = d->next) {\n        if (!strcmp(d->key, \"model\"))\n            data_model = d;\n        if (!strcmp(d->key, \"time\"))\n            data_time = d;\n    }\n\n    if (!data_model) {\n        // data isn't from device (maybe report for example)\n        // use hostname for measurement\n\n        mbuf_reserve(buf, 20000);\n        mbuf_snprintf(buf, \"rtl_433_%s\", influx->hostname);\n    }\n    else {\n        // use model for measurement\n\n        mbuf_reserve(buf, 1000);\n        str = &buf->buf[buf->len];\n        print_value(output, data_model->type, data_model->value, data_model->format);\n        influx_sanitize_tag(str, NULL);\n    }\n\n    // write tags\n    while (data) {\n        if (!strcmp(data->key, \"model\")\n                || !strcmp(data->key, \"time\")) {\n            // skip\n        }\n        else if (!strcmp(data->key, \"type\")\n                || !strcmp(data->key, \"subtype\")\n                || !strcmp(data->key, \"id\")\n                || !strcmp(data->key, \"channel\")\n                || !strcmp(data->key, \"mic\")) {\n            str = mbuf_snprintf(buf, \",%s=\", data->key);\n            str++;\n            end = &buf->buf[buf->len - 1];\n            influx_sanitize_tag(str, end);\n            str = end + 1;\n            print_value(output, data->type, data->value, data->format);\n            influx_sanitize_tag(str, NULL);\n        }\n        data = data->next;\n    }\n\n    mbuf_snprintf(buf, \" \");\n\n    // activate escaped output functions\n    influx->output.print_data   = print_influx_data_escaped;\n    influx->output.print_string = print_influx_string_escaped;\n\n    // write fields\n    data = data_org;\n    while (data) {\n        if (!strcmp(data->key, \"model\")\n                || !strcmp(data->key, \"time\")) {\n            // skip\n        }\n        else if (!strcmp(data->key, \"type\")\n                || !strcmp(data->key, \"subtype\")\n                || !strcmp(data->key, \"id\")\n                || !strcmp(data->key, \"channel\")\n                || !strcmp(data->key, \"mic\")) {\n            // skip\n        }\n        else {\n            str = mbuf_snprintf(buf, comma ? \",%s=\" : \"%s=\", data->key);\n            if (comma)\n                str++;\n            end = &buf->buf[buf->len - 1];\n            influx_sanitize_tag(str, end);\n            print_value(output, data->type, data->value, data->format);\n            comma = true;\n        }\n        data = data->next;\n    }\n\n    // restore original output functions\n    influx->output.print_data   = print_influx_data;\n    influx->output.print_string = print_influx_string;\n\n    // write time if available\n    if (data_time) {\n        str = mbuf_snprintf(buf, \" \");\n        print_value(output, data_time->type, data_time->value, data_time->format);\n        if (str[1] == '@' // relative time format configured\n                || str[11] == ' ' // date time format configured\n                || str[11] == 'T') {  // ISO date time format configured\n            // -> bad, because InfluxDB doesn't under stand those formats -> remove timestamp\n            buf->len = str - buf->buf;\n        }\n        else if ((str = strchr(str, '.'))) {\n            // unix usec timestamp format configured\n            mbuf_remove_part(buf, str, 1);\n            mbuf_snprintf(buf, \"000\");\n        }\n        else {\n            // unix timestamp with seconds resolution configured\n            mbuf_snprintf(buf, \"000000000\");\n        }\n    }\n    mbuf_snprintf(buf, \"\\n\");\n\n    influx_client_send(influx);\n}\n\nstatic void R_API_CALLCONV print_influx_double(data_output_t *output, double data, char const *format)\n{\n    UNUSED(format);\n    influx_client_t *influx = (influx_client_t *)output;\n    struct mbuf *buf = &influx->databufs[influx->databufidxfill];\n    mbuf_snprintf(buf, \"%f\", data);\n}\n\nstatic void R_API_CALLCONV print_influx_int(data_output_t *output, int data, char const *format)\n{\n    UNUSED(format);\n    influx_client_t *influx = (influx_client_t *)output;\n    struct mbuf *buf = &influx->databufs[influx->databufidxfill];\n    mbuf_snprintf(buf, \"%d\", data);\n}\n\nstatic void R_API_CALLCONV data_output_influx_free(data_output_t *output)\n{\n    influx_client_t *influx = (influx_client_t *)output;\n\n    if (!influx)\n        return;\n\n    // remove ctx from our connections\n    if (influx->conn) {\n        influx->conn->user_data = NULL;\n        influx->conn->flags |= MG_F_CLOSE_IMMEDIATELY;\n    }\n\n    free(influx);\n}\n\nstruct data_output *data_output_influx_create(struct mg_mgr *mgr, char *opts)\n{\n    influx_client_t *influx = calloc(1, sizeof(influx_client_t));\n    if (!influx) {\n        FATAL_CALLOC(\"data_output_influx_create()\");\n    }\n\n    gethostname(influx->hostname, sizeof(influx->hostname) - 1);\n    influx->hostname[sizeof(influx->hostname) - 1] = '\\0';\n    // only use hostname, not domain part\n    char *dot = strchr(influx->hostname, '.');\n    if (dot)\n        *dot = '\\0';\n    influx_sanitize_tag(influx->hostname, NULL);\n\n    char *token = NULL;\n\n    // param/opts starts with URL\n    if (!opts) {\n        opts = \"\";\n    }\n    char *url = opts;\n    opts = strchr(opts, ',');\n    if (opts) {\n        *opts = '\\0';\n        opts++;\n    }\n    if (strncmp(url, \"influx\", 6) == 0) {\n        url += 2;\n        memcpy(url, \"http\", 4);\n    }\n    if (strncmp(url, \"https\", 5) == 0) {\n        influx->tls_opts.tls_ca_cert = \"*\"; // TLS is enabled but no cert verification is performed.\n    }\n\n    // check if valid URL has been provided\n    struct mg_str host, path, query;\n    if (mg_parse_uri(mg_mk_str(url), NULL, NULL, &host, NULL, &path,\n                &query, NULL) != 0\n            || !host.len || !path.len || !query.len) {\n        print_logf(LOG_FATAL, __func__, \"Invalid URL to InfluxDB specified.%s%s%s\"\n                        \" Something like \\\"influx://<host>/write?org=<org>&bucket=<bucket>\\\" required at least.\",\n                !host.len ? \" No host specified.\" : \"\",\n                !path.len ? \" No path component specified.\" : \"\",\n                !query.len ? \" No query parameters specified.\" : \"\");\n        exit(1);\n    }\n\n    // parse auth and format options\n    char *key, *val;\n    while (getkwargs(&opts, &key, &val)) {\n        key = remove_ws(key);\n        val = trim_ws(val);\n        if (!key || !*key)\n            continue;\n        else if (!strcasecmp(key, \"t\") || !strcasecmp(key, \"token\"))\n            token = val;\n        else if (!tls_param(&influx->tls_opts, key, val)) {\n            // ok\n        }\n        else {\n            print_logf(LOG_FATAL, __func__, \"Invalid key \\\"%s\\\" option.\", key);\n            exit(1);\n        }\n    }\n\n    influx->output.print_data   = print_influx_data;\n    influx->output.print_array  = print_influx_array;\n    influx->output.print_string = print_influx_string;\n    influx->output.print_double = print_influx_double;\n    influx->output.print_int    = print_influx_int;\n    influx->output.output_free  = data_output_influx_free;\n\n    print_logf(LOG_CRITICAL, \"InfluxDB\", \"Publishing data to InfluxDB (%s)\", url);\n\n    influx->mgr = mgr;\n\n    // add dummy socket to receive timer events\n    struct mg_add_sock_opts timer_opts = {.user_data = influx};\n    influx->timer = mg_add_sock_opt(mgr, INVALID_SOCKET, influx_client_timer, timer_opts);\n\n    influx_client_init(influx, url, token);\n\n    return (struct data_output *)influx;\n}\n"
  },
  {
    "path": "src/output_log.c",
    "content": "/** @file\n    Log outputs for rtl_433 events.\n\n    Copyright (C) 2022 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"output_log.h\"\n\n#include \"data.h\"\n#include \"r_util.h\"\n#include \"fatal.h\"\n\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n\n/* LOG printer */\n\ntypedef struct {\n    struct data_output output;\n    FILE *file;\n} data_output_log_t;\n\nstatic void R_API_CALLCONV print_log_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    data_output_log_t *log = (data_output_log_t *)output;\n\n    fprintf(log->file, \"[\");\n    for (int c = 0; c < array->num_values; ++c) {\n        if (c)\n            fprintf(log->file, \", \");\n        print_array_value(output, array, format, c);\n    }\n    fprintf(log->file, \"]\");\n}\n\nstatic void R_API_CALLCONV print_log_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_output_log_t *log = (data_output_log_t *)output;\n\n    fputc('{', log->file);\n    for (bool separator = false; data; data = data->next) {\n        if (separator)\n            fprintf(log->file, \", \");\n        output->print_string(output, data->key, NULL);\n        fprintf(log->file, \": \");\n        print_value(output, data->type, data->value, data->format);\n        separator = true;\n    }\n    fputc('}', log->file);\n}\n\nstatic void R_API_CALLCONV print_log_string(data_output_t *output, const char *str, char const *format)\n{\n    UNUSED(format);\n    data_output_log_t *log = (data_output_log_t *)output;\n\n    fprintf(log->file, \"%s\", str);\n}\n\nstatic void R_API_CALLCONV print_log_double(data_output_t *output, double data, char const *format)\n{\n    UNUSED(format);\n    data_output_log_t *log = (data_output_log_t *)output;\n\n    fprintf(log->file, \"%.3f\", data);\n}\n\nstatic void R_API_CALLCONV print_log_int(data_output_t *output, int data, char const *format)\n{\n    UNUSED(format);\n    data_output_log_t *log = (data_output_log_t *)output;\n\n    fprintf(log->file, \"%d\", data);\n}\n\nstatic void R_API_CALLCONV data_output_log_print(data_output_t *output, data_t *data)\n{\n    data_output_log_t *log = (data_output_log_t *)output;\n\n    // collect well-known top level keys\n    data_t *data_src = NULL;\n    data_t *data_lvl = NULL;\n    data_t *data_msg = NULL;\n    for (data_t *d = data; d; d = d->next) {\n        if (!strcmp(d->key, \"src\"))\n            data_src = d;\n        else if (!strcmp(d->key, \"lvl\"))\n            data_lvl = d;\n        else if (!strcmp(d->key, \"msg\"))\n            data_msg = d;\n    }\n\n    int is_log = data_src && data_lvl && data_msg;\n    if (!is_log) {\n        return; // print log messages only\n    }\n\n    // int level = 0;\n    // if (data_lvl->type == DATA_INT) {\n    //     level = data_lvl->value.v_int;\n    // }\n    print_value(output, data_src->type, data_src->value, data_src->format);\n    // fprintf(log->file, \"(\");\n    // print_value(output, data_lvl->type, data_lvl->value, data_lvl->format);\n    // fprintf(log->file, \") \");\n    fprintf(log->file, \": \");\n    print_value(output, data_msg->type, data_msg->value, data_msg->format);\n\n    for (; data; data = data->next) {\n        // skip logging keys\n        if (!strcmp(data->key, \"time\")\n                || !strcmp(data->key, \"src\")\n                || !strcmp(data->key, \"lvl\")\n                || !strcmp(data->key, \"msg\")\n                || !strcmp(data->key, \"num_rows\")) {\n            continue;\n        }\n\n        fprintf(log->file, \" \");\n        output->print_string(output, data->key, NULL);\n        fprintf(log->file, \" \");\n        print_value(output, data->type, data->value, data->format);\n    }\n\n    fputc('\\n', log->file);\n    fflush(log->file);\n}\n\nstatic void R_API_CALLCONV data_output_log_free(data_output_t *output)\n{\n    if (!output) {\n        return;\n    }\n    free(output);\n}\n\nstruct data_output *data_output_log_create(int log_level, FILE *file)\n{\n    data_output_log_t *log = calloc(1, sizeof(data_output_log_t));\n    if (!log) {\n        WARN_CALLOC(\"data_output_log_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    if (!file) {\n        file = stderr; // print to stderr by default\n    }\n\n    log->output.log_level    = log_level;\n    log->output.print_data   = print_log_data;\n    log->output.print_array  = print_log_array;\n    log->output.print_string = print_log_string;\n    log->output.print_double = print_log_double;\n    log->output.print_int    = print_log_int;\n    log->output.output_print = data_output_log_print;\n    log->output.output_free  = data_output_log_free;\n    log->file                = file;\n\n    return (struct data_output *)log;\n}\n"
  },
  {
    "path": "src/output_mqtt.c",
    "content": "/** @file\n    MQTT output for rtl_433 events\n\n    Copyright (C) 2019 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n// note: our unit header includes unistd.h for gethostname() via data.h\n#include \"output_mqtt.h\"\n#include \"optparse.h\"\n#include \"bit_util.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include \"r_util.h\"\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"mongoose.h\"\n\n/* MQTT client abstraction */\n\ntypedef struct mqtt_client {\n    struct mg_connect_opts connect_opts;\n    struct mg_send_mqtt_handshake_opts mqtt_opts;\n    struct mg_connection *conn;\n    struct mg_connection *timer;\n    int reconnect_delay;\n    int prev_status;\n    char address[253 + 6 + 1]; // dns max + port\n    char client_id[256];\n    uint16_t message_id;\n    int publish_flags; // MG_MQTT_RETAIN | MG_MQTT_QOS(0)\n} mqtt_client_t;\n\nchar const *mqtt_availability_online  = \"online\";\nchar const *mqtt_availability_offline = \"offline\";\n\nstatic void mqtt_client_event(struct mg_connection *nc, int ev, void *ev_data)\n{\n    // note that while shutting down the ctx is NULL\n    mqtt_client_t *ctx = (mqtt_client_t *)nc->user_data;\n    // only valid in MG_EV_MQTT_ events\n    struct mg_mqtt_message *msg = (struct mg_mqtt_message *)ev_data;\n\n    //if (ev != MG_EV_POLL)\n    //    fprintf(stderr, \"MQTT user handler got event %d\\n\", ev);\n\n    switch (ev) {\n    case MG_EV_CONNECT: {\n        int connect_status = *(int *)ev_data;\n        if (connect_status == 0) {\n            // Success\n            print_log(LOG_NOTICE, \"MQTT\", \"MQTT Connected...\");\n            mg_set_protocol_mqtt(nc);\n            if (ctx) {\n                ctx->reconnect_delay = 0;\n                mg_send_mqtt_handshake_opt(nc, ctx->client_id, ctx->mqtt_opts);\n            }\n        }\n        else {\n            // Error, print only once\n            if (ctx && ctx->prev_status != connect_status) {\n                print_logf(LOG_WARNING, \"MQTT\", \"MQTT connect error: %s\", strerror(connect_status));\n            }\n        }\n        if (ctx) {\n            ctx->prev_status = connect_status;\n        }\n        break;\n    }\n    case MG_EV_MQTT_CONNACK:\n        if (msg->connack_ret_code != MG_EV_MQTT_CONNACK_ACCEPTED) {\n            print_logf(LOG_WARNING, \"MQTT\", \"MQTT Connection error: %u\", msg->connack_ret_code);\n        }\n        else {\n            print_log(LOG_NOTICE, \"MQTT\", \"MQTT Connection established.\");\n            if (ctx->mqtt_opts.will_topic) {\n                ctx->message_id++;\n                mg_mqtt_publish(ctx->conn, ctx->mqtt_opts.will_topic, ctx->message_id, MG_MQTT_QOS(0) | MG_MQTT_RETAIN, mqtt_availability_online, strlen(mqtt_availability_online));\n            }\n        }\n        break;\n    case MG_EV_MQTT_PUBACK:\n        print_logf(LOG_NOTICE, \"MQTT\", \"MQTT Message publishing acknowledged (msg_id: %u)\", msg->message_id);\n        break;\n    case MG_EV_MQTT_SUBACK:\n        print_log(LOG_NOTICE, \"MQTT\", \"MQTT Subscription acknowledged.\");\n        break;\n    case MG_EV_MQTT_PUBLISH: {\n        print_logf(LOG_NOTICE, \"MQTT\", \"MQTT Incoming message %.*s: %.*s\", (int)msg->topic.len,\n                msg->topic.p, (int)msg->payload.len, msg->payload.p);\n        break;\n    }\n    case MG_EV_CLOSE:\n        if (!ctx) {\n            break; // shutting down\n        }\n        ctx->conn = NULL;\n        if (!ctx->timer) {\n            break; // shutting down\n        }\n        if (ctx->prev_status == 0) {\n            print_log(LOG_WARNING, \"MQTT\", \"MQTT Connection lost, reconnecting...\");\n        }\n        // Timer for reconnect attempt, sends us MG_EV_TIMER event\n        mg_set_timer(ctx->timer, mg_time() + ctx->reconnect_delay);\n        if (ctx->reconnect_delay < 60) {\n            // 0, 1, 3, 6, 10, 16, 25, 39, 60\n            ctx->reconnect_delay = (ctx->reconnect_delay + 1) * 3 / 2;\n        }\n        break;\n    }\n}\n\nstatic void mqtt_client_timer(struct mg_connection *nc, int ev, void *ev_data)\n{\n    // note that while shutting down the ctx is NULL\n    mqtt_client_t *ctx = (mqtt_client_t *)nc->user_data;\n    (void)ev_data;\n\n    //if (ev != MG_EV_POLL)\n    //    fprintf(stderr, \"MQTT timer handler got event %d\\n\", ev);\n\n    switch (ev) {\n    case MG_EV_TIMER: {\n        // Try to reconnect\n        char const *error_string = NULL;\n        ctx->connect_opts.error_string = &error_string;\n        ctx->conn = mg_connect_opt(nc->mgr, ctx->address, mqtt_client_event, ctx->connect_opts);\n        ctx->connect_opts.error_string = NULL;\n        if (!ctx->conn) {\n            print_logf(LOG_WARNING, \"MQTT\", \"MQTT connect (%s) failed%s%s\", ctx->address,\n                    error_string ? \": \" : \"\", error_string ? error_string : \"\");\n        }\n        break;\n    }\n    }\n}\n\nstatic mqtt_client_t *mqtt_client_init(struct mg_mgr *mgr, tls_opts_t *tls_opts, char const *host, char const *port, char const *user, char const *pass, char const *client_id, int retain, int qos, char const *availability)\n{\n    mqtt_client_t *ctx = calloc(1, sizeof(*ctx));\n    if (!ctx)\n        FATAL_CALLOC(\"mqtt_client_init()\");\n\n    ctx->mqtt_opts.user_name = user;\n    ctx->mqtt_opts.password  = pass;\n    ctx->mqtt_opts.will_topic = availability;\n    ctx->mqtt_opts.will_message = mqtt_availability_offline;\n    ctx->mqtt_opts.flags |= (availability ? MG_MQTT_WILL_RETAIN : 0);\n    ctx->publish_flags  = MG_MQTT_QOS(qos) | (retain ? MG_MQTT_RETAIN : 0);\n    // TODO: these should be user configurable options\n    //ctx->mqtt_opts.keepalive = 60;\n    //ctx->timeout = 10000L;\n    //ctx->cleansession = 1;\n    snprintf(ctx->client_id, sizeof(ctx->client_id), \"%s\", client_id);\n\n    // if the host is an IPv6 address it needs quoting\n    if (strchr(host, ':'))\n        snprintf(ctx->address, sizeof(ctx->address), \"[%s]:%s\", host, port);\n    else\n        snprintf(ctx->address, sizeof(ctx->address), \"%s:%s\", host, port);\n\n    ctx->connect_opts.user_data = ctx;\n    if (tls_opts && tls_opts->tls_ca_cert) {\n        print_logf(LOG_INFO, \"MQTT\", \"mqtts (TLS) parameters are: \"\n                                       \"tls_cert=%s \"\n                                       \"tls_key=%s \"\n                                       \"tls_ca_cert=%s \"\n                                       \"tls_cipher_suites=%s \"\n                                       \"tls_server_name=%s \"\n                                       \"tls_psk_identity=%s \"\n                                       \"tls_psk_key=%s \",\n                tls_opts->tls_cert,\n                tls_opts->tls_key,\n                tls_opts->tls_ca_cert,\n                tls_opts->tls_cipher_suites,\n                tls_opts->tls_server_name,\n                tls_opts->tls_psk_identity,\n                tls_opts->tls_psk_key);\n#if MG_ENABLE_SSL\n        ctx->connect_opts.ssl_cert          = tls_opts->tls_cert;\n        ctx->connect_opts.ssl_key           = tls_opts->tls_key;\n        ctx->connect_opts.ssl_ca_cert       = tls_opts->tls_ca_cert;\n        ctx->connect_opts.ssl_cipher_suites = tls_opts->tls_cipher_suites;\n        ctx->connect_opts.ssl_server_name   = tls_opts->tls_server_name;\n        ctx->connect_opts.ssl_psk_identity  = tls_opts->tls_psk_identity;\n        ctx->connect_opts.ssl_psk_key       = tls_opts->tls_psk_key;\n#else\n        print_log(LOG_FATAL, __func__, \"mqtts (TLS) not available\");\n        exit(1);\n#endif\n    }\n\n    // add dummy socket to receive timer events\n    struct mg_add_sock_opts opts = {.user_data = ctx};\n    ctx->timer = mg_add_sock_opt(mgr, INVALID_SOCKET, mqtt_client_timer, opts);\n\n    char const *error_string = NULL;\n    ctx->connect_opts.error_string = &error_string;\n    ctx->conn = mg_connect_opt(mgr, ctx->address, mqtt_client_event, ctx->connect_opts);\n    ctx->connect_opts.error_string = NULL;\n    if (!ctx->conn) {\n        print_logf(LOG_FATAL, \"MQTT\", \"MQTT connect (%s) failed%s%s\", ctx->address,\n                error_string ? \": \" : \"\", error_string ? error_string : \"\");\n        exit(1);\n    }\n\n    return ctx;\n}\n\nstatic void mqtt_client_publish(mqtt_client_t *ctx, char const *topic, char const *str)\n{\n    if (!ctx->conn || !ctx->conn->proto_handler)\n        return;\n\n    ctx->message_id++;\n    mg_mqtt_publish(ctx->conn, topic, ctx->message_id, ctx->publish_flags, str, strlen(str));\n}\n\nstatic void mqtt_client_free(mqtt_client_t *ctx)\n{\n    if (ctx && ctx->conn) {\n        ctx->conn->user_data = NULL;\n        ctx->conn->flags |= MG_F_CLOSE_IMMEDIATELY;\n    }\n    free(ctx);\n}\n\n/* Helper */\n\n/// clean the topic inplace to [-.A-Za-z0-9], esp. not whitespace, +, #, /, $\nstatic char *mqtt_sanitize_topic(char *topic)\n{\n    for (char *p = topic; *p; ++p)\n        if (*p != '-' && *p != '.' && (*p < 'A' || *p > 'Z') && (*p < 'a' || *p > 'z') && (*p < '0' || *p > '9'))\n            *p = '_';\n\n    return topic;\n}\n\n/* MQTT printer */\n\ntypedef struct {\n    struct data_output output;\n    mqtt_client_t *mqc;\n    char topic[256];\n    char hostname[64];\n    char *availability;\n    char *devices;\n    char *events;\n    char *states;\n    //char *homie;\n    //char *hass;\n} data_output_mqtt_t;\n\nstatic void R_API_CALLCONV print_mqtt_array(data_output_t *output, data_array_t *array, char const *format)\n{\n    data_output_mqtt_t *mqtt = (data_output_mqtt_t *)output;\n\n    char *orig = mqtt->topic + strlen(mqtt->topic); // save current topic\n\n    for (int c = 0; c < array->num_values; ++c) {\n        sprintf(orig, \"/%d\", c);\n        print_array_value(output, array, format, c);\n    }\n    *orig = '\\0'; // restore topic\n}\n\nstatic char *append_topic(char *topic, data_t *data)\n{\n    if (data->type == DATA_STRING) {\n        strcpy(topic, data->value.v_ptr); // NOLINT\n        mqtt_sanitize_topic(topic);\n        topic += strlen(data->value.v_ptr);\n    }\n    else if (data->type == DATA_INT) {\n        topic += sprintf(topic, \"%d\", data->value.v_int);\n    }\n    else {\n        print_logf(LOG_ERROR, __func__, \"Can't append data type %d to topic\", data->type);\n    }\n\n    return topic;\n}\n\nstatic char *expand_topic(char *topic, char const *format, data_t *data, char const *hostname)\n{\n    // collect well-known top level keys\n    data_t *data_type    = NULL;\n    data_t *data_model   = NULL;\n    data_t *data_subtype = NULL;\n    data_t *data_channel = NULL;\n    data_t *data_id      = NULL;\n    data_t *data_protocol = NULL;\n    for (data_t *d = data; d; d = d->next) {\n        if (!strcmp(d->key, \"type\"))\n            data_type = d;\n        else if (!strcmp(d->key, \"model\"))\n            data_model = d;\n        else if (!strcmp(d->key, \"subtype\"))\n            data_subtype = d;\n        else if (!strcmp(d->key, \"channel\"))\n            data_channel = d;\n        else if (!strcmp(d->key, \"id\"))\n            data_id = d;\n        else if (!strcmp(d->key, \"protocol\")) // NOTE: needs \"-M protocol\"\n            data_protocol = d;\n    }\n\n    // consume entire format string\n    while (format && *format) {\n        data_t *data_token  = NULL;\n        char const *string_token = NULL;\n        int leading_slash   = 0;\n        char const *t_start = NULL;\n        char const *t_end   = NULL;\n        char const *d_start = NULL;\n        char const *d_end   = NULL;\n        // copy until '['\n        while (*format && *format != '[')\n            *topic++ = *format++;\n        // skip '['\n        if (!*format)\n            break;\n        ++format;\n        // read slash\n        if (!leading_slash && (*format < 'a' || *format > 'z')) {\n            leading_slash = *format;\n            format++;\n        }\n        // read key until : or ]\n        t_start = t_end = format;\n        while (*format && *format != ':' && *format != ']' && *format != '[')\n            t_end = ++format;\n        // read default until ]\n        if (*format == ':') {\n            d_start = d_end = ++format;\n            while (*format && *format != ']' && *format != '[')\n                d_end = ++format;\n        }\n        // check for proper closing\n        if (*format != ']') {\n            print_log(LOG_FATAL, __func__, \"unterminated token\");\n            exit(1);\n        }\n        ++format;\n\n        // resolve token\n        if (!strncmp(t_start, \"hostname\", t_end - t_start))\n            string_token = hostname;\n        else if (!strncmp(t_start, \"type\", t_end - t_start))\n            data_token = data_type;\n        else if (!strncmp(t_start, \"model\", t_end - t_start))\n            data_token = data_model;\n        else if (!strncmp(t_start, \"subtype\", t_end - t_start))\n            data_token = data_subtype;\n        else if (!strncmp(t_start, \"channel\", t_end - t_start))\n            data_token = data_channel;\n        else if (!strncmp(t_start, \"id\", t_end - t_start))\n            data_token = data_id;\n        else if (!strncmp(t_start, \"protocol\", t_end - t_start))\n            data_token = data_protocol;\n        else {\n            print_logf(LOG_FATAL, __func__, \"unknown token \\\"%.*s\\\"\", (int)(t_end - t_start), t_start);\n            exit(1);\n        }\n\n        // append token or default\n        if (!data_token && !string_token && !d_start)\n            continue;\n        if (leading_slash)\n            *topic++ = leading_slash;\n        if (data_token)\n            topic = append_topic(topic, data_token);\n        else if (string_token)\n            topic += sprintf(topic, \"%s\", string_token);\n        else\n            topic += sprintf(topic, \"%.*s\", (int)(d_end - d_start), d_start);\n    }\n\n    *topic = '\\0';\n    return topic;\n}\n\n// <prefix>[/type][/model][/subtype][/channel][/id]/battery: \"OK\"|\"LOW\"\nstatic void R_API_CALLCONV print_mqtt_data(data_output_t *output, data_t *data, char const *format)\n{\n    UNUSED(format);\n    data_output_mqtt_t *mqtt = (data_output_mqtt_t *)output;\n\n    char *orig = mqtt->topic + strlen(mqtt->topic); // save current topic\n    char *end  = orig;\n\n    // top-level only\n    if (!*mqtt->topic) {\n        // collect well-known top level keys\n        data_t *data_model = NULL;\n        for (data_t *d = data; d; d = d->next) {\n            if (!strcmp(d->key, \"model\"))\n                data_model = d;\n        }\n\n        // \"states\" topic\n        if (!data_model) {\n            if (mqtt->states) {\n                size_t message_size = 20000; // state message need a large buffer\n                char *message       = malloc(message_size);\n                if (!message) {\n                    WARN_MALLOC(\"print_mqtt_data()\");\n                    return; // NOTE: skip output on alloc failure.\n                }\n                data_print_jsons(data, message, message_size);\n                expand_topic(mqtt->topic, mqtt->states, data, mqtt->hostname);\n                mqtt_client_publish(mqtt->mqc, mqtt->topic, message);\n                *mqtt->topic = '\\0'; // clear topic\n                free(message);\n            }\n            return;\n        }\n\n        // \"events\" topic\n        if (mqtt->events) {\n            char message[2048]; // we expect the biggest strings to be around 500 bytes.\n            data_print_jsons(data, message, sizeof(message));\n            expand_topic(mqtt->topic, mqtt->events, data, mqtt->hostname);\n            mqtt_client_publish(mqtt->mqc, mqtt->topic, message);\n            *mqtt->topic = '\\0'; // clear topic\n        }\n\n        // \"devices\" topic\n        if (!mqtt->devices) {\n            return;\n        }\n\n        end = expand_topic(mqtt->topic, mqtt->devices, data, mqtt->hostname);\n    }\n\n    while (data) {\n        if (!strcmp(data->key, \"type\")\n                || !strcmp(data->key, \"model\")\n                || !strcmp(data->key, \"subtype\")) {\n            // skip, except \"id\", \"channel\"\n        }\n        else {\n            // push topic\n            *end = '/';\n            strcpy(end + 1, data->key); // NOLINT\n            print_value(output, data->type, data->value, data->format);\n            *end = '\\0'; // pop topic\n        }\n        data = data->next;\n    }\n    *orig = '\\0'; // restore topic\n}\n\nstatic void R_API_CALLCONV print_mqtt_string(data_output_t *output, char const *str, char const *format)\n{\n    UNUSED(format);\n    data_output_mqtt_t *mqtt = (data_output_mqtt_t *)output;\n    mqtt_client_publish(mqtt->mqc, mqtt->topic, str);\n}\n\nstatic void R_API_CALLCONV print_mqtt_double(data_output_t *output, double data, char const *format)\n{\n    char str[20];\n    // use scientific notation for very big/small values\n    if (data > 1e7 || data < 1e-4) {\n        snprintf(str, sizeof(str), \"%g\", data);\n    }\n    else {\n        int ret = snprintf(str, sizeof(str), \"%.5f\", data);\n        // remove trailing zeros, always keep one digit after the decimal point\n        char *p = str + ret - 1;\n        while (*p == '0' && p[-1] != '.') {\n            *p-- = '\\0';\n        }\n    }\n\n    print_mqtt_string(output, str, format);\n}\n\nstatic void R_API_CALLCONV print_mqtt_int(data_output_t *output, int data, char const *format)\n{\n    char str[20];\n    snprintf(str, sizeof(str), \"%d\", data);\n    print_mqtt_string(output, str, format);\n}\n\nstatic void R_API_CALLCONV data_output_mqtt_free(data_output_t *output)\n{\n    data_output_mqtt_t *mqtt = (data_output_mqtt_t *)output;\n\n    if (!mqtt)\n        return;\n\n    free(mqtt->availability);\n    free(mqtt->devices);\n    free(mqtt->events);\n    free(mqtt->states);\n    //free(mqtt->homie);\n    //free(mqtt->hass);\n\n    mqtt_client_free(mqtt->mqc);\n\n    free(mqtt);\n}\n\nstatic char *mqtt_topic_default(char const *topic, char const *base, char const *suffix)\n{\n    char path[256];\n    char const *p;\n    if (topic) {\n        p = topic;\n    }\n    else if (!base) {\n        p = suffix;\n    }\n    else {\n        snprintf(path, sizeof(path), \"%s/%s\", base, suffix);\n        p = path;\n    }\n\n    char *ret = strdup(p);\n    if (!ret)\n        WARN_STRDUP(\"mqtt_topic_default()\");\n    return ret;\n}\n\nstruct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char *param, char const *dev_hint)\n{\n    data_output_mqtt_t *mqtt = calloc(1, sizeof(data_output_mqtt_t));\n    if (!mqtt)\n        FATAL_CALLOC(\"data_output_mqtt_create()\");\n\n    gethostname(mqtt->hostname, sizeof(mqtt->hostname) - 1);\n    mqtt->hostname[sizeof(mqtt->hostname) - 1] = '\\0';\n    // only use hostname, not domain part\n    char *dot = strchr(mqtt->hostname, '.');\n    if (dot)\n        *dot = '\\0';\n    //fprintf(stderr, \"Hostname: %s\\n\", hostname);\n\n    // generate a short deterministic client_id to identify this input device on restart\n    uint16_t host_crc = crc16((uint8_t *)mqtt->hostname, strlen(mqtt->hostname), 0x1021, 0xffff);\n    uint16_t devq_crc = crc16((uint8_t *)dev_hint, dev_hint ? strlen(dev_hint) : 0, 0x1021, 0xffff);\n    uint16_t parm_crc = crc16((uint8_t *)param, param ? strlen(param) : 0, 0x1021, 0xffff);\n    char client_id[21];\n    /// MQTT 3.1.1 specifies that the broker MUST accept clients id's between 1 and 23 characters\n    snprintf(client_id, sizeof(client_id), \"rtl_433-%04x%04x%04x\", host_crc, devq_crc, parm_crc);\n\n    // default base topic\n    char default_base_topic[8 + sizeof(mqtt->hostname)];\n    snprintf(default_base_topic, sizeof(default_base_topic), \"rtl_433/%s\", mqtt->hostname);\n    char const *base_topic = default_base_topic;\n\n    // default topics\n    char const *path_availability = \"availability\";\n    char const *path_devices = \"devices[/type][/model][/subtype][/channel][/id]\";\n    char const *path_events = \"events\";\n    char const *path_states = \"states\";\n\n    // get user and pass from env vars if available.\n    char const *user = getenv(\"MQTT_USERNAME\");\n    char const *pass = getenv(\"MQTT_PASSWORD\");\n    int retain       = 0;\n    int qos          = 0;\n\n    // parse host and port\n    tls_opts_t tls_opts = {0};\n    if (param && strncmp(param, \"mqtts\", 5) == 0) {\n        tls_opts.tls_ca_cert = \"*\"; // TLS is enabled but no cert verification is performed.\n    }\n    param      = arg_param(param); // strip scheme\n    char const *host = \"localhost\";\n    char const *port = tls_opts.tls_ca_cert ? \"8883\" : \"1883\";\n    char *opts = hostport_param(param, &host, &port);\n    print_logf(LOG_CRITICAL, \"MQTT\", \"Publishing MQTT data to %s port %s%s\", host, port, tls_opts.tls_ca_cert ? \" (TLS)\" : \"\");\n\n    // parse auth and format options\n    char *key, *val;\n    while (getkwargs(&opts, &key, &val)) {\n        key = remove_ws(key);\n        val = trim_ws(val);\n        if (!key || !*key)\n            continue;\n        else if (!strcasecmp(key, \"u\") || !strcasecmp(key, \"user\"))\n            user = val;\n        else if (!strcasecmp(key, \"p\") || !strcasecmp(key, \"pass\"))\n            pass = val;\n        else if (!strcasecmp(key, \"r\") || !strcasecmp(key, \"retain\"))\n            retain = atobv(val, 1);\n        else if (!strcasecmp(key, \"q\") || !strcasecmp(key, \"qos\"))\n            qos = atoiv(val, 1);\n        else if (!strcasecmp(key, \"b\") || !strcasecmp(key, \"base\"))\n            base_topic = val;\n        // LWT availability status topic\n        else if (!strcasecmp(key, \"a\") || !strcasecmp(key, \"availability\"))\n            mqtt->availability = mqtt_topic_default(val, base_topic, path_availability);\n        // Simple key-topic mapping\n        else if (!strcasecmp(key, \"d\") || !strcasecmp(key, \"devices\"))\n            mqtt->devices = mqtt_topic_default(val, base_topic, path_devices);\n        // deprecated, remove this\n        else if (!strcasecmp(key, \"c\") || !strcasecmp(key, \"usechannel\")) {\n            print_log(LOG_FATAL, \"MQTT\", \"\\\"usechannel=...\\\" has been removed. Use a topic format string:\");\n            print_log(LOG_FATAL, \"MQTT\", \"for \\\"afterid\\\"   use e.g. \\\"devices=rtl_433/[hostname]/devices[/type][/model][/subtype][/id][/channel]\\\"\");\n            print_log(LOG_FATAL, \"MQTT\", \"for \\\"beforeid\\\"  use e.g. \\\"devices=rtl_433/[hostname]/devices[/type][/model][/subtype][/channel][/id]\\\"\");\n            print_log(LOG_FATAL, \"MQTT\", \"for \\\"replaceid\\\" use e.g. \\\"devices=rtl_433/[hostname]/devices[/type][/model][/subtype][/channel]\\\"\");\n            print_log(LOG_FATAL, \"MQTT\", \"for \\\"no\\\"        use e.g. \\\"devices=rtl_433/[hostname]/devices[/type][/model][/subtype][/id]\\\"\");\n            exit(1);\n        }\n        // JSON events to single topic\n        else if (!strcasecmp(key, \"e\") || !strcasecmp(key, \"events\"))\n            mqtt->events = mqtt_topic_default(val, base_topic, path_events);\n        // JSON states to single topic\n        else if (!strcasecmp(key, \"s\") || !strcasecmp(key, \"states\"))\n            mqtt->states = mqtt_topic_default(val, base_topic, path_states);\n        // TODO: Homie Convention https://homieiot.github.io/\n        //else if (!strcasecmp(key, \"homie\"))\n        //    mqtt->homie = mqtt_topic_default(val, NULL, \"homie\"); // base topic\n        // TODO: Home Assistant MQTT discovery https://www.home-assistant.io/docs/mqtt/discovery/\n        //else if (!strcasecmp(key, \"hass\"))\n        //    mqtt->hass = mqtt_topic_default(val, NULL, \"homeassistant\"); // discovery prefix\n        else if (!tls_param(&tls_opts, key, val)) {\n            // ok\n        }\n        else {\n            print_logf(LOG_FATAL, __func__, \"Invalid key \\\"%s\\\" option.\", key);\n            exit(1);\n        }\n    }\n\n    // Default is to use all formats\n    if (!mqtt->devices && !mqtt->events && !mqtt->states) {\n        mqtt->devices = mqtt_topic_default(NULL, base_topic, path_devices);\n        mqtt->events  = mqtt_topic_default(NULL, base_topic, path_events);\n        mqtt->states  = mqtt_topic_default(NULL, base_topic, path_states);\n    }\n    if (!mqtt->availability) {\n        mqtt->availability = mqtt_topic_default(NULL, base_topic, path_availability);\n    }\n    if (mqtt->availability)\n        print_logf(LOG_NOTICE, \"MQTT\", \"Publishing availability to MQTT topic \\\"%s\\\".\", mqtt->availability);\n    if (mqtt->devices)\n        print_logf(LOG_NOTICE, \"MQTT\", \"Publishing device info to MQTT topic \\\"%s\\\".\", mqtt->devices);\n    if (mqtt->events)\n        print_logf(LOG_NOTICE, \"MQTT\", \"Publishing events info to MQTT topic \\\"%s\\\".\", mqtt->events);\n    if (mqtt->states)\n        print_logf(LOG_NOTICE, \"MQTT\", \"Publishing states info to MQTT topic \\\"%s\\\".\", mqtt->states);\n\n    mqtt->output.print_data   = print_mqtt_data;\n    mqtt->output.print_array  = print_mqtt_array;\n    mqtt->output.print_string = print_mqtt_string;\n    mqtt->output.print_double = print_mqtt_double;\n    mqtt->output.print_int    = print_mqtt_int;\n    mqtt->output.output_free  = data_output_mqtt_free;\n\n    mqtt->mqc = mqtt_client_init(mgr, &tls_opts, host, port, user, pass, client_id, retain, qos, mqtt->availability);\n\n    return (struct data_output *)mqtt;\n}\n"
  },
  {
    "path": "src/output_rtltcp.c",
    "content": "/** @file\n    rtl_tcp output for rtl_433 raw data.\n\n    Copyright (C) 2022 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"output_rtltcp.h\"\n\n#include \"rtl_433.h\"\n#include \"r_api.h\"\n#include \"r_util.h\"\n#include \"optparse.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include \"compat_pthread.h\"\n\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <signal.h>\n\n#include <limits.h>\n// gethostname() needs _XOPEN_SOURCE 500 on unistd.h\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 500\n#endif\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifdef _WIN32\n    #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)\n    #undef _WIN32_WINNT\n    #define _WIN32_WINNT 0x0600   /* Needed to pull in 'struct sockaddr_storage' */\n    #endif\n\n    #include <winsock2.h>\n    #include <ws2tcpip.h>\n#else\n    #include <sys/types.h>\n    #include <sys/socket.h>\n    #include <sys/select.h>\n    #include <netdb.h>\n    #include <netinet/in.h>\n\n    #define SOCKET          int\n    #define INVALID_SOCKET  (-1)\n    #define closesocket(x)  close(x)\n#endif\n\n#include <time.h>\n\n#ifdef _WIN32\n    #define _POSIX_HOST_NAME_MAX  128\n    #define perror(str)           ws2_perror(str)\n\n    static void ws2_perror(const char *str)\n    {\n        if (str && *str)\n            fprintf(stderr, \"%s: \", str);\n        fprintf(stderr, \"Winsock error %d.\\n\", WSAGetLastError());\n    }\n#endif\n#ifdef ESP32\n    #include <tcpip_adapter.h>\n    #define _POSIX_HOST_NAME_MAX 128\n    #define gai_strerror strerror\n#endif\n\n#ifdef _MSC_VER\n#include <BaseTsd.h>\ntypedef SSIZE_T ssize_t;\n#endif\n\n// _POSIX_HOST_NAME_MAX is broken in gcc-13 at least on MacOS\n#ifndef _POSIX_HOST_NAME_MAX\n//#warning The limits.h include is missing the _POSIX_HOST_NAME_MAX define.\n#define _POSIX_HOST_NAME_MAX 255\n#endif\n\n// MSG_NOSIGNAL is Linux and most BSDs only, not macOS or Windows\n#ifndef MSG_NOSIGNAL\n#define MSG_NOSIGNAL 0\n#endif\n\n/* rtl_tcp server */\n\n// Only available if Threads are enabled.\n// Currently serves a maximum of 1 client connection.\n// The data backing from the SDR is assumed to be persistent, which is the case\n// since we never restart the SDR with different parameters or close it while active.\n// Should use shared memory for sendfile() someday.\n\n#ifdef THREADS\n\ntypedef struct rtltcp_server {\n    struct sockaddr_storage addr;\n    socklen_t addr_len;\n    SOCKET sock;\n    int client_count; ///< number of connected clients\n    int control;      ///< are clients allowed to change SDR parameters\n\n    uint8_t const *data_buf; ///< data buffer with most recent data, NULL otherwise\n    uint32_t data_len;       ///< data buffer length in bytes, 0 otherwise\n    unsigned data_cnt;       ///< data buffer update counter\n\n    pthread_t thread;\n    pthread_mutex_t lock; ///< lock for data buffer\n    pthread_cond_t cond;  ///< wait for data buffer\n    r_cfg_t *cfg;\n    struct raw_output *output;\n} rtltcp_server_t;\n\nstatic ssize_t send_all(int sockfd, void const *buf, size_t len, int flags)\n{\n    size_t sent = 0;\n    while (sent < len) {\n        ssize_t ret = send(sockfd, (uint8_t *)buf + sent, len - sent, flags);\n        if (ret < 0)\n            return ret;\n        sent += (size_t)ret;\n    }\n    return sent;\n}\n\nstatic void send_header(SOCKET sock)\n{\n    uint8_t msg[] = {'R', 'T', 'L', '0', 0, 0, 0, 0, 0, 0, 0, 0};\n    send_all(sock, msg, sizeof(msg), MSG_NOSIGNAL); // ignore SIGPIPE\n}\n\n#define RTLTCP_SET_FREQ 0x01\n#define RTLTCP_SET_SAMPLE_RATE 0x02\n#define RTLTCP_SET_GAIN_MODE 0x03\n#define RTLTCP_SET_GAIN 0x04\n#define RTLTCP_SET_FREQ_CORRECTION 0x05\n#define RTLTCP_SET_IF_TUNER_GAIN 0x06\n#define RTLTCP_SET_TEST_MODE 0x07\n#define RTLTCP_SET_AGC_MODE 0x08\n#define RTLTCP_SET_DIRECT_SAMPLING 0x09\n#define RTLTCP_SET_OFFSET_TUNING 0x0a\n#define RTLTCP_SET_RTL_XTAL 0x0b\n#define RTLTCP_SET_TUNER_XTAL 0x0c\n#define RTLTCP_SET_TUNER_GAIN_BY_ID 0x0d\n#define RTLTCP_SET_BIAS_TEE 0x0e\n\n/*\nE.g. initialization from Gqrx:\n- RTLTCP_SET_GAIN_MODE  with 1\n- RTLTCP_SET_AGC_MODE   with 0\n- RTLTCP_SET_DIRECT_SAMPLING  with 0\n- RTLTCP_SET_OFFSET_TUNING  with 0\n- RTLTCP_SET_BIAS_TEE  with 0\n- RTLTCP_SET_SAMPLE_RATE  with 250000\n- RTLTCP_SET_FREQ  with 52000000\n- RTLTCP_SET_GAIN  with 0\n- RTLTCP_SET_GAIN  with 0\n- RTLTCP_SET_FREQ  with 433968000\n*/\n\nstatic int parse_command(r_cfg_t *cfg, int control, uint8_t const *buf, int len)\n{\n    UNUSED(cfg);\n\n    if (len < 5)\n        return 0;\n    int cmd = buf[0];\n    unsigned arg = (unsigned)buf[1] << 24 | buf[2] << 16 | buf[3] << 8 | buf[4];\n    // print_logf(LOG_TRACE, \"rtl_tcp\", \"CMD: %d with %u (%d) %02x %02x %02x %02x\", cmd, arg, (int)arg, buf[1], buf[2], buf[3], buf[4]);\n    // len -= 5;\n\n    switch (cmd) {\n    case RTLTCP_SET_FREQ:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_FREQ with %u\", arg);\n        if (control)\n            set_center_freq(cfg, arg);\n        break;\n    case RTLTCP_SET_SAMPLE_RATE:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_SAMPLE_RATE with %u\", arg);\n        if (control)\n            set_sample_rate(cfg, arg);\n        break;\n    case RTLTCP_SET_GAIN_MODE:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_GAIN_MODE with %u\", arg);\n        // if (control)\n        // if (arg == 0 /* =auto */) sdr_set_auto_gain(dev, 0);\n        break;\n    case RTLTCP_SET_GAIN:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_GAIN with %u\", arg);\n        // if (control)\n        // sdr_set_tuner_gain(dev, char const *gain_str, 0)\n        break;\n    case RTLTCP_SET_FREQ_CORRECTION:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_FREQ_CORRECTION with %u\", arg);\n        if (control)\n            set_freq_correction(cfg, (int)arg);\n        break;\n    case RTLTCP_SET_IF_TUNER_GAIN:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_IF_TUNER_GAIN with %u\", arg);\n        break;\n    case RTLTCP_SET_TEST_MODE:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_TEST_MODE with %u\", arg);\n        break;\n    case RTLTCP_SET_AGC_MODE:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_AGC_MODE with %u\", arg);\n        // ...\n        break;\n    case RTLTCP_SET_DIRECT_SAMPLING:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_DIRECT_SAMPLING with %u\", arg);\n        break;\n    case RTLTCP_SET_OFFSET_TUNING:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_OFFSET_TUNING with %u\", arg);\n        break;\n    case RTLTCP_SET_RTL_XTAL:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_RTL_XTAL with %u\", arg);\n        break;\n    case RTLTCP_SET_TUNER_XTAL:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_TUNER_XTAL with %u\", arg);\n        break;\n    case RTLTCP_SET_TUNER_GAIN_BY_ID:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_TUNER_GAIN_BY_ID with %u\", arg);\n        break;\n    case RTLTCP_SET_BIAS_TEE:\n        print_logf(LOG_DEBUG, \"rtl_tcp\", \"received command SET_BIAS_TEE with %u\", arg);\n        break;\n    default:\n        print_logf(LOG_WARNING, \"rtl_tcp\", \"received unknown command %d with %u\", cmd, arg);\n        break;\n    }\n\n    return 5;\n}\n\n// event handler to broadcast to all our sockets\nstatic void rtltcp_broadcast_send(rtltcp_server_t *srv, uint8_t const *data, uint32_t len)\n{\n    // print_logf(LOG_TRACE, __func__, \"%d byte frame\", len);\n    pthread_mutex_lock(&srv->lock);\n\n    // update the data buffer reference\n    srv->data_buf = data;\n    srv->data_len = len;\n    srv->data_cnt += 1;\n\n    pthread_mutex_unlock(&srv->lock);\n    pthread_cond_signal(&srv->cond);\n    // perhaps broadcast if we want to support multiple clients\n    //int pthread_cond_broadcast(&srv->cond);\n}\n\nstatic THREAD_RETURN THREAD_CALL accept_thread(void *arg)\n{\n    rtltcp_server_t *srv = arg;\n\n    // Start listening for clients, waits for an incoming connection\n    int listen_sock = srv->sock; // make it easy for the checker\n    int r = listen(listen_sock, 1);\n    if (r < 0) {\n        perror(\"ERROR on listen\");\n        closesocket(listen_sock);\n        srv->sock = INVALID_SOCKET;\n        return 0;\n    }\n    // print_log(LOG_DEBUG, \"rtl_tcp\", \"rtl_tcp listening...\");\n\n    for (;;) {\n        // Accept actual connection from the client\n        struct sockaddr_storage addr = {0};\n        unsigned addr_len = sizeof(addr);\n        int sock = accept(listen_sock, (struct sockaddr *)&addr, &addr_len);\n\n        // TODO: ignore ECONNABORTED (Software caused connection abort)\n        if (sock < 0) {\n            perror(\"ERROR on accept\");\n            continue;\n        }\n\n        // Prevent SIGPIPE per file descriptor, supported on MacOS and most BSDs\n#ifdef SO_NOSIGPIPE\n        int opt = 1;\n        if (setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt)) == -1) {\n            perror(\"setsockopt\");\n            closesocket(sock);\n            continue;\n        }\n#endif\n\n        char host[INET6_ADDRSTRLEN] = {0};\n        char port[NI_MAXSERV]       = {0};\n\n        int err = getnameinfo((struct sockaddr *)&addr, addr_len,\n                host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV);\n        if (err != 0) {\n            print_logf(LOG_ERROR, __func__, \"failed to convert address to string (code=%d)\", err);\n            closesocket(sock);\n            continue;\n        }\n        print_logf(LOG_NOTICE, \"rtl_tcp\", \"client connected from %s port %s\", host, port);\n\n        pthread_mutex_lock(&srv->lock);\n        srv->client_count += 1;\n        unsigned prev_cnt = srv->data_cnt + 9; // data sent in previous loop, random value to get the current buffer\n        pthread_mutex_unlock(&srv->lock);\n\n        send_header(sock);\n\n        // Client loop\n        for (;;) {\n            // Read available commands\n            int abort = 0;\n            for (;;) {\n                fd_set fds;\n                FD_ZERO(&fds);\n                FD_SET(sock, &fds);\n                struct timeval timeout = {0};\n\n                int ready = select(sock + 1, &fds, NULL, NULL, &timeout);\n                if (ready <= 0)\n                    break;\n\n                uint8_t buf[128] = {0};\n                ssize_t len = recv(sock, buf, sizeof(buf), 0);\n                //print_logf(LOG_TRACE, \"rtl_tcp\", \"recv %zd bytes (%d)\", len, ready);\n                if (len <= 0) {\n                    abort = 1;\n                    break;\n                }\n                int pos = 0;\n                while (pos + 5 <= len) {\n                    pos += parse_command(srv->cfg, srv->control, & buf[pos], (int)len - pos);\n                }\n            }\n            if (abort) {\n                break;\n            }\n\n            // Wait for send buffer to clear\n            fd_set fds;\n            FD_ZERO(&fds);\n            FD_SET(sock, &fds);\n            struct timeval timeout = {.tv_usec = 100000}; // Wait at most 100 ms\n\n            int ready = select(sock + 1, NULL, &fds, NULL, &timeout);\n            if (ready <= 0) {\n                print_log(LOG_ERROR, \"rtl_tcp\", \"send not ready for write?\");\n                break; // Cancel the connection on network problems\n            }\n\n            // Wait for next frame\n            pthread_mutex_lock(&srv->lock);\n            while (srv->data_cnt == prev_cnt || srv->data_buf == NULL)\n                pthread_cond_wait(&srv->cond, &srv->lock);\n            // Maybe timeout to check recv()\n            // pthread_cond_timedwait(&srv->cond, &srv->lock, const struct timespec *abstime);\n\n            // Get data buffer reference\n            void const *data = srv->data_buf;\n            int data_len     = srv->data_len;\n            prev_cnt         = srv->data_cnt;\n\n            pthread_mutex_unlock(&srv->lock);\n\n            // Send frame\n            send_all(sock, data, data_len, MSG_NOSIGNAL); // ignore SIGPIPE\n        }\n\n        pthread_mutex_lock(&srv->lock);\n        srv->client_count -= 1;\n        pthread_mutex_unlock(&srv->lock);\n\n        print_logf(LOG_NOTICE, \"rtl_tcp\", \"client disconnected from %s port %s\", host, port);\n        closesocket(sock);\n    }\n    return 0;\n}\n\nstatic int rtltcp_server_start(rtltcp_server_t *srv, char const *host, char const *port, r_cfg_t *cfg, struct raw_output *output)\n{\n    if (!host || !port)\n        return -1;\n\n    struct addrinfo hints, *res, *res0;\n    int error;\n    SOCKET sock;\n\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = PF_UNSPEC;\n    hints.ai_socktype = SOCK_STREAM;\n    hints.ai_flags    = AI_ADDRCONFIG;\n    error             = getaddrinfo(host, port, &hints, &res0);\n    if (error) {\n        print_log(LOG_ERROR, __func__, gai_strerror(error));\n        return -1;\n    }\n    sock = INVALID_SOCKET;\n    for (res = res0; res; res = res->ai_next) {\n        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n        if (sock >= 0) {\n            memset(&srv->addr, 0, sizeof(srv->addr));\n            memcpy(&srv->addr, res->ai_addr, res->ai_addrlen);\n            srv->addr_len = res->ai_addrlen;\n            break; // success\n        }\n    }\n    freeaddrinfo(res0);\n    if (sock == INVALID_SOCKET) {\n        perror(\"socket\");\n        return -1;\n    }\n\n    if (bind(sock, (struct sockaddr *)&srv->addr, srv->addr_len) < 0) {\n        perror(\"error on binding\");\n        closesocket(sock);\n        return -1;\n    }\n\n    srv->sock    = sock;\n    srv->cfg     = cfg;\n    srv->output  = output;\n\n    char address[INET6_ADDRSTRLEN] = {0};\n    char portstr[NI_MAXSERV] = {0};\n\n    int err = getnameinfo((struct sockaddr *)&srv->addr, srv->addr_len,\n            address, sizeof(address), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV);\n    if (err != 0) {\n        print_logf(LOG_ERROR, __func__, \"failed to convert address to string (code=%d)\", err);\n        closesocket(sock);\n        return -1;\n    }\n    print_logf(LOG_CRITICAL, \"rtl_tcp server\", \"Serving rtl_tcp on address %s %s\", address, portstr);\n\n    pthread_mutex_init(&srv->lock, NULL);\n    pthread_cond_init(&srv->cond, NULL);\n\n#ifndef _WIN32\n    // Block all signals from the worker thread\n    sigset_t sigset;\n    sigset_t oldset;\n    sigfillset(&sigset);\n    pthread_sigmask(SIG_SETMASK, &sigset, &oldset);\n#endif\n    int r = pthread_create(&srv->thread, NULL, accept_thread, srv);\n#ifndef _WIN32\n    pthread_sigmask(SIG_SETMASK, &oldset, NULL);\n#endif\n    if (r) {\n        fprintf(stderr, \"%s: error in pthread_create, rc: %d\\n\", __func__, r);\n        closesocket(sock);\n    }\n\n    return r;\n}\n\nstatic int rtltcp_server_stop(rtltcp_server_t *srv)\n{\n    if (!srv)\n        return 0;\n\n    print_logf(LOG_NOTICE, \"rtl_tcp server\", \"Stopping rtl_tcp server...\");\n\n    // thread is likely blocking in accept, recv, or send\n    int r = pthread_cancel(srv->thread);\n    if (r) {\n        fprintf(stderr, \"%s: error in pthread_cancel, rc: %d\\n\", __func__, r);\n    }\n    pthread_mutex_destroy(&srv->lock);\n    pthread_cond_destroy(&srv->cond);\n\n    srv->client_count = 0;\n\n    // close server socket\n    int ret = 0;\n    if (srv->sock != INVALID_SOCKET) {\n        ret = closesocket(srv->sock);\n        srv->sock = INVALID_SOCKET;\n    }\n\n#ifdef _WIN32\n    WSACleanup();\n#endif\n\n    return ret;\n}\n\n/* rtl_tcp raw output */\n\ntypedef struct raw_output_rtltcp {\n    struct raw_output output;\n    rtltcp_server_t server;\n} raw_output_rtltcp_t;\n\nstatic void raw_output_rtltcp_frame(raw_output_t *output, uint8_t const *data, uint32_t len)\n{\n    raw_output_rtltcp_t *rtltcp = (raw_output_rtltcp_t *)output;\n\n    rtltcp_broadcast_send(&rtltcp->server, data, len);\n}\n\nstatic void raw_output_rtltcp_free(raw_output_t *output)\n{\n    raw_output_rtltcp_t *rtltcp = (raw_output_rtltcp_t *)output;\n\n    if (!rtltcp)\n        return;\n\n    rtltcp_server_stop(&rtltcp->server);\n\n    free(rtltcp);\n}\n\nstruct raw_output *raw_output_rtltcp_create(const char *host, const char *port, char const *opts, r_cfg_t *cfg)\n{\n    raw_output_rtltcp_t *rtltcp = calloc(1, sizeof(raw_output_rtltcp_t));\n    if (!rtltcp) {\n        WARN_CALLOC(\"raw_output_rtltcp_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n#ifdef _WIN32\n    WSADATA wsa;\n\n    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {\n        perror(\"WSAStartup()\");\n        free(rtltcp);\n        return NULL;\n    }\n#endif\n\n    // If clients allowed to change SDR parameters\n    if (opts && !strcasecmp(opts, \"control\"))\n        rtltcp->server.control = 1;\n    else if (opts && *opts) {\n        print_logf(LOG_FATAL, __func__, \"Invalid \\\"%s\\\" option.\", opts);\n        exit(1);\n    }\n\n    rtltcp->output.output_frame  = raw_output_rtltcp_frame;\n    rtltcp->output.output_free   = raw_output_rtltcp_free;\n\n    int ret = rtltcp_server_start(&rtltcp->server, host, port, cfg, &rtltcp->output);\n    if (ret != 0) {\n        exit(1);\n    }\n\n    return (struct raw_output *)rtltcp;\n}\n\n#else\n\nstruct raw_output *raw_output_rtltcp_create(const char *host, const char *port, char const *opts, r_cfg_t *cfg)\n{\n    UNUSED(host);\n    UNUSED(port);\n    UNUSED(opts);\n    UNUSED(cfg);\n    print_log(LOG_ERROR, \"rtl_tcp server\", \"rtl_tcp output not available in this build!\");\n    return NULL;\n}\n\n#endif\n"
  },
  {
    "path": "src/output_trigger.c",
    "content": "/** @file\n    Trigger output for rtl_433 events.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"output_trigger.h\"\n\n#include \"data.h\"\n#include \"r_util.h\"\n#include \"fatal.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n\n/* Trigger printer */\n\ntypedef struct {\n    struct data_output output;\n    FILE *file;\n} data_output_trigger_t;\n\nstatic void R_API_CALLCONV data_output_trigger_print(data_output_t *output, data_t *data)\n{\n    UNUSED(data);\n    data_output_trigger_t *trigger = (data_output_trigger_t *)output;\n\n    fputc('1', trigger->file);\n    fflush(trigger->file);\n}\n\nstatic void R_API_CALLCONV data_output_trigger_free(data_output_t *output)\n{\n    if (!output)\n        return;\n\n    free(output);\n}\n\nstruct data_output *data_output_trigger_create(FILE *file)\n{\n    data_output_trigger_t *trigger = calloc(1, sizeof(data_output_trigger_t));\n    if (!trigger) {\n        WARN_CALLOC(\"data_output_trigger_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    trigger->output.output_print = data_output_trigger_print;\n    trigger->output.output_free  = data_output_trigger_free;\n    trigger->file                = file;\n\n    return (struct data_output *)trigger;\n}\n"
  },
  {
    "path": "src/output_udp.c",
    "content": "/** @file\n    UDP syslog output for rtl_433 events.\n\n    Copyright (C) 2021 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"output_udp.h\"\n\n#include \"data.h\"\n#include \"abuf.h\"\n#include \"r_util.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n\n#include <limits.h>\n// gethostname() needs _XOPEN_SOURCE 500 on unistd.h\n#ifndef _XOPEN_SOURCE\n#define _XOPEN_SOURCE 500\n#endif\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifdef _WIN32\n    #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)\n    #undef _WIN32_WINNT\n    #define _WIN32_WINNT 0x0600   /* Needed to pull in 'struct sockaddr_storage' */\n    #endif\n\n    #include <winsock2.h>\n    #include <ws2tcpip.h>\n#else\n    #include <sys/types.h>\n    #include <sys/socket.h>\n    #include <netdb.h>\n    #include <netinet/in.h>\n\n    #define SOCKET          int\n    #define INVALID_SOCKET  (-1)\n    #define closesocket(x)  close(x)\n#endif\n\n#include <time.h>\n\n#ifdef _WIN32\n    #define _POSIX_HOST_NAME_MAX  128\n    #define perror(str)           ws2_perror(str)\n\n    static void ws2_perror (const char *str)\n    {\n        if (str && *str)\n            fprintf(stderr, \"%s: \", str);\n        fprintf(stderr, \"Winsock error %d.\\n\", WSAGetLastError());\n    }\n#endif\n#ifdef ESP32\n    #include <tcpip_adapter.h>\n    #define _POSIX_HOST_NAME_MAX 128\n    #define gai_strerror strerror\n#endif\n\n// _POSIX_HOST_NAME_MAX is broken in gcc-13 at least on MacOS\n#ifndef _POSIX_HOST_NAME_MAX\n//#warning The limits.h include is missing the _POSIX_HOST_NAME_MAX define.\n#define _POSIX_HOST_NAME_MAX 255\n#endif\n\n/* Datagram (UDP) client */\n\ntypedef struct {\n    struct sockaddr_storage addr;\n    socklen_t addr_len;\n    SOCKET sock;\n} datagram_client_t;\n\nstatic int datagram_client_open(datagram_client_t *client, const char *host, const char *port)\n{\n    if (!host || !port)\n        return -1;\n\n    struct addrinfo hints, *res, *res0;\n    int    error;\n    SOCKET sock;\n\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family = PF_UNSPEC;\n    hints.ai_socktype = SOCK_DGRAM;\n    hints.ai_flags = AI_ADDRCONFIG;\n    error = getaddrinfo(host, port, &hints, &res0);\n    if (error) {\n        print_log(LOG_ERROR, __func__, gai_strerror(error));\n        return -1;\n    }\n    sock = INVALID_SOCKET;\n    for (res = res0; res; res = res->ai_next) {\n        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n        if (sock >= 0) {\n            client->sock = sock;\n            memset(&client->addr, 0, sizeof(client->addr));\n            memcpy(&client->addr, res->ai_addr, res->ai_addrlen);\n            client->addr_len = res->ai_addrlen;\n            break; // success\n        }\n    }\n    freeaddrinfo(res0);\n    if (sock == INVALID_SOCKET) {\n        perror(\"socket\");\n        return -1;\n    }\n\n    // Enable SO_BROADCAST for all tested platforms, so far Linux and MacOS only\n#if defined(__linux__) || defined(__APPLE__)\n    int broadcast = 1;\n    int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));\n    if (ret) {\n        print_logf(LOG_ERROR, __func__, \"Failed to set broadcast flag (%d)\\n\", errno);\n    }\n#endif\n\n    return 0;\n}\n\nstatic void datagram_client_close(datagram_client_t *client)\n{\n    if (!client)\n        return;\n\n    if (client->sock != INVALID_SOCKET) {\n        closesocket(client->sock);\n        client->sock = INVALID_SOCKET;\n    }\n\n#ifdef _WIN32\n    WSACleanup();\n#endif\n}\n\nstatic void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)\n{\n    int r =  sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);\n    if (r == -1) {\n        perror(\"sendto\");\n    }\n}\n\n/* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */\n\ntypedef struct {\n    struct data_output output;\n    datagram_client_t client;\n    int pri;\n    char hostname[_POSIX_HOST_NAME_MAX + 1];\n} data_output_syslog_t;\n\nstatic void R_API_CALLCONV data_output_syslog_print(data_output_t *output, data_t *data)\n{\n    data_output_syslog_t *syslog = (data_output_syslog_t *)output;\n\n    // we expect a normal message around 500 bytes\n    // full stats report would be 12k and we want a max of MTU anyway\n    char message[1024];\n    abuf_t msg = {0};\n    abuf_init(&msg, message, sizeof(message));\n\n    time_t now;\n    struct tm tm_info;\n    time(&now);\n#ifdef _WIN32\n    gmtime_s(&tm_info, &now);\n#else\n    gmtime_r(&now, &tm_info);\n#endif\n    char timestamp[21];\n    strftime(timestamp, 21, \"%Y-%m-%dT%H:%M:%SZ\", &tm_info);\n\n    abuf_printf(&msg, \"<%d>1 %s %s rtl_433 - - - \", syslog->pri, timestamp, syslog->hostname);\n\n    msg.tail += data_print_jsons(data, msg.tail, msg.left);\n    if (msg.tail >= msg.head + sizeof(message))\n        return; // abort on overflow, we don't actually want to send more than fits the MTU\n\n    size_t abuf_len = msg.tail - msg.head;\n    datagram_client_send(&syslog->client, message, abuf_len);\n}\n\nstatic void R_API_CALLCONV data_output_syslog_free(data_output_t *output)\n{\n    data_output_syslog_t *syslog = (data_output_syslog_t *)output;\n\n    if (!syslog)\n        return;\n\n    datagram_client_close(&syslog->client);\n\n    free(syslog);\n}\n\nstruct data_output *data_output_syslog_create(int log_level, const char *host, const char *port)\n{\n    data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));\n    if (!syslog) {\n        WARN_CALLOC(\"data_output_syslog_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n#ifdef _WIN32\n    WSADATA wsa;\n\n    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {\n        perror(\"WSAStartup()\");\n        free(syslog);\n        return NULL;\n    }\n#endif\n\n    syslog->output.log_level    = log_level;\n    syslog->output.output_print = data_output_syslog_print;\n    syslog->output.output_free  = data_output_syslog_free;\n    // Severity 5 \"Notice\", Facility 20 \"local use 4\"\n    syslog->pri = 20 * 8 + 5;\n    #ifdef ESP32\n    const char* adapter_hostname = NULL;\n    tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &adapter_hostname);\n    if (adapter_hostname) {\n        memcpy(syslog->hostname, adapter_hostname, _POSIX_HOST_NAME_MAX);\n    }\n    else {\n        syslog->hostname[0] = '\\0';\n    }\n    #else\n    gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);\n    #endif\n    syslog->hostname[_POSIX_HOST_NAME_MAX] = '\\0';\n    datagram_client_open(&syslog->client, host, port);\n\n    return (struct data_output *)syslog;\n}\n"
  },
  {
    "path": "src/pulse_analyzer.c",
    "content": "/** @file\n    Pulse analyzer functions.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"pulse_analyzer.h\"\n#include \"pulse_slicer.h\"\n#include \"c_util.h\" // for MIN(), MAX()\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n\n#define MAX_HIST_BINS 16\n\n/// Histogram data for single bin\ntypedef struct {\n    unsigned count;\n    int sum;\n    int mean;\n    int min;\n    int max;\n} hist_bin_t;\n\n/// Histogram data for all bins\ntypedef struct {\n    unsigned bins_count;\n    hist_bin_t bins[MAX_HIST_BINS];\n} histogram_t;\n\n/// Generate a histogram (unsorted)\nstatic void histogram_sum(histogram_t *hist, int const *data, unsigned len, float tolerance)\n{\n    unsigned bin;    // Iterator will be used outside for!\n\n    for (unsigned n = 0; n < len; ++n) {\n        // Search for match in existing bins\n        for (bin = 0; bin < hist->bins_count; ++bin) {\n            int bn = data[n];\n            int bm = hist->bins[bin].mean;\n            if (abs(bn - bm) < (tolerance * MAX(bn, bm))) {\n                hist->bins[bin].count++;\n                hist->bins[bin].sum += data[n];\n                hist->bins[bin].mean = hist->bins[bin].sum / hist->bins[bin].count;\n                hist->bins[bin].min    = MIN(data[n], hist->bins[bin].min);\n                hist->bins[bin].max    = MAX(data[n], hist->bins[bin].max);\n                break;    // Match found! Data added to existing bin\n            }\n        }\n        // No match found? Add new bin\n        if (bin == hist->bins_count && bin < MAX_HIST_BINS) {\n            hist->bins[bin].count    = 1;\n            hist->bins[bin].sum        = data[n];\n            hist->bins[bin].mean    = data[n];\n            hist->bins[bin].min        = data[n];\n            hist->bins[bin].max        = data[n];\n            hist->bins_count++;\n        } // for bin\n    } // for data\n}\n\n/// Delete bin from histogram\nstatic void histogram_delete_bin(histogram_t *hist, unsigned index)\n{\n    hist_bin_t const zerobin = {0};\n    if (hist->bins_count < 1) return;    // Avoid out of bounds\n    // Move all bins afterwards one forward\n    for (unsigned n = index; n < hist->bins_count-1; ++n) {\n        hist->bins[n] = hist->bins[n+1];\n    }\n    hist->bins_count--;\n    hist->bins[hist->bins_count] = zerobin;    // Clear previously last bin\n}\n\n\n/// Swap two bins in histogram\nstatic void histogram_swap_bins(histogram_t *hist, unsigned index1, unsigned index2)\n{\n    hist_bin_t    tempbin;\n    if ((index1 < hist->bins_count) && (index2 < hist->bins_count)) {        // Avoid out of bounds\n        tempbin = hist->bins[index1];\n        hist->bins[index1] = hist->bins[index2];\n        hist->bins[index2] = tempbin;\n    }\n}\n\n\n/// Sort histogram with mean value (order lowest to highest)\nstatic void histogram_sort_mean(histogram_t *hist)\n{\n    if (hist->bins_count < 2) return;        // Avoid underflow\n    // Compare all bins (bubble sort)\n    for (unsigned n = 0; n < hist->bins_count-1; ++n) {\n        for (unsigned m = n+1; m < hist->bins_count; ++m) {\n            if (hist->bins[m].mean < hist->bins[n].mean) {\n                histogram_swap_bins(hist, m, n);\n            }\n        }\n    }\n}\n\n\n/// Sort histogram with count value (order lowest to highest)\nstatic void histogram_sort_count(histogram_t *hist)\n{\n    if (hist->bins_count < 2) return;        // Avoid underflow\n    // Compare all bins (bubble sort)\n    for (unsigned n = 0; n < hist->bins_count-1; ++n) {\n        for (unsigned m = n+1; m < hist->bins_count; ++m) {\n            if (hist->bins[m].count < hist->bins[n].count) {\n                histogram_swap_bins(hist, m, n);\n            }\n        }\n    }\n}\n\n\n/// Fuse histogram bins with means within tolerance\nstatic void histogram_fuse_bins(histogram_t *hist, float tolerance)\n{\n    if (hist->bins_count < 2) return;        // Avoid underflow\n    // Compare all bins\n    for (unsigned n = 0; n < hist->bins_count-1; ++n) {\n        for (unsigned m = n+1; m < hist->bins_count; ++m) {\n            int bn = hist->bins[n].mean;\n            int bm = hist->bins[m].mean;\n            // if within tolerance\n            if (abs(bn - bm) < (tolerance * MAX(bn, bm))) {\n                // Fuse data for bin[n] and bin[m]\n                hist->bins[n].count += hist->bins[m].count;\n                hist->bins[n].sum    += hist->bins[m].sum;\n                hist->bins[n].mean    = hist->bins[n].sum / hist->bins[n].count;\n                hist->bins[n].min    = MIN(hist->bins[n].min, hist->bins[m].min);\n                hist->bins[n].max    = MAX(hist->bins[n].max, hist->bins[m].max);\n                // Delete bin[m]\n                histogram_delete_bin(hist, m);\n                m--;    // Compare new bin in same place!\n            }\n        }\n    }\n}\n\n/// Find bin index\nstatic int histogram_find_bin_index(histogram_t const *hist, int width)\n{\n    for (unsigned n = 0; n < hist->bins_count; ++n) {\n        if (hist->bins[n].min <= width && width <= hist->bins[n].max) {\n            return n;\n        }\n    }\n    return -1;\n}\n\n/// Print a histogram\nstatic void histogram_print(histogram_t const *hist, uint32_t samp_rate)\n{\n    for (unsigned n = 0; n < hist->bins_count; ++n) {\n        fprintf(stderr, \" [%2u] count: %4u,  width: %4.0f us [%.0f;%.0f]\\t(%4i S)\\n\", n,\n                hist->bins[n].count,\n                hist->bins[n].mean * 1e6 / samp_rate,\n                hist->bins[n].min * 1e6 / samp_rate,\n                hist->bins[n].max * 1e6 / samp_rate,\n                hist->bins[n].mean);\n    }\n}\n\n#define HEXSTR_BUILDER_SIZE 1024\n#define HEXSTR_MAX_COUNT 32\n\n/// Hex string builder\ntypedef struct hexstr {\n    uint8_t p[HEXSTR_BUILDER_SIZE];\n    unsigned idx;\n} hexstr_t;\n\nstatic void hexstr_push_byte(hexstr_t *h, uint8_t v)\n{\n    if (h->idx < HEXSTR_BUILDER_SIZE)\n        h->p[h->idx++] = v;\n}\n\nstatic void hexstr_push_word(hexstr_t *h, uint16_t v)\n{\n    if (h->idx + 1 < HEXSTR_BUILDER_SIZE) {\n        h->p[h->idx++] = v >> 8;\n        h->p[h->idx++] = v & 0xff;\n    }\n}\n\nstatic void hexstr_print(hexstr_t *h, FILE *out)\n{\n    for (unsigned i = 0; i < h->idx; ++i)\n        fprintf(out, \"%02X\", h->p[i]);\n}\n\n#define TOLERANCE (0.2f) // 20% tolerance should still discern between the pulse widths: 0.33, 0.66, 1.0\n\n/// Analyze the statistics of a pulse data structure and print result\nvoid pulse_analyzer(pulse_data_t *data, int package_type, r_device* device)\n{\n    if (data->num_pulses == 0) {\n        fprintf(stderr, \"No pulses detected.\\n\");\n        return;\n    }\n\n    double to_ms = 1e3 / data->sample_rate;\n    double to_us = 1e6 / data->sample_rate;\n    // Generate pulse period data (pulse + gap, trailing gap)\n    int pulse_total_period = 0;\n    pulse_data_t pulse_periods_pg = {0};\n    pulse_periods_pg.num_pulses = data->num_pulses;\n    for (unsigned n = 0; n < pulse_periods_pg.num_pulses; ++n) {\n        pulse_periods_pg.pulse[n] = data->pulse[n] + data->gap[n];\n        pulse_total_period += data->pulse[n] + data->gap[n];\n    }\n    pulse_total_period -= data->gap[pulse_periods_pg.num_pulses - 1];\n    // Generate pulse period data (gap + pulse, leading gap)\n    pulse_data_t pulse_periods_gp = {0};\n    pulse_periods_gp.num_pulses   = data->num_pulses;\n    pulse_periods_gp.pulse[0] = data->pulse[0];\n    for (unsigned n = 1; n < pulse_periods_gp.num_pulses; ++n) {\n        pulse_periods_gp.pulse[n] = data->pulse[n] + data->gap[n - 1];\n    }\n\n    histogram_t hist_pulses  = {0};\n    histogram_t hist_gaps    = {0};\n    histogram_t hist_periods_pg = {0}; // Pulse+Gap periods\n    histogram_t hist_periods_gp = {0}; // Gap+Pulse periods\n    histogram_t hist_timings = {0};\n\n    // Generate statistics\n    histogram_sum(&hist_pulses, data->pulse, data->num_pulses, TOLERANCE);\n    histogram_sum(&hist_gaps, data->gap, data->num_pulses - 1, TOLERANCE);                      // Leave out last gap (end)\n    histogram_sum(&hist_periods_pg, pulse_periods_pg.pulse, pulse_periods_pg.num_pulses - 1, TOLERANCE); // Leave out last gap (end)\n    histogram_sum(&hist_periods_gp, pulse_periods_gp.pulse, pulse_periods_gp.num_pulses, TOLERANCE);\n    histogram_sum(&hist_timings, data->pulse, data->num_pulses, TOLERANCE);\n    histogram_sum(&hist_timings, data->gap, data->num_pulses, TOLERANCE);\n\n    // Fuse overlapping bins\n    histogram_fuse_bins(&hist_pulses, TOLERANCE);\n    histogram_fuse_bins(&hist_gaps, TOLERANCE);\n    histogram_fuse_bins(&hist_periods_pg, TOLERANCE);\n    histogram_fuse_bins(&hist_timings, TOLERANCE);\n\n    fprintf(stderr, \"Analyzing pulses...\\n\");\n    fprintf(stderr, \"Total count: %4u,  width: %4.2f ms\\t\\t(%5i S)\\n\",\n            data->num_pulses, pulse_total_period * to_ms, pulse_total_period);\n    fprintf(stderr, \"Pulse width distribution:\\n\");\n    histogram_print(&hist_pulses, data->sample_rate);\n    fprintf(stderr, \"Gap width distribution:\\n\");\n    histogram_print(&hist_gaps, data->sample_rate);\n    fprintf(stderr, \"Pulse+gap period distribution:\\n\");\n    histogram_print(&hist_periods_pg, data->sample_rate);\n    fprintf(stderr, \"Gap+pulse period distribution:\\n\");\n    histogram_print(&hist_periods_gp, data->sample_rate);\n    fprintf(stderr, \"Timing distribution:\\n\");\n    histogram_print(&hist_timings, data->sample_rate);\n    fprintf(stderr, \"Level estimates [high, low]: %6i, %6i\\n\",\n            data->ook_high_estimate, data->ook_low_estimate);\n    fprintf(stderr, \"RSSI: %.1f dB SNR: %.1f dB Noise: %.1f dB\\n\",\n            data->rssi_db, data->snr_db, data->noise_db);\n    fprintf(stderr, \"Frequency offsets [F1, F2]:  %6i, %6i\\t(%+.1f kHz, %+.1f kHz)\\n\",\n            data->fsk_f1_est, data->fsk_f2_est,\n            (float)data->fsk_f1_est / INT16_MAX * data->sample_rate / 2.0 / 1000.0,\n            (float)data->fsk_f2_est / INT16_MAX * data->sample_rate / 2.0 / 1000.0);\n\n    fprintf(stderr, \"Guessing modulation: \");\n    device->name    = \"Analyzer Device\",\n    device->verbose = 2,\n    histogram_sort_mean(&hist_pulses); // Easier to work with sorted data\n    histogram_sort_mean(&hist_gaps);\n    if (hist_pulses.bins[0].mean == 0) {\n        histogram_delete_bin(&hist_pulses, 0);\n    } // Remove FSK initial zero-bin\n\n    // Attempt to find a matching modulation\n    if (data->num_pulses == 1) {\n        fprintf(stderr, \"Single pulse detected. Probably Frequency Shift Keying or just noise...\\n\");\n    }\n    else if (hist_pulses.bins_count == 1 && hist_gaps.bins_count == 1) {\n        fprintf(stderr, \"Un-modulated signal. Maybe a preamble...\\n\");\n    }\n    else if (hist_pulses.bins_count == 1 && hist_gaps.bins_count > 1) {\n        fprintf(stderr, \"Pulse Position Modulation with fixed pulse width\\n\");\n        device->modulation  = OOK_PULSE_PPM; // TODO: there is not FSK_PULSE_PPM\n        device->short_width = to_us * hist_gaps.bins[0].mean;\n        device->long_width  = to_us * hist_gaps.bins[1].mean;\n        device->gap_limit   = to_us * (hist_gaps.bins[1].max + 1);                        // Set limit above next lower gap\n        device->reset_limit = to_us * (hist_gaps.bins[hist_gaps.bins_count - 1].max + 1); // Set limit above biggest gap\n    }\n    else if (hist_pulses.bins_count == 2 && hist_gaps.bins_count == 1) {\n        fprintf(stderr, \"Pulse Width Modulation with fixed gap\\n\");\n        device->modulation  = (package_type == PULSE_DATA_FSK) ? FSK_PULSE_PWM : OOK_PULSE_PWM;\n        device->short_width = to_us * hist_pulses.bins[0].mean;\n        device->long_width  = to_us * hist_pulses.bins[1].mean;\n        device->tolerance   = (device->long_width - device->short_width) * 0.4;\n        device->reset_limit = to_us * (hist_gaps.bins[hist_gaps.bins_count - 1].max + 1); // Set limit above biggest gap\n    }\n    else if (hist_pulses.bins_count == 2 && hist_gaps.bins_count == 2 && hist_periods_pg.bins_count == 1) {\n        fprintf(stderr, \"Pulse Width Modulation with fixed period\\n\");\n        device->modulation  = (package_type == PULSE_DATA_FSK) ? FSK_PULSE_PWM : OOK_PULSE_PWM;\n        device->short_width = to_us * hist_pulses.bins[0].mean;\n        device->long_width  = to_us * hist_pulses.bins[1].mean;\n        device->tolerance   = (device->long_width - device->short_width) * 0.4;\n        device->reset_limit = to_us * (hist_gaps.bins[hist_gaps.bins_count - 1].max + 1); // Set limit above biggest gap\n    }\n    else if (hist_pulses.bins_count == 2 && hist_gaps.bins_count == 2 && hist_periods_pg.bins_count == 3) {\n        fprintf(stderr, \"Manchester coding\\n\");\n        device->modulation  = (package_type == PULSE_DATA_FSK) ? FSK_PULSE_MANCHESTER_ZEROBIT : OOK_PULSE_MANCHESTER_ZEROBIT;\n        device->short_width = to_us * MIN(hist_pulses.bins[0].mean, hist_pulses.bins[1].mean); // Assume shortest pulse is half period\n        device->long_width  = 0;                                                               // Not used\n        device->reset_limit = to_us * (hist_gaps.bins[hist_gaps.bins_count - 1].max + 1);      // Set limit above biggest gap\n    }\n    else if (hist_pulses.bins_count == 2 && hist_gaps.bins_count >= 3) {\n        fprintf(stderr, \"Pulse Width Modulation with multiple packets\\n\");\n        device->modulation  = (package_type == PULSE_DATA_FSK) ? FSK_PULSE_PWM : OOK_PULSE_PWM;\n        device->short_width = to_us * hist_pulses.bins[0].mean;\n        device->long_width  = to_us * hist_pulses.bins[1].mean;\n        device->gap_limit   = to_us * (hist_gaps.bins[1].max + 1); // Set limit above second gap\n        device->tolerance   = (device->long_width - device->short_width) * 0.4;\n        device->reset_limit = to_us * (hist_gaps.bins[hist_gaps.bins_count - 1].max + 1); // Set limit above biggest gap\n    }\n    else if ((hist_pulses.bins_count >= 3 && hist_gaps.bins_count >= 3)\n            && (abs(hist_pulses.bins[1].mean - 2*hist_pulses.bins[0].mean) <= hist_pulses.bins[0].mean/8)    // Pulses are multiples of shortest pulse\n            && (abs(hist_pulses.bins[2].mean - 3*hist_pulses.bins[0].mean) <= hist_pulses.bins[0].mean/8)\n            && (abs(hist_gaps.bins[0].mean   -   hist_pulses.bins[0].mean) <= hist_pulses.bins[0].mean/8)    // Gaps are multiples of shortest pulse\n            && (abs(hist_gaps.bins[1].mean   - 2*hist_pulses.bins[0].mean) <= hist_pulses.bins[0].mean/8)\n            && (abs(hist_gaps.bins[2].mean   - 3*hist_pulses.bins[0].mean) <= hist_pulses.bins[0].mean/8)) {\n        fprintf(stderr, \"Non Return to Zero coding (Pulse Code)\\n\");\n        device->modulation  = (package_type == PULSE_DATA_FSK) ? FSK_PULSE_PCM : OOK_PULSE_PCM;\n        device->short_width = to_us * hist_pulses.bins[0].mean;        // Shortest pulse is bit width\n        device->long_width  = to_us * hist_pulses.bins[0].mean;        // Bit period equal to pulse length (NRZ)\n        device->reset_limit = to_us * hist_pulses.bins[0].mean * 1024; // No limit to run of zeros...\n    }\n    else if (hist_pulses.bins_count == 3) {\n        fprintf(stderr, \"Pulse Width Modulation with sync/delimiter\\n\");\n        // Re-sort to find lowest pulse count index (is probably delimiter)\n        histogram_sort_count(&hist_pulses);\n        int p1 = hist_pulses.bins[1].mean;\n        int p2 = hist_pulses.bins[2].mean;\n        device->modulation  = (package_type == PULSE_DATA_FSK) ? FSK_PULSE_PWM : OOK_PULSE_PWM;\n        device->short_width = to_us * (p1 < p2 ? p1 : p2);                                // Set to shorter pulse width\n        device->long_width  = to_us * (p1 < p2 ? p2 : p1);                                // Set to longer pulse width\n        device->sync_width  = to_us * hist_pulses.bins[0].mean;                           // Set to lowest count pulse width\n        device->reset_limit = to_us * (hist_gaps.bins[hist_gaps.bins_count - 1].max + 1); // Set limit above biggest gap\n    }\n    else {\n        fprintf(stderr, \"No clue...\\n\");\n    }\n\n    // Output RfRaw line (if possible)\n    if (hist_timings.bins_count <= 8) {\n        // if there is no 3rd gap length output one long B1 code\n        if (hist_gaps.bins_count <= 2) {\n            hexstr_t hexstr = {.p = {0}};\n            hexstr_push_byte(&hexstr, 0xaa);\n            hexstr_push_byte(&hexstr, 0xb1);\n            hexstr_push_byte(&hexstr, hist_timings.bins_count);\n            for (unsigned b = 0; b < hist_timings.bins_count; ++b) {\n                double w = hist_timings.bins[b].mean * to_us;\n                hexstr_push_word(&hexstr, w < USHRT_MAX ? w : USHRT_MAX);\n            }\n            for (unsigned i = 0; i < data->num_pulses; ++i) {\n                int p = histogram_find_bin_index(&hist_timings, data->pulse[i]);\n                int g = histogram_find_bin_index(&hist_timings, data->gap[i]);\n                if (p < 0 || g < 0) {\n                    fprintf(stderr, \"%s: this can't happen\\n\", __func__);\n                    exit(1);\n                }\n                hexstr_push_byte(&hexstr, 0x80 | (p << 4) | g);\n            }\n            hexstr_push_byte(&hexstr, 0x55);\n            fprintf(stderr, \"view at https://triq.org/pdv/#\");\n            hexstr_print(&hexstr, stderr);\n            fprintf(stderr, \"\\n\");\n        }\n        // otherwise try to group as B0 codes\n        else {\n            // pick last gap length but a most the 4th\n            int limit_bin = MIN(3, hist_gaps.bins_count - 1);\n            int limit = hist_gaps.bins[limit_bin].min;\n            hexstr_t hexstrs[HEXSTR_MAX_COUNT] = {{.p = {0}}};\n            unsigned hexstr_cnt = 0;\n            unsigned i = 0;\n            while (i < data->num_pulses && hexstr_cnt < HEXSTR_MAX_COUNT) {\n                hexstr_t *hexstr = &hexstrs[hexstr_cnt];\n                hexstr_push_byte(hexstr, 0xaa);\n                hexstr_push_byte(hexstr, 0xb0);\n                hexstr_push_byte(hexstr, 0); // len\n                hexstr_push_byte(hexstr, hist_timings.bins_count);\n                hexstr_push_byte(hexstr, 1); // repeats\n                for (unsigned b = 0; b < hist_timings.bins_count; ++b) {\n                    double w =hist_timings.bins[b].mean * to_us;\n                    hexstr_push_word(hexstr, w < USHRT_MAX ? w : USHRT_MAX);\n                }\n                for (; i < data->num_pulses; ++i) {\n                    int p = histogram_find_bin_index(&hist_timings, data->pulse[i]);\n                    int g = histogram_find_bin_index(&hist_timings, data->gap[i]);\n                    if (p < 0 || g < 0) {\n                        fprintf(stderr, \"%s: this can't happen\\n\", __func__);\n                        exit(1);\n                    }\n                    hexstr_push_byte(hexstr, 0x80 | (p << 4) | g);\n                    if (data->gap[i] >= limit) {\n                        ++i;\n                        break;\n                    }\n                }\n                hexstr_push_byte(hexstr, 0x55);\n                hexstr->p[2] = hexstr->idx - 4 <= 255 ? hexstr->idx - 4 : 0; // len\n                if (hexstr_cnt > 0 && hexstrs[hexstr_cnt - 1].idx == hexstr->idx\n                        && !memcmp(&hexstrs[hexstr_cnt - 1].p[5], &hexstr->p[5], hexstr->idx - 5)) {\n                    hexstr->idx = 0; // clear\n                    hexstrs[hexstr_cnt - 1].p[4] += 1; // repeats\n                } else {\n                    hexstr_cnt++;\n                }\n            }\n\n            fprintf(stderr, \"view at https://triq.org/pdv/#\");\n            for (unsigned j = 0; j < hexstr_cnt; ++j) {\n                if (j > 0)\n                    fprintf(stderr, \"+\");\n                hexstr_print(&hexstrs[j], stderr);\n            }\n            fprintf(stderr, \"\\n\");\n            if (hexstr_cnt >= HEXSTR_MAX_COUNT) {\n                fprintf(stderr, \"Too many pulse groups (%u pulses missed in rfraw)\\n\", data->num_pulses - i);\n            }\n        }\n    }\n\n    // Demodulate (if detected)\n    if (device->modulation) {\n        fprintf(stderr, \"Attempting demodulation... short_width: %.0f, long_width: %.0f, reset_limit: %.0f, sync_width: %.0f\\n\",\n                device->short_width, device->long_width,\n                device->reset_limit, device->sync_width);\n        switch (device->modulation) {\n        case FSK_PULSE_PCM:\n            fprintf(stderr, \"Use a flex decoder with -X 'n=name,m=FSK_PCM,s=%.0f,l=%.0f,r=%.0f'\\n\",\n                    device->short_width, device->long_width, device->reset_limit);\n            pulse_slicer_pcm(data, device);\n            break;\n        case OOK_PULSE_PPM:\n            fprintf(stderr, \"Use a flex decoder with -X 'n=name,m=OOK_PPM,s=%.0f,l=%.0f,g=%.0f,r=%.0f'\\n\",\n                    device->short_width, device->long_width,\n                    device->gap_limit, device->reset_limit);\n            data->gap[data->num_pulses - 1] = device->reset_limit / to_us + 1; // Be sure to terminate package\n            pulse_slicer_ppm(data, device);\n            break;\n        case OOK_PULSE_PWM:\n            fprintf(stderr, \"Use a flex decoder with -X 'n=name,m=OOK_PWM,s=%.0f,l=%.0f,r=%.0f,g=%.0f,t=%.0f,y=%.0f'\\n\",\n                    device->short_width, device->long_width, device->reset_limit,\n                    device->gap_limit, device->tolerance, device->sync_width);\n            data->gap[data->num_pulses - 1] = device->reset_limit / to_us + 1; // Be sure to terminate package\n            pulse_slicer_pwm(data, device);\n            break;\n        case FSK_PULSE_PWM:\n            fprintf(stderr, \"Use a flex decoder with -X 'n=name,m=FSK_PWM,s=%.0f,l=%.0f,r=%.0f,g=%.0f,t=%.0f,y=%.0f'\\n\",\n                    device->short_width, device->long_width, device->reset_limit,\n                    device->gap_limit, device->tolerance, device->sync_width);\n            data->gap[data->num_pulses - 1] = device->reset_limit / to_us + 1; // Be sure to terminate package\n            pulse_slicer_pwm(data, device);\n            break;\n        case OOK_PULSE_MANCHESTER_ZEROBIT:\n            fprintf(stderr, \"Use a flex decoder with -X 'n=name,m=OOK_MC_ZEROBIT,s=%.0f,l=%.0f,r=%.0f'\\n\",\n                    device->short_width, device->long_width, device->reset_limit);\n            data->gap[data->num_pulses - 1] = device->reset_limit / to_us + 1; // Be sure to terminate package\n            pulse_slicer_manchester_zerobit(data, device);\n            break;\n        default:\n            fprintf(stderr, \"Unsupported\\n\");\n        }\n    }\n\n    fprintf(stderr, \"\\n\");\n}\n"
  },
  {
    "path": "src/pulse_data.c",
    "content": "/** @file\n    Pulse data functions.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"pulse_data.h\"\n#include \"rfraw.h\"\n#include \"r_util.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid pulse_data_clear(pulse_data_t *data)\n{\n    *data = (pulse_data_t const){0};\n}\n\nvoid pulse_data_shift(pulse_data_t *data)\n{\n    int offs = PD_MAX_PULSES / 2; // shift out half the data\n    memmove(data->pulse, &data->pulse[offs], (PD_MAX_PULSES - offs) * sizeof(*data->pulse));\n    memmove(data->gap, &data->gap[offs], (PD_MAX_PULSES - offs) * sizeof(*data->gap));\n    data->num_pulses -= offs;\n    data->offset += offs;\n}\n\nvoid pulse_data_print(pulse_data_t const *data)\n{\n    fprintf(stderr, \"Pulse data: %u pulses\\n\", data->num_pulses);\n    for (unsigned n = 0; n < data->num_pulses; ++n) {\n        fprintf(stderr, \"[%3u] Pulse: %4d, Gap: %4d, Period: %4d\\n\", n, data->pulse[n], data->gap[n], data->pulse[n] + data->gap[n]);\n    }\n}\n\nstatic void *bounded_memset(void *b, int c, int64_t size, int64_t offset, int64_t len)\n{\n    if (offset < 0) {\n        len += offset; // reduce len by negative offset\n        offset = 0;\n    }\n    if (offset + len > size) {\n        len = size - offset; // clip excessive len\n    }\n    if (len > 0)\n        memset((char *)b + offset, c, (size_t)len);\n    return b;\n}\n\nvoid pulse_data_dump_raw(uint8_t *buf, unsigned len, uint64_t buf_offset, pulse_data_t const *data, uint8_t bits)\n{\n    int64_t pos = data->offset - buf_offset;\n    for (unsigned n = 0; n < data->num_pulses; ++n) {\n        bounded_memset(buf, 0x01 | bits, len, pos, data->pulse[n]);\n        pos += data->pulse[n];\n        bounded_memset(buf, 0x01, len, pos, data->gap[n]);\n        pos += data->gap[n];\n    }\n}\n\nstatic inline void chk_ret(int ret)\n{\n    if (ret < 0) {\n        perror(\"File output error\");\n        exit(1);\n    }\n}\n\nvoid pulse_data_print_vcd_header(FILE *file, uint32_t sample_rate)\n{\n    if (!file) {\n        FATAL(\"Invalid stream in pulse_data_print_vcd_header()\");\n    }\n\n    char time_str[LOCAL_TIME_BUFLEN];\n    char *timescale;\n    if (sample_rate <= 500000)\n        timescale = \"1 us\";\n    else\n        timescale = \"100 ns\";\n    chk_ret(fprintf(file, \"$date %s $end\\n\", usecs_time_str(time_str, NULL, 0, 0)));\n    chk_ret(fprintf(file, \"$version rtl_433 0.1.0 $end\\n\"));\n    chk_ret(fprintf(file, \"$comment Acquisition at %s Hz $end\\n\", nice_freq(sample_rate)));\n    chk_ret(fprintf(file, \"$timescale %s $end\\n\", timescale));\n    chk_ret(fprintf(file, \"$scope module rtl_433 $end\\n\"));\n    chk_ret(fprintf(file, \"$var wire 1 / FRAME $end\\n\"));\n    chk_ret(fprintf(file, \"$var wire 1 ' AM $end\\n\"));\n    chk_ret(fprintf(file, \"$var wire 1 \\\" FM $end\\n\"));\n    chk_ret(fprintf(file, \"$upscope $end\\n\"));\n    chk_ret(fprintf(file, \"$enddefinitions $end\\n\"));\n    chk_ret(fprintf(file, \"#0 0/ 0' 0\\\"\\n\"));\n}\n\nvoid pulse_data_print_vcd(FILE *file, pulse_data_t const *data, int ch_id)\n{\n    float scale;\n    if (data->sample_rate <= 500000)\n        scale = 1000000 / data->sample_rate; // unit: 1 us\n    else\n        scale = 10000000 / data->sample_rate; // unit: 100 ns\n    uint64_t pos = data->offset;\n    for (unsigned n = 0; n < data->num_pulses; ++n) {\n        if (n == 0)\n            chk_ret(fprintf(file, \"#%.f 1/ 1%c\\n\", pos * scale, ch_id));\n        else\n            chk_ret(fprintf(file, \"#%.f 1%c\\n\", pos * scale, ch_id));\n        pos += data->pulse[n];\n        chk_ret(fprintf(file, \"#%.f 0%c\\n\", pos * scale, ch_id));\n        pos += data->gap[n];\n    }\n    if (data->num_pulses > 0)\n        chk_ret(fprintf(file, \"#%.f 0/\\n\", pos * scale));\n}\n\nvoid pulse_data_load(FILE *file, struct timeval *now, pulse_data_t *data, uint32_t sample_rate)\n{\n    char s[1024];\n    int i    = 0;\n    int size = sizeof(data->pulse) / sizeof(*data->pulse);\n\n    pulse_data_clear(data);\n    data->sample_rate = sample_rate;\n    double to_sample  = sample_rate / 1e6;\n    // read line-by-line\n    while (i < size && fgets(s, sizeof(s), file)) {\n        // TODO: we should parse sample rate and timescale\n        if (!strncmp(s, \";freq1\", 6)) {\n            data->freq1_hz = strtol(s + 6, NULL, 10);\n        }\n        if (!strncmp(s, \";freq2\", 6)) {\n            data->freq2_hz = strtol(s + 6, NULL, 10);\n        }\n        if (!strncmp(s, \";received \", 10)) {\n            const char* tail = parse_time_str(s + 10, now);\n            if (!tail) {\n                print_logf(LOG_WARNING, __func__, \"failed to parse received time `%s`\", s + 10);\n            }\n        }\n        if (*s == ';') {\n            if (i) {\n                break; // end or next header found\n            }\n            else {\n                continue; // still reading a header\n            }\n        }\n        if (rfraw_check(s)) {\n            rfraw_parse(data, s);\n            i = data->num_pulses;\n            continue;\n        }\n        // parse two ints.\n        char *p = s;\n        char *endptr;\n        long mark  = strtol(p, &endptr, 10);\n        p          = endptr + 1;\n        long space = strtol(p, &endptr, 10);\n        // fprintf(stderr, \"read: mark %ld space %ld\\n\", mark, space);\n        data->pulse[i] = (int)(to_sample * mark);\n        data->gap[i++] = (int)(to_sample * space);\n    }\n    // fprintf(stderr, \"read %d pulses\\n\", i);\n    data->num_pulses = i;\n}\n\nvoid pulse_data_print_pulse_header(FILE *file)\n{\n    if (!file) {\n        FATAL(\"Invalid stream in pulse_data_print_pulse_header()\");\n    }\n\n    char time_str[LOCAL_TIME_BUFLEN];\n\n    chk_ret(fprintf(file, \";pulse data\\n\"));\n    chk_ret(fprintf(file, \";version 1\\n\"));\n    chk_ret(fprintf(file, \";timescale 1us\\n\"));\n    // chk_ret(fprintf(file, \";samplerate %u\\n\", data->sample_rate));\n    chk_ret(fprintf(file, \";created %s\\n\", usecs_time_str(time_str, NULL, 1, 0)));\n}\n\nvoid pulse_data_dump(FILE *file, pulse_data_t const *data)\n{\n    if (!file) {\n        FATAL(\"Invalid stream in pulse_data_dump()\");\n    }\n\n    char time_str[LOCAL_TIME_BUFLEN];\n\n    chk_ret(fprintf(file, \";received %s\\n\", usecs_time_str(time_str, NULL, 1, 0)));\n    if (data->fsk_f2_est) {\n        chk_ret(fprintf(file, \";fsk %u pulses\\n\", data->num_pulses));\n        chk_ret(fprintf(file, \";freq1 %.0f\\n\", data->freq1_hz));\n        chk_ret(fprintf(file, \";freq2 %.0f\\n\", data->freq2_hz));\n    }\n    else {\n        chk_ret(fprintf(file, \";ook %u pulses\\n\", data->num_pulses));\n        chk_ret(fprintf(file, \";freq1 %.0f\\n\", data->freq1_hz));\n    }\n    chk_ret(fprintf(file, \";centerfreq %.0f Hz\\n\", data->centerfreq_hz));\n    chk_ret(fprintf(file, \";samplerate %u Hz\\n\", data->sample_rate));\n    chk_ret(fprintf(file, \";sampledepth %u bits\\n\", data->depth_bits));\n    chk_ret(fprintf(file, \";range %.1f dB\\n\", data->range_db));\n    chk_ret(fprintf(file, \";rssi %.1f dB\\n\", data->rssi_db));\n    chk_ret(fprintf(file, \";snr %.1f dB\\n\", data->snr_db));\n    chk_ret(fprintf(file, \";noise %.1f dB\\n\", data->noise_db));\n\n    double to_us = 1e6 / data->sample_rate;\n    for (unsigned i = 0; i < data->num_pulses; ++i) {\n        chk_ret(fprintf(file, \"%.0f %.0f\\n\", data->pulse[i] * to_us, data->gap[i] * to_us));\n    }\n    chk_ret(fprintf(file, \";end\\n\"));\n}\n\ndata_t *pulse_data_print_data(pulse_data_t const *data)\n{\n    int pulses[2 * PD_MAX_PULSES];\n    double to_us = 1e6 / data->sample_rate;\n    for (unsigned i = 0; i < data->num_pulses; ++i) {\n        pulses[i * 2 + 0] = data->pulse[i] * to_us;\n        pulses[i * 2 + 1] = data->gap[i] * to_us;\n    }\n\n    /* clang-format off */\n    return data_make(\n            \"mod\",              \"\", DATA_STRING, (data->fsk_f2_est) ? \"FSK\" : \"OOK\",\n            \"count\",            \"\", DATA_INT,    data->num_pulses,\n            \"pulses\",           \"\", DATA_ARRAY,  data_array(2 * data->num_pulses, DATA_INT, pulses),\n            \"freq1_Hz\",         \"\", DATA_FORMAT, \"%u Hz\", DATA_INT, (unsigned)data->freq1_hz,\n            \"freq2_Hz\",         \"\", DATA_COND,   data->fsk_f2_est, DATA_FORMAT, \"%u Hz\", DATA_INT, (unsigned)data->freq2_hz,\n            \"freq_Hz\",          \"\", DATA_INT,    (unsigned)data->centerfreq_hz,\n            \"rate_Hz\",          \"\", DATA_INT,    data->sample_rate,\n            \"depth_bits\",       \"\", DATA_INT,    data->depth_bits,\n            \"range_dB\",         \"\", DATA_FORMAT, \"%.1f dB\", DATA_DOUBLE, data->range_db,\n            \"rssi_dB\",          \"\", DATA_FORMAT, \"%.1f dB\", DATA_DOUBLE, data->rssi_db,\n            \"snr_dB\",           \"\", DATA_FORMAT, \"%.1f dB\", DATA_DOUBLE, data->snr_db,\n            \"noise_dB\",         \"\", DATA_FORMAT, \"%.1f dB\", DATA_DOUBLE, data->noise_db,\n            NULL);\n    /* clang-format on */\n}\n"
  },
  {
    "path": "src/pulse_detect.c",
    "content": "/** @file\n    Pulse detection functions.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"pulse_detect.h\"\n#include \"pulse_detect_fsk.h\"\n#include \"pulse_data.h\"\n#include \"baseband.h\"\n#include \"c_util.h\" // for MIN(), MAX()\n#include \"logger.h\"\n#include \"fatal.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\n// OOK adaptive level estimator constants\n#define OOK_MAX_HIGH_LEVEL  DB_TO_AMP(0)   // Maximum estimate for high level (-0 dB)\n#define OOK_MAX_LOW_LEVEL   DB_TO_AMP(-15) // Maximum estimate for low level\n#define OOK_EST_HIGH_RATIO  64          // Constant for slowness of OOK high level estimator\n#define OOK_EST_LOW_RATIO   1024        // Constant for slowness of OOK low level (noise) estimator (very slow)\n\n/// Internal state data for pulse_pulse_package()\nstruct pulse_detect {\n    int use_mag_est;          ///< Whether the envelope data is an amplitude or magnitude.\n    int ook_fixed_high_level; ///< Manual detection level override, 0 = auto.\n    int ook_min_high_level;   ///< Minimum estimate of high level (-12 dB: 1000 amp, 4000 mag).\n    int ook_high_low_ratio;   ///< Default ratio between high and low (noise) level (9 dB: x8 amp, 11 dB: x3.6 mag).\n\n    enum {\n        PD_OOK_STATE_IDLE      = 0,\n        PD_OOK_STATE_PULSE     = 1,\n        PD_OOK_STATE_GAP_START = 2,\n        PD_OOK_STATE_GAP       = 3,\n    } ook_state;\n    int pulse_length; ///< Counter for internal pulse detection\n    int max_pulse;    ///< Size of biggest pulse detected\n\n    int data_counter;    ///< Counter for how much of data chunk is processed\n    int lead_in_counter; ///< Counter for allowing initial noise estimate to settle\n\n    int ook_low_estimate;  ///< Estimate for the OOK low level (base noise level) in the envelope data\n    int ook_high_estimate; ///< Estimate for the OOK high level\n\n    int verbosity; ///< Debug output verbosity, 0=None, 1=Levels, 2=Histograms\n\n    pulse_detect_fsk_t pulse_detect_fsk;\n};\n\npulse_detect_t *pulse_detect_create(void)\n{\n    pulse_detect_t *pulse_detect = calloc(1, sizeof(pulse_detect_t));\n    if (!pulse_detect) {\n        WARN_CALLOC(\"pulse_detect_create()\");\n        return NULL;\n    }\n\n    pulse_detect_set_levels(pulse_detect, 0, 0.0f, -12.1442f, 9.0f, 0);\n\n    return pulse_detect;\n}\n\nvoid pulse_detect_free(pulse_detect_t *pulse_detect)\n{\n    free(pulse_detect);\n}\n\nvoid pulse_detect_reset(pulse_detect_t *pulse_detect)\n{\n    pulse_detect->ook_state         = PD_OOK_STATE_IDLE;\n    pulse_detect->pulse_length      = 0;\n    pulse_detect->max_pulse         = 0;\n    pulse_detect->data_counter      = 0;\n    pulse_detect->lead_in_counter   = 0;\n    pulse_detect->ook_low_estimate  = 0;\n    pulse_detect->ook_high_estimate = 0;\n    pulse_detect_fsk_init(&pulse_detect->pulse_detect_fsk);\n}\n\nvoid pulse_detect_set_levels(pulse_detect_t *pulse_detect, int use_mag_est, float fixed_high_level, float min_high_level, float high_low_ratio, int verbosity)\n{\n    pulse_detect->use_mag_est = use_mag_est;\n    if (use_mag_est) {\n        pulse_detect->ook_fixed_high_level = fixed_high_level < 0.0 ? DB_TO_MAG(fixed_high_level) : 0;\n        pulse_detect->ook_min_high_level   = DB_TO_MAG(min_high_level);\n        pulse_detect->ook_high_low_ratio = DB_TO_MAG_F(high_low_ratio);\n    }\n    else { // amp est\n        pulse_detect->ook_fixed_high_level = fixed_high_level < 0.0 ? DB_TO_AMP(fixed_high_level) : 0;\n        pulse_detect->ook_min_high_level   = DB_TO_AMP(min_high_level);\n        pulse_detect->ook_high_low_ratio = DB_TO_AMP_F(high_low_ratio);\n    }\n    pulse_detect->verbosity = verbosity;\n\n    //fprintf(stderr, \"fixed_high_level %.1f (%d), min_high_level %.1f (%d), high_low_ratio %.1f (%d)\\n\",\n    //        fixed_high_level, pulse_detect->ook_fixed_high_level,\n    //        min_high_level, pulse_detect->ook_min_high_level,\n    //        high_low_ratio, pulse_detect->ook_high_low_ratio);\n}\n\n/// convert amplitude (16384 FS) to attenuation in (integer) dB, offset by 3.\nstatic inline int amp_to_att(int a)\n{\n    if (a > 32690) return 0;  // = 10^(( 3 + 42.1442) / 10)\n    if (a > 25967) return 1;  // = 10^(( 2 + 42.1442) / 10)\n    if (a > 20626) return 2;  // = 10^(( 1 + 42.1442) / 10)\n    if (a > 16383) return 3;  // = 10^(( 0 + 42.1442) / 10)\n    if (a > 13014) return 4;  // = 10^((-1 + 42.1442) / 10)\n    if (a > 10338) return 5;  // = 10^((-2 + 42.1442) / 10)\n    if (a >  8211) return 6;  // = 10^((-3 + 42.1442) / 10)\n    if (a >  6523) return 7;  // = 10^((-4 + 42.1442) / 10)\n    if (a >  5181) return 8;  // = 10^((-5 + 42.1442) / 10)\n    if (a >  4115) return 9;  // = 10^((-6 + 42.1442) / 10)\n    if (a >  3269) return 10; // = 10^((-7 + 42.1442) / 10)\n    if (a >  2597) return 11; // = 10^((-8 + 42.1442) / 10)\n    if (a >  2063) return 12; // = 10^((-9 + 42.1442) / 10)\n    if (a >  1638) return 13; // = 10^((-10 + 42.1442) / 10)\n    if (a >  1301) return 14; // = 10^((-11 + 42.1442) / 10)\n    if (a >  1034) return 15; // = 10^((-12 + 42.1442) / 10)\n    if (a >   821) return 16; // = 10^((-13 + 42.1442) / 10)\n    if (a >   652) return 17; // = 10^((-14 + 42.1442) / 10)\n    if (a >   518) return 18; // = 10^((-15 + 42.1442) / 10)\n    if (a >   412) return 19; // = 10^((-16 + 42.1442) / 10)\n    if (a >   327) return 20; // = 10^((-17 + 42.1442) / 10)\n    if (a >   260) return 21; // = 10^((-18 + 42.1442) / 10)\n    if (a >   206) return 22; // = 10^((-19 + 42.1442) / 10)\n    if (a >   164) return 23; // = 10^((-20 + 42.1442) / 10)\n    if (a >   130) return 24; // = 10^((-21 + 42.1442) / 10)\n    if (a >   103) return 25; // = 10^((-22 + 42.1442) / 10)\n    if (a >    82) return 26; // = 10^((-23 + 42.1442) / 10)\n    if (a >    65) return 27; // = 10^((-24 + 42.1442) / 10)\n    if (a >    52) return 28; // = 10^((-25 + 42.1442) / 10)\n    if (a >    41) return 29; // = 10^((-26 + 42.1442) / 10)\n    if (a >    33) return 30; // = 10^((-27 + 42.1442) / 10)\n    if (a >    26) return 31; // = 10^((-28 + 42.1442) / 10)\n    if (a >    21) return 32; // = 10^((-29 + 42.1442) / 10)\n    if (a >    16) return 33; // = 10^((-30 + 42.1442) / 10)\n    if (a >    13) return 34; // = 10^((-31 + 42.1442) / 10)\n    if (a >    10) return 35; // = 10^((-32 + 42.1442) / 10)\n    return 36;\n}\n/// convert magnitude (16384 FS) to attenuation in (integer) dB, offset by 3.\nstatic inline int mag_to_att(int m)\n{\n    if (m > 23143) return 0;  // = 10^(( 3 + 84.2884) / 20)\n    if (m > 20626) return 1;  // = 10^(( 2 + 84.2884) / 20)\n    if (m > 18383) return 2;  // = 10^(( 1 + 84.2884) / 20)\n    if (m > 16383) return 3;  // = 10^(( 0 + 84.2884) / 20)\n    if (m > 14602) return 4;  // = 10^((-1 + 84.2884) / 20)\n    if (m > 13014) return 5;  // = 10^((-2 + 84.2884) / 20)\n    if (m > 11599) return 6;  // = 10^((-3 + 84.2884) / 20)\n    if (m > 10338) return 7;  // = 10^((-4 + 84.2884) / 20)\n    if (m >  9213) return 8;  // = 10^((-5 + 84.2884) / 20)\n    if (m >  8211) return 9;  // = 10^((-6 + 84.2884) / 20)\n    if (m >  7318) return 10; // = 10^((-7 + 84.2884) / 20)\n    if (m >  6523) return 11; // = 10^((-8 + 84.2884) / 20)\n    if (m >  5813) return 12; // = 10^((-9 + 84.2884) / 20)\n    if (m >  5181) return 13; // = 10^((-10 + 84.2884) / 20)\n    if (m >  4618) return 14; // = 10^((-11 + 84.2884) / 20)\n    if (m >  4115) return 15; // = 10^((-12 + 84.2884) / 20)\n    if (m >  3668) return 16; // = 10^((-13 + 84.2884) / 20)\n    if (m >  3269) return 17; // = 10^((-14 + 84.2884) / 20)\n    if (m >  2914) return 18; // = 10^((-15 + 84.2884) / 20)\n    if (m >  2597) return 19; // = 10^((-16 + 84.2884) / 20)\n    if (m >  2314) return 20; // = 10^((-17 + 84.2884) / 20)\n    if (m >  2063) return 21; // = 10^((-18 + 84.2884) / 20)\n    if (m >  1838) return 22; // = 10^((-19 + 84.2884) / 20)\n    if (m >  1638) return 23; // = 10^((-20 + 84.2884) / 20)\n    if (m >  1460) return 24; // = 10^((-21 + 84.2884) / 20)\n    if (m >  1301) return 25; // = 10^((-22 + 84.2884) / 20)\n    if (m >  1160) return 26; // = 10^((-23 + 84.2884) / 20)\n    if (m >  1034) return 27; // = 10^((-24 + 84.2884) / 20)\n    if (m >   921) return 28; // = 10^((-25 + 84.2884) / 20)\n    if (m >   821) return 29; // = 10^((-26 + 84.2884) / 20)\n    if (m >   732) return 30; // = 10^((-27 + 84.2884) / 20)\n    if (m >   652) return 31; // = 10^((-28 + 84.2884) / 20)\n    if (m >   581) return 32; // = 10^((-29 + 84.2884) / 20)\n    if (m >   518) return 33; // = 10^((-30 + 84.2884) / 20)\n    if (m >   462) return 34; // = 10^((-31 + 84.2884) / 20)\n    if (m >   412) return 35; // = 10^((-32 + 84.2884) / 20)\n    return 36;\n}\n/// print a simple attenuation histogram.\nstatic void print_att_hist(char const *s, int att_hist[])\n{\n    fprintf(stderr, \"\\n%s\\n\", s);\n    for (int i = 0; i < 37; ++i) {\n        fprintf(stderr, \">%3d dB: %5d smps\\n\", 3 - i, att_hist[i]);\n    }\n}\n\n/// Demodulate On/Off Keying (OOK) and Frequency Shift Keying (FSK) from an envelope signal\nint pulse_detect_package(pulse_detect_t *pulse_detect, int16_t const *envelope_data, int16_t const *fm_data, int len, uint32_t samp_rate, uint64_t sample_offset, pulse_data_t *pulses, pulse_data_t *fsk_pulses, unsigned fpdm)\n{\n    int att_hist[37] = {0};\n    int const samples_per_ms = samp_rate / 1000;\n    pulse_detect_t *s = pulse_detect;\n    s->ook_high_estimate = MAX(s->ook_high_estimate, pulse_detect->ook_min_high_level);    // Be sure to set initial minimum level\n\n    if (s->data_counter == 0) {\n        // age the pulse_data if this is a fresh buffer\n        pulses->start_ago += len;\n        fsk_pulses->start_ago += len;\n    }\n\n    int eop_on_spurious = 0;\n    // Process all new samples\n    while (s->data_counter < len) {\n        // Calculate OOK detection threshold and hysteresis\n        int16_t const am_n    = envelope_data[s->data_counter];\n        if (pulse_detect->verbosity >= LOG_NOTICE) {\n            int att = pulse_detect->use_mag_est ? mag_to_att(am_n) : amp_to_att(am_n);\n            att_hist[att] += 1;\n        }\n        int16_t ook_threshold = (s->ook_low_estimate + MIN(s->ook_high_estimate, OOK_MAX_HIGH_LEVEL)) / 2;\n        if (pulse_detect->ook_fixed_high_level != 0) {\n            ook_threshold = pulse_detect->ook_fixed_high_level; // Manual override\n        }\n        int16_t const ook_hysteresis = ook_threshold / 8; // +-12%\n\n        // OOK State machine\n        switch (s->ook_state) {\n            case PD_OOK_STATE_IDLE:\n                if (am_n > (ook_threshold + ook_hysteresis)    // Above threshold?\n                        && s->lead_in_counter > OOK_EST_LOW_RATIO) { // Lead in counter to stabilize noise estimate\n                    // Initialize all data\n                    pulse_data_clear(pulses);\n                    pulse_data_clear(fsk_pulses);\n                    pulses->sample_rate = samp_rate;\n                    fsk_pulses->sample_rate = samp_rate;\n                    pulses->offset = sample_offset + s->data_counter;\n                    fsk_pulses->offset = sample_offset + s->data_counter;\n                    pulses->start_ago = len - s->data_counter;\n                    fsk_pulses->start_ago = len - s->data_counter;\n                    s->pulse_length = 0;\n                    s->max_pulse = 0;\n                    pulse_detect_fsk_init(&s->pulse_detect_fsk);\n                    s->ook_state = PD_OOK_STATE_PULSE;\n                }\n                else {    // We are still idle..\n                    // Estimate low (noise) level\n                    int const ook_low_delta = am_n - s->ook_low_estimate;\n                    s->ook_low_estimate += ook_low_delta / OOK_EST_LOW_RATIO;\n                    s->ook_low_estimate += ((ook_low_delta > 0) ? 1 : -1);    // Hack to compensate for lack of fixed-point scaling\n                    // Calculate default OOK high level estimate\n                    s->ook_high_estimate = pulse_detect->ook_high_low_ratio * s->ook_low_estimate; // Default is a ratio of low level\n                    s->ook_high_estimate = MAX(s->ook_high_estimate, pulse_detect->ook_min_high_level);\n                    if (s->lead_in_counter <= OOK_EST_LOW_RATIO) s->lead_in_counter += 1;        // Allow initial estimate to settle\n                }\n                break;\n            case PD_OOK_STATE_PULSE:\n                s->pulse_length += 1;\n                // End of pulse detected?\n                if (am_n < (ook_threshold - ook_hysteresis)) {    // Gap?\n                    // Check for spurious short pulses\n                    if (s->pulse_length < PD_MIN_PULSE_SAMPLES) {\n                        if (pulses->num_pulses <= 1) {\n                            // if this was the first pulse go back to idle\n                            s->ook_state = PD_OOK_STATE_IDLE;\n                        } else {\n                            // otherwise emit a package, which then goes back to idle\n                            eop_on_spurious = 1;\n                            s->ook_state = PD_OOK_STATE_GAP;\n                        }\n                    }\n                    else {\n                        // Continue with OOK decoding\n                        pulses->pulse[pulses->num_pulses] = s->pulse_length;    // Store pulse width\n                        s->max_pulse = MAX(s->pulse_length, s->max_pulse);    // Find largest pulse\n                        s->pulse_length = 0;\n                        s->ook_state = PD_OOK_STATE_GAP_START;\n                    }\n                }\n                // Still pulse\n                else {\n                    // Calculate OOK high level estimate\n                    s->ook_high_estimate += am_n / OOK_EST_HIGH_RATIO - s->ook_high_estimate / OOK_EST_HIGH_RATIO;\n                    s->ook_high_estimate = MAX(s->ook_high_estimate, pulse_detect->ook_min_high_level);\n                    // Estimate pulse carrier frequency\n                    pulses->fsk_f1_est += fm_data[s->data_counter] / OOK_EST_HIGH_RATIO - pulses->fsk_f1_est / OOK_EST_HIGH_RATIO;\n                }\n                // FSK Demodulation\n                if (pulses->num_pulses == 0) {    // Only during first pulse\n                    if (fpdm == FSK_PULSE_DETECT_OLD) {\n                        pulse_detect_fsk_classic(&s->pulse_detect_fsk, fm_data[s->data_counter], fsk_pulses);\n                    } else {\n                        pulse_detect_fsk_minmax(&s->pulse_detect_fsk, fm_data[s->data_counter], fsk_pulses);\n                    }\n                }\n                break;\n            case PD_OOK_STATE_GAP_START:    // Beginning of gap - it might be a spurious gap\n                s->pulse_length += 1;\n                // Pulse detected again already? (This is a spurious short gap)\n                if (am_n > (ook_threshold + ook_hysteresis)) {    // New pulse?\n                    s->pulse_length += pulses->pulse[pulses->num_pulses];    // Restore counter\n                    s->ook_state = PD_OOK_STATE_PULSE;\n                }\n                // Or this gap is for real?\n                else if (s->pulse_length >= PD_MIN_PULSE_SAMPLES) {\n                    s->ook_state = PD_OOK_STATE_GAP;\n                    // Determine if FSK modulation is detected\n                    if (fsk_pulses->num_pulses > PD_MIN_PULSES) {\n                        // Store last pulse/gap\n                        if (fpdm == FSK_PULSE_DETECT_OLD)\n                            pulse_detect_fsk_wrap_up(&s->pulse_detect_fsk, fsk_pulses);\n                        // Store estimates\n                        fsk_pulses->fsk_f1_est = s->pulse_detect_fsk.fm_f1_est;\n                        fsk_pulses->fsk_f2_est = s->pulse_detect_fsk.fm_f2_est;\n                        fsk_pulses->ook_low_estimate = s->ook_low_estimate;\n                        fsk_pulses->ook_high_estimate = s->ook_high_estimate;\n                        pulses->end_ago = len - s->data_counter;\n                        fsk_pulses->end_ago = len - s->data_counter;\n                        s->ook_state = PD_OOK_STATE_IDLE;    // Ensure everything is reset\n                        if (pulse_detect->verbosity >= LOG_INFO) {\n                            print_att_hist(\"PULSE_DATA_FSK\", att_hist);\n                        }\n                        if (pulse_detect->verbosity >= LOG_NOTICE) {\n                            fprintf(stderr, \"Levels low: -%d dB  high: -%d dB  thres: -%d dB  hyst: (-%d to -%d dB)\\n\",\n                                    mag_to_att(s->ook_low_estimate), mag_to_att(s->ook_high_estimate),\n                                    mag_to_att(ook_threshold),\n                                    mag_to_att(ook_threshold + ook_hysteresis),\n                                    mag_to_att(ook_threshold - ook_hysteresis));\n                        }\n                        return PULSE_DATA_FSK;\n                    }\n                } // if\n                // FSK Demodulation (continue during short gap - we might return...)\n                if (pulses->num_pulses == 0) {    // Only during first pulse\n                    if (fpdm == FSK_PULSE_DETECT_OLD) {\n                        pulse_detect_fsk_classic(&s->pulse_detect_fsk, fm_data[s->data_counter], fsk_pulses);\n                    } else {\n                        pulse_detect_fsk_minmax(&s->pulse_detect_fsk, fm_data[s->data_counter], fsk_pulses);\n                    }\n                }\n                break;\n            case PD_OOK_STATE_GAP:\n                s->pulse_length += 1;\n                // New pulse detected?\n                if (am_n > (ook_threshold + ook_hysteresis)) {    // New pulse?\n                    pulses->gap[pulses->num_pulses] = s->pulse_length;    // Store gap width\n                    pulses->num_pulses += 1;    // Next pulse\n\n                    // EOP if too many pulses\n                    if (pulses->num_pulses >= PD_MAX_PULSES) {\n                        s->ook_state = PD_OOK_STATE_IDLE;\n                        // Store estimates\n                        pulses->ook_low_estimate = s->ook_low_estimate;\n                        pulses->ook_high_estimate = s->ook_high_estimate;\n                        pulses->end_ago = len - s->data_counter;\n                        if (pulse_detect->verbosity >= LOG_INFO) {\n                            print_att_hist(\"PULSE_DATA_OOK MAX_PULSES\", att_hist);\n                        }\n                        return PULSE_DATA_OOK;    // End Of Package!!\n                    }\n\n                    s->pulse_length = 0;\n                    s->ook_state = PD_OOK_STATE_PULSE;\n                }\n\n                // EOP if gap is too long\n                if (eop_on_spurious\n                        || (s->pulse_length > (PD_MAX_GAP_RATIO * s->max_pulse)    // gap/pulse ratio exceeded\n                            && s->pulse_length > (PD_MIN_GAP_MS * samples_per_ms)) // Minimum gap exceeded\n                        || s->pulse_length > (PD_MAX_GAP_MS * samples_per_ms)) {   // maximum gap exceeded\n                    pulses->gap[pulses->num_pulses] = s->pulse_length;    // Store gap width\n                    pulses->num_pulses += 1;    // Store last pulse\n                    s->ook_state = PD_OOK_STATE_IDLE;\n                    // Store estimates\n                    pulses->ook_low_estimate = s->ook_low_estimate;\n                    pulses->ook_high_estimate = s->ook_high_estimate;\n                    pulses->end_ago = len - s->data_counter;\n                    if (pulse_detect->verbosity >= LOG_INFO) {\n                        print_att_hist(\"PULSE_DATA_OOK EOP\", att_hist);\n                    }\n                    if (pulse_detect->verbosity >= LOG_NOTICE) {\n                        fprintf(stderr, \"Levels low: -%d dB  high: -%d dB  thres: -%d dB  hyst: (-%d to -%d dB)\\n\",\n                                mag_to_att(s->ook_low_estimate), mag_to_att(s->ook_high_estimate),\n                                mag_to_att(ook_threshold),\n                                mag_to_att(ook_threshold + ook_hysteresis),\n                                mag_to_att(ook_threshold - ook_hysteresis));\n                    }\n                    return PULSE_DATA_OOK;    // End Of Package!!\n                }\n                break;\n            default:\n                fprintf(stderr, \"demod_OOK(): Unknown state!!\\n\");\n                s->ook_state = PD_OOK_STATE_IDLE;\n        } // switch\n        s->data_counter += 1;\n    } // while\n\n    s->data_counter = 0;\n    if (pulse_detect->verbosity >= LOG_DEBUG) {\n        print_att_hist(\"Out of data\", att_hist);\n    }\n    return 0;    // Out of data\n}\n"
  },
  {
    "path": "src/pulse_detect_fsk.c",
    "content": "/** @file\n    Pulse detection functions, FSK pulse detector.\n\n    Copyright (C) 2015 Tommy Vestermark\n    Copyright (C) 2019 Benjamin Larsson.\n    Copyright (C) 2022 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"pulse_detect_fsk.h\"\n#include \"c_util.h\" // for MIN(), MAX()\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n// FSK adaptive frequency estimator constants\n#define FSK_DEFAULT_FM_DELTA 6000       // Default estimate for frequency delta\n#define FSK_EST_SLOW        64          // Constant for slowness of FSK estimators\n#define FSK_EST_FAST        16          // Constant for slowness of FSK estimators\n\nvoid pulse_detect_fsk_init(pulse_detect_fsk_t *s)\n{\n    *s              = (pulse_detect_fsk_t){0};\n    s->var_test_max = INT16_MIN;\n    s->var_test_min = INT16_MAX;\n    s->skip_samples = 40;\n}\n\nvoid pulse_detect_fsk_classic(pulse_detect_fsk_t *s, int16_t fm_n, pulse_data_t *fsk_pulses)\n{\n    int const fm_f1_delta = abs(fm_n - s->fm_f1_est); // Get delta from F1 frequency estimate\n    int const fm_f2_delta = abs(fm_n - s->fm_f2_est); // Get delta from F2 frequency estimate\n    s->fsk_pulse_length += 1;\n\n    switch(s->fsk_state) {\n        case PD_FSK_STATE_INIT:        // Initial frequency - High or low?\n            // Initial samples?\n            if (s->fsk_pulse_length < PD_MIN_PULSE_SAMPLES) {\n                s->fm_f1_est = s->fm_f1_est/2 + fm_n/2;        // Quick initial estimator\n            }\n            // Above default frequency delta?\n            else if (fm_f1_delta > (FSK_DEFAULT_FM_DELTA/2)) {\n                // Positive frequency delta - Initial frequency was low (gap)\n                if (fm_n > s->fm_f1_est) {\n                    s->fsk_state = PD_FSK_STATE_FH;\n                    s->fm_f2_est = s->fm_f1_est;    // Switch estimates\n                    s->fm_f1_est = fm_n;            // Prime F1 estimate\n                    fsk_pulses->pulse[0] = 0;        // Initial frequency was a gap...\n                    fsk_pulses->gap[0] = s->fsk_pulse_length;        // Store gap width\n                    fsk_pulses->num_pulses += 1;\n                    s->fsk_pulse_length = 0;\n                }\n                // Negative Frequency delta - Initial frequency was high (pulse)\n                else {\n                    s->fsk_state = PD_FSK_STATE_FL;\n                    s->fm_f2_est = fm_n;    // Prime F2 estimate\n                    fsk_pulses->pulse[0] = s->fsk_pulse_length;    // Store pulse width\n                    s->fsk_pulse_length = 0;\n                }\n            }\n            // Still below threshold\n            else {\n                s->fm_f1_est += fm_n/FSK_EST_FAST - s->fm_f1_est/FSK_EST_FAST;    // Fast estimator\n            }\n            break;\n        case PD_FSK_STATE_FH:        // Pulse high at F1 frequency\n            // Closer to F2 than F1?\n            if (fm_f1_delta > fm_f2_delta) {\n                s->fsk_state = PD_FSK_STATE_FL;\n                // Store if pulse is not too short (suppress spurious)\n                if (s->fsk_pulse_length >= PD_MIN_PULSE_SAMPLES) {\n                    fsk_pulses->pulse[fsk_pulses->num_pulses] = s->fsk_pulse_length;    // Store pulse width\n                    s->fsk_pulse_length = 0;\n                }\n                // Else rewind to last gap\n                else {\n                    s->fsk_pulse_length += fsk_pulses->gap[fsk_pulses->num_pulses-1];    // Restore counter\n                    fsk_pulses->num_pulses -= 1;        // Rewind one pulse\n                    // Are we back to initial frequency? (Was initial frequency a gap?)\n                    if ((fsk_pulses->num_pulses == 0) && (fsk_pulses->pulse[0] == 0)) {\n                        s->fm_f1_est = s->fm_f2_est;    // Switch back estimates\n                        s->fsk_state = PD_FSK_STATE_INIT;\n                    }\n                }\n            }\n            // Still below threshold\n            else {\n                if (fm_n > s->fm_f1_est) {\n                    s->fm_f1_est += fm_n/FSK_EST_FAST - s->fm_f1_est/FSK_EST_FAST;    // Fast estimator\n                } else {\n                    s->fm_f1_est += fm_n/FSK_EST_SLOW - s->fm_f1_est/FSK_EST_SLOW;    // Slow estimator\n                }\n            }\n            break;\n        case PD_FSK_STATE_FL:        // Pulse gap at F2 frequency\n            // Freq closer to F1 than F2 ?\n            if (fm_f2_delta > fm_f1_delta) {\n                s->fsk_state = PD_FSK_STATE_FH;\n                // Store if pulse is not too short (suppress spurious)\n                if (s->fsk_pulse_length >= PD_MIN_PULSE_SAMPLES) {\n                    fsk_pulses->gap[fsk_pulses->num_pulses] = s->fsk_pulse_length;    // Store gap width\n                    fsk_pulses->num_pulses += 1;    // Go to next pulse\n                    s->fsk_pulse_length = 0;\n                    // When pulse buffer is full go to error state\n                    if (fsk_pulses->num_pulses >= PD_MAX_PULSES) {\n                        //fprintf(stderr, \"pulse_detect_fsk_classic(): Maximum number of pulses reached!\\n\");\n                        //s->fsk_state = PD_FSK_STATE_ERROR;\n                        // TODO: workaround, specifically for the Inkbird-ITH20R: free some of the buffer\n                        pulse_data_shift(fsk_pulses);\n                    }\n                }\n                // Else rewind to last pulse\n                else {\n                    s->fsk_pulse_length += fsk_pulses->pulse[fsk_pulses->num_pulses];    // Restore counter\n                    // Are we back to initial frequency?\n                    if (fsk_pulses->num_pulses == 0) {\n                        s->fsk_state = PD_FSK_STATE_INIT;\n                    }\n                }\n            }\n            // Still below threshold\n            else {\n                if (fm_n < s->fm_f2_est) {\n                    s->fm_f2_est += fm_n/FSK_EST_FAST - s->fm_f2_est/FSK_EST_FAST;    // Fast estimator\n                } else {\n                    s->fm_f2_est += fm_n/FSK_EST_SLOW - s->fm_f2_est/FSK_EST_SLOW;    // Slow estimator\n                }\n            }\n            break;\n        case PD_FSK_STATE_ERROR:        // Stay here until cleared\n            break;\n        default:\n            fprintf(stderr, \"pulse_detect_fsk_classic(): Unknown FSK state!!\\n\");\n            s->fsk_state = PD_FSK_STATE_ERROR;\n    } // switch(s->fsk_state)\n}\n\nvoid pulse_detect_fsk_wrap_up(pulse_detect_fsk_t *s, pulse_data_t *fsk_pulses)\n{\n    if (fsk_pulses->num_pulses < PD_MAX_PULSES) { // Avoid overflow\n        s->fsk_pulse_length += 1;\n        if (s->fsk_state == PD_FSK_STATE_FH) {\n            fsk_pulses->pulse[fsk_pulses->num_pulses] = s->fsk_pulse_length; // Store last pulse\n            fsk_pulses->gap[fsk_pulses->num_pulses]   = 0;                   // Zero gap at end\n        }\n        else {\n            fsk_pulses->gap[fsk_pulses->num_pulses] = s->fsk_pulse_length; // Store last gap\n        }\n        fsk_pulses->num_pulses += 1;\n    }\n}\n\nvoid pulse_detect_fsk_minmax(pulse_detect_fsk_t *s, int16_t fm_n, pulse_data_t *fsk_pulses)\n{\n    int16_t mid = 0;\n\n    /* Skip a few samples in the beginning, need for framing\n     * otherwise the min/max trackers won't converge properly\n     */\n    if (!s->skip_samples) {\n        s->var_test_max = MAX(fm_n, s->var_test_max);\n        s->var_test_min = MIN(fm_n, s->var_test_min);\n        mid = (s->var_test_max + s->var_test_min) / 2;\n        if (fm_n > mid) {\n            s->var_test_max -= 10;\n        }\n        if (fm_n < mid) {\n            s->var_test_min += 10;\n        }\n\n        s->fsk_pulse_length += 1;\n        switch(s->fsk_state) {\n            case PD_FSK_STATE_INIT:\n                if (fm_n > mid) {\n                    s->fsk_state = PD_FSK_STATE_FH;\n                }\n                if (fm_n <= mid) {\n                    s->fsk_state = PD_FSK_STATE_FL;\n                }\n                break;\n            case PD_FSK_STATE_FH:\n                if (fm_n < mid) {\n                    s->fsk_state = PD_FSK_STATE_FL;\n                    fsk_pulses->pulse[fsk_pulses->num_pulses] = s->fsk_pulse_length;\n                    s->fsk_pulse_length = 0;\n                }\n                s->fm_f2_est += fm_n / FSK_EST_SLOW - s->fm_f2_est / FSK_EST_SLOW; // Slow estimator\n                break;\n            case PD_FSK_STATE_FL:\n                if (fm_n > mid) {\n                    s->fsk_state = PD_FSK_STATE_FH;\n                    fsk_pulses->gap[fsk_pulses->num_pulses] = s->fsk_pulse_length;\n                    fsk_pulses->num_pulses += 1;\n                    s->fsk_pulse_length = 0;\n                    // When pulse buffer is full go to error state\n                    if (fsk_pulses->num_pulses >= PD_MAX_PULSES) {\n                        //fprintf(stderr, \"pulse_detect_fsk_minmax(): Maximum number of pulses reached!\\n\");\n                        //s->fsk_state = PD_FSK_STATE_ERROR;\n                        // TODO: workaround, specifically for the Inkbird-ITH20R: free some of the buffer\n                        pulse_data_shift(fsk_pulses);\n                    }\n                }\n                s->fm_f1_est += fm_n / FSK_EST_SLOW - s->fm_f1_est / FSK_EST_SLOW; // Slow estimator\n                break;\n            case PD_FSK_STATE_ERROR:        // Stay here until cleared\n                break;\n            default:\n                fprintf(stderr, \"pulse_detect_fsk_minmax(): Unknown FSK state!!\\n\");\n                s->fsk_state = PD_FSK_STATE_ERROR;\n                break;\n        }\n    }\n    if (s->skip_samples > 0) {\n        s->skip_samples -= 1;\n    }\n}\n"
  },
  {
    "path": "src/pulse_slicer.c",
    "content": "/** @file\n    Pulse demodulation functions.\n\n    Binary demodulators (PWM/PPM/Manchester/...) using a pulse data structure as input.\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"pulse_slicer.h\"\n#include \"pulse_data.h\"\n#include \"bitbuffer.h\"\n#include \"c_util.h\" // for MIN()\n#include \"logger.h\"\n#include \"decoder_util.h\" // TODO: this should be refactored\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <math.h>\n#include <limits.h>\n\nstatic int account_event(r_device *device, bitbuffer_t *bits, char const *demod_name)\n{\n    // run decoder\n    int ret = 0;\n    if (device->decode_fn) {\n        ret = device->decode_fn(device, bits);\n    }\n\n    // statistics accounting\n    device->decode_events += 1;\n    if (ret > 0) {\n        device->decode_ok += 1;\n        device->decode_messages += ret;\n    }\n    else if (ret >= DECODE_FAIL_SANITY) {\n        device->decode_fails[-ret] += 1;\n        ret = 0;\n    }\n    else {\n        print_logf(LOG_ERROR, demod_name, \"Decoder \\\"%s\\\" gave invalid return value %d: notify maintainer\", device->name, ret);\n        exit(1);\n    }\n\n    // Find longest row\n    unsigned max_bits = 0;\n    for (int row = 0; row < bits->num_rows; ++row) {\n        if (bits->bits_per_row[row] > max_bits) {\n            max_bits = bits->bits_per_row[row];\n        }\n    }\n\n    // Debug printout\n    if (!device->decode_fn || (device->verbose && ret > 0) || (device->verbose > 1 && max_bits > 16) || (device->verbose > 2)) {\n        decoder_log_bitbuffer(device, ret > 0 ? 1 : 2, demod_name, bits, device->name);\n    }\n\n    return ret;\n}\n\nint pulse_slicer_pcm(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    // precision reciprocals\n    float f_short = device->short_width > 0.0f ? 1.0f / (device->short_width * samples_per_us) : 0;\n    float f_long  = device->long_width > 0.0f ? 1.0f / (device->long_width * samples_per_us) : 0;\n\n    int events = 0;\n    bitbuffer_t bits = {0};\n\n    int const gap_limit = s_gap ? s_gap : s_reset;\n    int const max_zeros = gap_limit / s_long;\n    if (s_tolerance <= 0)\n        s_tolerance = s_long / 4; // default tolerance is +-25% of a bit period\n\n    // if there is a run of bit-wide toggles (preamble) tune the bit period\n    int min_count = s_short == s_long ? 12 : 4;\n    int preamble_len = 0;\n    // RZ\n    for (unsigned n = 0; s_short != s_long && n < pulses->num_pulses; ++n) {\n        int swidth = 0;\n        int lwidth = 0;\n        int count = 0;\n        while (n < pulses->num_pulses\n                && pulses->pulse[n] >= s_short - s_tolerance\n                && pulses->pulse[n] <= s_short + s_tolerance\n                && pulses->pulse[n] + pulses->gap[n] >= s_long - s_tolerance\n                && pulses->pulse[n] + pulses->gap[n] <= s_long + s_tolerance) {\n            swidth += pulses->pulse[n];\n            lwidth += pulses->pulse[n] + pulses->gap[n];\n            count += 1;\n            n++;\n        }\n        // require at least min_count bits preamble\n        if (count >= min_count) {\n            f_long  = (float)count / lwidth;\n            f_short = (float)count / swidth;\n            min_count = count;\n            preamble_len = count;\n            if (device->verbose > 1) {\n                float to_us = 1e6f / pulses->sample_rate;\n                print_logf(LOG_INFO, __func__, \"Exact bit width (in us) is %.2f vs %.2f (pulse width %.2f vs %.2f), %d bit preamble\",\n                        to_us / f_long, to_us * s_long,\n                        to_us / f_short, to_us * s_short, count);\n            }\n        }\n    }\n    // RZ bits within tolerance anywhere\n    int rzs_width = 0;\n    int rzl_width = 0;\n    int rz_count = 0;\n    for (unsigned n = 0; preamble_len == 0 && s_short != s_long && n < pulses->num_pulses; ++n) {\n        if (pulses->pulse[n] >= s_short - s_tolerance\n                && pulses->pulse[n] <= s_short + s_tolerance\n                && pulses->pulse[n] + pulses->gap[n] >= s_long - s_tolerance\n                && pulses->pulse[n] + pulses->gap[n] <= s_long + s_tolerance) {\n            rzs_width += pulses->pulse[n];\n            rzl_width += pulses->pulse[n] + pulses->gap[n];\n            rz_count += 1;\n        }\n    }\n    // require at least 8 bits measured\n    if (rz_count > 8) {\n        f_long  = (float)rz_count / rzl_width;\n        f_short = (float)rz_count / rzs_width;\n        if (device->verbose > 1) {\n            float to_us = 1e6 / pulses->sample_rate;\n            print_logf(LOG_INFO, __func__, \"Exact bit width (in us) is %.2f vs %.2f (pulse width %.2f vs %.2f), %d bit measured\",\n                    to_us / f_long, to_us * s_long,\n                    to_us / f_short, to_us * s_short, rz_count);\n        }\n    }\n    // NRZ\n    for (unsigned n = 0; s_short == s_long && n < pulses->num_pulses; ++n) {\n        int width = 0;\n        int count = 0;\n        while (n < pulses->num_pulses\n                && (int)(pulses->pulse[n] * f_short + 0.5) == 1\n                && (int)(pulses->gap[n] * f_long + 0.5) == 1) {\n            width += pulses->pulse[n] + pulses->gap[n];\n            count += 2;\n            n++;\n        }\n        // require at least min_count full bits preamble\n        if (count >= min_count) {\n            f_short = f_long = (float)count / width;\n            min_count = count;\n            preamble_len = count;\n            if (device->verbose > 1) {\n                float to_us = 1e6f / pulses->sample_rate;\n                print_logf(LOG_INFO, __func__, \"Exact bit width (in us) is %.2f vs %.2f, %d bit preamble\",\n                        to_us / f_short, to_us * s_short, count);\n            }\n        }\n    }\n    // NRZ pulse/gap of len 1 or 2 within tolerance anywhere\n    int nrz_width = 0;\n    int nrz_count = 0;\n    for (unsigned n = 0; preamble_len == 0 && s_short == s_long && n < pulses->num_pulses; ++n) {\n        if (pulses->pulse[n] >= s_short - s_tolerance\n                && pulses->pulse[n] <= s_short + s_tolerance) {\n            nrz_width += pulses->pulse[n];\n            nrz_count += 1;\n        }\n        if (pulses->pulse[n] >= 2 * s_short - s_tolerance\n                && pulses->pulse[n] <= 2 * s_short + s_tolerance) {\n            nrz_width += pulses->pulse[n];\n            nrz_count += 2;\n        }\n        if (pulses->gap[n] >= s_long - s_tolerance\n                && pulses->gap[n] <= s_long + s_tolerance) {\n            nrz_width += pulses->gap[n];\n            nrz_count += 1;\n        }\n        if (pulses->gap[n] >= 2 * s_long - s_tolerance\n                && pulses->gap[n] <= 2 * s_long + s_tolerance) {\n            nrz_width += pulses->gap[n];\n            nrz_count += 2;\n        }\n    }\n    // require at least 10 bits measured\n    if (nrz_count > 20) {\n        f_short = f_long = (float)nrz_count / nrz_width;\n        if (device->verbose > 1) {\n            float to_us = 1e6 / pulses->sample_rate;\n            print_logf(LOG_INFO, __func__, \"%s: Exact bit width (in us) is %.2f vs %.2f, %d bit measured\", device->name,\n                    to_us / f_short, to_us * s_short, nrz_count);\n        }\n    }\n\n    for (unsigned n = 0; n < pulses->num_pulses; ++n) {\n        // Determine number of high bit periods for NRZ coding, where bits may not be separated\n        int highs = (pulses->pulse[n]) * f_short + 0.5f;\n        // Determine number of low bit periods in current gap length (rounded)\n        // for RZ subtract the nominal bit-gap\n        int lows = (pulses->gap[n] + s_short - s_long) * f_long + 0.5f;\n\n        // Add run of ones (1 for RZ, many for NRZ)\n        for (int i = 0; i < highs; ++i) {\n            bitbuffer_add_bit(&bits, 1);\n        }\n        // Add run of zeros, handle possibly negative \"lows\" gracefully\n        lows = MIN(lows, max_zeros); // Don't overflow at end of message\n        for (int i = 0; i < lows; ++i) {\n            bitbuffer_add_bit(&bits, 0);\n        }\n\n        // Validate data\n        if ((s_short != s_long)                                       // Only for RZ coding\n                && (abs(pulses->pulse[n] - s_short) > s_tolerance)) { // Pulse must be within tolerance\n\n            // Data is corrupt\n            if (device->verbose > 3) {\n                print_logf(LOG_TRACE, __func__, \"bitbuffer cleared at %u: pulse %d, gap %d, period %d\",\n                        n, pulses->pulse[n], pulses->gap[n],\n                        pulses->pulse[n] + pulses->gap[n]);\n            }\n            bitbuffer_clear(&bits);\n        }\n\n        // Check for new packet in multipacket\n        else if (pulses->gap[n] > gap_limit && pulses->gap[n] <= s_reset) {\n            bitbuffer_add_row(&bits);\n        }\n        // End of Message?\n        if (((n == pulses->num_pulses - 1)                            // No more pulses? (FSK)\n                    || (pulses->gap[n] > s_reset))      // Long silence (OOK)\n                && (bits.bits_per_row[0] > 0 || bits.num_rows > 1)) { // Only if data has been accumulated\n\n            events += account_event(device, &bits, __func__);\n            bitbuffer_clear(&bits);\n        }\n    } // for\n    return events;\n}\n\nint pulse_slicer_ppm(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    int events = 0;\n    bitbuffer_t bits = {0};\n\n    // lower and upper bounds (non inclusive)\n    int zero_l, zero_u;\n    int one_l, one_u;\n    int sync_l = 0, sync_u = 0;\n\n    if (s_tolerance > 0) {\n        // precise\n        zero_l = s_short - s_tolerance;\n        zero_u = s_short + s_tolerance;\n        one_l  = s_long - s_tolerance;\n        one_u  = s_long + s_tolerance;\n        if (s_sync > 0) {\n            sync_l = s_sync - s_tolerance;\n            sync_u = s_sync + s_tolerance;\n        }\n    }\n    else {\n        // no sync, short=0, long=1\n        zero_l = 0;\n        zero_u = (s_short + s_long) / 2 + 1;\n        one_l  = zero_u - 1;\n        one_u  = s_gap ? s_gap : s_reset;\n    }\n\n    for (unsigned n = 0; n < pulses->num_pulses; ++n) {\n        if (pulses->gap[n] > zero_l && pulses->gap[n] < zero_u) {\n            // Short gap\n            bitbuffer_add_bit(&bits, 0);\n        }\n        else if (pulses->gap[n] > one_l && pulses->gap[n] < one_u) {\n            // Long gap\n            bitbuffer_add_bit(&bits, 1);\n        }\n        else if (pulses->gap[n] > sync_l && pulses->gap[n] < sync_u) {\n            // Sync gap\n            bitbuffer_add_sync(&bits);\n        }\n\n        // Check for new packet in multipacket\n        else if (pulses->gap[n] < s_reset) {\n            bitbuffer_add_row(&bits);\n        }\n        // End of Message?\n        if (((n == pulses->num_pulses - 1)                            // No more pulses? (FSK)\n                    || (pulses->gap[n] >= s_reset))     // Long silence (OOK)\n                && (bits.bits_per_row[0] > 0 || bits.num_rows > 1)) { // Only if data has been accumulated\n\n            events += account_event(device, &bits, __func__);\n            bitbuffer_clear(&bits);\n        }\n    } // for pulses\n    return events;\n}\n\nint pulse_slicer_pwm(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    int events = 0;\n    bitbuffer_t bits = {0};\n\n    // lower and upper bounds (non inclusive)\n    int one_l, one_u;\n    int zero_l, zero_u;\n    int sync_l = 0, sync_u = 0;\n\n    if (s_tolerance > 0) {\n        // precise\n        one_l  = s_short - s_tolerance;\n        one_u  = s_short + s_tolerance;\n        zero_l = s_long - s_tolerance;\n        zero_u = s_long + s_tolerance;\n        if (s_sync > 0) {\n            sync_l = s_sync - s_tolerance;\n            sync_u = s_sync + s_tolerance;\n        }\n    }\n    else if (s_sync <= 0) {\n        // no sync, short=1, long=0\n        one_l  = 0;\n        one_u  = (s_short + s_long) / 2 + 1;\n        zero_l = one_u - 1;\n        zero_u = INT_MAX;\n    }\n    else if (s_sync < s_short) {\n        // short=sync, middle=1, long=0\n        sync_l = 0;\n        sync_u = (s_sync + s_short) / 2 + 1;\n        one_l  = sync_u - 1;\n        one_u  = (s_short + s_long) / 2 + 1;\n        zero_l = one_u - 1;\n        zero_u = INT_MAX;\n    }\n    else if (s_sync < s_long) {\n        // short=1, middle=sync, long=0\n        one_l  = 0;\n        one_u  = (s_short + s_sync) / 2 + 1;\n        sync_l = one_u - 1;\n        sync_u = (s_sync + s_long) / 2 + 1;\n        zero_l = sync_u - 1;\n        zero_u = INT_MAX;\n    }\n    else {\n        // short=1, middle=0, long=sync\n        one_l  = 0;\n        one_u  = (s_short + s_long) / 2 + 1;\n        zero_l = one_u - 1;\n        zero_u = (s_long + s_sync) / 2 + 1;\n        sync_l = zero_u - 1;\n        sync_u = INT_MAX;\n    }\n\n    for (unsigned n = 0; n < pulses->num_pulses; ++n) {\n        if (pulses->pulse[n] > one_l && pulses->pulse[n] < one_u) {\n            // 'Short' 1 pulse\n            bitbuffer_add_bit(&bits, 1);\n        }\n        else if (pulses->pulse[n] > zero_l && pulses->pulse[n] < zero_u) {\n            // 'Long' 0 pulse\n            bitbuffer_add_bit(&bits, 0);\n        }\n        else if (pulses->pulse[n] > sync_l && pulses->pulse[n] < sync_u) {\n            // Sync pulse\n            bitbuffer_add_sync(&bits);\n        }\n        else if (pulses->pulse[n] <= one_l) {\n            // Ignore spurious short pulses\n        }\n        else {\n            // Pulse outside specified timing\n            bitbuffer_add_row(&bits);\n        }\n\n        // End of Message?\n        if (((n == pulses->num_pulses - 1)                       // No more pulses? (FSK)\n                    || (pulses->gap[n] > s_reset)) // Long silence (OOK)\n                && (bits.num_rows > 0)) {                        // Only if data has been accumulated\n            events += account_event(device, &bits, __func__);\n            bitbuffer_clear(&bits);\n        }\n        else if (s_gap > 0 && pulses->gap[n] > s_gap\n                && bits.num_rows > 0 && bits.bits_per_row[bits.num_rows - 1] > 0) {\n            // New packet in multipacket\n            bitbuffer_add_row(&bits);\n        }\n    }\n    return events;\n}\n\nint pulse_slicer_manchester_zerobit(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    int events = 0;\n    int time_since_last = 0;\n    bitbuffer_t bits = {0};\n\n    // First rising edge is always counted as a zero (Seems to be hardcoded policy for the Oregon Scientific sensors...)\n    bitbuffer_add_bit(&bits, 0);\n\n    for (unsigned n = 0; n < pulses->num_pulses; ++n) {\n        // The pulse or gap is too long or too short, thus invalid\n        if (s_tolerance > 0\n                && (pulses->pulse[n] < s_short - s_tolerance\n                || pulses->pulse[n] > s_short * 2 + s_tolerance\n                || pulses->gap[n] < s_short - s_tolerance\n                || pulses->gap[n] > s_short * 2 + s_tolerance)) {\n            if (pulses->pulse[n] > s_short * 1.5\n                    && pulses->pulse[n] <= s_short * 2 + s_tolerance) {\n                // Long last pulse means with the gap this is a [1]10 transition, add a one\n                bitbuffer_add_bit(&bits, 1);\n            }\n            bitbuffer_add_row(&bits);\n            bitbuffer_add_bit(&bits, 0); // Prepare for new message with hardcoded 0\n            time_since_last = 0;\n        }\n        // Falling edge is on end of pulse\n        else if (pulses->pulse[n] + time_since_last > (s_short * 1.5)) {\n            // Last bit was recorded more than short_width*1.5 samples ago\n            // so this pulse start must be a data edge (falling data edge means bit = 1)\n            bitbuffer_add_bit(&bits, 1);\n            time_since_last = 0;\n        }\n        else {\n            time_since_last += pulses->pulse[n];\n        }\n\n        // End of Message?\n        if (((n == pulses->num_pulses - 1)                       // No more pulses? (FSK)\n                    || (pulses->gap[n] > s_reset)) // Long silence (OOK)\n                && (bits.num_rows > 0)) {                        // Only if data has been accumulated\n            events += account_event(device, &bits, __func__);\n            bitbuffer_clear(&bits);\n            bitbuffer_add_bit(&bits, 0); // Prepare for new message with hardcoded 0\n            time_since_last = 0;\n        }\n        // Rising edge is on end of gap\n        else if (pulses->gap[n] + time_since_last > (s_short * 1.5)) {\n            // Last bit was recorded more than short_width*1.5 samples ago\n            // so this pulse end is a data edge (rising data edge means bit = 0)\n            bitbuffer_add_bit(&bits, 0);\n            time_since_last = 0;\n        }\n        else {\n            time_since_last += pulses->gap[n];\n        }\n    }\n    return events;\n}\n\nstatic inline int pulse_slicer_get_symbol(pulse_data_t const *pulses, unsigned int n)\n{\n    if (n % 2 == 0)\n        return pulses->pulse[n / 2];\n    else\n        return pulses->gap[n / 2];\n}\n\nint pulse_slicer_dmc(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    bitbuffer_t bits = {0};\n    int events = 0;\n\n    for (unsigned int n = 0; n < pulses->num_pulses * 2; ++n) {\n        int symbol = pulse_slicer_get_symbol(pulses, n);\n\n        if (abs(symbol - s_short) < s_tolerance) {\n            // Short - 1\n            bitbuffer_add_bit(&bits, 1);\n            symbol = n + 1 < pulses->num_pulses * 2 ? pulse_slicer_get_symbol(pulses, ++n) : 0;\n            if (abs(symbol - s_short) > s_tolerance) {\n                if (symbol >= s_reset - s_tolerance) {\n                    // Don't expect another short gap at end of message\n                    n--;\n                }\n                else if (bits.num_rows > 0 && bits.bits_per_row[bits.num_rows - 1] > 0) {\n                    bitbuffer_add_row(&bits);\n/*\n                    print_logf(LOG_WARNING, __func__, \"Detected error during pulse_slicer_dmc(): %s\",\n                            device->name);\n*/\n                }\n            }\n        }\n        else if (abs(symbol - s_long) < s_tolerance) {\n            // Long - 0\n            bitbuffer_add_bit(&bits, 0);\n        }\n        else if (symbol >= s_reset - s_tolerance\n                && bits.num_rows > 0) { // Only if data has been accumulated\n            //END message ?\n            events += account_event(device, &bits, __func__);\n        }\n    }\n\n    return events;\n}\n\nint pulse_slicer_piwm_raw(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    // precision reciprocal\n    float f_short = device->short_width > 0.0f ? 1.0f / (device->short_width * samples_per_us) : 0;\n\n    int w;\n\n    bitbuffer_t bits = {0};\n    int events = 0;\n\n    for (unsigned int n = 0; n < pulses->num_pulses * 2; ++n) {\n        int symbol = pulse_slicer_get_symbol(pulses, n);\n        w = symbol * f_short + 0.5;\n        if (symbol > s_long) {\n            bitbuffer_add_row(&bits);\n        }\n        else if (abs(symbol - w * s_short) < s_tolerance) {\n            // Add w symbols\n            for (; w > 0; --w)\n                bitbuffer_add_bit(&bits, 1 - n % 2);\n        }\n        else if (symbol < s_reset\n                && bits.num_rows > 0\n                && bits.bits_per_row[bits.num_rows - 1] > 0) {\n            bitbuffer_add_row(&bits);\n/*\n            print_logf(LOG_WARNING, __func__, \"Detected error during pulse_slicer_piwm_raw(): %s\",\n                    device->name);\n*/\n        }\n\n        if (((n == pulses->num_pulses * 2 - 1)              // No more pulses? (FSK)\n                    || (symbol > s_reset)) // Long silence (OOK)\n                && (bits.num_rows > 0)) {                   // Only if data has been accumulated\n            //END message ?\n            events += account_event(device, &bits, __func__);\n        }\n    }\n\n    return events;\n}\n\nint pulse_slicer_piwm_dc(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    bitbuffer_t bits = {0};\n    int events = 0;\n\n    for (unsigned int n = 0; n < pulses->num_pulses * 2; ++n) {\n        int symbol = pulse_slicer_get_symbol(pulses, n);\n        if (abs(symbol - s_short) < s_tolerance) {\n            // Short - 1\n            bitbuffer_add_bit(&bits, 1);\n        }\n        else if (abs(symbol - s_long) < s_tolerance) {\n            // Long - 0\n            bitbuffer_add_bit(&bits, 0);\n        }\n        else if (symbol < s_reset\n                && bits.num_rows > 0\n                && bits.bits_per_row[bits.num_rows - 1] > 0) {\n            bitbuffer_add_row(&bits);\n/*\n            print_logf(LOG_WARNING, __func__, \"Detected error during pulse_slicer_piwm_dc(): %s\",\n                    device->name);\n*/\n        }\n\n        if (((n == pulses->num_pulses * 2 - 1)              // No more pulses? (FSK)\n                    || (symbol > s_reset)) // Long silence (OOK)\n                && (bits.num_rows > 0)) {                   // Only if data has been accumulated\n            //END message ?\n            events += account_event(device, &bits, __func__);\n        }\n    }\n\n    return events;\n}\n\nint pulse_slicer_nrzs(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    int events = 0;\n    bitbuffer_t bits = {0};\n    int limit = s_short;\n\n    for (unsigned n = 0; n < pulses->num_pulses; ++n) {\n        if (pulses->pulse[n] > limit) {\n            for (int i = 0 ; i < (pulses->pulse[n]/limit) ; i++) {\n                bitbuffer_add_bit(&bits, 1);\n            }\n            bitbuffer_add_bit(&bits, 0);\n        } else if (pulses->pulse[n] < limit) {\n            bitbuffer_add_bit(&bits, 0);\n        }\n\n        if (n == pulses->num_pulses - 1\n                    || pulses->gap[n] >= s_reset) {\n\n            events += account_event(device, &bits, __func__);\n        }\n    }\n\n    return events;\n}\n\n\n/*\n * Oregon Scientific V1 Protocol\n * Starts with a clean preamble of 12 pulses with\n * consistent timing followed by an out of time Sync pulse.\n * Data then follows with manchester encoding, but\n * care must be taken with the gap after the sync pulse since it\n * is outside of the normal clocking.  Because of this a data stream\n * beginning with a 0 will have data in this gap.\n * This code looks at pulse and gap width and clocks bits\n * in from this.  Since this is manchester encoded every other\n * bit is discarded.\n */\n\nint pulse_slicer_osv1(pulse_data_t const *pulses, r_device *device)\n{\n    float samples_per_us = pulses->sample_rate / 1.0e6f;\n\n    int s_short = device->short_width * samples_per_us;\n    int s_long  = device->long_width * samples_per_us;\n    int s_reset = device->reset_limit * samples_per_us;\n    int s_gap   = device->gap_limit * samples_per_us;\n    int s_sync  = device->sync_width * samples_per_us;\n    int s_tolerance = device->tolerance * samples_per_us;\n\n    // check for rounding to zero\n    if ((device->short_width > 0 && s_short <= 0)\n            || (device->long_width > 0 && s_long <= 0)\n            || (device->reset_limit > 0 && s_reset <= 0)\n            || (device->gap_limit > 0 && s_gap <= 0)\n            || (device->sync_width > 0 && s_sync <= 0)\n            || (device->tolerance > 0 && s_tolerance <= 0)) {\n        print_logf(LOG_WARNING, __func__, \"sample rate too low for protocol %u \\\"%s\\\"\", device->protocol_num, device->name);\n        return 0;\n    }\n\n    unsigned int n;\n    int preamble = 0;\n    int events = 0;\n    int manbit = 0;\n    bitbuffer_t bits = {0};\n    int halfbit_min = s_short / 2;\n    int halfbit_max = s_short * 3 / 2;\n    int sync_min = 2 * halfbit_max;\n\n    /* preamble */\n    for (n = 0; n < pulses->num_pulses; ++n) {\n        if (pulses->pulse[n] > halfbit_min && pulses->gap[n] > halfbit_min) {\n            preamble++;\n            if (pulses->gap[n] > halfbit_max)\n                break;\n        }\n        else\n            return events;\n    }\n    if (preamble != 12) {\n        if (device->verbose)\n            print_logf(LOG_WARNING, __func__, \"preamble %d  %d %d\", preamble, pulses->pulse[0], pulses->gap[0]);\n        return events;\n    }\n\n    /* sync */\n    ++n;\n    if (pulses->pulse[n] < sync_min || pulses->gap[n] < sync_min) {\n        return events;\n    }\n\n    /* data bits - manchester encoding */\n\n    /* sync gap could be part of data when the first bit is 0 */\n    if (pulses->gap[n] > pulses->pulse[n]) {\n        manbit ^= 1;\n        if (manbit)\n            bitbuffer_add_bit(&bits, 0);\n    }\n\n    /* remaining data bits */\n    for (n++; n < pulses->num_pulses; ++n) {\n        manbit ^= 1;\n        if (manbit)\n            bitbuffer_add_bit(&bits, 1);\n        if (pulses->pulse[n] > halfbit_max) {\n            manbit ^= 1;\n            if (manbit)\n                bitbuffer_add_bit(&bits, 1);\n        }\n        if ((n == pulses->num_pulses - 1\n                    || pulses->gap[n] > s_reset)\n                && (bits.num_rows > 0)) { // Only if data has been accumulated\n            //END message ?\n            events += account_event(device, &bits, __func__);\n            return events;\n        }\n        manbit ^= 1;\n        if (manbit)\n            bitbuffer_add_bit(&bits, 0);\n        if (pulses->gap[n] > halfbit_max) {\n            manbit ^= 1;\n            if (manbit)\n                bitbuffer_add_bit(&bits, 0);\n        }\n    }\n    return events;\n}\n\nint pulse_slicer_string(const char *code, r_device *device)\n{\n    int events = 0;\n    bitbuffer_t bits = {0};\n\n    bitbuffer_parse(&bits, code);\n\n    events += account_event(device, &bits, __func__);\n\n    return events;\n}\n"
  },
  {
    "path": "src/r_api.c",
    "content": "/** @file\n    Generic RF data receiver and decoder for ISM band devices using RTL-SDR and SoapySDR.\n\n    Copyright (C) 2019 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <string.h>\n#include <math.h>\n\n#include \"r_api.h\"\n#include \"r_util.h\"\n#include \"rtl_433.h\"\n#include \"r_private.h\"\n#include \"rtl_433_devices.h\"\n#include \"r_device.h\"\n#include \"pulse_slicer.h\"\n#include \"pulse_detect_fsk.h\"\n#include \"sdr.h\"\n#include \"data.h\"\n#include \"data_tag.h\"\n#include \"list.h\"\n#include \"optparse.h\"\n#include \"output_file.h\"\n#include \"output_log.h\"\n#include \"output_udp.h\"\n#include \"output_mqtt.h\"\n#include \"output_influx.h\"\n#include \"output_trigger.h\"\n#include \"output_rtltcp.h\"\n#include \"write_sigrok.h\"\n#include \"mongoose.h\"\n#include \"compat_time.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include \"http_server.h\"\n\n#ifndef _WIN32\n#include <sys/stat.h>\n#endif\n\n#ifdef _WIN32\n#include <io.h>\n#include <fcntl.h>\n#ifdef _MSC_VER\n#define F_OK 0\n#endif\n#endif\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifndef _MSC_VER\n#include <getopt.h>\n#else\n#include \"getopt/getopt.h\"\n#endif\n\nchar const *version_string(void)\n{\n    return \"rtl_433\"\n#ifdef GIT_VERSION\n#define STR_VALUE(arg) #arg\n#define STR_EXPAND(s) STR_VALUE(s)\n            \" version \" STR_EXPAND(GIT_VERSION)\n#ifdef GIT_BRANCH\n            \" branch \" STR_EXPAND(GIT_BRANCH)\n#endif\n#ifdef GIT_TIMESTAMP\n            \" at \" STR_EXPAND(GIT_TIMESTAMP)\n#endif\n#undef STR_VALUE\n#undef STR_EXPAND\n#else\n            \" version unknown\"\n#endif\n            \" inputs file rtl_tcp\"\n#ifdef RTLSDR\n            \" RTL-SDR\"\n#endif\n#ifdef SOAPYSDR\n            \" SoapySDR\"\n#endif\n#ifdef OPENSSL\n            \" with TLS\"\n#endif\n            ;\n}\n\n/* helper */\n\nstruct mg_mgr *get_mgr(r_cfg_t *cfg)\n{\n    if (!cfg->mgr) {\n        cfg->mgr = calloc(1, sizeof(*cfg->mgr));\n        if (!cfg->mgr)\n            FATAL_CALLOC(\"get_mgr()\");\n        mg_mgr_init(cfg->mgr, NULL);\n    }\n\n    return cfg->mgr;\n}\n\nvoid set_center_freq(r_cfg_t *cfg, uint32_t center_freq)\n{\n    cfg->frequencies = 1;\n    cfg->frequency_index = 0;\n    cfg->frequency[0] = center_freq;\n    // cfg->center_frequency = center_freq; // actually applied in the sdr event\n    sdr_set_center_freq(cfg->dev, center_freq, 1);\n}\n\nvoid set_freq_correction(r_cfg_t *cfg, int freq_correction)\n{\n    // cfg->ppm_error = freq_correction; // actually applied in the sdr event\n    sdr_set_freq_correction(cfg->dev, freq_correction, 0);\n}\n\nvoid set_sample_rate(r_cfg_t *cfg, uint32_t sample_rate)\n{\n    // cfg->samp_rate = sample_rate; // actually applied in the sdr event\n    sdr_set_sample_rate(cfg->dev, sample_rate, 0);\n}\n\nvoid set_gain_str(struct r_cfg *cfg, char const *gain_str)\n{\n    free(cfg->gain_str);\n    if (!gain_str) {\n        cfg->gain_str = NULL; // auto gain\n    }\n    else {\n        cfg->gain_str = strdup(gain_str);\n        if (!cfg->gain_str)\n            WARN_STRDUP(\"set_gain_str()\");\n    }\n    sdr_set_tuner_gain(cfg->dev, gain_str, 0);\n}\n\n/* general */\n\nvoid r_init_cfg(r_cfg_t *cfg)\n{\n    cfg->out_block_size  = DEFAULT_BUF_LENGTH;\n    cfg->samp_rate       = DEFAULT_SAMPLE_RATE;\n    cfg->conversion_mode = CONVERT_NATIVE;\n    cfg->fsk_pulse_detect_mode = FSK_PULSE_DETECT_AUTO;\n    // Default log level is to show all LOG_FATAL, LOG_ERROR, LOG_WARNING\n    // abnormal messages and LOG_CRITICAL information.\n    cfg->verbosity = LOG_WARNING;\n\n    list_ensure_size(&cfg->in_files, 100);\n    list_ensure_size(&cfg->output_handler, 16);\n\n    // collect devices list, this should be a module\n    r_device r_devices[] = {\n#define DECL(name) name,\n            DEVICES\n#undef DECL\n    };\n\n    cfg->num_r_devices = sizeof(r_devices) / sizeof(*r_devices);\n    for (unsigned i = 0; i < cfg->num_r_devices; i++) {\n        r_devices[i].protocol_num = i + 1;\n    }\n    cfg->devices = malloc(sizeof(r_devices));\n    if (!cfg->devices)\n        FATAL_CALLOC(\"r_init_cfg()\");\n\n    memcpy(cfg->devices, r_devices, sizeof(r_devices));\n\n    cfg->demod = calloc(1, sizeof(*cfg->demod));\n    if (!cfg->demod)\n        FATAL_CALLOC(\"r_init_cfg()\");\n\n    cfg->demod->level_limit = 0.0f;\n    cfg->demod->min_level = -12.1442f;\n    cfg->demod->min_snr = 9.0f;\n    // Pulse detect will only print LOG_NOTICE and lower.\n    cfg->demod->detect_verbosity = LOG_WARNING;\n\n    // note: this should be optional\n    cfg->demod->pulse_detect = pulse_detect_create();\n    // initialize tables\n    baseband_init();\n\n    time(&cfg->running_since);\n    time(&cfg->frames_since);\n    get_time_now(&cfg->demod->now);\n\n    list_ensure_size(&cfg->demod->r_devs, 100);\n    list_ensure_size(&cfg->demod->dumper, 32);\n}\n\nr_cfg_t *r_create_cfg(void)\n{\n    r_cfg_t *cfg = calloc(1, sizeof(*cfg));\n    if (!cfg)\n        FATAL_CALLOC(\"r_create_cfg()\");\n\n    r_init_cfg(cfg);\n\n    return cfg;\n}\n\nvoid r_free_cfg(r_cfg_t *cfg)\n{\n    if (cfg->dev) {\n        sdr_deactivate(cfg->dev);\n        sdr_close(cfg->dev);\n        cfg->dev = NULL;\n    }\n\n    free(cfg->gain_str);\n    cfg->gain_str = NULL;\n\n    for (void **iter = cfg->demod->dumper.elems; iter && *iter; ++iter) {\n        file_info_t const *dumper = *iter;\n        if (dumper->file && (dumper->file != stdout))\n            fclose(dumper->file);\n    }\n    list_free_elems(&cfg->demod->dumper, free);\n\n    list_free_elems(&cfg->demod->r_devs, (list_elem_free_fn)free_protocol);\n\n    if (cfg->demod->am_analyze)\n        am_analyze_free(cfg->demod->am_analyze);\n    cfg->demod->am_analyze = NULL;\n\n    pulse_detect_free(cfg->demod->pulse_detect);\n    cfg->demod->pulse_detect = NULL;\n\n    list_free_elems(&cfg->raw_handler, (list_elem_free_fn)raw_output_free);\n\n    r_logger_set_log_handler(NULL, NULL);\n\n    list_free_elems(&cfg->output_handler, (list_elem_free_fn)data_output_free);\n\n    list_free_elems(&cfg->data_tags, (list_elem_free_fn)data_tag_free);\n\n    list_free_elems(&cfg->in_files, NULL);\n\n    free(cfg->demod);\n    cfg->demod = NULL;\n\n    free(cfg->devices);\n    cfg->devices = NULL;\n\n    mg_mgr_free(cfg->mgr);\n    free(cfg->mgr);\n    cfg->mgr = NULL;\n\n    //free(cfg);\n}\n\n/* device decoder protocols */\n\nvoid register_protocol(r_cfg_t *cfg, r_device *r_dev, char *arg)\n{\n    // use arg of 'v', 'vv', 'vvv' as device verbosity\n    int dev_verbose = 0;\n    if (arg && *arg == 'v') {\n        for (; *arg == 'v'; ++arg) {\n            dev_verbose++;\n        }\n        if (*arg) {\n            arg++; // skip separator\n        }\n    }\n\n    // use any other arg as device parameter\n    r_device *p;\n    if (r_dev->create_fn) {\n        p = r_dev->create_fn(arg);\n    }\n    else {\n        if (arg && *arg) {\n            fprintf(stderr, \"Protocol [%u] \\\"%s\\\" does not take arguments \\\"%s\\\"!\\n\", r_dev->protocol_num, r_dev->name, arg);\n        }\n        p  = malloc(sizeof(*p));\n        if (!p)\n            FATAL_CALLOC(\"register_protocol()\");\n        *p = *r_dev; // copy\n    }\n\n    p->verbose      = dev_verbose ? dev_verbose : (cfg->verbosity > 4 ? cfg->verbosity - 5 : 0);\n    p->verbose_bits = cfg->verbose_bits;\n    p->log_fn       = log_device_handler;\n\n    p->output_fn  = data_acquired_handler;\n    p->output_ctx = cfg;\n\n    list_push(&cfg->demod->r_devs, p);\n\n    if (cfg->verbosity >= LOG_INFO) {\n        fprintf(stderr, \"Registering protocol [%u] \\\"%s\\\"\\n\", r_dev->protocol_num, r_dev->name);\n    }\n}\n\nvoid free_protocol(r_device *r_dev)\n{\n    // free(r_dev->name);\n    free(r_dev->decode_ctx);\n    free(r_dev);\n}\n\nvoid unregister_protocol(r_cfg_t *cfg, r_device *r_dev)\n{\n    for (size_t i = 0; i < cfg->demod->r_devs.len; ++i) { // list might contain NULLs\n        r_device *p = cfg->demod->r_devs.elems[i];\n        if (!strcmp(p->name, r_dev->name)) {\n            list_remove(&cfg->demod->r_devs, i, (list_elem_free_fn)free_protocol);\n            i--; // so we don't skip the next elem now shifted down\n        }\n    }\n}\n\nvoid register_all_protocols(r_cfg_t *cfg, unsigned disabled)\n{\n    for (int i = 0; i < cfg->num_r_devices; i++) {\n        // register all device protocols that are not disabled\n        if (cfg->devices[i].disabled <= disabled) {\n            register_protocol(cfg, &cfg->devices[i], NULL);\n        }\n    }\n}\n\n/* output helper */\n\nvoid calc_rssi_snr(r_cfg_t *cfg, pulse_data_t *pulse_data)\n{\n    float ook_high_estimate = pulse_data->ook_high_estimate > 0 ? pulse_data->ook_high_estimate : 1;\n    float ook_low_estimate = pulse_data->ook_low_estimate > 0 ? pulse_data->ook_low_estimate : 1;\n    int const OOK_MAX_HIGH_LEVEL = DB_TO_AMP(0); // Maximum estimate for high level (-0 dB)\n    float ook_max_estimate = ook_high_estimate < OOK_MAX_HIGH_LEVEL ? ook_high_estimate : OOK_MAX_HIGH_LEVEL;\n    float asnr   = ook_max_estimate / ook_low_estimate;\n    float foffs1 = (float)pulse_data->fsk_f1_est / INT16_MAX * cfg->samp_rate / 2.0f;\n    float foffs2 = (float)pulse_data->fsk_f2_est / INT16_MAX * cfg->samp_rate / 2.0f;\n    pulse_data->freq1_hz = (foffs1 + cfg->center_frequency);\n    pulse_data->freq2_hz = (foffs2 + cfg->center_frequency);\n    pulse_data->centerfreq_hz = cfg->center_frequency;\n    pulse_data->depth_bits    = cfg->demod->sample_size * 4;\n    // NOTE: for (CU8) amplitude is 10x (because it's squares)\n    if (cfg->demod->sample_size == 2 && !cfg->demod->use_mag_est) { // amplitude (CU8)\n        pulse_data->range_db = 42.1442f; // 10*log10f(16384.0f) == 20*log10f(128.0f)\n        pulse_data->rssi_db  = 10.0f * log10f(ook_high_estimate) - 42.1442f; // 10*log10f(16384.0f)\n        pulse_data->noise_db = 10.0f * log10f(ook_low_estimate) - 42.1442f; // 10*log10f(16384.0f)\n        pulse_data->snr_db   = 10.0f * log10f(asnr);\n    }\n    else { // magnitude (CU8, CS16)\n        pulse_data->range_db = 84.2884f; // 20*log10f(16384.0f)\n        // lowest (scaled x128) reading at  8 bit is -20*log10(128) = -42.1442 (eff. -36 dB)\n        // lowest (scaled div2) reading at 12 bit is -20*log10(1024) = -60.2060 (eff. -54 dB)\n        // lowest (scaled div2) reading at 16 bit is -20*log10(16384) = -84.2884 (eff. -78 dB)\n        pulse_data->rssi_db  = 20.0f * log10f(ook_high_estimate) - 84.2884f; // 20*log10f(16384.0f)\n        pulse_data->noise_db = 20.0f * log10f(ook_low_estimate) - 84.2884f; // 20*log10f(16384.0f)\n        pulse_data->snr_db   = 20.0f * log10f(asnr);\n    }\n}\n\nchar *time_pos_str(r_cfg_t *cfg, unsigned samples_ago, char *buf)\n{\n    if (cfg->report_time == REPORT_TIME_SAMPLES) {\n        double s_per_sample = 1.0f / cfg->samp_rate;\n        return sample_pos_str(cfg->demod->sample_file_pos - samples_ago * s_per_sample, buf);\n    }\n    else {\n        struct timeval ago = cfg->demod->now;\n        double us_per_sample = 1e6 / cfg->samp_rate;\n        unsigned usecs_ago   = samples_ago * us_per_sample;\n        while (ago.tv_usec < (int)usecs_ago) {\n            ago.tv_sec -= 1;\n            ago.tv_usec += 1000000;\n        }\n        ago.tv_usec -= usecs_ago;\n\n        char const *format = NULL;\n        if (cfg->report_time == REPORT_TIME_UNIX)\n            format = \"%s\";\n        else if (cfg->report_time == REPORT_TIME_ISO)\n            format = \"%Y-%m-%dT%H:%M:%S\";\n\n        if (cfg->report_time_hires)\n            return usecs_time_str(buf, format, cfg->report_time_tz, &ago);\n        else\n            return format_time_str(buf, format, cfg->report_time_tz, ago.tv_sec);\n    }\n}\n\n// well-known fields \"time\", \"msg\" and \"codes\" are used to output general decoder messages\n// well-known field \"bits\" is only used when verbose bits (-M bits) is requested\n// well-known field \"tag\" is only used when output tagging is requested\n// well-known field \"protocol\" is only used when model protocol is requested\n// well-known field \"description\" is only used when model description is requested\n// well-known fields \"mod\", \"freq\", \"freq1\", \"freq2\", \"rssi\", \"snr\", \"noise\" are used by meta report option\nchar const **well_known_output_fields(r_cfg_t *cfg)\n{\n    list_t field_list = {0};\n    list_ensure_size(&field_list, 15);\n\n    list_push(&field_list, \"time\");\n    list_push(&field_list, \"msg\");\n    list_push(&field_list, \"codes\");\n\n    if (cfg->verbose_bits)\n        list_push(&field_list, \"bits\");\n\n    for (void **iter = cfg->data_tags.elems; iter && *iter; ++iter) {\n        data_tag_t *tag = *iter;\n        if (tag->key) {\n            list_push(&field_list, (void *)tag->key);\n        }\n        else {\n            list_push_all(&field_list, (void **)tag->includes);\n        }\n    }\n\n    if (cfg->report_protocol)\n        list_push(&field_list, \"protocol\");\n    if (cfg->report_description)\n        list_push(&field_list, \"description\");\n    if (cfg->report_meta) {\n        list_push(&field_list, \"mod\");\n        list_push(&field_list, \"freq\");\n        list_push(&field_list, \"freq1\");\n        list_push(&field_list, \"freq2\");\n        list_push(&field_list, \"rssi\");\n        list_push(&field_list, \"snr\");\n        list_push(&field_list, \"noise\");\n    }\n\n    return (char const **)field_list.elems;\n}\n\n/** Convert CSV keys according to selected conversion mode. Replacement is static but in-place. */\nstatic char const **convert_csv_fields(r_cfg_t *cfg, char const **fields)\n{\n    if (cfg->conversion_mode == CONVERT_SI) {\n        for (char const **p = fields; *p; ++p) {\n            if (!strcmp(*p, \"temperature_F\")) *p = \"temperature_C\";\n            else if (!strcmp(*p, \"pressure_PSI\")) *p = \"pressure_kPa\";\n            else if (!strcmp(*p, \"rain_in\")) *p = \"rain_mm\";\n            else if (!strcmp(*p, \"rain_rate_in_h\")) *p = \"rain_rate_mm_h\";\n            else if (!strcmp(*p, \"wind_avg_mi_h\")) *p = \"wind_avg_km_h\";\n            else if (!strcmp(*p, \"wind_max_mi_h\")) *p = \"wind_max_km_h\";\n        }\n    }\n\n    if (cfg->conversion_mode == CONVERT_CUSTOMARY) {\n        for (char const **p = fields; *p; ++p) {\n            if (!strcmp(*p, \"temperature_C\")) *p = \"temperature_F\";\n            else if (!strcmp(*p, \"temperature_1_C\")) *p = \"temperature_1_F\";\n            else if (!strcmp(*p, \"temperature_2_C\")) *p = \"temperature_2_F\";\n            else if (!strcmp(*p, \"setpoint_C\")) *p = \"setpoint_F\";\n            else if (!strcmp(*p, \"pressure_hPa\")) *p = \"pressure_inHg\";\n            else if (!strcmp(*p, \"pressure_kPa\")) *p = \"pressure_PSI\";\n            else if (!strcmp(*p, \"rain_mm\")) *p = \"rain_in\";\n            else if (!strcmp(*p, \"rain_rate_mm_h\")) *p = \"rain_rate_in_h\";\n            else if (!strcmp(*p, \"wind_avg_km_h\")) *p = \"wind_avg_mi_h\";\n            else if (!strcmp(*p, \"wind_max_km_h\")) *p = \"wind_max_mi_h\";\n        }\n    }\n    return fields;\n}\n\n// find the fields output for CSV\nchar const **determine_csv_fields(r_cfg_t *cfg, char const *const *well_known, int *num_fields)\n{\n    list_t field_list = {0};\n    list_ensure_size(&field_list, 100);\n\n    // always add well-known fields\n    list_push_all(&field_list, (void **)well_known);\n\n    list_t *r_devs = &cfg->demod->r_devs;\n    for (void **iter = r_devs->elems; iter && *iter; ++iter) {\n        r_device *r_dev = *iter;\n        if (r_dev->fields)\n            list_push_all(&field_list, (void **)r_dev->fields);\n        else\n            fprintf(stderr, \"rtl_433: warning: %u \\\"%s\\\" does not support CSV output\\n\",\n                    r_dev->protocol_num, r_dev->name);\n    }\n    convert_csv_fields(cfg, (char const **)field_list.elems);\n\n    if (num_fields)\n        *num_fields = field_list.len;\n    return (char const **)field_list.elems;\n}\n\nint run_ook_demods(list_t *r_devs, pulse_data_t *pulse_data)\n{\n    int p_events = 0;\n\n    unsigned next_priority = 0; // next smallest on each loop through decoders\n    // run all decoders of each priority, stop if an event is produced\n    for (unsigned priority = 0; !p_events && priority < UINT_MAX; priority = next_priority) {\n        next_priority = UINT_MAX;\n        for (void **iter = r_devs->elems; iter && *iter; ++iter) {\n            r_device *r_dev = *iter;\n\n            // Find next smallest priority\n            if (r_dev->priority > priority && r_dev->priority < next_priority)\n                next_priority = r_dev->priority;\n            // Run only current priority\n            if (r_dev->priority != priority)\n                continue;\n\n            switch (r_dev->modulation) {\n            case OOK_PULSE_PCM:\n            // case OOK_PULSE_RZ:\n                p_events += pulse_slicer_pcm(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_PPM:\n                p_events += pulse_slicer_ppm(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_PWM:\n                p_events += pulse_slicer_pwm(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_MANCHESTER_ZEROBIT:\n                p_events += pulse_slicer_manchester_zerobit(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_PIWM_RAW:\n                p_events += pulse_slicer_piwm_raw(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_PIWM_DC:\n                p_events += pulse_slicer_piwm_dc(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_DMC:\n                p_events += pulse_slicer_dmc(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_PWM_OSV1:\n                p_events += pulse_slicer_osv1(pulse_data, r_dev);\n                break;\n            case OOK_PULSE_NRZS:\n                p_events += pulse_slicer_nrzs(pulse_data, r_dev);\n                break;\n            // FSK decoders\n            case FSK_PULSE_PCM:\n            case FSK_PULSE_PWM:\n            case FSK_PULSE_MANCHESTER_ZEROBIT:\n                break;\n            default:\n                fprintf(stderr, \"Unknown modulation %u in protocol!\\n\", r_dev->modulation);\n            }\n        }\n    }\n\n    return p_events;\n}\n\nint run_fsk_demods(list_t *r_devs, pulse_data_t *fsk_pulse_data)\n{\n    int p_events = 0;\n\n    unsigned next_priority = 0; // next smallest on each loop through decoders\n    // run all decoders of each priority, stop if an event is produced\n    for (unsigned priority = 0; !p_events && priority < UINT_MAX; priority = next_priority) {\n        next_priority = UINT_MAX;\n        for (void **iter = r_devs->elems; iter && *iter; ++iter) {\n            r_device *r_dev = *iter;\n\n            // Find next smallest priority\n            if (r_dev->priority > priority && r_dev->priority < next_priority)\n                next_priority = r_dev->priority;\n            // Run only current priority\n            if (r_dev->priority != priority)\n                continue;\n\n            switch (r_dev->modulation) {\n            // OOK decoders\n            case OOK_PULSE_PCM:\n            // case OOK_PULSE_RZ:\n            case OOK_PULSE_PPM:\n            case OOK_PULSE_PWM:\n            case OOK_PULSE_MANCHESTER_ZEROBIT:\n            case OOK_PULSE_PIWM_RAW:\n            case OOK_PULSE_PIWM_DC:\n            case OOK_PULSE_DMC:\n            case OOK_PULSE_PWM_OSV1:\n            case OOK_PULSE_NRZS:\n                break;\n            case FSK_PULSE_PCM:\n                p_events += pulse_slicer_pcm(fsk_pulse_data, r_dev);\n                break;\n            case FSK_PULSE_PWM:\n                p_events += pulse_slicer_pwm(fsk_pulse_data, r_dev);\n                break;\n            case FSK_PULSE_MANCHESTER_ZEROBIT:\n                p_events += pulse_slicer_manchester_zerobit(fsk_pulse_data, r_dev);\n                break;\n            default:\n                fprintf(stderr, \"Unknown modulation %u in protocol!\\n\", r_dev->modulation);\n            }\n        }\n    }\n\n    return p_events;\n}\n\n/* handlers */\n\nstatic void log_handler(log_level_t level, char const *src, char const *msg, void *userdata)\n{\n    r_cfg_t *cfg = userdata;\n\n    if (cfg->verbosity < (int)level) {\n        return;\n    }\n    /* clang-format off */\n    data_t *data = data_make(\n            \"src\",     \"\",     DATA_STRING, src,\n            \"lvl\",      \"\",     DATA_INT,    level,\n            \"msg\",      \"\",     DATA_STRING, msg,\n            NULL);\n    /* clang-format on */\n\n    // prepend \"time\" if requested\n    if (cfg->report_time != REPORT_TIME_OFF) {\n        char time_str[LOCAL_TIME_BUFLEN];\n        time_pos_str(cfg, 0, time_str);\n        data = data_prepend(data,\n                data_str(NULL, \"time\", \"\", NULL, time_str));\n    }\n\n    for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs\n        data_output_t *output = cfg->output_handler.elems[i];\n        if (output && output->log_level >= (int)level) {\n            data_output_print(output, data);\n        }\n    }\n    data_free(data);\n}\n\nvoid r_redirect_logging(r_cfg_t *cfg)\n{\n    r_logger_set_log_handler(log_handler, cfg);\n}\n\n/** Pass the data structure to all output handlers. Frees data afterwards. */\nvoid event_occurred_handler(r_cfg_t *cfg, data_t *data)\n{\n    // prepend \"time\" if requested\n    if (cfg->report_time != REPORT_TIME_OFF) {\n        char time_str[LOCAL_TIME_BUFLEN];\n        time_pos_str(cfg, 0, time_str);\n        data = data_prepend(data,\n                data_str(NULL, \"time\", \"\", NULL, time_str));\n    }\n\n    for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs\n        data_output_t *output = cfg->output_handler.elems[i];\n        data_output_print(output, data);\n    }\n    data_free(data);\n}\n\n/** Pass the data structure to all output handlers. Frees data afterwards. */\nvoid log_device_handler(r_device *r_dev, int level, data_t *data)\n{\n    r_cfg_t *cfg = r_dev->output_ctx;\n\n    // prepend \"time\" if requested\n    if (cfg->report_time != REPORT_TIME_OFF) {\n        char time_str[LOCAL_TIME_BUFLEN];\n        time_pos_str(cfg, cfg->demod->pulse_data.start_ago, time_str);\n        data = data_prepend(data,\n                data_str(NULL, \"time\", \"\", NULL, time_str));\n    }\n\n    for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs\n        data_output_t *output = cfg->output_handler.elems[i];\n        if (output && output->log_level >= level) {\n            data_output_print(output, data);\n        }\n    }\n    data_free(data);\n}\n\n/** Pass the data structure to all output handlers. Frees data afterwards. */\nvoid data_acquired_handler(r_device *r_dev, data_t *data)\n{\n    r_cfg_t *cfg = r_dev->output_ctx;\n\n#ifndef NDEBUG\n    // check for undeclared csv fields\n    for (data_t *d = data; d; d = d->next) {\n        int found = 0;\n        for (char const *const *p = r_dev->fields; *p; ++p) {\n            if (!strcmp(d->key, *p)) {\n                found = 1;\n                break;\n            }\n        }\n        if (!found) {\n            fprintf(stderr, \"WARNING: Undeclared field \\\"%s\\\" in [%u] \\\"%s\\\"\\n\", d->key, r_dev->protocol_num, r_dev->name);\n        }\n    }\n#endif\n\n    if (cfg->conversion_mode == CONVERT_SI) {\n        for (data_t *d = data; d; d = d->next) {\n            // Convert double type fields ending in _F to _C\n            if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_F\")) {\n                d->value.v_dbl = fahrenheit2celsius(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_F\", \"_C\");\n                free(d->key);\n                d->key = new_label;\n                char *pos;\n                if (d->format && (pos = strrchr(d->format, 'F'))) {\n                    *pos = 'C';\n                }\n            }\n            // Convert double type fields ending in _mi_h to _km_h\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_mi_h\")) {\n                d->value.v_dbl = mph2kmph(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_mi_h\", \"_km_h\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"mi/h\", \"km/h\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _in to _mm\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_in\")) {\n                d->value.v_dbl = inch2mm(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_in\", \"_mm\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"in\", \"mm\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _in_h to _mm_h\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_in_h\")) {\n                d->value.v_dbl = inch2mm(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_in_h\", \"_mm_h\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"in/h\", \"mm/h\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _inHg to _hPa\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_inHg\")) {\n                d->value.v_dbl = inhg2hpa(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_inHg\", \"_hPa\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"inHg\", \"hPa\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _PSI to _kPa\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_PSI\")) {\n                d->value.v_dbl = psi2kpa(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_PSI\", \"_kPa\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"PSI\", \"kPa\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n        }\n    }\n    if (cfg->conversion_mode == CONVERT_CUSTOMARY) {\n        for (data_t *d = data; d; d = d->next) {\n            // Convert double type fields ending in _C to _F\n            if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_C\")) {\n                d->value.v_dbl = celsius2fahrenheit(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_C\", \"_F\");\n                free(d->key);\n                d->key = new_label;\n                char *pos;\n                if (d->format && (pos = strrchr(d->format, 'C'))) {\n                    *pos = 'F';\n                }\n            }\n            // Convert double type fields ending in _km_h to _mi_h\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_km_h\")) {\n                d->value.v_dbl = kmph2mph(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_km_h\", \"_mi_h\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"km/h\", \"mi/h\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _mm to _in\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_mm\")) {\n                d->value.v_dbl = mm2inch(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_mm\", \"_in\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"mm\", \"in\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _mm_h to _in_h\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_mm_h\")) {\n                d->value.v_dbl = mm2inch(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_mm_h\", \"_in_h\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"mm/h\", \"in/h\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _hPa to _inHg\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_hPa\")) {\n                d->value.v_dbl = hpa2inhg(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_hPa\", \"_inHg\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"hPa\", \"inHg\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n            // Convert double type fields ending in _kPa to _PSI\n            else if ((d->type == DATA_DOUBLE) && str_endswith(d->key, \"_kPa\")) {\n                d->value.v_dbl = kpa2psi(d->value.v_dbl);\n                char *new_label = str_replace(d->key, \"_kPa\", \"_PSI\");\n                free(d->key);\n                d->key = new_label;\n                char *new_format_label = str_replace(d->format, \"kPa\", \"PSI\");\n                free(d->format);\n                d->format = new_format_label;\n            }\n        }\n    }\n\n    // prepend \"description\" if requested\n    if (cfg->report_description) {\n        data = data_prepend(data,\n                data_str(NULL, \"description\", \"Description\", NULL, r_dev->name));\n    }\n\n    // prepend \"protocol\" if requested\n    if (cfg->report_protocol && r_dev->protocol_num) {\n        data = data_prepend(data,\n                data_int(NULL, \"protocol\", \"Protocol\", NULL, r_dev->protocol_num));\n    }\n\n    if (cfg->report_meta && cfg->demod->fsk_pulse_data.fsk_f2_est) {\n        data = data_str(data, \"mod\",   \"Modulation\",  NULL,         \"FSK\");\n        data = data_dbl(data, \"freq1\", \"Freq1\",       \"%.1f MHz\",   cfg->demod->fsk_pulse_data.freq1_hz / 1000000.0);\n        data = data_dbl(data, \"freq2\", \"Freq2\",       \"%.1f MHz\",   cfg->demod->fsk_pulse_data.freq2_hz / 1000000.0);\n        data = data_dbl(data, \"rssi\",  \"RSSI\",        \"%.1f dB\",    cfg->demod->fsk_pulse_data.rssi_db);\n        data = data_dbl(data, \"snr\",   \"SNR\",         \"%.1f dB\",    cfg->demod->fsk_pulse_data.snr_db);\n        data = data_dbl(data, \"noise\", \"Noise\",       \"%.1f dB\",    cfg->demod->fsk_pulse_data.noise_db);\n    }\n    else if (cfg->report_meta) {\n        data = data_str(data, \"mod\",   \"Modulation\",  NULL,         \"ASK\");\n        data = data_dbl(data, \"freq\",  \"Freq\",        \"%.1f MHz\",   cfg->demod->pulse_data.freq1_hz / 1000000.0);\n        data = data_dbl(data, \"rssi\",  \"RSSI\",        \"%.1f dB\",    cfg->demod->pulse_data.rssi_db);\n        data = data_dbl(data, \"snr\",   \"SNR\",         \"%.1f dB\",    cfg->demod->pulse_data.snr_db);\n        data = data_dbl(data, \"noise\", \"Noise\",       \"%.1f dB\",    cfg->demod->pulse_data.noise_db);\n    }\n\n    // prepend \"time\" if requested\n    if (cfg->report_time != REPORT_TIME_OFF) {\n        char time_str[LOCAL_TIME_BUFLEN];\n        time_pos_str(cfg, cfg->demod->pulse_data.start_ago, time_str);\n        data = data_prepend(data,\n                data_str(NULL, \"time\", \"\", NULL, time_str));\n    }\n\n    // apply all tags\n    for (void **iter = cfg->data_tags.elems; iter && *iter; ++iter) {\n        data_tag_t *tag = *iter;\n        data            = data_tag_apply(tag, data, cfg->in_filename);\n    }\n\n    for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs\n        data_output_t *output = cfg->output_handler.elems[i];\n        data_output_print(output, data);\n    }\n    data_free(data);\n}\n\n// level 0: do not report (don't call this), 1: report successful devices, 2: report active devices, 3: report all\ndata_t *create_report_data(r_cfg_t *cfg, int level)\n{\n    list_t *r_devs = &cfg->demod->r_devs;\n    data_t *data;\n    list_t dev_data_list = {0};\n    list_ensure_size(&dev_data_list, r_devs->len);\n\n    for (void **iter = r_devs->elems; iter && *iter; ++iter) {\n        r_device *r_dev = *iter;\n        if (level <= 2 && r_dev->decode_events == 0)\n            continue;\n        if (level <= 1 && r_dev->decode_ok == 0)\n            continue;\n        if (level <= 0)\n            continue;\n\n        data = data_make(\n                \"device\",       \"\", DATA_INT, r_dev->protocol_num,\n                \"name\",         \"\", DATA_STRING, r_dev->name,\n                \"events\",       \"\", DATA_INT, r_dev->decode_events,\n                \"ok\",           \"\", DATA_INT, r_dev->decode_ok,\n                \"messages\",     \"\", DATA_INT, r_dev->decode_messages,\n                NULL);\n\n        if (r_dev->decode_fails[-DECODE_FAIL_OTHER])\n            data = data_int(data, \"fail_other\",   \"\", NULL, r_dev->decode_fails[-DECODE_FAIL_OTHER]);\n        if (r_dev->decode_fails[-DECODE_ABORT_LENGTH])\n            data = data_int(data, \"abort_length\", \"\", NULL, r_dev->decode_fails[-DECODE_ABORT_LENGTH]);\n        if (r_dev->decode_fails[-DECODE_ABORT_EARLY])\n            data = data_int(data, \"abort_early\",  \"\", NULL, r_dev->decode_fails[-DECODE_ABORT_EARLY]);\n        if (r_dev->decode_fails[-DECODE_FAIL_MIC])\n            data = data_int(data, \"fail_mic\",     \"\", NULL, r_dev->decode_fails[-DECODE_FAIL_MIC]);\n        if (r_dev->decode_fails[-DECODE_FAIL_SANITY])\n            data = data_int(data, \"fail_sanity\",  \"\", NULL, r_dev->decode_fails[-DECODE_FAIL_SANITY]);\n\n        list_push(&dev_data_list, data);\n    }\n\n    data = data_make(\n            \"count\",            \"\", DATA_INT, cfg->frames_ook,\n            \"fsk\",              \"\", DATA_INT, cfg->frames_fsk,\n            \"events\",           \"\", DATA_INT, cfg->frames_events,\n            NULL);\n\n    char since_str[LOCAL_TIME_BUFLEN];\n    format_time_str(since_str, \"%Y-%m-%dT%H:%M:%S\", cfg->report_time_tz, cfg->frames_since);\n\n    data = data_make(\n            \"enabled\",          \"\", DATA_INT, r_devs->len,\n            \"since\",            \"\", DATA_STRING, since_str,\n            \"frames\",           \"\", DATA_DATA, data,\n            \"stats\",            \"\", DATA_ARRAY, data_array(dev_data_list.len, DATA_DATA, dev_data_list.elems),\n            NULL);\n\n    list_free_elems(&dev_data_list, NULL);\n    return data;\n}\n\nvoid flush_report_data(r_cfg_t *cfg)\n{\n    list_t *r_devs = &cfg->demod->r_devs;\n\n    time(&cfg->frames_since);\n    cfg->frames_ook = 0;\n    cfg->frames_fsk = 0;\n    cfg->frames_events = 0;\n\n    for (void **iter = r_devs->elems; iter && *iter; ++iter) {\n        r_device *r_dev = *iter;\n\n        r_dev->decode_events = 0;\n        r_dev->decode_ok = 0;\n        r_dev->decode_messages = 0;\n        r_dev->decode_fails[0] = 0;\n        r_dev->decode_fails[1] = 0;\n        r_dev->decode_fails[2] = 0;\n        r_dev->decode_fails[3] = 0;\n        r_dev->decode_fails[4] = 0;\n    }\n}\n\n/* setup */\n\nstatic int lvlarg_param(char **param, int default_verb)\n{\n    if (!param || !*param) {\n        return default_verb;\n    }\n    // parse \", v = %d\"\n    char *p = *param;\n    if (*p != ',') {\n        return default_verb;\n    }\n    p++;\n    while (*p == ' ' || *p == '\\t')\n        p++;\n    if (*p != 'v') {\n        fprintf(stderr, \"Unknown output option \\\"%s\\\"\\n\", *param);\n        exit(1);\n    }\n    p++;\n    while (*p == ' ' || *p == '\\t')\n        p++;\n    if (*p != '=') {\n        fprintf(stderr, \"Unknown output option \\\"%s\\\"\\n\", *param);\n        exit(1);\n    }\n    p++;\n    while (*p == ' ' || *p == '\\t')\n        p++;\n    char *endptr;\n    int val = strtol(p, &endptr, 10);\n    if (p == endptr) {\n        fprintf(stderr, \"Invalid output option \\\"%s\\\"\\n\", *param);\n        exit(1);\n    }\n    *param = endptr;\n    return val;\n}\n\n/// Opens the path @p param (or STDOUT if empty or `-`) for append writing, removes leading `,` and `:` from path name.\nstatic FILE *fopen_output(char const *param)\n{\n    if (!param || !*param) {\n        return stdout; // No path given\n    }\n    while (*param == ',') {\n        param++; // Skip all leading `,`\n    }\n    if (*param == ':') {\n        param++; // Skip one leading `:`\n    }\n    if (*param == '-' && param[1] == '\\0') {\n        return stdout; // STDOUT requested\n    }\n    FILE *file = fopen(param, \"a\");\n    if (!file) {\n        fprintf(stderr, \"rtl_433: failed to open output file\\n\");\n        exit(1);\n    }\n    return file;\n}\n\nvoid add_json_output(r_cfg_t *cfg, char *param)\n{\n    int log_level = lvlarg_param(&param, 0);\n    list_push(&cfg->output_handler, data_output_json_create(log_level, fopen_output(param)));\n}\n\nvoid add_csv_output(r_cfg_t *cfg, char *param)\n{\n    int log_level = lvlarg_param(&param, 0);\n    list_push(&cfg->output_handler, data_output_csv_create(log_level, fopen_output(param)));\n}\n\nvoid start_outputs(r_cfg_t *cfg, char const *const *well_known)\n{\n    int num_output_fields;\n    char const **output_fields = determine_csv_fields(cfg, well_known, &num_output_fields);\n\n    for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs\n        data_output_t *output = cfg->output_handler.elems[i];\n        data_output_start(output, output_fields, num_output_fields);\n    }\n\n    free((void *)output_fields);\n}\n\nvoid add_log_output(r_cfg_t *cfg, char *param)\n{\n    int log_level = lvlarg_param(&param, LOG_TRACE);\n    list_push(&cfg->output_handler, data_output_log_create(log_level, fopen_output(param)));\n}\n\nvoid add_kv_output(r_cfg_t *cfg, char *param)\n{\n    int log_level = lvlarg_param(&param, LOG_TRACE);\n    list_push(&cfg->output_handler, data_output_kv_create(log_level, fopen_output(param)));\n}\n\nvoid add_mqtt_output(r_cfg_t *cfg, char *param)\n{\n    list_push(&cfg->output_handler, data_output_mqtt_create(get_mgr(cfg), param, cfg->dev_query));\n}\n\nvoid add_influx_output(r_cfg_t *cfg, char *param)\n{\n    list_push(&cfg->output_handler, data_output_influx_create(get_mgr(cfg), param));\n}\n\nvoid add_syslog_output(r_cfg_t *cfg, char *param)\n{\n    int log_level = lvlarg_param(&param, LOG_WARNING);\n    char const *host = \"localhost\";\n    char const *port = \"514\";\n    char const *extra = hostport_param(param, &host, &port);\n    if (extra && *extra) {\n        print_logf(LOG_FATAL, \"Syslog UDP\", \"Unknown parameters \\\"%s\\\"\", extra);\n    }\n    print_logf(LOG_CRITICAL, \"Syslog UDP\", \"Sending datagrams to %s port %s\", host, port);\n\n    list_push(&cfg->output_handler, data_output_syslog_create(log_level, host, port));\n}\n\nvoid add_http_output(r_cfg_t *cfg, char *param)\n{\n    // Note: no log_level, the HTTP-API consumes all log levels.\n    char const *host = \"0.0.0.0\";\n    char const *port = \"8433\";\n    char const *extra = hostport_param(param, &host, &port);\n    if (extra && *extra) {\n        print_logf(LOG_FATAL, \"HTTP server\", \"Unknown parameters \\\"%s\\\"\", extra);\n    }\n    print_logf(LOG_CRITICAL, \"HTTP server\", \"Starting HTTP server at %s port %s\", host, port);\n\n    list_push(&cfg->output_handler, data_output_http_create(get_mgr(cfg), host, port, cfg));\n}\n\nvoid add_trigger_output(r_cfg_t *cfg, char *param)\n{\n    // Note: no log_level, we never trigger on logs.\n    list_push(&cfg->output_handler, data_output_trigger_create(fopen_output(param)));\n}\n\nvoid add_null_output(r_cfg_t *cfg, char *param)\n{\n    UNUSED(param);\n    list_push(&cfg->output_handler, NULL);\n}\n\nvoid add_rtltcp_output(r_cfg_t *cfg, char *param)\n{\n    char const *host = \"localhost\";\n    char const *port = \"1234\";\n    char const *extra = hostport_param(param, &host, &port);\n    if (extra && *extra) {\n        print_logf(LOG_FATAL, \"rtl_tcp server\", \"Unknown parameters \\\"%s\\\"\", extra);\n    }\n    print_logf(LOG_CRITICAL, \"rtl_tcp server\", \"Starting rtl_tcp server at %s port %s\", host, port);\n\n    list_push(&cfg->raw_handler, raw_output_rtltcp_create(host, port, extra, cfg));\n}\n\nvoid add_sr_dumper(r_cfg_t *cfg, char const *spec, int overwrite)\n{\n    // create channels\n    add_dumper(cfg, \"U8:LOGIC:logic-1-1\", overwrite);\n    add_dumper(cfg, \"F32:I:analog-1-4-1\", overwrite);\n    add_dumper(cfg, \"F32:Q:analog-1-5-1\", overwrite);\n    add_dumper(cfg, \"F32:AM:analog-1-6-1\", overwrite);\n    add_dumper(cfg, \"F32:FM:analog-1-7-1\", overwrite);\n    cfg->sr_filename = spec;\n    cfg->sr_execopen = overwrite;\n}\n\nvoid reopen_dumpers(struct r_cfg *cfg)\n{\n#ifndef _WIN32\n    for (void **iter = cfg->demod->dumper.elems; iter && *iter; ++iter) {\n        file_info_t *dumper = *iter;\n        if (dumper->file && (dumper->file != stdout)) {\n            // Get current file inode\n            struct stat old_st = {0};\n            int ret = fstat(fileno(dumper->file), &old_st);\n            if (ret) {\n                fprintf(stderr, \"Failed to fstat %s (%d)\\n\", dumper->path, errno);\n                exit(1);\n            }\n\n            // Get new path inode if available\n            struct stat new_st = {0};\n            stat(dumper->path, &new_st);\n            // ok for stat() to fail, the file might not exist\n            if (old_st.st_ino == new_st.st_ino) {\n                continue;\n            }\n\n            // Reopen the file\n            print_logf(LOG_INFO, \"Dumper\", \"Reopening \\\"%s\\\"\", dumper->path);\n            fclose(dumper->file);\n            dumper->file = fopen(dumper->path, \"wb\");\n            if (!dumper->file) {\n                fprintf(stderr, \"Failed to open %s\\n\", dumper->path);\n                exit(1);\n            }\n            if (dumper->format == VCD_LOGIC) {\n                pulse_data_print_vcd_header(dumper->file, cfg->samp_rate);\n            }\n            if (dumper->format == PULSE_OOK) {\n                pulse_data_print_pulse_header(dumper->file);\n            }\n        }\n    }\n#endif\n}\n\nvoid close_dumpers(struct r_cfg *cfg)\n{\n    for (void **iter = cfg->demod->dumper.elems; iter && *iter; ++iter) {\n        file_info_t *dumper = *iter;\n        if (dumper->file && (dumper->file != stdout)) {\n            fclose(dumper->file);\n            dumper->file = NULL;\n        }\n    }\n\n    char const *labels[] = {\n            \"FRAME\", // probe1\n            \"ASK\", // probe2\n            \"FSK\", // probe3\n            \"I\", // analog4\n            \"Q\", // analog5\n            \"AM\", // analog6\n            \"FM\", // analog7\n    };\n    if (cfg->sr_filename) {\n        write_sigrok(cfg->sr_filename, cfg->samp_rate, 3, 4, labels);\n    }\n    if (cfg->sr_execopen) {\n        open_pulseview(cfg->sr_filename);\n    }\n}\n\nvoid add_dumper(r_cfg_t *cfg, char const *spec, int overwrite)\n{\n    size_t spec_len = strlen(spec);\n    if (spec_len >= 3 && !strcmp(&spec[spec_len - 3], \".sr\")) {\n        add_sr_dumper(cfg, spec, overwrite);\n        return;\n    }\n\n    file_info_t *dumper = calloc(1, sizeof(*dumper));\n    if (!dumper)\n        FATAL_CALLOC(\"add_dumper()\");\n    list_push(&cfg->demod->dumper, dumper);\n\n    file_info_parse_filename(dumper, spec);\n    if (strcmp(dumper->path, \"-\") == 0) { /* Write samples to stdout */\n        dumper->file = stdout;\n#ifdef _WIN32\n        _setmode(_fileno(stdin), _O_BINARY);\n#endif\n    }\n    else {\n        if (access(dumper->path, F_OK) == 0 && !overwrite) {\n            fprintf(stderr, \"Output file %s already exists, exiting\\n\", spec);\n            exit(1);\n        }\n        dumper->file = fopen(dumper->path, \"wb\");\n        if (!dumper->file) {\n            fprintf(stderr, \"Failed to open %s\\n\", spec);\n            exit(1);\n        }\n    }\n    if (dumper->format == VCD_LOGIC) {\n        pulse_data_print_vcd_header(dumper->file, cfg->samp_rate);\n    }\n    if (dumper->format == PULSE_OOK) {\n        pulse_data_print_pulse_header(dumper->file);\n    }\n}\n\nvoid add_infile(r_cfg_t *cfg, char *in_file)\n{\n    list_push(&cfg->in_files, in_file);\n}\n\nvoid add_data_tag(struct r_cfg *cfg, char *param)\n{\n    list_push(&cfg->data_tags, data_tag_create(param, get_mgr(cfg)));\n}\n"
  },
  {
    "path": "src/r_util.c",
    "content": "/** @file\n    Various utility functions for use by applications\n\n    Copyright (C) 2015 Tommy Vestermark\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"r_util.h\"\n#include \"fatal.h\"\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\nvoid get_time_now(struct timeval *tv)\n{\n    int ret = gettimeofday(tv, NULL);\n    if (ret)\n        perror(\"gettimeofday\");\n}\n\nchar *format_time_str(char *buf, char const *format, int with_tz, time_t time_secs)\n{\n    time_t etime;\n    struct tm tm_info;\n\n    if (time_secs == 0) {\n        time(&etime);\n    }\n    else {\n        etime = time_secs;\n    }\n\n#ifdef _WIN32 /* MinGW might have localtime_r but apparently not MinGW64 */\n    localtime_s(&tm_info, &etime); // win32 doesn't have localtime_r()\n#else\n    localtime_r(&etime, &tm_info); // thread-safe\n#endif\n\n    if (!format || !*format)\n        format = \"%Y-%m-%d %H:%M:%S\";\n\n    size_t l = strftime(buf, LOCAL_TIME_BUFLEN, format, &tm_info);\n    if (with_tz) {\n        strftime(buf + l, LOCAL_TIME_BUFLEN - l, \"%z\", &tm_info);\n        if (!strcmp(buf + l, \"+0000\"))\n            strcpy(buf + l, \"Z\"); // NOLINT\n    }\n    return buf;\n}\n\nchar *usecs_time_str(char *buf, char const *format, int with_tz, struct timeval *tv)\n{\n    struct timeval now;\n    struct tm tm_info;\n\n    if (!tv) {\n        tv = &now;\n        get_time_now(tv);\n    }\n\n    time_t t_secs = tv->tv_sec;\n#ifdef _WIN32 /* MinGW might have localtime_r but apparently not MinGW64 */\n    localtime_s(&tm_info, &t_secs); // win32 doesn't have localtime_r()\n#else\n    localtime_r(&t_secs, &tm_info); // thread-safe\n#endif\n\n    if (!format || !*format)\n        format = \"%Y-%m-%d %H:%M:%S\";\n\n    size_t l = strftime(buf, LOCAL_TIME_BUFLEN, format, &tm_info);\n    l += snprintf(buf + l, LOCAL_TIME_BUFLEN - l, \".%06ld\", (long)tv->tv_usec);\n    if (with_tz) {\n        strftime(buf + l, LOCAL_TIME_BUFLEN - l, \"%z\", &tm_info);\n        if (!strcmp(buf + l, \"+0000\"))\n            strcpy(buf + l, \"Z\"); // NOLINT\n    }\n    return buf;\n}\n\nconst char *parse_time_str(const char *buf, struct timeval *out)\n{\n    int year, mon, day, hour, min, sec, consumed;\n    long micros = 0;\n\n    if (sscanf(buf, \"%4d-%2d-%2d%*1[ T]%2d:%2d:%2d%n\", &year, &mon, &day, &hour, &min, &sec, &consumed) == 6) {\n        buf += consumed;\n    }\n    else {\n        return NULL;\n    }\n\n    if (*buf == '.') {\n        long digit_micros = 100000;\n        buf++;\n        while (*buf >= '0' && *buf <= '9') {\n            micros += (*buf - '0') * digit_micros;\n            digit_micros /= 10;\n            buf++;\n        }\n    }\n\n    int gmtoff = 0;\n    if (*buf == '+' || *buf == '-') {\n        int tz_hours = 0, tz_mins = 0, tz_sign = 1;\n        if (*buf == '-')\n            tz_sign = -1;\n        buf += 1;\n        if (sscanf(buf, \"%2d%2d%n\", &tz_hours, &tz_mins, &consumed) == 2) {\n            buf += consumed;\n            gmtoff = tz_sign * (tz_hours * 3600 + tz_mins * 60);\n        }\n        else {\n            return NULL;\n        }\n    }\n    else if (*buf == 'Z') {\n        buf++;\n    }\n    else if (*buf != '\\0') {\n        return NULL;\n    }\n\n    struct tm tm = {0};\n    tm.tm_year   = year - 1900;\n    tm.tm_mon    = mon - 1;\n    tm.tm_mday   = day;\n    tm.tm_hour   = hour;\n    tm.tm_min    = min;\n    tm.tm_sec    = sec;\n    tm.tm_isdst  = -1;\n\n    time_t epoch_sec = timegm(&tm);\n    if (epoch_sec == -1) {\n        return NULL;\n    }\n    out->tv_sec  = epoch_sec - gmtoff;\n    out->tv_usec = micros;\n\n    return buf;\n}\n\nchar *sample_pos_str(float sample_file_pos, char *buf)\n{\n    snprintf(buf, LOCAL_TIME_BUFLEN, \"@%fs\", sample_file_pos);\n    return buf;\n}\n\nfloat celsius2fahrenheit(float celsius)\n{\n  return celsius * (9.0f / 5.0f) + 32;\n}\n\n\nfloat fahrenheit2celsius(float fahrenheit)\n{\n    return (fahrenheit - 32) * (5.0f / 9.0f);\n}\n\n\nfloat kmph2mph(float kmph)\n{\n    return kmph * (1.0f / 1.609344f);\n}\n\nfloat mph2kmph(float mph)\n{\n    return mph * 1.609344f;\n}\n\n\nfloat mm2inch(float mm)\n{\n    return mm * 0.039370f;\n}\n\nfloat inch2mm(float inch)\n{\n    return inch * 25.4f;\n}\n\n\nfloat kpa2psi(float kpa)\n{\n    return kpa * (1.0f / 6.89475729f);\n}\n\nfloat psi2kpa(float psi)\n{\n    return psi * 6.89475729f;\n}\n\n\nfloat hpa2inhg(float hpa)\n{\n    return hpa * (1.0f / 33.8639f);\n}\n\nfloat inhg2hpa(float inhg)\n{\n    return inhg * 33.8639f;\n}\n\n\nbool str_endswith(char const *restrict str, char const *restrict suffix)\n{\n    if (!suffix) {\n        return true;\n    }\n    if (!str) {\n        return false;\n    }\n    int str_len = strlen(str);\n    int suffix_len = strlen(suffix);\n\n    return (str_len >= suffix_len) &&\n           (0 == strcmp(str + (str_len - suffix_len), suffix));\n}\n\n// Original string replacement function was found here:\n// https://stackoverflow.com/questions/779875/what-is-the-function-to-replace-string-in-c/779960#779960\n//\n// You must free the result if result is non-NULL.\nchar *str_replace(char const *orig, char const *rep, char const *with)\n{\n    char *result;  // the return string\n    char const *ins; // the next insert point\n    char *tmp;     // varies\n    int len_rep;   // length of rep (the string to remove)\n    int len_with;  // length of with (the string to replace rep with)\n    int len_front; // distance between rep and end of last rep\n    int count;     // number of replacements\n\n    // sanity checks and initialization\n    if (!orig || !rep)\n        return NULL;\n    len_rep = strlen(rep);\n    if (len_rep == 0)\n        return NULL; // empty rep causes infinite loop during count\n    if (!with)\n        with = \"\";\n    len_with = strlen(with);\n\n    // count the number of replacements needed\n    ins = orig;\n    for (count = 0; (tmp = strstr(ins, rep)); ++count) {\n        ins = tmp + len_rep;\n    }\n\n    tmp = result = malloc(strlen(orig) + (len_with - len_rep) * (size_t)count + 1);\n    if (!result) {\n        WARN_MALLOC(\"str_replace()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    // first time through the loop, all the variables are set correctly\n    // from here on,\n    //    tmp points to the end of the result string\n    //    ins points to the next occurrence of rep in orig\n    //    orig points to the remainder of orig after \"end of rep\"\n    while (count--) {\n        ins = strstr(orig, rep);\n        len_front = ins - orig;\n        tmp = strncpy(tmp, orig, len_front) + len_front;\n        tmp = strcpy(tmp, with) + len_with; // NOLINT\n        orig += len_front + len_rep; // move to next \"end of rep\"\n    }\n    strcpy(tmp, orig); // NOLINT\n    return result;\n}\n\n// Make a more readable string for a frequency.\nchar const *nice_freq (double freq)\n{\n  static char buf[30];\n\n  if (freq >= 1E9)\n     snprintf (buf, sizeof(buf), \"%.3fGHz\", freq/1E9);\n  else if (freq >= 1E6)\n     snprintf (buf, sizeof(buf), \"%.3fMHz\", freq/1E6);\n  else if (freq >= 1E3)\n     snprintf (buf, sizeof(buf), \"%.3fkHz\", freq/1E3);\n  else\n     snprintf (buf, sizeof(buf), \"%f\", freq);\n  return (buf);\n}\n\n#ifdef _TEST\n#define TEST_PARSE_TIME_STR(str, epoch, millis) \\\n    do { \\\n        struct timeval tv; \\\n        if (parse_time_str(str, &tv) != NULL) { \\\n            if (tv.tv_sec == (epoch) && tv.tv_usec == (millis)) { \\\n                ++passed; \\\n            } \\\n            else { \\\n                ++failed; \\\n                fprintf(stderr, \"FAIL: parse_time_str \\\"%s\\\" = %ld.%06ld, expected %ld.%06d\\n\", str, tv.tv_sec, (long)tv.tv_usec, (long)(epoch), (millis)); \\\n            } \\\n        } \\\n        else { \\\n            ++failed; \\\n            fprintf(stderr, \"FAIL: parse_time_str failed to parse \\\"%s\\\"\\n\", str); \\\n        } \\\n    } while (0)\n\nint main(void)\n{\n    unsigned passed = 0;\n    unsigned failed = 0;\n\n    TEST_PARSE_TIME_STR(\"2026-02-11T12:34:56.123456Z\", 1770813296, 123456);\n    TEST_PARSE_TIME_STR(\"2026-02-11 12:34:56.123456Z\", 1770813296, 123456);\n    TEST_PARSE_TIME_STR(\"2026-02-11 12:34:56.123Z\", 1770813296, 123000);\n    TEST_PARSE_TIME_STR(\"2026-02-11 12:34:56.111111111Z\", 1770813296, 111111);\n    TEST_PARSE_TIME_STR(\"2026-02-11 12:34:56Z\", 1770813296, 0);\n    TEST_PARSE_TIME_STR(\"2026-02-11 12:34:56.123456-0700\", 1770838496, 123456);\n    TEST_PARSE_TIME_STR(\"2026-02-11 12:34:56.123456+0845\", 1770781796, 123456);\n\n    fprintf(stderr, \"r_util test (%u/%u) passed, (%u) failed.\\n\", passed, passed + failed, failed);\n    return failed;\n}\n#endif /* _TEST */\n"
  },
  {
    "path": "src/raw_output.c",
    "content": "/** @file\n    Raw I/Q data output handler.\n\n    Copyright (C) 2022 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"raw_output.h\"\n\n#include <stdint.h>\n\n/* generic raw_output */\n\nvoid raw_output_frame(struct raw_output *output, uint8_t const *data, uint32_t len)\n{\n    if (!output)\n        return;\n    output->output_frame(output, data, len);\n}\n\nvoid raw_output_free(struct raw_output *output)\n{\n    if (!output)\n        return;\n    output->output_free(output);\n}\n"
  },
  {
    "path": "src/rfraw.c",
    "content": "/** @file\n    RfRaw format functions.\n\n    Copyright (C) 2020 Christian W. Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include \"rfraw.h\"\n#include \"fatal.h\"\n#include <string.h>\n\nstatic int hexstr_get_nibble(char const **p)\n{\n    if (!p || !*p || !**p) return -1;\n    while (**p == ' ' || **p == '\\t' || **p == '-' || **p == ':') ++*p;\n\n    int c = **p;\n    if (c >= '0' && c <= '9') {\n        ++*p;\n        return c - '0';\n    }\n    if (c >= 'A' && c <= 'F') {\n        ++*p;\n        return c - 'A' + 10;\n    }\n    if (c >= 'a' && c <= 'f') {\n        ++*p;\n        return c - 'a' + 10;\n    }\n\n    return -1;\n}\n\nstatic int hexstr_get_byte(char const **p)\n{\n    int h = hexstr_get_nibble(p);\n    int l = hexstr_get_nibble(p);\n    if (h >= 0 && l >= 0)\n        return (h << 4) | l;\n    return -1;\n}\n\nstatic int hexstr_get_word(char const **p)\n{\n    int h = hexstr_get_byte(p);\n    int l = hexstr_get_byte(p);\n    if (h >= 0 && l >= 0)\n        return (h << 8) | l;\n    return -1;\n}\n\nstatic int hexstr_peek_byte(char const *p)\n{\n    int h = hexstr_get_nibble(&p);\n    int l = hexstr_get_nibble(&p);\n    if (h >= 0 && l >= 0)\n        return (h << 4) | l;\n    return -1;\n}\n\nbool rfraw_check(char const *p)\n{\n    // require 0xaa 0xb0 or 0xaa 0xb1\n    return hexstr_get_nibble(&p) == 0xa\n            && hexstr_get_nibble(&p) == 0xa\n            && hexstr_get_nibble(&p) == 0xb\n            && (hexstr_get_nibble(&p) | 1) == 0x1;\n/*\n    if (!p || !*p) return false;\n    while (*p == ' ' || *p == '\\t' || *p == '-' || *p == ':') ++p;\n    if (*p != 'A' && *p != 'a') return false;\n    p++;\n    while (*p == ' ' || *p == '\\t' || *p == '-' || *p == ':') ++p;\n    if (*p != 'A' && *p != 'a') return false;\n    p++;\n    while (*p == ' ' || *p == '\\t' || *p == '-' || *p == ':') ++p;\n    if (*p != 'B' && *p != 'b') return false;\n    p++;\n    while (*p == ' ' || *p == '\\t' || *p == '-' || *p == ':') ++p;\n    if (*p != '0' && *p != '1') return false;\n    p++;\n    while (*p == ' ' || *p == '\\t' || *p == '-' || *p == ':') ++p;\n    if (*p != '0') return false;\n    return true;\n*/\n}\n\nstatic bool parse_rfraw(pulse_data_t *data, char const **p)\n{\n    if (!p || !*p || !**p) return false;\n\n    int hdr = hexstr_get_byte(p);\n    if (hdr !=0xaa) return false;\n\n    int fmt = hexstr_get_byte(p);\n    if (fmt != 0xb0 && fmt != 0xb1)\n        return false;\n\n    if (fmt == 0xb0) {\n        hexstr_get_byte(p); // ignore len\n    }\n\n    int bins_len = hexstr_get_byte(p);\n    if (bins_len > 8) return false;\n\n    int repeats = 1;\n    if (fmt == 0xb0) {\n        repeats = hexstr_get_byte(p);\n    }\n\n    int bins[8] = {0};\n    for (int i = 0; i < bins_len; ++i) {\n        bins[i] = hexstr_get_word(p);\n    }\n\n    // check if this is the old or new format\n    bool oldfmt = true;\n    char const *t = *p;\n    while (*t) {\n        int b = hexstr_get_byte(&t);\n        if (b < 0 || b == 0x55) {\n            break;\n        }\n        if (b & 0x88) {\n            oldfmt = false;\n            break;\n        }\n    }\n\n    unsigned prev_pulses = data->num_pulses;\n    bool pulse_needed = true;\n    bool aligned = true;\n    while (*p) {\n        if (aligned && hexstr_peek_byte(*p) == 0x55) {\n            hexstr_get_byte(p); // consume 0x55\n            break;\n        }\n\n        int w = hexstr_get_nibble(p);\n        aligned = !aligned;\n        if (w < 0) return false;\n        if (w >= 8 || (oldfmt && !aligned)) { // pulse\n            if (!pulse_needed) {\n                data->gap[data->num_pulses] = 0;\n                data->num_pulses++;\n            }\n            data->pulse[data->num_pulses] = bins[w & 7];\n            pulse_needed = false;\n        }\n        else { // gap\n            if (pulse_needed) {\n                data->pulse[data->num_pulses] = 0;\n            }\n            data->gap[data->num_pulses] = bins[w];\n            data->num_pulses++;\n            pulse_needed = true;\n        }\n        // abort reading if the pulse data array is full\n        if (data->num_pulses >= PD_MAX_PULSES) {\n            break;\n        }\n    }\n    //data->gap[data->num_pulses - 1] = 3000; // TODO: extend last gap?\n\n    // expand reapeats as long as the pulse data array has enough space\n    unsigned pkt_pulses = data->num_pulses - prev_pulses;\n    for (int i = 1; i < repeats && data->num_pulses + pkt_pulses <= PD_MAX_PULSES; ++i) {\n        memcpy(&data->pulse[data->num_pulses], &data->pulse[prev_pulses], pkt_pulses * sizeof (*data->pulse));\n        memcpy(&data->gap[data->num_pulses], &data->gap[prev_pulses], pkt_pulses * sizeof (*data->pulse));\n        data->num_pulses += pkt_pulses;\n    }\n    //pulse_data_print(data);\n\n    data->sample_rate = 1000000; // us\n    return true;\n}\n\nbool rfraw_parse(pulse_data_t *data, char const *p)\n{\n    if (!p || !*p)\n        return false;\n\n    // don't reset pulse data\n    // pulse_data_clear(data);\n\n    while (*p) {\n        // skip whitespace and separators\n        while (*p == ' ' || *p == '\\t' || *p == '\\r' || *p == '\\n' || *p == '+' || *p == '-')\n            ++p;\n\n        if (!parse_rfraw(data, &p))\n            break;\n    }\n    //pulse_data_print(data);\n    return true;\n}\n"
  },
  {
    "path": "src/rtl_433.c",
    "content": "/** @file\n    rtl_433, turns your Realtek RTL2832 based DVB dongle into a 433.92MHz generic data receiver.\n\n    Copyright (C) 2012 by Benjamin Larsson <benjamin@southpole.se>\n\n    Based on rtl_sdr\n    Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <string.h>\n#include <errno.h>\n#include <signal.h>\n\n#include \"rtl_433.h\"\n#include \"r_private.h\"\n#include \"r_device.h\"\n#include \"r_api.h\"\n#include \"sdr.h\"\n#include \"baseband.h\"\n#include \"pulse_analyzer.h\"\n#include \"pulse_detect.h\"\n#include \"pulse_detect_fsk.h\"\n#include \"pulse_slicer.h\"\n#include \"rfraw.h\"\n#include \"data.h\"\n#include \"raw_output.h\"\n#include \"r_util.h\"\n#include \"optparse.h\"\n#include \"abuf.h\"\n#include \"fileformat.h\"\n#include \"samp_grab.h\"\n#include \"am_analyze.h\"\n#include \"confparse.h\"\n#include \"term_ctl.h\"\n#include \"compat_paths.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include \"write_sigrok.h\"\n#include \"mongoose.h\"\n\n#ifdef _WIN32\n#include <io.h>\n#include <fcntl.h>\n#ifdef _MSC_VER\n#define F_OK 0\n#endif\n#endif\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifndef _MSC_VER\n#include <getopt.h>\n#else\n#include \"getopt/getopt.h\"\n#endif\n\n// note that Clang has _Noreturn but it's C11\n// #if defined(__clang__) ...\n#if !defined _Noreturn\n#if defined(__GNUC__)\n#define _Noreturn __attribute__((noreturn))\n#elif defined(_MSC_VER)\n#define _Noreturn __declspec(noreturn)\n#else\n#define _Noreturn\n#endif\n#endif\n\n// STDERR_FILENO is not defined in at least MSVC\n#ifndef STDERR_FILENO\n#define STDERR_FILENO 2\n#endif\n\n#ifdef _WIN32\n#include <windows.h>\n#define usleep(us) Sleep((us) / 1000)\n#endif\n\ntypedef struct timeval delay_timer_t;\n\nstatic void delay_timer_init(delay_timer_t *delay_timer)\n{\n    // set to current wall clock\n    get_time_now(delay_timer);\n}\n\nstatic void delay_timer_wait(delay_timer_t *delay_timer, unsigned delay_us)\n{\n    // sync to wall clock\n    struct timeval now_tv;\n    get_time_now(&now_tv);\n\n    time_t elapsed_s  = now_tv.tv_sec - delay_timer->tv_sec;\n    time_t elapsed_us = 1000000 * elapsed_s + now_tv.tv_usec - delay_timer->tv_usec;\n\n    // set next wanted start time\n    delay_timer->tv_usec += delay_us;\n    while (delay_timer->tv_usec > 1000000) {\n        delay_timer->tv_usec -= 1000000;\n        delay_timer->tv_sec += 1;\n    }\n\n    if ((time_t)delay_us > elapsed_us)\n        usleep(delay_us - elapsed_us);\n}\n\nr_device *flex_create_device(char *spec); // maybe put this in some header file?\n\nstatic void print_version(void)\n{\n    fprintf(stderr, \"%s\\n\", version_string());\n}\n\n_Noreturn\nstatic void usage(int exit_code)\n{\n    term_help_fprintf(exit_code ? stderr : stdout,\n            \"Generic RF data receiver and decoder for ISM band devices using RTL-SDR and SoapySDR.\\n\"\n            \"Full documentation is available at https://triq.org/\\n\"\n            \"\\nUsage:\\n\"\n#ifdef _WIN32\n            \"  A \\\"rtl_433.conf\\\" file is searched in the current dir, %%LocalAppData%%, %%ProgramData%%,\\n\"\n            \"  e.g. \\\"C:\\\\Users\\\\username\\\\AppData\\\\Local\\\\rtl_433\\\\\\\", \\\"C:\\\\ProgramData\\\\rtl_433\\\\\\\",\\n\"\n            \"  then command line args will be parsed in order.\\n\"\n#else\n            \"  A \\\"rtl_433.conf\\\" file is searched in \\\"./\\\", XDG_CONFIG_HOME e.g. \\\"$HOME/.config/rtl_433/\\\",\\n\"\n            \"  SYSCONFDIR e.g. \\\"/usr/local/etc/rtl_433/\\\", then command line args will be parsed in order.\\n\"\n#endif\n            \"\\t\\t= General options =\\n\"\n            \"  [-V] Output the version string and exit\\n\"\n            \"  [-v] Increase verbosity (can be used multiple times).\\n\"\n            \"       -v : verbose notice, -vv : verbose info, -vvv : debug, -vvvv : trace.\\n\"\n            \"  [-c <path>] Read config options from a file\\n\"\n            \"\\t\\t= Tuner options =\\n\"\n            \"  [-d <RTL-SDR USB device index> | :<RTL-SDR USB device serial> | <SoapySDR device query> | rtl_tcp | help]\\n\"\n            \"  [-g <gain> | help] (default: auto)\\n\"\n            \"  [-t <settings>] apply a list of keyword=value settings to the SDR device\\n\"\n            \"       e.g. for SoapySDR -t \\\"antenna=A,bandwidth=4.5M,rfnotch_ctrl=false\\\"\\n\"\n            \"       for RTL-SDR use \\\"direct_samp[=1]\\\", \\\"offset_tune[=1]\\\", \\\"digital_agc[=1]\\\", \\\"biastee[=1]\\\"\\n\"\n            \"  [-f <frequency>] Receive frequency(s) (default: %d Hz)\\n\"\n            \"  [-H <seconds>] Hop interval for polling of multiple frequencies (default: %d seconds)\\n\"\n            \"  [-p <ppm_error>] Correct rtl-sdr tuner frequency offset error (default: 0)\\n\"\n            \"  [-s <sample rate>] Set sample rate (default: %d Hz)\\n\"\n            \"  [-D quit | restart | pause | manual] Input device run mode options (default: quit).\\n\"\n            \"\\t\\t= Demodulator options =\\n\"\n            \"  [-R <device> | help] Enable only the specified device decoding protocol (can be used multiple times)\\n\"\n            \"       Specify a negative number to disable a device decoding protocol (can be used multiple times)\\n\"\n            \"  [-X <spec> | help] Add a general purpose decoder (prepend -R 0 to disable all decoders)\\n\"\n            \"  [-Y auto | classic | minmax] FSK pulse detector mode.\\n\"\n            \"  [-Y level=<dB level>] Manual detection level used to determine pulses (-1.0 to -30.0) (0=auto).\\n\"\n            \"  [-Y minlevel=<dB level>] Manual minimum detection level used to determine pulses (-1.0 to -99.0).\\n\"\n            \"  [-Y minsnr=<dB level>] Minimum SNR to determine pulses (1.0 to 99.0).\\n\"\n            \"  [-Y autolevel] Set minlevel automatically based on average estimated noise.\\n\"\n            \"  [-Y squelch] Skip frames below estimated noise level to reduce cpu load.\\n\"\n            \"  [-Y ampest | magest] Choose amplitude or magnitude level estimator.\\n\"\n            \"\\t\\t= Analyze/Debug options =\\n\"\n            \"  [-A] Pulse Analyzer. Enable pulse analysis and decode attempt.\\n\"\n            \"       Disable all decoders with -R 0 if you want analyzer output only.\\n\"\n            \"  [-y <code>] Verify decoding of demodulated test data (e.g. \\\"{25}fb2dd58\\\") with enabled devices\\n\"\n            \"\\t\\t= File I/O options =\\n\"\n            \"  [-S none | all | unknown | known] Signal auto save. Creates one file per signal.\\n\"\n            \"       Note: Saves raw I/Q samples (uint8 pcm, 2 channel). Preferred mode for generating test files.\\n\"\n            \"  [-r <filename> | help] Read data from input file instead of a receiver\\n\"\n            \"  [-w <filename> | help] Save data stream to output file (a '-' dumps samples to stdout)\\n\"\n            \"  [-W <filename> | help] Save data stream to output file, overwrite existing file\\n\"\n            \"\\t\\t= Data output options =\\n\"\n            \"  [-F log | kv | json | csv | mqtt | influx | syslog | trigger | rtl_tcp | http | null | help] Produce decoded output in given format.\\n\"\n            \"       Append output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\\n\"\n            \"       Specify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\\n\"\n            \"  [-M time[:<options>] | protocol | level | noise[:<secs>] | stats | bits | help] Add various meta data to each output.\\n\"\n            \"  [-K FILE | PATH | <tag> | <key>=<tag>] Add an expanded token or fixed tag to every output line.\\n\"\n            \"  [-C native | si | customary] Convert units in decoded output.\\n\"\n            \"  [-n <value>] Specify number of samples to take (each sample is an I/Q pair)\\n\"\n            \"  [-T <seconds>] Specify number of seconds to run, also 12:34 or 1h23m45s\\n\"\n            \"  [-E hop | quit] Hop/Quit after outputting successful event(s)\\n\"\n            \"  [-h] Output this usage help and exit\\n\"\n            \"       Use -d, -g, -R, -X, -F, -M, -r, -w, or -W without argument for more help\\n\\n\",\n            DEFAULT_FREQUENCY, DEFAULT_HOP_TIME, DEFAULT_SAMPLE_RATE);\n    exit(exit_code);\n}\n\n_Noreturn\nstatic void help_protocols(r_device *devices, unsigned num_devices, int exit_code)\n{\n    unsigned i;\n    char disabledc;\n\n    if (devices) {\n        FILE *fp = exit_code ? stderr : stdout;\n        term_help_fprintf(fp,\n                \"\\t\\t= Supported device protocols =\\n\");\n        for (i = 0; i < num_devices; i++) {\n            disabledc = devices[i].disabled ? '*' : ' ';\n            if (devices[i].disabled <= 2) // if not hidden\n                fprintf(fp, \"    [%02u]%c %s\\n\", i + 1, disabledc, devices[i].name);\n        }\n        fprintf(fp, \"\\n* Disabled by default, use -R n or a conf file to enable\\n\");\n    }\n    exit(exit_code);\n}\n\n_Noreturn\nstatic void help_device_selection(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Input device selection =\\n\"\n#ifdef RTLSDR\n            \"\\tRTL-SDR device driver is available.\\n\"\n#else\n            \"\\tRTL-SDR device driver is not available.\\n\"\n#endif\n            \"  [-d <RTL-SDR USB device index>] (default: 0)\\n\"\n            \"  [-d :<RTL-SDR USB device serial (can be set with rtl_eeprom -s)>]\\n\"\n            \"\\tTo set gain for RTL-SDR use -g <gain> to set an overall gain in dB.\\n\"\n#ifdef SOAPYSDR\n            \"\\tSoapySDR device driver is available.\\n\"\n#else\n            \"\\tSoapySDR device driver is not available.\\n\"\n#endif\n            \"  [-d \\\"\\\"] Open default SoapySDR device\\n\"\n            \"  [-d driver=rtlsdr] Open e.g. specific SoapySDR device\\n\"\n            \"\\tTo set gain for SoapySDR use -g ELEM=val,ELEM=val,... e.g. -g LNA=20,TIA=8,PGA=2 (for LimeSDR).\\n\"\n            \"  [-d rtl_tcp[:[//]host[:port]] (default: localhost:1234)\\n\"\n            \"\\tSpecify host/port to connect to with e.g. -d rtl_tcp:127.0.0.1:1234\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_gain(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Gain option =\\n\"\n            \"  [-g <gain>] (default: auto)\\n\"\n            \"\\tFor RTL-SDR: gain in dB (\\\"0\\\" is auto).\\n\"\n            \"\\tFor SoapySDR: gain in dB for automatic distribution (\\\"\\\" is auto), or string of gain elements.\\n\"\n            \"\\tE.g. \\\"LNA=20,TIA=8,PGA=2\\\" for LimeSDR.\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_device_mode(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Input device run mode =\\n\"\n            \"  [-D quit | restart | pause | manual] Input device run mode options (default: quit).\\n\"\n            \"\\tSupported input device run modes:\\n\"\n            \"\\t  quit: Quit on input device errors (default)\\n\"\n            \"\\t  restart: Restart the input device on errors\\n\"\n            \"\\t  pause: Pause the input device on errors, waits for e.g. HTTP-API control\\n\"\n            \"\\t  manual: Do not start an input device, waits for e.g. HTTP-API control\\n\"\n            \"\\tWithout this option the default is to start the SDR and quit on errors.\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_output(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Output format option =\\n\"\n            \"  [-F log|kv|json|csv|mqtt|influx|syslog|trigger|rtl_tcp|http|null] Produce decoded output in given format.\\n\"\n            \"\\tWithout this option the default is LOG and KV output. Use \\\"-F null\\\" to remove the default.\\n\"\n            \"\\tAppend output to file with :<filename> (e.g. -F csv:log.csv), defaults to stdout.\\n\"\n            \"  [-F mqtt[s][:[//]host[:port][,<options>]] (default: localhost:1883)\\n\"\n            \"\\tSpecify MQTT server with e.g. -F mqtt://localhost:1883\\n\"\n            \"\\tDefault user and password are read from MQTT_USERNAME and MQTT_PASSWORD env vars.\\n\"\n            \"\\tAdd MQTT options with e.g. -F \\\"mqtt://host:1883,opt=arg\\\"\\n\"\n            \"\\tMQTT options are: user=foo, pass=bar, retain[=0|1], <format>[=topic]\\n\"\n            \"\\tSupported MQTT formats: (default is all)\\n\"\n            \"\\t  availability: posts availability (online/offline)\\n\"\n            \"\\t  events: posts JSON event data, default \\\"<base>/events\\\"\\n\"\n            \"\\t  states: posts JSON state data, default \\\"<base>/states\\\"\\n\"\n            \"\\t  devices: posts device and sensor info in nested topics,\\n\"\n            \"\\t           default \\\"<base>/devices[/type][/model][/subtype][/channel][/id]\\\"\\n\"\n            \"\\tA base topic can be set with base=<topic>, default is \\\"rtl_433/HOSTNAME\\\".\\n\"\n            \"\\tAny topic string overrides the base topic and will expand keys like [/model]\\n\"\n            \"\\tE.g. -F \\\"mqtt://localhost:1883,user=USERNAME,pass=PASSWORD,retain=0,devices=rtl_433[/id]\\\"\\n\"\n            \"\\tFor TLS use e.g. -F \\\"mqtts://host,tls_cert=<path>,tls_key=<path>,tls_ca_cert=<path>\\\"\\n\"\n            \"\\tWith MQTT each rtl_433 instance needs a distinct driver selection. The MQTT Client-ID is computed from the driver string.\\n\"\n            \"\\tIf you use multiple RTL-SDR, perhaps set a serial and select by that (helps not to get the wrong antenna).\\n\"\n            \"  [-F influx[:[//]host[:port][/<path and options>]]\\n\"\n            \"\\tSpecify InfluxDB 2.0 server with e.g. -F \\\"influx://localhost:9999/api/v2/write?org=<org>&bucket=<bucket>,token=<authtoken>\\\"\\n\"\n            \"\\tSpecify InfluxDB 1.x server with e.g. -F \\\"influx://localhost:8086/write?db=<db>&p=<password>&u=<user>\\\"\\n\"\n            \"\\t  Additional parameter -M time:unix:usec:utc for correct timestamps in InfluxDB recommended\\n\"\n            \"  [-F syslog[:[//]host[:port] (default: localhost:514)\\n\"\n            \"\\tSpecify host/port for syslog with e.g. -F syslog:127.0.0.1:1514\\n\"\n            \"  [-F trigger:/path/to/file]\\n\"\n            \"\\tAdd an output that writes a \\\"1\\\" to the path for each event, use with a e.g. a GPIO\\n\"\n            \"  [-F rtl_tcp[:[//]bind[:port]] (default: localhost:1234)\\n\"\n            \"\\tAdd a rtl_tcp pass-through server\\n\"\n            \"  [-F http[:[//]bind[:port]] (default: 0.0.0.0:8433)\\n\"\n            \"\\tAdd a HTTP API server, a UI is at e.g. http://localhost:8433/\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_tags(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Data tags option =\\n\"\n            \"  [-K FILE | PATH | <tag> | <key>=<tag>] Add an expanded token or fixed tag to every output line.\\n\"\n            \"\\tIf <tag> is \\\"FILE\\\" or \\\"PATH\\\" an expanded token will be added.\\n\"\n            \"\\tThe <tag> can also be a GPSd URL, e.g.\\n\"\n            \"\\t\\t\\\"-K gpsd,lat,lon\\\" (report lat and lon keys from local gpsd)\\n\"\n            \"\\t\\t\\\"-K loc=gpsd,lat,lon\\\" (report lat and lon in loc object)\\n\"\n            \"\\t\\t\\\"-K gpsd\\\" (full json TPV report, in default \\\"gps\\\" object)\\n\"\n            \"\\t\\t\\\"-K foo=gpsd://127.0.0.1:2947\\\" (with key and address)\\n\"\n            \"\\t\\t\\\"-K bar=gpsd,nmea\\\" (NMEA default GPGGA report)\\n\"\n            \"\\t\\t\\\"-K rmc=gpsd,nmea,filter='$GPRMC'\\\" (NMEA GPRMC report)\\n\"\n            \"\\tAlso <tag> can be a generic tcp address, e.g.\\n\"\n            \"\\t\\t\\\"-K foo=tcp:localhost:4000\\\" (read lines as TCP client)\\n\"\n            \"\\t\\t\\\"-K bar=tcp://127.0.0.1:3000,init='subscribe tags\\\\r\\\\n'\\\"\\n\"\n            \"\\t\\t\\\"-K baz=tcp://127.0.0.1:5000,filter='a prefix to match'\\\"\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_meta(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Meta information option =\\n\"\n            \"  [-M time[:<options>]|protocol|level|noise[:<secs>]|stats|bits] Add various metadata to every output line.\\n\"\n            \"\\tUse \\\"time\\\" to add current date and time meta data (preset for live inputs).\\n\"\n            \"\\tUse \\\"time:rel\\\" to add sample position meta data (preset for read-file and stdin).\\n\"\n            \"\\tUse \\\"time:unix\\\" to show the seconds since unix epoch as time meta data. This is always UTC.\\n\"\n            \"\\tUse \\\"time:iso\\\" to show the time with ISO-8601 format (YYYY-MM-DD\\\"T\\\"hh:mm:ss).\\n\"\n            \"\\tUse \\\"time:off\\\" to remove time meta data.\\n\"\n            \"\\tUse \\\"time:usec\\\" to add microseconds to date time meta data.\\n\"\n            \"\\tUse \\\"time:tz\\\" to output time with timezone offset.\\n\"\n            \"\\tUse \\\"time:utc\\\" to output time in UTC.\\n\"\n            \"\\t\\t(this may also be accomplished by invocation with TZ environment variable set).\\n\"\n            \"\\t\\t\\\"usec\\\" and \\\"utc\\\" can be combined with other options, eg. \\\"time:iso:utc\\\" or \\\"time:unix:usec\\\".\\n\"\n            \"\\tUse \\\"replay[:N]\\\" to replay file inputs at (N-times) realtime.\\n\"\n            \"\\tUse \\\"protocol\\\" / \\\"noprotocol\\\" to output the decoder protocol number meta data.\\n\"\n            \"\\tUse \\\"level\\\" to add Modulation, Frequency, RSSI, SNR, and Noise meta data.\\n\"\n            \"\\tUse \\\"noise[:<secs>]\\\" to report estimated noise level at intervals (default: 10 seconds).\\n\"\n            \"\\tUse \\\"stats[:[<level>][:<interval>]]\\\" to report statistics (default: 600 seconds).\\n\"\n            \"\\t  level 0: no report, 1: report successful devices, 2: report active devices, 3: report all\\n\"\n            \"\\tUse \\\"bits\\\" to add bit representation to code outputs (for debug).\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_read(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Read file option =\\n\"\n            \"  [-r <filename>] Read data from input file instead of a receiver\\n\"\n            \"\\tParameters are detected from the full path, file name, and extension.\\n\\n\"\n            \"\\tA center frequency is detected as (fractional) number suffixed with 'M',\\n\"\n            \"\\t'Hz', 'kHz', 'MHz', or 'GHz'.\\n\\n\"\n            \"\\tA sample rate is detected as (fractional) number suffixed with 'k',\\n\"\n            \"\\t'sps', 'ksps', 'Msps', or 'Gsps'.\\n\\n\"\n            \"\\tFile content and format are detected as parameters, possible options are:\\n\"\n            \"\\t'cu8', 'cs16', 'cf32' ('IQ' implied), and 'am.s16'.\\n\\n\"\n            \"\\tParameters must be separated by non-alphanumeric chars and are case-insensitive.\\n\"\n            \"\\tOverrides can be prefixed, separated by colon (':')\\n\\n\"\n            \"\\tE.g. default detection by extension: path/filename.am.s16\\n\"\n            \"\\tforced overrides: am:s16:path/filename.ext\\n\\n\"\n            \"\\tReading from pipes also support format options.\\n\"\n            \"\\tE.g reading complex 32-bit float: CU32:-\\n\");\n    exit(0);\n}\n\n_Noreturn\nstatic void help_write(void)\n{\n    term_help_fprintf(stdout,\n            \"\\t\\t= Write file option =\\n\"\n            \"  [-w <filename>] Save data stream to output file (a '-' dumps samples to stdout)\\n\"\n            \"  [-W <filename>] Save data stream to output file, overwrite existing file\\n\"\n            \"\\tParameters are detected from the full path, file name, and extension.\\n\\n\"\n            \"\\tFile content and format are detected as parameters, possible options are:\\n\"\n            \"\\t'cu8', 'cs8', 'cs16', 'cf32' ('IQ' implied),\\n\"\n            \"\\t'am.s16', 'am.f32', 'fm.s16', 'fm.f32',\\n\"\n            \"\\t'i.f32', 'q.f32', 'logic.u8', 'ook', and 'vcd'.\\n\\n\"\n            \"\\tParameters must be separated by non-alphanumeric chars and are case-insensitive.\\n\"\n            \"\\tOverrides can be prefixed, separated by colon (':')\\n\\n\"\n            \"\\tE.g. default detection by extension: path/filename.am.s16\\n\"\n            \"\\tforced overrides: am:s16:path/filename.ext\\n\");\n    exit(0);\n}\n\nstatic void reset_sdr_callback(r_cfg_t *cfg)\n{\n    struct dm_state *demod = cfg->demod;\n\n    get_time_now(&demod->now);\n\n    demod->frame_start_ago   = 0;\n    demod->frame_end_ago     = 0;\n    demod->frame_event_count = 0;\n\n    demod->min_level_auto = 0.0f;\n    demod->noise_level    = 0.0f;\n\n    baseband_low_pass_filter_reset(&demod->lowpass_filter_state);\n    baseband_demod_FM_reset(&demod->demod_FM_state);\n\n    pulse_detect_reset(demod->pulse_detect);\n}\n\nstatic void sdr_callback(unsigned char *iq_buf, uint32_t len, void *ctx)\n{\n    //fprintf(stderr, \"sdr_callback... %u\\n\", len);\n    r_cfg_t *cfg = ctx;\n    struct dm_state *demod = cfg->demod;\n    char time_str[LOCAL_TIME_BUFLEN];\n    unsigned long n_samples;\n\n    if (!demod) {\n        // might happen when the demod closed and we get a last data frame\n        return; // ignore the data\n    }\n\n    // do this here and not in sdr_handler so realtime replay can use rtl_tcp output\n    for (void **iter = cfg->raw_handler.elems; iter && *iter; ++iter) {\n        raw_output_t *output = *iter;\n        raw_output_frame(output, iq_buf, len);\n    }\n\n    if ((cfg->bytes_to_read > 0) && (cfg->bytes_to_read <= len)) {\n        len = cfg->bytes_to_read;\n        cfg->exit_async = 1;\n    }\n\n    // save last frame time to see if a new second started\n    time_t last_frame_sec = demod->now.tv_sec;\n    get_time_now(&demod->now);\n\n    n_samples = len / demod->sample_size;\n    if (n_samples * demod->sample_size != len) {\n        print_log(LOG_WARNING, __func__, \"Sample buffer length not aligned to sample size!\");\n    }\n    if (!n_samples) {\n        print_log(LOG_WARNING, __func__, \"Sample buffer too short!\");\n        return; // keep the watchdog timer running\n    }\n\n    // age the frame position if there is one\n    if (demod->frame_start_ago)\n        demod->frame_start_ago += n_samples;\n    if (demod->frame_end_ago)\n        demod->frame_end_ago += n_samples;\n\n    cfg->watchdog++; // reset the frame acquire watchdog\n\n    if (demod->samp_grab) {\n        samp_grab_push(demod->samp_grab, iq_buf, len);\n    }\n\n    // AM demodulation\n    float avg_db;\n    if (demod->sample_size == 2) { // CU8\n        if (demod->use_mag_est) {\n            //magnitude_true_cu8(iq_buf, demod->buf.temp, n_samples);\n            avg_db = magnitude_est_cu8(iq_buf, demod->buf.temp, n_samples);\n        }\n        else { // amp est\n            avg_db = envelope_detect(iq_buf, demod->buf.temp, n_samples);\n        }\n    } else { // CS16\n        //magnitude_true_cs16((int16_t *)iq_buf, demod->buf.temp, n_samples);\n        avg_db = magnitude_est_cs16((int16_t *)iq_buf, demod->buf.temp, n_samples);\n    }\n\n    //fprintf(stderr, \"noise level: %.1f dB current: %.1f dB min level: %.1f dB\\n\", demod->noise_level, avg_db, demod->min_level_auto);\n    if (demod->min_level_auto == 0.0f) {\n        demod->min_level_auto = demod->min_level;\n    }\n    if (demod->noise_level == 0.0f) {\n        demod->noise_level = demod->min_level_auto - 3.0f;\n    }\n    int noise_only = avg_db < demod->noise_level + 3.0f; // or demod->min_level_auto?\n    // always process frames if loader, dumper, or analyzers are in use, otherwise skip silent frames\n    int process_frame = demod->squelch_offset <= 0 || !noise_only || demod->load_info.format || demod->analyze_pulses || demod->dumper.len || demod->samp_grab;\n    cfg->total_frames_count += 1;\n    if (noise_only) {\n        cfg->total_frames_squelch += 1;\n        demod->noise_level = (demod->noise_level * 7 + avg_db) / 8; // fast fall over 8 frames\n        // If auto_level and noise level well below min_level and significant change in noise level\n        if (demod->auto_level > 0 && demod->noise_level < demod->min_level - 3.0f\n                && fabsf(demod->min_level_auto - demod->noise_level - 3.0f) > 1.0f) {\n            demod->min_level_auto = demod->noise_level + 3.0f;\n            print_logf(LOG_WARNING, \"Auto Level\", \"Estimated noise level is %.1f dB, adjusting minimum detection level to %.1f dB\",\n                    demod->noise_level, demod->min_level_auto);\n            pulse_detect_set_levels(demod->pulse_detect, demod->use_mag_est, demod->level_limit, demod->min_level_auto, demod->min_snr, demod->detect_verbosity);\n        }\n    } else {\n        demod->noise_level = (demod->noise_level * 31 + avg_db) / 32; // slow rise over 32 frames\n    }\n    // Report noise every report_noise seconds, but only for the first frame that second\n    if (cfg->report_noise && last_frame_sec != demod->now.tv_sec && demod->now.tv_sec % cfg->report_noise == 0) {\n        print_logf(LOG_WARNING, \"Auto Level\", \"Current %s level %.1f dB, estimated noise %.1f dB\",\n                noise_only ? \"noise\" : \"signal\", avg_db, demod->noise_level);\n    }\n\n    if (process_frame) {\n        baseband_low_pass_filter(&demod->lowpass_filter_state, demod->buf.temp, demod->am_buf, n_samples);\n    }\n\n    // FM demodulation\n    // Select the correct fsk pulse detector\n    unsigned fpdm = cfg->fsk_pulse_detect_mode;\n    if (cfg->fsk_pulse_detect_mode == FSK_PULSE_DETECT_AUTO) {\n        if (cfg->frequency[cfg->frequency_index] > FSK_PULSE_DETECTOR_LIMIT)\n            fpdm = FSK_PULSE_DETECT_NEW;\n        else\n            fpdm = FSK_PULSE_DETECT_OLD;\n    }\n\n    if (demod->enable_FM_demod && process_frame) {\n        float low_pass = demod->low_pass != 0.0f ? demod->low_pass : fpdm ? 0.2f : 0.1f;\n        if (demod->sample_size == 2) { // CU8\n            baseband_demod_FM(&demod->demod_FM_state, iq_buf, demod->buf.fm, n_samples, cfg->samp_rate, low_pass);\n        } else { // CS16\n            baseband_demod_FM_cs16(&demod->demod_FM_state, (int16_t *)iq_buf, demod->buf.fm, n_samples, cfg->samp_rate, low_pass);\n        }\n    }\n\n    // Handle special input formats\n    if (demod->load_info.format == S16_AM) { // The IQ buffer is really AM demodulated data\n        if (len > sizeof(demod->am_buf))\n            FATAL(\"Buffer too small\");\n        memcpy(demod->am_buf, iq_buf, len);\n    } else if (demod->load_info.format == S16_FM) { // The IQ buffer is really FM demodulated data\n        // we would need AM for the envelope too\n        if (len > sizeof(demod->buf.fm))\n            FATAL(\"Buffer too small\");\n        memcpy(demod->buf.fm, iq_buf, len);\n    }\n\n    int d_events = 0; // Sensor events successfully detected\n    if (demod->r_devs.len || demod->analyze_pulses || demod->dumper.len || demod->samp_grab) {\n        // Detect a package and loop through demodulators with pulse data\n        int package_type = PULSE_DATA_OOK;  // Just to get us started\n        for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {\n            file_info_t const *dumper = *iter;\n            if (dumper->format == U8_LOGIC) {\n                memset(demod->u8_buf, 0, n_samples);\n                break;\n            }\n        }\n        while (package_type && process_frame) {\n            int p_events = 0; // Sensor events successfully detected per package\n            package_type = pulse_detect_package(demod->pulse_detect, demod->am_buf, demod->buf.fm, n_samples, cfg->samp_rate, cfg->input_pos, &demod->pulse_data, &demod->fsk_pulse_data, fpdm);\n            if (package_type) {\n                // new package: set a first frame start if we are not tracking one already\n                if (!demod->frame_start_ago)\n                    demod->frame_start_ago = demod->pulse_data.start_ago;\n                // always update the last frame end\n                demod->frame_end_ago = demod->pulse_data.end_ago;\n            }\n            if (package_type == PULSE_DATA_OOK) {\n                calc_rssi_snr(cfg, &demod->pulse_data);\n                if (demod->analyze_pulses) fprintf(stderr, \"Detected OOK package\\t%s\\n\", time_pos_str(cfg, demod->pulse_data.start_ago, time_str));\n\n                p_events += run_ook_demods(&demod->r_devs, &demod->pulse_data);\n                cfg->total_frames_ook += 1;\n                cfg->total_frames_events += p_events > 0;\n                cfg->frames_ook +=1;\n                cfg->frames_events += p_events > 0;\n\n                for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {\n                    file_info_t const *dumper = *iter;\n                    if (dumper->format == VCD_LOGIC) pulse_data_print_vcd(dumper->file, &demod->pulse_data, '\\'');\n                    if (dumper->format == U8_LOGIC) pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->pulse_data, 0x02);\n                    if (dumper->format == PULSE_OOK) pulse_data_dump(dumper->file, &demod->pulse_data);\n                }\n\n                if (cfg->verbosity >= LOG_TRACE) pulse_data_print(&demod->pulse_data);\n                if (cfg->raw_mode == 1 || (cfg->raw_mode == 2 && p_events == 0) || (cfg->raw_mode == 3 && p_events > 0)) {\n                    data_t *data = pulse_data_print_data(&demod->pulse_data);\n                    event_occurred_handler(cfg, data);\n                }\n                if (demod->analyze_pulses && (cfg->grab_mode <= 1 || (cfg->grab_mode == 2 && p_events == 0) || (cfg->grab_mode == 3 && p_events > 0)) ) {\n                    r_device device = {.log_fn = log_device_handler, .output_ctx = cfg};\n                    pulse_analyzer(&demod->pulse_data, package_type, &device);\n                }\n\n            } else if (package_type == PULSE_DATA_FSK) {\n                calc_rssi_snr(cfg, &demod->fsk_pulse_data);\n                if (demod->analyze_pulses) fprintf(stderr, \"Detected FSK package\\t%s\\n\", time_pos_str(cfg, demod->fsk_pulse_data.start_ago, time_str));\n\n                p_events += run_fsk_demods(&demod->r_devs, &demod->fsk_pulse_data);\n                cfg->total_frames_fsk +=1;\n                cfg->total_frames_events += p_events > 0;\n                cfg->frames_fsk += 1;\n                cfg->frames_events += p_events > 0;\n\n                for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {\n                    file_info_t const *dumper = *iter;\n                    if (dumper->format == VCD_LOGIC) pulse_data_print_vcd(dumper->file, &demod->fsk_pulse_data, '\"');\n                    if (dumper->format == U8_LOGIC) pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->fsk_pulse_data, 0x04);\n                    if (dumper->format == PULSE_OOK) pulse_data_dump(dumper->file, &demod->fsk_pulse_data);\n                }\n\n                if (cfg->verbosity >= LOG_TRACE) pulse_data_print(&demod->fsk_pulse_data);\n                if (cfg->raw_mode == 1 || (cfg->raw_mode == 2 && p_events == 0) || (cfg->raw_mode == 3 && p_events > 0)) {\n                    data_t *data = pulse_data_print_data(&demod->fsk_pulse_data);\n                    event_occurred_handler(cfg, data);\n                }\n                if (demod->analyze_pulses && (cfg->grab_mode <= 1 || (cfg->grab_mode == 2 && p_events == 0) || (cfg->grab_mode == 3 && p_events > 0))) {\n                    r_device device = {.log_fn = log_device_handler, .output_ctx = cfg};\n                    pulse_analyzer(&demod->fsk_pulse_data, package_type, &device);\n                }\n            } // if (package_type == ...\n            d_events += p_events;\n        } // while (package_type)...\n\n        // add event counter to the frames currently tracked\n        demod->frame_event_count += d_events;\n\n        // end frame tracking if older than a whole buffer\n        if (demod->frame_start_ago && demod->frame_end_ago > n_samples) {\n            if (demod->samp_grab) {\n                if (cfg->grab_mode == 1\n                        || (cfg->grab_mode == 2 && demod->frame_event_count == 0)\n                        || (cfg->grab_mode == 3 && demod->frame_event_count > 0)) {\n                    unsigned frame_pad = n_samples / 8; // this could also be a fixed value, e.g. 10000 samples\n                    unsigned start_padded = demod->frame_start_ago + frame_pad;\n                    unsigned end_padded = demod->frame_end_ago - frame_pad;\n                    unsigned len_padded = start_padded - end_padded;\n                    samp_grab_write(demod->samp_grab, len_padded, end_padded);\n                }\n            }\n            demod->frame_start_ago = 0;\n            demod->frame_event_count = 0;\n        }\n\n        // dump partial pulse_data for this buffer\n        for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {\n            file_info_t const *dumper = *iter;\n            if (dumper->format == U8_LOGIC) {\n                pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->pulse_data, 0x02);\n                pulse_data_dump_raw(demod->u8_buf, n_samples, cfg->input_pos, &demod->fsk_pulse_data, 0x04);\n                break;\n            }\n        }\n    }\n\n    if (demod->am_analyze) {\n        am_analyze(demod->am_analyze, demod->am_buf, n_samples, cfg->verbosity >= LOG_INFO, NULL);\n    }\n\n    for (void **iter = demod->dumper.elems; iter && *iter; ++iter) {\n        file_info_t const *dumper = *iter;\n        if (!dumper->file\n                || dumper->format == VCD_LOGIC\n                || dumper->format == PULSE_OOK)\n            continue;\n        uint8_t *out_buf = iq_buf;  // Default is to dump IQ samples\n        unsigned long out_len = n_samples * demod->sample_size;\n\n        if (dumper->format == CU8_IQ) {\n            if (demod->sample_size == 4) {\n                for (unsigned long n = 0; n < n_samples * 2; ++n)\n                    ((uint8_t *)demod->buf.temp)[n] = (((int16_t *)iq_buf)[n] / 256) + 128; // scale Q0.15 to Q0.7\n                out_buf = (uint8_t *)demod->buf.temp;\n                out_len = n_samples * 2 * sizeof(uint8_t);\n            }\n        }\n        else if (dumper->format == CS16_IQ) {\n            if (demod->sample_size == 2) {\n                for (unsigned long n = 0; n < n_samples * 2; ++n)\n                    ((int16_t *)demod->buf.temp)[n] = (iq_buf[n] * 256) - 32768; // scale Q0.7 to Q0.15\n                out_buf = (uint8_t *)demod->buf.temp; // this buffer is too small if out_block_size is large\n                out_len = n_samples * 2 * sizeof(int16_t);\n            }\n        }\n        else if (dumper->format == CS8_IQ) {\n            if (demod->sample_size == 2) {\n                for (unsigned long n = 0; n < n_samples * 2; ++n)\n                    ((int8_t *)demod->buf.temp)[n] = (iq_buf[n] - 128);\n            }\n            else if (demod->sample_size == 4) {\n                for (unsigned long n = 0; n < n_samples * 2; ++n)\n                    ((int8_t *)demod->buf.temp)[n] = ((int16_t *)iq_buf)[n] >> 8;\n            }\n            out_buf = (uint8_t *)demod->buf.temp;\n            out_len = n_samples * 2 * sizeof(int8_t);\n        }\n        else if (dumper->format == CF32_IQ) {\n            if (demod->sample_size == 2) {\n                for (unsigned long n = 0; n < n_samples * 2; ++n)\n                    ((float *)demod->buf.temp)[n] = (iq_buf[n] - 128) / 128.0f;\n            }\n            else if (demod->sample_size == 4) {\n                for (unsigned long n = 0; n < n_samples * 2; ++n)\n                    ((float *)demod->buf.temp)[n] = ((int16_t *)iq_buf)[n] / 32768.0f;\n            }\n            out_buf = (uint8_t *)demod->buf.temp; // this buffer is too small if out_block_size is large\n            out_len = n_samples * 2 * sizeof(float);\n        }\n        else if (dumper->format == S16_AM) {\n            out_buf = (uint8_t *)demod->am_buf;\n            out_len = n_samples * sizeof(int16_t);\n        }\n        else if (dumper->format == S16_FM) {\n            out_buf = (uint8_t *)demod->buf.fm;\n            out_len = n_samples * sizeof(int16_t);\n        }\n        else if (dumper->format == F32_AM) {\n            for (unsigned long n = 0; n < n_samples; ++n)\n                demod->f32_buf[n] = demod->am_buf[n] * (1.0f / 0x8000); // scale from Q0.15\n            out_buf = (uint8_t *)demod->f32_buf;\n            out_len = n_samples * sizeof(float);\n        }\n        else if (dumper->format == F32_FM) {\n            for (unsigned long n = 0; n < n_samples; ++n)\n                demod->f32_buf[n] = demod->buf.fm[n] * (1.0f / 0x8000); // scale from Q0.15\n            out_buf = (uint8_t *)demod->f32_buf;\n            out_len = n_samples * sizeof(float);\n        }\n        else if (dumper->format == F32_I) {\n            if (demod->sample_size == 2)\n                for (unsigned long n = 0; n < n_samples; ++n)\n                    demod->f32_buf[n] = (iq_buf[n * 2] - 128) * (1.0f / 0x80); // scale from Q0.7\n            else\n                for (unsigned long n = 0; n < n_samples; ++n)\n                    demod->f32_buf[n] = ((int16_t *)iq_buf)[n * 2] * (1.0f / 0x8000); // scale from Q0.15\n            out_buf = (uint8_t *)demod->f32_buf;\n            out_len = n_samples * sizeof(float);\n        }\n        else if (dumper->format == F32_Q) {\n            if (demod->sample_size == 2)\n                for (unsigned long n = 0; n < n_samples; ++n)\n                    demod->f32_buf[n] = (iq_buf[n * 2 + 1] - 128) * (1.0f / 0x80); // scale from Q0.7\n            else\n                for (unsigned long n = 0; n < n_samples; ++n)\n                    demod->f32_buf[n] = ((int16_t *)iq_buf)[n * 2 + 1] * (1.0f / 0x8000); // scale from Q0.15\n            out_buf = (uint8_t *)demod->f32_buf;\n            out_len = n_samples * sizeof(float);\n        }\n        else if (dumper->format == U8_LOGIC) { // state data\n            out_buf = demod->u8_buf;\n            out_len = n_samples;\n        }\n\n        if (fwrite(out_buf, 1, out_len, dumper->file) != out_len) {\n            print_log(LOG_ERROR, __func__, \"Short write, samples lost, exiting!\");\n            cfg->exit_async = 1;\n        }\n    }\n\n    cfg->input_pos += n_samples;\n    if (cfg->bytes_to_read > 0)\n        cfg->bytes_to_read -= len;\n\n    if (cfg->after_successful_events_flag && (d_events > 0)) {\n        if (cfg->after_successful_events_flag == 1) {\n            cfg->exit_async = 1;\n        }\n        else {\n            cfg->hop_now = 1;\n        }\n    }\n\n    time_t rawtime;\n    time(&rawtime);\n    // choose hop_index as frequency_index, if there are too few hop_times use the last one\n    int hop_index = cfg->hop_times > cfg->frequency_index ? cfg->frequency_index : cfg->hop_times - 1;\n    if (cfg->hop_times > 0 && cfg->frequencies > 1\n            && difftime(rawtime, cfg->hop_start_time) >= cfg->hop_time[hop_index]) {\n        cfg->hop_now = 1;\n    }\n    if (cfg->duration > 0 && rawtime >= cfg->stop_time) {\n        cfg->exit_async = 1;\n        print_log(LOG_CRITICAL, __func__, \"Time expired, exiting!\");\n    }\n    if (cfg->stats_now || (cfg->report_stats && cfg->stats_interval && rawtime >= cfg->stats_time)) {\n        event_occurred_handler(cfg, create_report_data(cfg, cfg->stats_now ? 3 : cfg->report_stats));\n        flush_report_data(cfg);\n        if (rawtime >= cfg->stats_time)\n            cfg->stats_time += cfg->stats_interval;\n        if (cfg->stats_now)\n            cfg->stats_now--;\n    }\n\n    if (cfg->hop_now && !cfg->exit_async) {\n        cfg->hop_now = 0;\n        time(&cfg->hop_start_time);\n        cfg->frequency_index = (cfg->frequency_index + 1) % cfg->frequencies;\n        sdr_set_center_freq(cfg->dev, cfg->frequency[cfg->frequency_index], 1);\n    }\n}\n\nstatic int hasopt(int test, int argc, char *argv[], char const *optstring)\n{\n    int opt;\n\n    optind = 1; // reset getopt\n    while ((opt = getopt(argc, argv, optstring)) != -1) {\n        if (opt == test || optopt == test)\n            return opt;\n    }\n    return 0;\n}\n\nstatic void parse_conf_option(r_cfg_t *cfg, int opt, char *arg);\n\n#define OPTSTRING \"hVvqD:c:x:z:p:a:AI:S:m:M:r:w:W:l:d:t:f:H:g:s:b:n:R:X:F:K:C:T:UGy:E:Y:\"\n\n// these should match the short options exactly\nstatic struct conf_keywords const conf_keywords[] = {\n        {\"help\", 'h'},\n        {\"verbose\", 'v'},\n        {\"version\", 'V'},\n        {\"config_file\", 'c'},\n        {\"report_meta\", 'M'},\n        {\"device\", 'd'},\n        {\"device_mode\", 'D'},\n        {\"settings\", 't'},\n        {\"gain\", 'g'},\n        {\"frequency\", 'f'},\n        {\"hop_interval\", 'H'},\n        {\"ppm_error\", 'p'},\n        {\"sample_rate\", 's'},\n        {\"protocol\", 'R'},\n        {\"decoder\", 'X'},\n        {\"register_all\", 'G'},\n        {\"out_block_size\", 'b'},\n        {\"level_limit\", 'l'},\n        {\"samples_to_read\", 'n'},\n        {\"analyze\", 'a'},\n        {\"analyze_pulses\", 'A'},\n        {\"include_only\", 'I'},\n        {\"read_file\", 'r'},\n        {\"write_file\", 'w'},\n        {\"overwrite_file\", 'W'},\n        {\"signal_grabber\", 'S'},\n        {\"override_short\", 'z'},\n        {\"override_long\", 'x'},\n        {\"pulse_detect\", 'Y'},\n        {\"output\", 'F'},\n        {\"output_tag\", 'K'},\n        {\"convert\", 'C'},\n        {\"duration\", 'T'},\n        {\"test_data\", 'y'},\n        {\"stop_after_successful_events\", 'E'},\n        {NULL, 0}};\n\nstatic void parse_conf_text(r_cfg_t *cfg, char *conf)\n{\n    int opt;\n    char *arg;\n    char *p = conf;\n\n    if (!conf || !*conf)\n        return;\n\n    while ((opt = getconf(&p, conf_keywords, &arg)) != -1) {\n        parse_conf_option(cfg, opt, arg);\n    }\n}\n\nstatic void parse_conf_file(r_cfg_t *cfg, char const *path)\n{\n    if (!path || !*path || !strcmp(path, \"null\") || !strcmp(path, \"0\"))\n        return;\n\n    char *conf = readconf(path);\n    parse_conf_text(cfg, conf);\n    //free(conf); // TODO: check no args are dangling, then use free\n}\n\nstatic void parse_conf_try_default_files(r_cfg_t *cfg)\n{\n    char **paths = compat_get_default_conf_paths();\n    for (int a = 0; paths[a]; a++) {\n        // fprintf(stderr, \"Trying conf file at \\\"%s\\\"...\\n\", paths[a]);\n        if (hasconf(paths[a])) {\n            fprintf(stderr, \"Reading conf from \\\"%s\\\".\\n\", paths[a]);\n            parse_conf_file(cfg, paths[a]);\n            break;\n        }\n    }\n}\n\nstatic void parse_conf_args(r_cfg_t *cfg, int argc, char *argv[])\n{\n    int opt;\n\n    optind = 1; // reset getopt\n    while ((opt = getopt(argc, argv, OPTSTRING)) != -1) {\n        if (opt == '?')\n            opt = optopt; // allow missing arguments\n        parse_conf_option(cfg, opt, optarg);\n    }\n}\n\nstatic void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)\n{\n    int n;\n    r_device *flex_device;\n\n    if (arg && (!strcmp(arg, \"help\") || !strcmp(arg, \"?\"))) {\n        arg = NULL; // remove the arg if it's a request for the usage help\n    }\n\n    switch (opt) {\n    case 'h':\n        usage(0);\n        break;\n    case 'V':\n        exit(0); // we already printed the version\n        break;\n    case 'v':\n        if (!arg)\n            cfg->verbosity++;\n        else\n            cfg->verbosity = atobv(arg, 1);\n        break;\n    case 'c':\n        parse_conf_file(cfg, arg);\n        break;\n    case 'd':\n        if (!arg)\n            help_device_selection();\n\n        cfg->dev_query = arg;\n        break;\n    case 'D':\n        if (!arg)\n            help_device_mode();\n\n        if (strcmp(arg, \"quit\") == 0) {\n            cfg->dev_mode = DEVICE_MODE_QUIT;\n        }\n        else if (strcmp(arg, \"restart\") == 0) {\n            cfg->dev_mode = DEVICE_MODE_RESTART;\n        }\n        else if (strcmp(arg, \"pause\") == 0) {\n            cfg->dev_mode = DEVICE_MODE_PAUSE;\n        }\n        else if (strcmp(arg, \"manual\") == 0) {\n            cfg->dev_mode = DEVICE_MODE_MANUAL;\n        }\n        else {\n            fprintf(stderr, \"Invalid input device run mode: %s\\n\", arg);\n            help_device_mode();\n        }\n        break;\n    case 't':\n        // this option changed, check and warn if old meaning is used\n        if (!arg || *arg == '-') {\n            fprintf(stderr, \"test_mode (-t) is deprecated. Use -S none|all|unknown|known\\n\");\n            exit(1);\n        }\n        cfg->settings_str = arg;\n        break;\n    case 'f':\n        if (cfg->frequencies < MAX_FREQS) {\n            uint32_t sr = atouint32_metric(arg, \"-f: \");\n            /* If the frequency is above 800MHz sample at 1MS/s */\n            if ((sr > FSK_PULSE_DETECTOR_LIMIT) && (cfg->samp_rate == DEFAULT_SAMPLE_RATE)) {\n                cfg->samp_rate = 1000000;\n                fprintf(stderr, \"\\nNew defaults active, use \\\"-Y classic -s 250k\\\" if you need the old defaults\\n\\n\");\n            }\n            cfg->frequency[cfg->frequencies++] = sr;\n        } else\n            fprintf(stderr, \"Max number of frequencies reached %d\\n\", MAX_FREQS);\n        break;\n    case 'H':\n        if (cfg->hop_times < MAX_FREQS)\n            cfg->hop_time[cfg->hop_times++] = atoi_time(arg, \"-H: \");\n        else\n            fprintf(stderr, \"Max number of hop times reached %d\\n\", MAX_FREQS);\n        break;\n    case 'g':\n        if (!arg)\n            help_gain();\n\n        free(cfg->gain_str);\n        cfg->gain_str = strdup(arg);\n        if (!cfg->gain_str)\n            FATAL_STRDUP(\"parse_conf_option()\");\n        break;\n    case 'G':\n        fprintf(stderr, \"register_all (-G) is deprecated. Use -R or a config file to enable additional protocols.\\n\");\n        exit(1);\n        break;\n    case 'p':\n        cfg->ppm_error = atobv(arg, 0);\n        break;\n    case 's':\n        cfg->samp_rate = atouint32_metric(arg, \"-s: \");\n        break;\n    case 'b':\n        cfg->out_block_size = atouint32_metric(arg, \"-b: \");\n        break;\n    case 'l':\n        n = 1000;\n        if (arg && atoi(arg) > 0)\n            n = atoi(arg);\n        fprintf(stderr, \"\\n\\tLevel limit has changed from \\\"-l %d\\\" to \\\"-Y level=%.1f\\\" in dB.\\n\\n\", n, AMP_TO_DB(n));\n        exit(1);\n        break;\n    case 'n':\n        cfg->bytes_to_read = atouint32_metric(arg, \"-n: \") * 2;\n        break;\n    case 'a':\n        if (atobv(arg, 1) == 42 && !cfg->demod->am_analyze) {\n            cfg->demod->am_analyze = am_analyze_create();\n        }\n        else {\n            fprintf(stderr, \"\\n\\tUse -a for testing only. Enable if you know how ;)\\n\\n\");\n            exit(1);\n        }\n        break;\n    case 'A':\n        cfg->demod->analyze_pulses = atobv(arg, 1);\n        break;\n    case 'I':\n        fprintf(stderr, \"include_only (-I) is deprecated. Use -S none|all|unknown|known\\n\");\n        exit(1);\n        break;\n    case 'r':\n        if (!arg)\n            help_read();\n\n        add_infile(cfg, arg);\n        // TODO: file_info_check_read()\n        break;\n    case 'w':\n        if (!arg)\n            help_write();\n\n        add_dumper(cfg, arg, 0);\n        break;\n    case 'W':\n        if (!arg)\n            help_write();\n\n        add_dumper(cfg, arg, 1);\n        break;\n    case 'S':\n        if (!arg)\n            usage(1);\n        if (strcasecmp(arg, \"all\") == 0)\n            cfg->grab_mode = 1;\n        else if (strcasecmp(arg, \"unknown\") == 0)\n            cfg->grab_mode = 2;\n        else if (strcasecmp(arg, \"known\") == 0)\n            cfg->grab_mode = 3;\n        else\n            cfg->grab_mode = atobv(arg, 1);\n        if (cfg->grab_mode && !cfg->demod->samp_grab)\n            cfg->demod->samp_grab = samp_grab_create(SIGNAL_GRABBER_BUFFER);\n        break;\n    case 'm':\n        fprintf(stderr, \"sample mode option is deprecated.\\n\");\n        usage(1);\n        break;\n    case 'M':\n        if (!arg)\n            help_meta();\n\n        if (!strncasecmp(arg, \"time\", 4)) {\n            char *p = arg_param(arg);\n            // time  time:1  time:on  time:yes\n            // time:0  time:off  time:no\n            // time:rel\n            // time:unix\n            // time:iso\n            // time:...:usec  time:...:sec\n            // time:...:utc  time:...:local\n            cfg->report_time = REPORT_TIME_DATE;\n            while (p && *p) {\n                if (!strncasecmp(p, \"0\", 1) || !strncasecmp(p, \"no\", 2) || !strncasecmp(p, \"off\", 3))\n                    cfg->report_time = REPORT_TIME_OFF;\n                else if (!strncasecmp(p, \"1\", 1) || !strncasecmp(p, \"yes\", 3) || !strncasecmp(p, \"on\", 2))\n                    cfg->report_time = REPORT_TIME_DATE;\n                else if (!strncasecmp(p, \"rel\", 3))\n                    cfg->report_time = REPORT_TIME_SAMPLES;\n                else if (!strncasecmp(p, \"unix\", 4))\n                    cfg->report_time = REPORT_TIME_UNIX;\n                else if (!strncasecmp(p, \"iso\", 3))\n                    cfg->report_time = REPORT_TIME_ISO;\n                else if (!strncasecmp(p, \"usec\", 4))\n                    cfg->report_time_hires = 1;\n                else if (!strncasecmp(p, \"sec\", 3))\n                    cfg->report_time_hires = 0;\n                else if (!strncasecmp(p, \"tz\", 2))\n                    cfg->report_time_tz = 1;\n                else if (!strncasecmp(p, \"notz\", 4))\n                    cfg->report_time_tz = 0;\n                else if (!strncasecmp(p, \"utc\", 3))\n                    cfg->report_time_utc = 1;\n                else if (!strncasecmp(p, \"local\", 5))\n                    cfg->report_time_utc = 0;\n                else {\n                    fprintf(stderr, \"Unknown time format option: %s\\n\", p);\n                    help_meta();\n                }\n\n                p = arg_param(p);\n            }\n            // fprintf(stderr, \"time format: %d, usec:%d utc:%d\\n\", cfg->report_time, cfg->report_time_hires, cfg->report_time_utc);\n        }\n\n        // TODO: old time options, remove someday\n        else if (!strcasecmp(arg, \"reltime\"))\n            cfg->report_time = REPORT_TIME_SAMPLES;\n        else if (!strcasecmp(arg, \"notime\"))\n            cfg->report_time = REPORT_TIME_OFF;\n        else if (!strcasecmp(arg, \"hires\"))\n            cfg->report_time_hires = 1;\n        else if (!strcasecmp(arg, \"utc\"))\n            cfg->report_time_utc = 1;\n        else if (!strcasecmp(arg, \"noutc\"))\n            cfg->report_time_utc = 0;\n\n        else if (!strcasecmp(arg, \"protocol\"))\n            cfg->report_protocol = 1;\n        else if (!strcasecmp(arg, \"noprotocol\"))\n            cfg->report_protocol = 0;\n        else if (!strcasecmp(arg, \"level\"))\n            cfg->report_meta = 1;\n        else if (!strncasecmp(arg, \"noise\", 5))\n            cfg->report_noise = atoiv(arg_param(arg), 10); // atoi_time_default()\n        else if (!strcasecmp(arg, \"bits\"))\n            cfg->verbose_bits = 1;\n        else if (!strcasecmp(arg, \"description\"))\n            cfg->report_description = 1;\n        else if (!strcasecmp(arg, \"newmodel\"))\n            fprintf(stderr, \"newmodel option (-M) is deprecated.\\n\");\n        else if (!strcasecmp(arg, \"oldmodel\"))\n            fprintf(stderr, \"oldmodel option (-M) is deprecated.\\n\");\n        else if (!strncasecmp(arg, \"stats\", 5)) {\n            // there also should be options to set whether to flush on report\n            char *p = arg_param(arg);\n            cfg->report_stats = atoiv(p, 1);\n            cfg->stats_interval = atoiv(arg_param(p), 600); // atoi_time_default()\n            time(&cfg->stats_time);\n            cfg->stats_time += cfg->stats_interval;\n        }\n        else if (!strncasecmp(arg, \"replay\", 6))\n            cfg->in_replay = atobv(arg_param(arg), 1);\n        else\n            cfg->report_meta = atobv(arg, 1);\n        break;\n    case 'z':\n        fprintf(stderr, \"override_short (-z) is deprecated.\\n\");\n        break;\n    case 'x':\n        fprintf(stderr, \"override_long (-x) is deprecated.\\n\");\n        break;\n    case 'R':\n        if (!arg)\n            help_protocols(cfg->devices, cfg->num_r_devices, 0);\n\n        // use arg of 'v', 'vv', 'vvv' as global device verbosity\n        if (*arg == 'v') {\n            int decoder_verbosity = 0;\n            for (int i = 0; arg[i] == 'v'; ++i) {\n                decoder_verbosity += 1;\n            }\n            (void)decoder_verbosity; // FIXME: use this\n            break;\n        }\n\n        n = atoi(arg);\n        if (n > cfg->num_r_devices || -n > cfg->num_r_devices) {\n            fprintf(stderr, \"Protocol number specified (%d) is larger than number of protocols\\n\\n\", n);\n            help_protocols(cfg->devices, cfg->num_r_devices, 1);\n        }\n        if ((n > 0 && cfg->devices[n - 1].disabled > 2) || (n < 0 && cfg->devices[-n - 1].disabled > 2)) {\n            fprintf(stderr, \"Protocol number specified (%d) is invalid\\n\\n\", n);\n            help_protocols(cfg->devices, cfg->num_r_devices, 1);\n        }\n\n        if (n < 0 && !cfg->no_default_devices) {\n            register_all_protocols(cfg, 0); // register all defaults\n        }\n        cfg->no_default_devices = 1;\n\n        if (n >= 1) {\n            register_protocol(cfg, &cfg->devices[n - 1], arg_param(arg));\n        }\n        else if (n <= -1) {\n            unregister_protocol(cfg, &cfg->devices[-n - 1]);\n        }\n        else {\n            fprintf(stderr, \"Disabling all device decoders.\\n\");\n            list_clear(&cfg->demod->r_devs, (list_elem_free_fn)free_protocol);\n        }\n        break;\n    case 'X':\n        if (!arg)\n            flex_create_device(NULL);\n\n        flex_device = flex_create_device(arg);\n        register_protocol(cfg, flex_device, \"\");\n        break;\n    case 'q':\n        fprintf(stderr, \"quiet option (-q) is default and deprecated. See -v to increase verbosity\\n\");\n        break;\n    case 'F':\n        if (!arg)\n            help_output();\n\n        if (strncmp(arg, \"json\", 4) == 0) {\n            add_json_output(cfg, arg_param(arg));\n        }\n        else if (strncmp(arg, \"csv\", 3) == 0) {\n            add_csv_output(cfg, arg_param(arg));\n        }\n        else if (strncmp(arg, \"log\", 3) == 0) {\n            add_log_output(cfg, arg_param(arg));\n            cfg->has_logout = 1;\n        }\n        else if (strncmp(arg, \"kv\", 2) == 0) {\n            add_kv_output(cfg, arg_param(arg));\n            cfg->has_logout = 1;\n        }\n        else if (strncmp(arg, \"mqtt\", 4) == 0) {\n            add_mqtt_output(cfg, arg);\n        }\n        else if (strncmp(arg, \"influx\", 6) == 0) {\n            add_influx_output(cfg, arg);\n        }\n        else if (strncmp(arg, \"syslog\", 6) == 0) {\n            add_syslog_output(cfg, arg_param(arg));\n        }\n        else if (strncmp(arg, \"http\", 4) == 0) {\n            add_http_output(cfg, arg_param(arg));\n        }\n        else if (strncmp(arg, \"trigger\", 7) == 0) {\n            add_trigger_output(cfg, arg_param(arg));\n        }\n        else if (strncmp(arg, \"null\", 4) == 0) {\n            add_null_output(cfg, arg_param(arg));\n        }\n        else if (strncmp(arg, \"rtl_tcp\", 7) == 0) {\n            add_rtltcp_output(cfg, arg_param(arg));\n        }\n        else {\n            fprintf(stderr, \"Invalid output format: %s\\n\", arg);\n            usage(1);\n        }\n        break;\n    case 'K':\n        if (!arg)\n            help_tags();\n        add_data_tag(cfg, arg);\n        break;\n    case 'C':\n        if (!arg)\n            usage(1);\n        if (strcmp(arg, \"native\") == 0) {\n            cfg->conversion_mode = CONVERT_NATIVE;\n        }\n        else if (strcmp(arg, \"si\") == 0) {\n            cfg->conversion_mode = CONVERT_SI;\n        }\n        else if (strcmp(arg, \"customary\") == 0) {\n            cfg->conversion_mode = CONVERT_CUSTOMARY;\n        }\n        else {\n            fprintf(stderr, \"Invalid conversion mode: %s\\n\", arg);\n            usage(1);\n        }\n        break;\n    case 'U':\n        fprintf(stderr, \"UTC mode option (-U) is deprecated. Please use \\\"-M utc\\\".\\n\");\n        exit(1);\n        break;\n    case 'T':\n        cfg->duration = atoi_time(arg, \"-T: \");\n        if (cfg->duration < 1) {\n            fprintf(stderr, \"Duration '%s' not a positive number; will continue indefinitely\\n\", arg);\n        }\n        break;\n    case 'y':\n        cfg->test_data = arg;\n        break;\n    case 'Y':\n        if (!arg)\n            usage(1);\n        char const *p = arg;\n        while (p && *p) {\n            char const *val = NULL;\n            if (kwargs_match(p, \"autolevel\", &val))\n                cfg->demod->auto_level = atoiv(val, 1); // arg_float_default(p + 9, \"-Y autolevel: \");\n            else if (kwargs_match(p, \"squelch\", &val))\n                cfg->demod->squelch_offset = atoiv(val, 1); // arg_float_default(p + 7, \"-Y squelch: \");\n            else if (kwargs_match(p, \"auto\", &val))\n                cfg->fsk_pulse_detect_mode = FSK_PULSE_DETECT_AUTO;\n            else if (kwargs_match(p, \"classic\", &val))\n                cfg->fsk_pulse_detect_mode = FSK_PULSE_DETECT_OLD;\n            else if (kwargs_match(p, \"minmax\", &val))\n                cfg->fsk_pulse_detect_mode = FSK_PULSE_DETECT_NEW;\n            else if (kwargs_match(p, \"ampest\", &val))\n                cfg->demod->use_mag_est = 0;\n            else if (kwargs_match(p, \"verbose\", &val))\n                cfg->demod->detect_verbosity++;\n            else if (kwargs_match(p, \"magest\", &val))\n                cfg->demod->use_mag_est = 1;\n            else if (kwargs_match(p, \"level\", &val))\n                cfg->demod->level_limit = arg_float(val, \"-Y level: \");\n            else if (kwargs_match(p, \"minlevel\", &val))\n                cfg->demod->min_level = arg_float(val, \"-Y minlevel: \");\n            else if (kwargs_match(p, \"minsnr\", &val))\n                cfg->demod->min_snr = arg_float(val, \"-Y minsnr: \");\n            else if (kwargs_match(p, \"filter\", &val))\n                cfg->demod->low_pass = arg_float(val, \"-Y filter: \");\n            else {\n                fprintf(stderr, \"Unknown pulse detector setting: %s\\n\", p);\n                usage(1);\n            }\n            p = kwargs_skip(p);\n        }\n        break;\n    case 'E':\n        if (arg && !strcmp(arg, \"hop\")) {\n            cfg->after_successful_events_flag = 2;\n        }\n        else if (arg && !strcmp(arg, \"quit\")) {\n            cfg->after_successful_events_flag = 1;\n        }\n        else {\n            cfg->after_successful_events_flag = atobv(arg, 1);\n        }\n        break;\n    default:\n        usage(1);\n        break;\n    }\n}\n\nstatic r_cfg_t g_cfg;\nstatic volatile sig_atomic_t sig_hup;\n\n// TODO: SIGINFO is not in POSIX...\n#ifndef SIGINFO\n#define SIGINFO 29\n#endif\n\n// NOTE: printf is not async safe per signal-safety(7)\n// writes a static string, without the terminating zero, to stderr, ignores return value\n#define write_err(s) (void)!write(STDERR_FILENO, (s), sizeof(s) - 1)\n\n#ifdef _WIN32\nBOOL WINAPI\nconsole_handler(int signum)\n{\n    if (CTRL_C_EVENT == signum) {\n        write_err(\"Signal caught, exiting!\\n\");\n        g_cfg.exit_async = 1;\n        // Uninstall handler, next Ctrl-C is a hard abort\n        SetConsoleCtrlHandler((PHANDLER_ROUTINE)console_handler, FALSE);\n        return TRUE;\n    }\n    else if (CTRL_BREAK_EVENT == signum) {\n        write_err(\"CTRL-BREAK detected, hopping to next frequency (-f). Use CTRL-C to quit.\\n\");\n        g_cfg.hop_now = 1;\n        return TRUE;\n    }\n    return FALSE;\n}\n\n#else\nstatic void sighandler(int signum)\n{\n    if (signum == SIGPIPE) {\n        // NOTE: we already ignore most network SIGPIPE's, this might be a STDOUT/STDERR problem.\n        // Printing is likely not the correct way to handle this.\n        write_err(\"Signal SIGPIPE caught, broken pipe, exiting!\\n\");\n    }\n    else if (signum == SIGHUP) {\n        sig_hup = 1;\n        return;\n    }\n    else if (signum == SIGINFO/* TODO: maybe SIGUSR1 */) {\n        g_cfg.stats_now++;\n        return;\n    }\n    else if (signum == SIGUSR1) {\n        g_cfg.hop_now = 1;\n        return;\n    }\n    else {\n        write_err(\"Signal caught, exiting!\\n\");\n    }\n    g_cfg.exit_async = 1;\n\n    // Uninstall handler, next Ctrl-C is a hard abort\n    struct sigaction sigact;\n    sigact.sa_handler = NULL;\n    sigemptyset(&sigact.sa_mask);\n    sigact.sa_flags = 0;\n    sigaction(SIGINT, &sigact, NULL);\n    sigaction(SIGTERM, &sigact, NULL);\n    sigaction(SIGQUIT, &sigact, NULL);\n    sigaction(SIGPIPE, &sigact, NULL);\n}\n#endif\n\nstatic void timer_handler(struct mg_connection *nc, int ev, void *ev_data);\n\n// called by mg_mgr_poll() for each connection.\n// NOTE: this handler might be called while already in `r_free_cfg()`.\nstatic void sdr_handler(struct mg_connection *nc, int ev_type, void *ev_data)\n{\n    //fprintf(stderr, \"%s: %d, %d, %p, %p\\n\", __func__, nc->sock, ev_type, nc->user_data, ev_data);\n    // only process polls on the dummy nc\n    if (nc->sock != INVALID_SOCKET || ev_type != MG_EV_POLL) {\n        return;\n    }\n    // only process a broadcast on our defined timer nc\n    if (nc->handler != timer_handler) {\n        return;\n    }\n\n    r_cfg_t *cfg     = nc->user_data;\n    sdr_event_t *ev = ev_data;\n    //fprintf(stderr, \"sdr_handler...\\n\");\n\n    data_t *data = NULL;\n    if (ev->ev & SDR_EV_RATE) {\n        // cfg->samp_rate = ev->sample_rate;\n        data = data_int(data, \"sample_rate\", \"\", NULL, ev->sample_rate);\n    }\n    if (ev->ev & SDR_EV_CORR) {\n        // cfg->ppm_error = ev->freq_correction;\n        data = data_int(data, \"freq_correction\", \"\", NULL, ev->freq_correction);\n    }\n    if (ev->ev & SDR_EV_FREQ) {\n        // cfg->center_frequency = ev->center_frequency;\n        data = data_int(data, \"center_frequency\", \"\", NULL, ev->center_frequency);\n        if (cfg->frequencies > 1) {\n            data = data_ary(data, \"frequencies\", \"\", NULL, data_array(cfg->frequencies, DATA_INT, cfg->frequency));\n            data = data_ary(data, \"hop_times\", \"\", NULL, data_array(cfg->hop_times, DATA_INT, cfg->hop_time));\n        }\n    }\n    if (ev->ev & SDR_EV_GAIN) {\n        data = data_str(data, \"gain\", \"\", NULL, ev->gain_str);\n    }\n    if (data) {\n        event_occurred_handler(cfg, data);\n    }\n\n    if (ev->ev == SDR_EV_DATA) {\n        cfg->samp_rate        = ev->sample_rate;\n        cfg->center_frequency = ev->center_frequency;\n        sdr_callback((unsigned char *)ev->buf, ev->len, cfg);\n    }\n\n    if (cfg->exit_async) {\n        if (cfg->verbosity >= 2)\n            print_log(LOG_INFO, \"Input\", \"sdr_handler exit\");\n        sdr_stop(cfg->dev);\n        cfg->exit_async++;\n    }\n}\n\n// note that this function is called in a different thread\nstatic void acquire_callback(sdr_event_t *ev, void *ctx)\n{\n    //struct timeval now;\n    //get_time_now(&now);\n    //fprintf(stderr, \"%ld.%06ld acquire_callback...\\n\", (long)now.tv_sec, (long)now.tv_usec);\n\n    struct mg_mgr *mgr = ctx;\n\n    // TODO: We should run the demod here to unblock the event loop\n\n    // thread-safe dispatch, ev_data is the iq buffer pointer and length\n    // mg_mgr_poll() calls specified callback for each connection.\n    //fprintf(stderr, \"acquire_callback bc send...\\n\");\n    mg_broadcast(mgr, sdr_handler, (void *)ev, sizeof(*ev));\n    //fprintf(stderr, \"acquire_callback bc done...\\n\");\n}\n\nstatic int start_sdr(r_cfg_t *cfg)\n{\n    int r;\n    if (cfg->dev) {\n        r = sdr_close(cfg->dev);\n        cfg->dev = NULL;\n        if (r < 0) {\n            print_logf(LOG_ERROR, \"Input\", \"Closing SDR failed (%d)\", r);\n        }\n    }\n    r = sdr_open(&cfg->dev, cfg->dev_query, cfg->verbosity);\n    if (r < 0) {\n        return -1; // exit(2);\n    }\n    cfg->dev_info = sdr_get_dev_info(cfg->dev);\n    cfg->demod->sample_size = sdr_get_sample_size(cfg->dev);\n    // cfg->demod->sample_signed = sdr_get_sample_signed(cfg->dev);\n\n    /* Set the sample rate */\n    sdr_set_sample_rate(cfg->dev, cfg->samp_rate, 1); // always verbose\n\n    if (cfg->verbosity || cfg->demod->level_limit < 0.0)\n        print_logf(LOG_NOTICE, \"Input\", \"Bit detection level set to %.1f%s.\", cfg->demod->level_limit, (cfg->demod->level_limit < 0.0 ? \"\" : \" (Auto)\"));\n\n    sdr_apply_settings(cfg->dev, cfg->settings_str, 1); // always verbose for soapy\n\n    /* Enable automatic gain if gain_str empty (or 0 for RTL-SDR), set manual gain otherwise */\n    sdr_set_tuner_gain(cfg->dev, cfg->gain_str, 1); // always verbose\n\n    if (cfg->ppm_error) {\n        sdr_set_freq_correction(cfg->dev, cfg->ppm_error, 1); // always verbose\n    }\n\n    /* Reset endpoint before we start reading from it (mandatory) */\n    r = sdr_reset(cfg->dev, cfg->verbosity);\n    if (r < 0) {\n        print_log(LOG_ERROR, \"Input\", \"Failed to reset buffers.\");\n    }\n    sdr_activate(cfg->dev);\n\n    if (cfg->verbosity) {\n        print_log(LOG_NOTICE, \"Input\", \"Reading samples in async mode...\");\n    }\n\n    sdr_set_center_freq(cfg->dev, cfg->center_frequency, 1); // always verbose\n\n    r = sdr_start(cfg->dev, acquire_callback, (void *)get_mgr(cfg),\n            DEFAULT_ASYNC_BUF_NUMBER, cfg->out_block_size);\n    if (r < 0) {\n        print_logf(LOG_ERROR, \"Input\", \"async start failed (%d).\", r);\n    }\n\n    cfg->dev_state = DEVICE_STATE_STARTING;\n    return r;\n}\n\nstatic void timer_handler(struct mg_connection *nc, int ev, void *ev_data)\n{\n    //fprintf(stderr, \"%s: %d, %d, %p, %p\\n\", __func__, nc->sock, ev, nc->user_data, ev_data);\n    r_cfg_t *cfg = (r_cfg_t *)nc->user_data;\n    if (sig_hup) {\n        reopen_dumpers(cfg);\n        sig_hup = 0;\n    }\n    switch (ev) {\n    case MG_EV_TIMER: {\n        double now  = *(double *)ev_data;\n        (void) now; // unused\n        double next = mg_time() + 1.5;\n        //fprintf(stderr, \"timer event, current time: %.2lf, next timer: %.2lf\\n\", now, next);\n        mg_set_timer(nc, next); // Send us timer event again after 1.5 seconds\n\n        // Did we acquire data frames in the last interval?\n        if (cfg->watchdog != 0) {\n            if (cfg->dev_state == DEVICE_STATE_STARTING\n                    || cfg->dev_state == DEVICE_STATE_GRACE) {\n                cfg->dev_state = DEVICE_STATE_STARTED;\n                time(&cfg->sdr_since);\n            }\n            cfg->watchdog = 0;\n            break;\n        }\n\n        // Upon starting allow more time until the first frame\n        if (cfg->dev_state == DEVICE_STATE_STARTING) {\n            cfg->dev_state = DEVICE_STATE_GRACE;\n            break;\n        }\n        // We expect a frame at least every 250 ms but didn't get one\n        if (cfg->dev_state == DEVICE_STATE_GRACE) {\n            if (cfg->dev_mode == DEVICE_MODE_QUIT) {\n                print_log(LOG_ERROR, \"Input\", \"Input device start failed, exiting!\");\n            }\n            else if (cfg->dev_mode == DEVICE_MODE_RESTART) {\n                print_log(LOG_WARNING, \"Input\", \"Input device start failed, restarting!\");\n            }\n            else { // DEVICE_MODE_PAUSE or DEVICE_MODE_MANUAL\n                print_log(LOG_WARNING, \"Input\", \"Input device start failed, pausing!\");\n            }\n        }\n        else if (cfg->dev_state == DEVICE_STATE_STARTED) {\n            if (cfg->dev_mode == DEVICE_MODE_QUIT) {\n                print_log(LOG_ERROR, \"Input\", \"Async read stalled, exiting!\");\n            }\n            else if (cfg->dev_mode == DEVICE_MODE_RESTART) {\n                print_log(LOG_WARNING, \"Input\", \"Async read stalled, restarting!\");\n            }\n            else { // DEVICE_MODE_PAUSE or DEVICE_MODE_MANUAL\n                print_log(LOG_WARNING, \"Input\", \"Async read stalled, pausing!\");\n            }\n        }\n        if (cfg->dev_state != DEVICE_STATE_STOPPED) {\n            cfg->exit_async = 1;\n            cfg->exit_code = 3;\n            sdr_stop(cfg->dev);\n            cfg->dev_state = DEVICE_STATE_STOPPED;\n        }\n        if (cfg->dev_mode == DEVICE_MODE_QUIT) {\n            cfg->exit_async = 1;\n        }\n        if (cfg->dev_mode == DEVICE_MODE_RESTART) {\n            start_sdr(cfg);\n        }\n        // do nothing for DEVICE_MODE_PAUSE or DEVICE_MODE_MANUAL\n\n        break;\n    }\n    }\n}\n\nint main(int argc, char **argv) {\n    int r = 0;\n    struct dm_state *demod;\n    r_cfg_t *cfg = &g_cfg;\n\n    print_version(); // always print the version info\n    sdr_redirect_logging();\n\n    r_init_cfg(cfg);\n\n    setbuf(stdout, NULL);\n    setbuf(stderr, NULL);\n\n    demod = cfg->demod;\n\n    // if there is no explicit conf file option look for default conf files\n    if (!hasopt('c', argc, argv, OPTSTRING)) {\n        parse_conf_try_default_files(cfg);\n    }\n\n    parse_conf_args(cfg, argc, argv);\n    // apply hop defaults and set first frequency\n    if (cfg->frequencies == 0) {\n        cfg->frequency[0] = DEFAULT_FREQUENCY;\n        cfg->frequencies  = 1;\n    }\n    cfg->center_frequency = cfg->frequency[cfg->frequency_index];\n    if (cfg->frequencies > 1 && cfg->hop_times == 0) {\n        cfg->hop_time[cfg->hop_times++] = DEFAULT_HOP_TIME;\n    }\n    // save sample rate, this should be a hop config too\n    uint32_t sample_rate_0 = cfg->samp_rate;\n\n    // add all remaining positional arguments as input files\n    while (argc > optind) {\n        add_infile(cfg, argv[optind++]);\n    }\n\n    pulse_detect_set_levels(demod->pulse_detect, demod->use_mag_est, demod->level_limit, demod->min_level, demod->min_snr, demod->detect_verbosity);\n\n    if (demod->am_analyze) {\n        demod->am_analyze->level_limit = DB_TO_AMP(demod->level_limit);\n        demod->am_analyze->frequency   = &cfg->center_frequency;\n        demod->am_analyze->samp_rate   = &cfg->samp_rate;\n        demod->am_analyze->sample_size = &demod->sample_size;\n    }\n\n    if (demod->samp_grab) {\n        demod->samp_grab->frequency   = &cfg->center_frequency;\n        demod->samp_grab->samp_rate   = &cfg->samp_rate;\n        demod->samp_grab->sample_size = &demod->sample_size;\n    }\n\n    if (cfg->report_time == REPORT_TIME_DEFAULT) {\n        if (cfg->in_files.len)\n            cfg->report_time = REPORT_TIME_SAMPLES;\n        else\n            cfg->report_time = REPORT_TIME_DATE;\n    }\n    if (cfg->report_time_utc) {\n#ifdef _WIN32\n        putenv(\"TZ=UTC+0\");\n        _tzset();\n#else\n        r = setenv(\"TZ\", \"UTC\", 1);\n        if (r != 0)\n            fprintf(stderr, \"Unable to set TZ to UTC; error code: %d\\n\", r);\n#endif\n    }\n\n    if (!cfg->output_handler.len) {\n        add_kv_output(cfg, NULL);\n    }\n    else if (!cfg->has_logout) {\n        // Warn if no log outputs are enabled\n        fprintf(stderr, \"Use \\\"-F log\\\" if you want any messages, warnings, and errors in the console.\\n\");\n    }\n    // Change log handler after outputs are set up\n    r_redirect_logging(cfg);\n\n    // register default decoders if nothing is configured\n    if (!cfg->no_default_devices) {\n        register_all_protocols(cfg, 0); // register all defaults\n    }\n\n    // check if we need FM demod\n    for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {\n        r_device *r_dev = *iter;\n        if (r_dev->modulation >= FSK_DEMOD_MIN_VAL) {\n            demod->enable_FM_demod = 1;\n            break;\n        }\n    }\n    // if any dumpers are requested the FM demod might be needed\n    if (cfg->demod->dumper.len) {\n        demod->enable_FM_demod = 1;\n    }\n\n    {\n        char decoders_str[1024];\n        decoders_str[0] = '\\0';\n        if (cfg->verbosity <= LOG_NOTICE) {\n            abuf_t p = {0};\n            abuf_init(&p, decoders_str, sizeof(decoders_str));\n            // print registered decoder ranges\n            abuf_printf(&p, \" [\");\n            for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {\n                r_device *r_dev = *iter;\n                unsigned num = r_dev->protocol_num;\n                if (num == 0)\n                    continue;\n                while (iter[1]\n                        && r_dev->protocol_num + 1 == ((r_device *)iter[1])->protocol_num)\n                    r_dev = *++iter;\n                if (num == r_dev->protocol_num)\n                    abuf_printf(&p, \" %u\", num);\n                else\n                    abuf_printf(&p, \" %u-%u\", num, r_dev->protocol_num);\n            }\n            abuf_printf(&p, \" ]\");\n        }\n        print_logf(LOG_NOTICE, \"Protocols\", \"Registered %zu out of %u device decoding protocols%s\",\n                demod->r_devs.len, cfg->num_r_devices, decoders_str);\n    }\n\n    char const **well_known = well_known_output_fields(cfg);\n    start_outputs(cfg, well_known);\n    free((void *)well_known);\n\n    if (cfg->out_block_size < MINIMAL_BUF_LENGTH ||\n            cfg->out_block_size > MAXIMAL_BUF_LENGTH) {\n        print_logf(LOG_ERROR, \"Block Size\",\n                \"Output block size wrong value, falling back to default (%d)\", DEFAULT_BUF_LENGTH);\n        print_logf(LOG_ERROR, \"Block Size\",\n                \"Minimal length: %d\", MINIMAL_BUF_LENGTH);\n        print_logf(LOG_ERROR, \"Block Size\",\n                \"Maximal length: %d\", MAXIMAL_BUF_LENGTH);\n        cfg->out_block_size = DEFAULT_BUF_LENGTH;\n    }\n\n    // Special case for streaming test data\n    if (cfg->test_data && (!strcasecmp(cfg->test_data, \"-\") || *cfg->test_data == '@')) {\n        FILE *fp;\n        char line[INPUT_LINE_MAX];\n\n        if (*cfg->test_data == '@') {\n            print_logf(LOG_CRITICAL, \"Input\", \"Reading test data from \\\"%s\\\"\", &cfg->test_data[1]);\n            fp = fopen(&cfg->test_data[1], \"r\");\n        } else {\n            print_log(LOG_CRITICAL, \"Input\", \"Reading test data from stdin\");\n            fp = stdin;\n        }\n        if (!fp) {\n            print_logf(LOG_ERROR, \"Input\", \"Failed to open %s\", cfg->test_data);\n            exit(1);\n        }\n\n        while (fgets(line, INPUT_LINE_MAX, fp)) {\n            if (cfg->verbosity >= LOG_NOTICE)\n                print_logf(LOG_NOTICE, \"Input\", \"Processing test data \\\"%s\\\"...\", line);\n            r = 0;\n            // test a single decoder?\n            if (*line == '[') {\n                char *e = NULL;\n                unsigned d = (unsigned)strtol(&line[1], &e, 10);\n                if (!e || *e != ']') {\n                    print_logf(LOG_ERROR, \"Protocol\", \"Bad protocol number %.5s.\", line);\n                    exit(1);\n                }\n                e++;\n                r_device *r_dev = NULL;\n                for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {\n                    r_device *r_dev_i = *iter;\n                    if (r_dev_i->protocol_num == d) {\n                        r_dev = r_dev_i;\n                        break;\n                    }\n                }\n                if (!r_dev) {\n                    print_logf(LOG_ERROR, \"Protocol\", \"Unknown protocol number %u.\", d);\n                    exit(1);\n                }\n                if (cfg->verbosity >= LOG_NOTICE)\n                    print_logf(LOG_NOTICE, \"Input\", \"Verifying test data with device %s.\", r_dev->name);\n                if (rfraw_check(e)) {\n                    pulse_data_t pulse_data = {0};\n                    rfraw_parse(&pulse_data, e);\n                    list_t single_dev = {0};\n                    list_push(&single_dev, r_dev);\n                    if (!pulse_data.fsk_f2_est)\n                        r += run_ook_demods(&single_dev, &pulse_data);\n                    else\n                        r += run_fsk_demods(&single_dev, &pulse_data);\n                    list_free_elems(&single_dev, NULL);\n                } else\n                r += pulse_slicer_string(e, r_dev);\n                continue;\n            }\n            // otherwise test all decoders\n            if (rfraw_check(line)) {\n                pulse_data_t pulse_data = {0};\n                rfraw_parse(&pulse_data, line);\n                if (!pulse_data.fsk_f2_est)\n                    r += run_ook_demods(&demod->r_devs, &pulse_data);\n                else\n                    r += run_fsk_demods(&demod->r_devs, &pulse_data);\n            } else\n            for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {\n                r_device *r_dev = *iter;\n                if (cfg->verbosity >= LOG_NOTICE)\n                    print_logf(LOG_NOTICE, \"Input\", \"Verifying test data with device %s.\", r_dev->name);\n                r += pulse_slicer_string(line, r_dev);\n            }\n        }\n\n        if (*cfg->test_data == '@') {\n            fclose(fp);\n        }\n\n        r_free_cfg(cfg);\n        exit(!r);\n    }\n    // Special case for string test data\n    if (cfg->test_data) {\n        r = 0;\n        if (rfraw_check(cfg->test_data)) {\n            pulse_data_t pulse_data = {0};\n            rfraw_parse(&pulse_data, cfg->test_data);\n            if (!pulse_data.fsk_f2_est)\n                r += run_ook_demods(&demod->r_devs, &pulse_data);\n            else\n                r += run_fsk_demods(&demod->r_devs, &pulse_data);\n        } else\n        for (void **iter = demod->r_devs.elems; iter && *iter; ++iter) {\n            r_device *r_dev = *iter;\n            if (cfg->verbosity >= LOG_NOTICE)\n                print_logf(LOG_NOTICE, \"Input\", \"Verifying test data with device %s.\", r_dev->name);\n            r += pulse_slicer_string(cfg->test_data, r_dev);\n        }\n        r_free_cfg(cfg);\n        exit(!r);\n    }\n\n    // Special case for in files\n    if (cfg->in_files.len) {\n        unsigned char *test_mode_buf = malloc(DEFAULT_BUF_LENGTH * sizeof(unsigned char));\n        if (!test_mode_buf)\n            FATAL_MALLOC(\"test_mode_buf\");\n        float *test_mode_float_buf = malloc(DEFAULT_BUF_LENGTH / sizeof(int16_t) * sizeof(float));\n        if (!test_mode_float_buf)\n            FATAL_MALLOC(\"test_mode_float_buf\");\n\n        if (cfg->duration > 0) {\n            time(&cfg->stop_time);\n            cfg->stop_time += cfg->duration;\n        }\n\n        for (void **iter = cfg->in_files.elems; iter && *iter; ++iter) {\n            cfg->in_filename = *iter;\n\n            file_info_clear(&demod->load_info); // reset all info\n            file_info_parse_filename(&demod->load_info, cfg->in_filename);\n            // apply file info or default\n            cfg->samp_rate        = demod->load_info.sample_rate ? demod->load_info.sample_rate : sample_rate_0;\n            cfg->center_frequency = demod->load_info.center_frequency ? demod->load_info.center_frequency : cfg->frequency[0];\n\n            FILE *in_file;\n            if (strcmp(demod->load_info.path, \"-\") == 0) { // read samples from stdin\n                in_file = stdin;\n                cfg->in_filename = \"<stdin>\";\n            } else {\n                in_file = fopen(demod->load_info.path, \"rb\");\n                if (!in_file) {\n                    print_logf(LOG_ERROR, \"Input\", \"Opening file \\\"%s\\\" failed!\", cfg->in_filename);\n                    break;\n                }\n            }\n            print_logf(LOG_CRITICAL, \"Input\", \"Test mode active. Reading samples from file: %s\", cfg->in_filename); // Essential information (not quiet)\n            if (demod->load_info.format == CU8_IQ\n                    || demod->load_info.format == CS8_IQ\n                    || demod->load_info.format == S16_AM\n                    || demod->load_info.format == S16_FM) {\n                demod->sample_size = sizeof(uint8_t) * 2; // CU8, AM, FM\n            } else if (demod->load_info.format == CS16_IQ\n                    || demod->load_info.format == CF32_IQ) {\n                demod->sample_size = sizeof(int16_t) * 2; // CS16, CF32 (after conversion)\n            } else if (demod->load_info.format == PULSE_OOK) {\n                // ignore\n            } else {\n                print_logf(LOG_ERROR, \"Input\", \"Input format invalid \\\"%s\\\"\", file_info_string(&demod->load_info));\n                break;\n            }\n            if (cfg->verbosity >= LOG_NOTICE) {\n                print_logf(LOG_NOTICE, \"Input\", \"Input format \\\"%s\\\"\", file_info_string(&demod->load_info));\n            }\n            demod->sample_file_pos = 0.0;\n\n            // special case for pulse data file-inputs\n            if (demod->load_info.format == PULSE_OOK) {\n                while (!cfg->exit_async) {\n                    pulse_data_load(in_file, &demod->now, &demod->pulse_data, cfg->samp_rate);\n                    if (!demod->pulse_data.num_pulses)\n                        break;\n\n                    for (void **iter2 = demod->dumper.elems; iter2 && *iter2; ++iter2) {\n                        file_info_t const *dumper = *iter2;\n                        if (dumper->format == VCD_LOGIC) {\n                            pulse_data_print_vcd(dumper->file, &demod->pulse_data, '\\'');\n                        } else if (dumper->format == PULSE_OOK) {\n                            pulse_data_dump(dumper->file, &demod->pulse_data);\n                        } else {\n                            print_logf(LOG_ERROR, \"Input\", \"Dumper (%s) not supported on OOK input\", dumper->spec);\n                            exit(1);\n                        }\n                    }\n\n                    if (demod->pulse_data.fsk_f2_est) {\n                        run_fsk_demods(&demod->r_devs, &demod->pulse_data);\n                    }\n                    else {\n                        int p_events = run_ook_demods(&demod->r_devs, &demod->pulse_data);\n                        if (cfg->verbosity >= LOG_DEBUG)\n                            pulse_data_print(&demod->pulse_data);\n                        if (demod->analyze_pulses && (cfg->grab_mode <= 1 || (cfg->grab_mode == 2 && p_events == 0) || (cfg->grab_mode == 3 && p_events > 0))) {\n                            r_device device = {.log_fn = log_device_handler, .output_ctx = cfg};\n                            pulse_analyzer(&demod->pulse_data, PULSE_DATA_OOK, &device);\n                        }\n                    }\n                }\n\n                if (in_file != stdin) {\n                    fclose(in_file);\n                }\n\n                continue;\n            }\n\n            // default case for file-inputs\n            int n_blocks = 0;\n            unsigned long n_read;\n            delay_timer_t delay_timer;\n            delay_timer_init(&delay_timer);\n            do {\n                // Replay in realtime if requested\n                if (cfg->in_replay) {\n                    // per block delay\n                    unsigned delay_us = (unsigned)(1000000llu * DEFAULT_BUF_LENGTH / cfg->samp_rate / demod->sample_size / cfg->in_replay);\n                    if (demod->load_info.format == CF32_IQ)\n                        delay_us /= 2; // adjust for float only reading half as many samples\n                    delay_timer_wait(&delay_timer, delay_us);\n                }\n                // Convert CF32 file to CS16 buffer\n                if (demod->load_info.format == CF32_IQ) {\n                    n_read = fread(test_mode_float_buf, sizeof(float), DEFAULT_BUF_LENGTH / 2, in_file);\n                    // clamp float to [-1,1] and scale to Q0.15\n                    for (unsigned long n = 0; n < n_read; n++) {\n                        int s_tmp = test_mode_float_buf[n] * INT16_MAX;\n                        if (s_tmp < -INT16_MAX)\n                            s_tmp = -INT16_MAX;\n                        else if (s_tmp > INT16_MAX)\n                            s_tmp = INT16_MAX;\n                        ((int16_t *)test_mode_buf)[n] = s_tmp;\n                    }\n                    n_read *= 2; // convert to byte count\n                } else {\n                    n_read = fread(test_mode_buf, 1, DEFAULT_BUF_LENGTH, in_file);\n\n                    // Convert CS8 file to CU8 buffer\n                    if (demod->load_info.format == CS8_IQ) {\n                        for (unsigned long n = 0; n < n_read; n++) {\n                            test_mode_buf[n] = ((int8_t)test_mode_buf[n]) + 128;\n                        }\n                    }\n                }\n                if (n_read == 0) break;  // sdr_callback() will Segmentation Fault with len=0\n                demod->sample_file_pos = ((float)n_blocks * DEFAULT_BUF_LENGTH + n_read) / cfg->samp_rate / demod->sample_size;\n                n_blocks++; // this assumes n_read == DEFAULT_BUF_LENGTH\n                sdr_callback(test_mode_buf, n_read, cfg);\n            } while (n_read != 0 && !cfg->exit_async);\n\n            // Call a last time with cleared samples to ensure EOP detection\n            if (demod->sample_size == 2) { // CU8\n                memset(test_mode_buf, 128, DEFAULT_BUF_LENGTH); // 128 is 0 in unsigned data\n                // or is 127.5 a better 0 in cu8 data?\n                //for (unsigned long n = 0; n < DEFAULT_BUF_LENGTH/2; n++)\n                //    ((uint16_t *)test_mode_buf)[n] = 0x807f;\n            }\n            else { // CF32, CS16\n                    memset(test_mode_buf, 0, DEFAULT_BUF_LENGTH);\n            }\n            demod->sample_file_pos = ((float)n_blocks + 1) * DEFAULT_BUF_LENGTH / cfg->samp_rate / demod->sample_size;\n            sdr_callback(test_mode_buf, DEFAULT_BUF_LENGTH, cfg);\n\n            //Always classify a signal at the end of the file\n            if (demod->am_analyze)\n                am_analyze_classify(demod->am_analyze);\n            if (cfg->verbosity >= LOG_NOTICE) {\n                print_logf(LOG_NOTICE, \"Input\", \"Test mode file issued %d packets\", n_blocks);\n            }\n            reset_sdr_callback(cfg);\n\n            if (in_file != stdin) {\n                fclose(in_file);\n            }\n        }\n\n        close_dumpers(cfg);\n        free(test_mode_buf);\n        free(test_mode_float_buf);\n        r_free_cfg(cfg);\n        exit(0);\n    }\n\n    // Normal case, no test data, no in files\n    if (cfg->sr_filename) {\n        print_logf(LOG_ERROR, \"Input\", \"SR writing not recommended for live input\");\n        exit(1);\n    }\n\n#ifndef _WIN32\n    struct sigaction sigact;\n    sigact.sa_handler = sighandler;\n    sigemptyset(&sigact.sa_mask);\n    sigact.sa_flags = 0;\n    sigaction(SIGHUP, &sigact, NULL);\n    sigaction(SIGINT, &sigact, NULL);\n    sigaction(SIGTERM, &sigact, NULL);\n    sigaction(SIGQUIT, &sigact, NULL);\n    sigaction(SIGPIPE, &sigact, NULL);\n    sigaction(SIGUSR1, &sigact, NULL);\n    sigaction(SIGINFO, &sigact, NULL);\n#else\n    SetConsoleCtrlHandler((PHANDLER_ROUTINE)console_handler, TRUE);\n#endif\n\n    // TODO: remove this before next release\n    print_log(LOG_NOTICE, \"Input\", \"The internals of input handling changed, read about and report problems on PR #1978\");\n\n    if (cfg->dev_mode != DEVICE_MODE_MANUAL) {\n        r = start_sdr(cfg);\n        if (r < 0) {\n            exit(2);\n        }\n    }\n\n    if (cfg->duration > 0) {\n        time(&cfg->stop_time);\n        cfg->stop_time += cfg->duration;\n    }\n\n    time(&cfg->hop_start_time);\n\n    // add dummy socket to receive broadcasts\n    struct mg_add_sock_opts opts = {.user_data = cfg};\n    struct mg_connection *nc = mg_add_sock_opt(get_mgr(cfg), INVALID_SOCKET, timer_handler, opts);\n    // Send us MG_EV_TIMER event after 2.5 seconds\n    mg_set_timer(nc, mg_time() + 2.5);\n\n    while (!cfg->exit_async) {\n        mg_mgr_poll(cfg->mgr, 500);\n    }\n    if (cfg->verbosity >= LOG_INFO)\n        print_log(LOG_INFO, \"rtl_433\", \"stopping...\");\n    // final polls to drain the broadcast\n    //while (cfg->exit_async < 2) {\n    //    mg_mgr_poll(cfg->mgr, 100);\n    //}\n    sdr_stop(cfg->dev);\n    //print_log(LOG_INFO, \"rtl_433\", \"stopped.\");\n\n    if (cfg->report_stats > 0) {\n        event_occurred_handler(cfg, create_report_data(cfg, cfg->report_stats));\n        flush_report_data(cfg);\n    }\n\n    if (!cfg->exit_async) {\n        print_logf(LOG_ERROR, \"rtl_433\", \"Library error %d, exiting...\", r);\n        cfg->exit_code = r;\n    }\n\n    if (cfg->exit_code >= 0)\n        r = cfg->exit_code;\n    r_free_cfg(cfg);\n\n    return r >= 0 ? r : -r;\n}\n"
  },
  {
    "path": "src/samp_grab.c",
    "content": "/** @file\n    IQ sample grabber (ring buffer and dumper).\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#ifdef _WIN32\n#include <io.h>\n#include <fcntl.h>\n#ifdef _MSC_VER\n#define F_OK 0\n#endif\n#endif\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include \"samp_grab.h\"\n#include \"fatal.h\"\n\nsamp_grab_t *samp_grab_create(unsigned size)\n{\n    samp_grab_t *g;\n    g = calloc(1, sizeof(*g));\n    if (!g) {\n        WARN_CALLOC(\"samp_grab_create()\");\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    g->sg_size = size;\n    g->sg_counter = 1;\n\n    g->sg_buf = malloc(size);\n    if (!g->sg_buf) {\n        WARN_MALLOC(\"samp_grab_create()\");\n        free(g);\n        return NULL; // NOTE: returns NULL on alloc failure.\n    }\n\n    return g;\n}\n\nvoid samp_grab_free(samp_grab_t *g)\n{\n    if (g->sg_buf)\n        free(g->sg_buf);\n    free(g);\n}\n\nvoid samp_grab_push(samp_grab_t *g, unsigned char *iq_buf, uint32_t len)\n{\n    //fprintf(stderr, \"sg_index %d + len %d (size %d \", g->sg_index, len, g->sg_len);\n\n    g->sg_len += len;\n    if (g->sg_len > g->sg_size)\n        g->sg_len = g->sg_size;\n\n    //fprintf(stderr, \"-> %d)\\n\", g->sg_len);\n\n    while (len) {\n        unsigned chunk_len = len;\n        if (g->sg_index + chunk_len > g->sg_size)\n            chunk_len = g->sg_size - g->sg_index;\n\n        memcpy(&g->sg_buf[g->sg_index], iq_buf, chunk_len);\n        iq_buf += chunk_len;\n        len -= chunk_len;\n        g->sg_index += chunk_len;\n        if (g->sg_index >= g->sg_size)\n            g->sg_index = 0;\n    }\n}\n\nvoid samp_grab_reset(samp_grab_t *g)\n{\n    g->sg_len = 0;\n    g->sg_index = 0;\n}\n\n#define BLOCK_SIZE (128 * 1024) /* bytes */\n\nvoid samp_grab_write(samp_grab_t *g, unsigned grab_len, unsigned grab_end)\n{\n    if (!g->sg_buf)\n        return;\n\n    unsigned end_pos, start_pos, signal_bsize, wlen, wrest;\n    char f_name[64] = {0};\n    FILE *fp;\n\n    char *format = *g->sample_size == 2 ? \"cu8\" : \"cs16\";\n    double freq_mhz = *g->frequency / 1000000.0;\n    double rate_khz = *g->samp_rate / 1000.0;\n    while (1) {\n        snprintf(f_name, sizeof(f_name), \"g%03u_%gM_%gk.%s\", g->sg_counter, freq_mhz, rate_khz, format);\n        g->sg_counter++;\n        if (access(f_name, F_OK) == -1) {\n            break;\n        }\n    }\n\n    signal_bsize = *g->sample_size * grab_len;\n    signal_bsize += BLOCK_SIZE - (signal_bsize % BLOCK_SIZE);\n\n    if (signal_bsize > g->sg_len) {\n        fprintf(stderr, \"Signal bigger than buffer, signal = %u > buffer %u !!\\n\", signal_bsize, g->sg_len);\n        signal_bsize = g->sg_len;\n    }\n\n    // relative end in bytes from current sg_index down\n    end_pos = *g->sample_size * grab_end;\n    if (g->sg_index >= end_pos)\n        end_pos = g->sg_index - end_pos;\n    else\n        end_pos = g->sg_size - end_pos + g->sg_index;\n    // end_pos is now absolute in sg_buf\n\n    if (end_pos >= signal_bsize)\n        start_pos = end_pos - signal_bsize;\n    else\n        start_pos = g->sg_size - signal_bsize + end_pos;\n\n    //fprintf(stderr, \"signal_bsize = %d  -      sg_index = %d\\n\", signal_bsize, g->sg_index);\n    //fprintf(stderr, \"start_pos    = %d  -   buffer_size = %d\\n\", start_pos, g->sg_size);\n\n    fprintf(stderr, \"*** Saving signal to file %s (%u samples, %u bytes)\\n\", f_name, grab_len, signal_bsize);\n    fp = fopen(f_name, \"wb\");\n    if (!fp) {\n        fprintf(stderr, \"Failed to open %s\\n\", f_name);\n        return;\n    }\n\n    wlen = signal_bsize;\n    wrest = 0;\n    if (start_pos + signal_bsize > g->sg_size) {\n        wlen  = g->sg_size - start_pos;\n        wrest = signal_bsize - wlen;\n    }\n    //fprintf(stderr, \"*** Writing data from %d, len %d\\n\", start_pos, wlen);\n    fwrite(&g->sg_buf[start_pos], 1, wlen, fp);\n\n    if (wrest) {\n        //fprintf(stderr, \"*** Writing data from %d, len %d\\n\", 0, wrest);\n        fwrite(&g->sg_buf[0], 1, wrest, fp);\n    }\n\n    fclose(fp);\n}\n"
  },
  {
    "path": "src/sdr.c",
    "content": "/** @file\n    SDR input from RTLSDR or SoapySDR.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n    based on code\n    Copyright (C) 2012 by Steve Markgraf <steve@steve-m.de>\n    Copyright (C) 2014 by Kyle Keen <keenerd@gmail.com>\n    Copyright (C) 2016 by Robert X. Seger\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include \"sdr.h\"\n#include \"r_util.h\"\n#include \"optparse.h\"\n#include \"logger.h\"\n#include \"fatal.h\"\n#include \"compat_pthread.h\"\n#ifdef RTLSDR\n#include <rtl-sdr.h>\n#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__))\n// not available in rtlsdr 0.5.3, allow weak link for Linux\nint __attribute__((weak)) rtlsdr_set_bias_tee(rtlsdr_dev_t *dev, int on);\n#endif\n#ifdef LIBUSB1\n#include <libusb.h> /* libusb_error_name(), libusb_strerror() */\n#endif\n#endif\n#ifdef SOAPYSDR\n#include <SoapySDR/Version.h>\n#include <SoapySDR/Device.h>\n#include <SoapySDR/Formats.h>\n#include <SoapySDR/Logger.h>\n#if (SOAPY_SDR_API_VERSION < 0x00080000)\n#define SoapySDR_free(ptr) free(ptr)\n#endif\n#endif\n\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#ifdef _WIN32\n    #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)\n    #undef _WIN32_WINNT\n    #define _WIN32_WINNT 0x0600   /* Needed to pull in 'struct sockaddr_storage' */\n    #endif\n\n    #include <winsock2.h>\n    #include <ws2tcpip.h>\n    #define SHUT_RDWR SD_BOTH\n    #define perror(str)  ws2_perror(str)\n\n    static void ws2_perror (const char *str)\n    {\n        if (str && *str)\n            fprintf(stderr, \"%s: \", str);\n        fprintf(stderr, \"Winsock error %d.\\n\", WSAGetLastError());\n    }\n#else\n    #include <sys/types.h>\n    #include <sys/socket.h>\n    #include <netdb.h>\n    #include <netinet/in.h>\n\n    #define SOCKET          int\n    #define INVALID_SOCKET  (-1)\n    #define closesocket(x)  close(x)\n#endif\n\n#define GAIN_STR_MAX_SIZE 64\n\nstruct sdr_dev {\n    SOCKET rtl_tcp;\n    uint32_t rtl_tcp_freq; ///< last known center frequency, rtl_tcp only.\n    uint32_t rtl_tcp_rate; ///< last known sample rate, rtl_tcp only.\n\n#ifdef SOAPYSDR\n    SoapySDRDevice *soapy_dev;\n    SoapySDRStream *soapy_stream;\n    double fullScale;\n#endif\n\n#ifdef RTLSDR\n    rtlsdr_dev_t *rtlsdr_dev;\n    sdr_event_cb_t rtlsdr_cb;\n    void *rtlsdr_cb_ctx;\n#endif\n\n    char *dev_info;\n\n    int running;\n    uint8_t *buffer; ///< sdr data buffer current and past frames\n    size_t buffer_size; ///< sdr data buffer overall size (num * len)\n    size_t buffer_pos; ///< sdr data buffer next write position\n\n    int sample_size;\n    int sample_signed;\n\n    uint32_t sample_rate;\n    uint32_t center_frequency;\n\n#ifdef THREADS\n    pthread_t thread;\n    pthread_mutex_t lock; ///< lock for exit_acquire\n    int exit_acquire;\n\n    // acquire thread args\n    sdr_event_cb_t async_cb;\n    void *async_ctx;\n    uint32_t buf_num;\n    uint32_t buf_len;\n#endif\n};\n\n/* rtl_tcp helpers */\n\n#pragma pack(push, 1)\nstruct rtl_tcp_info {\n    char magic[4];             // \"RTL0\"\n    uint32_t tuner_number;     // big endian\n    uint32_t tuner_gain_count; // big endian\n};\n#pragma pack(pop)\n\nstatic int rtltcp_open(sdr_dev_t **out_dev, char const *dev_query, int verbose)\n{\n    UNUSED(verbose);\n    char const *host = \"localhost\";\n    char const *port = \"1234\";\n    char hostport[280]; // 253 chars DNS name plus extra chars\n\n    char *param = arg_param(dev_query); // strip scheme\n    hostport[0] = '\\0';\n    if (param) {\n        snprintf(hostport, sizeof(hostport), \"%s\", param);\n    }\n    hostport_param(hostport, &host, &port);\n\n    print_logf(LOG_CRITICAL, \"SDR\", \"rtl_tcp input from %s port %s\", host, port);\n\n#ifdef _WIN32\n    WSADATA wsa;\n\n    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {\n        perror(\"WSAStartup()\");\n        return -1;\n    }\n#endif\n\n    struct addrinfo hints, *res, *res0;\n    int ret;\n    SOCKET sock;\n\n    memset(&hints, 0, sizeof(hints));\n    hints.ai_family   = PF_UNSPEC;\n    hints.ai_socktype = SOCK_STREAM;\n    hints.ai_protocol = 0;\n    hints.ai_flags    = AI_ADDRCONFIG;\n\n    ret = getaddrinfo(host, port, &hints, &res0);\n    if (ret) {\n        print_log(LOG_ERROR, __func__, gai_strerror(ret));\n        return -1;\n    }\n    sock = INVALID_SOCKET;\n    for (res = res0; res; res = res->ai_next) {\n        sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n        if (sock >= 0) {\n            ret = connect(sock, res->ai_addr, res->ai_addrlen);\n            if (ret == -1) {\n                perror(\"connect\");\n                closesocket(sock);\n                sock = INVALID_SOCKET;\n            }\n            else\n                break; // success\n        }\n    }\n    freeaddrinfo(res0);\n    if (sock == INVALID_SOCKET) {\n        perror(\"socket\");\n        return -1;\n    }\n\n    //int const value_one = 1;\n    //ret = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&value_one, sizeof(value_one));\n    //if (ret < 0)\n    //    fprintf(stderr, \"rtl_tcp TCP_NODELAY failed\\n\");\n\n    struct rtl_tcp_info info;\n    ret = recv(sock, (char *)&info, sizeof (info), 0);\n    if (ret != 12) {\n        print_logf(LOG_ERROR, __func__, \"Bad rtl_tcp header (%d)\", ret);\n        return -1;\n    }\n    if (strncmp(info.magic, \"RTL0\", 4)) {\n        info.tuner_number = 0; // terminate magic\n        print_logf(LOG_ERROR, __func__, \"Bad rtl_tcp header magic \\\"%s\\\"\", info.magic);\n        return -1;\n    }\n\n    unsigned tuner_number = ntohl(info.tuner_number);\n    //int tuner_gain_count  = ntohl(info.tuner_gain_count);\n\n    char const *tuner_names[] = { \"Unknown\", \"E4000\", \"FC0012\", \"FC0013\", \"FC2580\", \"R820T\", \"R828D\" };\n    char const *tuner_name = tuner_number > sizeof (tuner_names) ? \"Invalid\" : tuner_names[tuner_number];\n\n    print_logf(LOG_CRITICAL, \"SDR\", \"rtl_tcp connected to %s:%s (Tuner: %s)\", host, port, tuner_name);\n\n    sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));\n    if (!dev) {\n        WARN_CALLOC(\"rtltcp_open()\");\n        return -1; // NOTE: returns error on alloc failure.\n    }\n#ifdef THREADS\n    pthread_mutex_init(&dev->lock, NULL);\n#endif\n\n    dev->rtl_tcp = sock;\n    dev->sample_size = sizeof(uint8_t) * 2; // CU8\n    dev->sample_signed = 0;\n\n    *out_dev = dev;\n    return 0;\n}\n\nstatic int rtltcp_close(SOCKET sock)\n{\n    int ret = shutdown(sock, SHUT_RDWR);\n    if (ret == -1) {\n        perror(\"shutdown\");\n        return -1;\n    }\n\n    ret = closesocket(sock);\n    if (ret == -1) {\n        perror(\"close\");\n        return -1;\n    }\n\n    return 0;\n}\n\nstatic int rtltcp_read_loop(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)\n{\n    size_t buffer_size = (size_t)buf_num * buf_len;\n    if (dev->buffer_size != buffer_size) {\n        free(dev->buffer);\n        dev->buffer = malloc(buffer_size);\n        if (!dev->buffer) {\n            WARN_MALLOC(\"rtltcp_read_loop()\");\n            return -1; // NOTE: returns error on alloc failure.\n        }\n        dev->buffer_size = buffer_size;\n        dev->buffer_pos = 0;\n    }\n\n    dev->running = 1;\n    do {\n        if (dev->buffer_pos + buf_len > buffer_size)\n            dev->buffer_pos = 0;\n        uint8_t *buffer = &dev->buffer[dev->buffer_pos];\n        dev->buffer_pos += buf_len;\n\n        unsigned n_read = 0;\n        int r;\n\n        do {\n            r = recv(dev->rtl_tcp, &buffer[n_read], buf_len - n_read, MSG_WAITALL);\n            if (r <= 0)\n                break;\n            n_read += r;\n            //fprintf(stderr, \"readStream ret=%d (of %u)\\n\", r, n_read);\n        } while (n_read < buf_len);\n        //fprintf(stderr, \"readStream ret=%d (read %u)\\n\", r, n_read);\n\n        if (r < 0) {\n            print_logf(LOG_WARNING, __func__, \"sync read failed. %d\", r);\n        }\n        if (n_read == 0) {\n            perror(\"rtl_tcp\");\n            dev->running = 0;\n        }\n\n#ifdef THREADS\n        pthread_mutex_lock(&dev->lock);\n#endif\n        uint32_t sample_rate      = dev->sample_rate;\n        uint32_t center_frequency = dev->center_frequency;\n#ifdef THREADS\n        pthread_mutex_unlock(&dev->lock);\n#endif\n        sdr_event_t ev = {\n                .ev               = SDR_EV_DATA,\n                .sample_rate      = sample_rate,\n                .center_frequency = center_frequency,\n                .buf              = buffer,\n                .len              = n_read,\n        };\n#ifdef THREADS\n        pthread_mutex_lock(&dev->lock);\n        int exit_acquire = dev->exit_acquire;\n        pthread_mutex_unlock(&dev->lock);\n        if (exit_acquire) {\n            break; // do not deliver any more events\n        }\n#endif\n        if (n_read > 0) // prevent a crash in callback\n            cb(&ev, ctx);\n\n    } while (dev->running);\n\n    return 0;\n}\n\n#pragma pack(push, 1)\nstruct command {\n    unsigned char cmd;\n    unsigned int param;\n};\n#pragma pack(pop)\n\n// rtl_tcp API\n#define RTLTCP_SET_FREQ 0x01\n#define RTLTCP_SET_SAMPLE_RATE 0x02\n#define RTLTCP_SET_GAIN_MODE 0x03\n#define RTLTCP_SET_GAIN 0x04\n#define RTLTCP_SET_FREQ_CORRECTION 0x05\n#define RTLTCP_SET_IF_TUNER_GAIN 0x06\n#define RTLTCP_SET_TEST_MODE 0x07\n#define RTLTCP_SET_AGC_MODE 0x08\n#define RTLTCP_SET_DIRECT_SAMPLING 0x09\n#define RTLTCP_SET_OFFSET_TUNING 0x0a\n#define RTLTCP_SET_RTL_XTAL 0x0b\n#define RTLTCP_SET_TUNER_XTAL 0x0c\n#define RTLTCP_SET_TUNER_GAIN_BY_ID 0x0d\n#define RTLTCP_SET_BIAS_TEE 0x0e\n\nstatic int rtltcp_command(sdr_dev_t *dev, char cmd, int param)\n{\n    struct command command;\n    command.cmd   = cmd;\n    command.param = htonl(param);\n\n    return sizeof(command) == send(dev->rtl_tcp, (const char*) &command, sizeof(command), 0) ? 0 : -1;\n}\n\n/* RTL-SDR helpers */\n\n#ifdef RTLSDR\n\nstatic int sdr_open_rtl(sdr_dev_t **out_dev, char const *dev_query, int verbose)\n{\n    uint32_t device_count = rtlsdr_get_device_count();\n    if (!device_count) {\n        print_log(LOG_CRITICAL, \"SDR\", \"No supported devices found.\");\n        return -1;\n    }\n\n    if (verbose)\n        print_logf(LOG_NOTICE, \"SDR\", \"Found %u device(s)\", device_count);\n\n    int dev_index = 0;\n    // select rtlsdr device by serial (-d :<serial>)\n    if (dev_query && *dev_query == ':') {\n        dev_index = rtlsdr_get_index_by_serial(&dev_query[1]);\n        if (dev_index < 0) {\n            if (verbose)\n                print_logf(LOG_ERROR, \"SDR\", \"Could not find device with serial '%s' (err %d)\",\n                        &dev_query[1], dev_index);\n            return -1;\n        }\n    }\n\n    // select rtlsdr device by number (-d <n>)\n    else if (dev_query) {\n        dev_index = atoi(dev_query);\n        // check if 0 is a parsing error?\n        if (dev_index < 0) {\n            // select first available rtlsdr device\n            dev_index = 0;\n            dev_query = NULL;\n        }\n    }\n\n    char vendor[256] = \"n/a\", product[256] = \"n/a\", serial[256] = \"n/a\";\n    int r = -1;\n    sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));\n    if (!dev) {\n        WARN_CALLOC(\"sdr_open_rtl()\");\n        return -1; // NOTE: returns error on alloc failure.\n    }\n#ifdef THREADS\n    pthread_mutex_init(&dev->lock, NULL);\n#endif\n\n    for (uint32_t i = dev_query ? dev_index : 0;\n            //cast quiets -Wsign-compare; if dev_index were < 0, would have returned -1 above\n            i < (dev_query ? (unsigned)dev_index + 1 : device_count);\n            i++) {\n        rtlsdr_get_device_usb_strings(i, vendor, product, serial);\n\n        if (verbose)\n            print_logf(LOG_NOTICE, \"SDR\", \"trying device %u: %s, %s, SN: %s\",\n                    i, vendor, product, serial);\n\n        r = rtlsdr_open(&dev->rtlsdr_dev, i);\n        if (r < 0) {\n            if (verbose)\n                print_logf(LOG_ERROR, __func__, \"Failed to open rtlsdr device #%u.\", i);\n        }\n        else {\n            if (verbose)\n                print_logf(LOG_CRITICAL, \"SDR\", \"Using device %u: %s, %s, SN: %s, \\\"%s\\\"\",\n                        i, vendor, product, serial, rtlsdr_get_device_name(i));\n            dev->sample_size = sizeof(uint8_t) * 2; // CU8\n            dev->sample_signed = 0;\n\n            size_t info_len = 41 + strlen(vendor) + strlen(product) + strlen(serial);\n            dev->dev_info = malloc(info_len);\n            if (!dev->dev_info)\n                FATAL_MALLOC(\"sdr_open_rtl\");\n            snprintf(dev->dev_info, info_len, \"{\\\"vendor\\\":\\\"%s\\\", \\\"product\\\":\\\"%s\\\", \\\"serial\\\":\\\"%s\\\"}\",\n                    vendor, product, serial);\n            break;\n        }\n    }\n    if (r < 0) {\n        free(dev);\n        if (verbose)\n            print_log(LOG_ERROR, __func__, \"Unable to open a device\");\n    }\n    else {\n        *out_dev = dev;\n    }\n    return r;\n}\n\nstatic int rtlsdr_find_tuner_gain(sdr_dev_t *dev, int centigain, int verbose)\n{\n    /* Get allowed gains */\n    int gains_count = rtlsdr_get_tuner_gains(dev->rtlsdr_dev, NULL);\n    if (gains_count < 0) {\n        if (verbose)\n            print_log(LOG_WARNING, __func__, \"Unable to get exact gains\");\n        return centigain;\n    }\n    if (gains_count < 1) {\n        if (verbose)\n            print_log(LOG_WARNING, __func__, \"No exact gains\");\n        return centigain;\n    }\n    if (gains_count > 29) {\n        print_log(LOG_ERROR, __func__, \"Unexpected gain count, notify maintainers please!\");\n        return centigain;\n    }\n    // We known the maximum nunmber of gains is 29.\n    // Let's not waste an alloc\n    int gains[29] = {0};\n    rtlsdr_get_tuner_gains(dev->rtlsdr_dev, gains);\n\n    /* Find allowed gain */\n    for (int i = 0; i < gains_count; ++i) {\n        if (centigain <= gains[i]) {\n            centigain = gains[i];\n            break;\n        }\n    }\n    if (centigain > gains[gains_count - 1]) {\n        centigain = gains[gains_count - 1];\n    }\n\n    return centigain;\n}\n\nstatic void rtlsdr_read_cb(unsigned char *iq_buf, uint32_t len, void *ctx)\n{\n    sdr_dev_t *dev = ctx;\n\n    //fprintf(stderr, \"rtlsdr_read_cb enter...\\n\");\n#ifdef THREADS\n    pthread_mutex_lock(&dev->lock);\n    int exit_acquire = dev->exit_acquire;\n    pthread_mutex_unlock(&dev->lock);\n    if (exit_acquire) {\n        // we get one more call after rtlsdr_cancel_async(),\n        // it then takes a full second until rtlsdr_read_async() ends.\n        //fprintf(stderr, \"rtlsdr_read_cb stopping...\\n\");\n        return; // do not deliver any more events\n    }\n#endif\n\n    if (dev->buffer_pos + len > dev->buffer_size)\n        dev->buffer_pos = 0;\n    uint8_t *buffer = &dev->buffer[dev->buffer_pos];\n    dev->buffer_pos += len;\n\n    // NOTE: we need to copy the buffer, it might go away on cancel_async\n    memcpy(buffer, iq_buf, len);\n\n#ifdef THREADS\n    pthread_mutex_lock(&dev->lock);\n#endif\n    uint32_t sample_rate      = dev->sample_rate;\n    uint32_t center_frequency = dev->center_frequency;\n#ifdef THREADS\n    pthread_mutex_unlock(&dev->lock);\n#endif\n    sdr_event_t ev = {\n            .ev               = SDR_EV_DATA,\n            .sample_rate      = sample_rate,\n            .center_frequency = center_frequency,\n            .buf              = buffer,\n            .len              = len,\n    };\n    //fprintf(stderr, \"rtlsdr_read_cb cb...\\n\");\n    if (len > 0) // prevent a crash in callback\n        dev->rtlsdr_cb(&ev, dev->rtlsdr_cb_ctx);\n    //fprintf(stderr, \"rtlsdr_read_cb cb done.\\n\");\n    // NOTE: we actually need to copy the buffer to prevent it going away on cancel_async\n}\n\nstatic int rtlsdr_read_loop(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)\n{\n    size_t buffer_size = (size_t)buf_num * buf_len;\n    if (dev->buffer_size != buffer_size) {\n        free(dev->buffer);\n        dev->buffer = malloc(buffer_size);\n        if (!dev->buffer) {\n            WARN_MALLOC(\"rtlsdr_read_loop()\");\n            return -1; // NOTE: returns error on alloc failure.\n        }\n        dev->buffer_size = buffer_size;\n        dev->buffer_pos = 0;\n    }\n\n    int r = 0;\n\n    dev->rtlsdr_cb = cb;\n    dev->rtlsdr_cb_ctx = ctx;\n\n    dev->running = 1;\n\n        r = rtlsdr_read_async(dev->rtlsdr_dev, rtlsdr_read_cb, dev, buf_num, buf_len);\n        // rtlsdr_read_async() returns possible error codes from:\n        //     if (!dev) return -1;\n        //     if (RTLSDR_INACTIVE != dev->async_status) return -2;\n        //     r = libusb_submit_transfer(dev->xfer[i]);\n        //     r = libusb_handle_events_timeout_completed(dev->ctx, &tv,\n        //     r = libusb_cancel_transfer(dev->xfer[i]);\n        // We can safely assume it's an libusb error.\n        if (r < 0) {\n#ifdef LIBUSB1\n            print_logf(LOG_ERROR, __func__, \"%s: %s! \"\n                            \"Check your RTL-SDR dongle, USB cables, and power supply.\",\n                    libusb_error_name(r), libusb_strerror(r));\n#else\n            print_logf(LOG_ERROR, __func__, \"LIBUSB_ERROR: %d! \"\n                            \"Check your RTL-SDR dongle, USB cables, and power supply.\",\n                    r);\n#endif\n            dev->running = 0;\n        }\n    print_log(LOG_DEBUG, __func__, \"rtlsdr_read_async done\");\n\n    return r;\n}\n\n#endif\n\n/* SoapySDR helpers */\n\n#ifdef SOAPYSDR\n\nstatic int soapysdr_set_bandwidth(SoapySDRDevice *dev, uint32_t bandwidth)\n{\n    int r;\n    r = (int)SoapySDRDevice_setBandwidth(dev, SOAPY_SDR_RX, 0, (double)bandwidth);\n    uint32_t applied_bw = 0;\n    if (r != 0) {\n        print_log(LOG_WARNING, \"SDR\", \"Failed to set bandwidth.\");\n    }\n    else if (bandwidth > 0) {\n        applied_bw = (uint32_t)SoapySDRDevice_getBandwidth(dev, SOAPY_SDR_RX, 0);\n        if (applied_bw)\n            print_logf(LOG_NOTICE, \"SDR\", \"Bandwidth parameter %u Hz resulted in %u Hz.\", bandwidth, applied_bw);\n        else\n            print_logf(LOG_NOTICE, \"SDR\", \"Set bandwidth parameter %u Hz.\", bandwidth);\n    }\n    else {\n        print_logf(LOG_NOTICE, \"SDR\", \"Bandwidth set to automatic resulted in %u Hz.\", applied_bw);\n    }\n    return r;\n}\n\nstatic int soapysdr_direct_sampling(SoapySDRDevice *dev, int on)\n{\n    int r = 0;\n    char const *value;\n    if (on == 0)\n        value = \"0\";\n    else if (on == 1)\n        value = \"1\";\n    else if (on == 2)\n        value = \"2\";\n    else\n        return -1;\n    SoapySDRDevice_writeSetting(dev, \"direct_samp\", value);\n    char *set_value = SoapySDRDevice_readSetting(dev, \"direct_samp\");\n\n    if (set_value == NULL) {\n        print_log(LOG_ERROR, __func__, \"Failed to set direct sampling moden\");\n        return r;\n    }\n    int set_num = atoi(set_value);\n    if (set_num == 0) {\n        print_log(LOG_CRITICAL, \"SDR\", \"Direct sampling mode disabled.\");}\n    else if (set_num == 1) {\n        print_log(LOG_CRITICAL, \"SDR\", \"Enabled direct sampling mode, input 1/I.\");}\n    else if (set_num == 2) {\n        print_log(LOG_CRITICAL, \"SDR\", \"Enabled direct sampling mode, input 2/Q.\");}\n    else if (set_num == 3) {\n        print_log(LOG_CRITICAL, \"SDR\", \"Enabled no-mod direct sampling mode.\");}\n    SoapySDR_free(set_value);\n    return r;\n}\n\nstatic int soapysdr_offset_tuning(SoapySDRDevice *dev)\n{\n    int r = 0;\n    SoapySDRDevice_writeSetting(dev, \"offset_tune\", \"true\");\n    char *set_value = SoapySDRDevice_readSetting(dev, \"offset_tune\");\n\n    if (strcmp(set_value, \"true\") != 0) {\n        /* TODO: detection of failure modes\n        if (r == -2)\n            print_log(LOG_WARNING, __func__, \"Failed to set offset tuning: tuner doesn't support offset tuning!\");\n        else if (r == -3)\n            print_log(LOG_WARNING, __func__, \"Failed to set offset tuning: direct sampling not combinable with offset tuning!\");\n        else\n        */\n            print_log(LOG_WARNING, __func__, \"Failed to set offset tuning.\");\n    }\n    else {\n        print_log(LOG_CRITICAL, \"SDR\", \"Offset tuning mode enabled.\");\n    }\n    SoapySDR_free(set_value);\n    return r;\n}\n\nstatic int soapysdr_auto_gain(SoapySDRDevice *dev, int verbose)\n{\n    int r = 0;\n\n    r = SoapySDRDevice_hasGainMode(dev, SOAPY_SDR_RX, 0);\n    if (r) {\n        r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 1);\n        if (r != 0) {\n            print_log(LOG_WARNING, __func__, \"Failed to enable automatic gain.\");\n        }\n        else {\n            if (verbose)\n                print_log(LOG_CRITICAL, \"SDR\", \"Tuner set to automatic gain.\");\n        }\n    }\n\n    // Per-driver hacks TODO: clean this up\n    char *driver = SoapySDRDevice_getDriverKey(dev);\n    if (strcmp(driver, \"HackRF\") == 0) {\n        // HackRF has three gains LNA, VGA, and AMP, setting total distributes amongst, 116.0 dB seems to work well,\n        // even though it logs HACKRF_ERROR_INVALID_PARAM? https://github.com/rxseger/rx_tools/issues/9\n        // Total gain is distributed amongst all gains, 116 = 37,65,1; the LNA is OK (<40) but VGA is out of range (65 > 62)\n        // TODO: generic means to set all gains, of any SDR? string parsing LNA=#,VGA=#,AMP=#?\n        r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, \"LNA\", 40.); // max 40\n        if (r != 0) {\n            print_log(LOG_WARNING, __func__, \"Failed to set LNA tuner gain.\");\n        }\n        r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, \"VGA\", 20.); // max 65\n        if (r != 0) {\n            print_log(LOG_WARNING, __func__, \"Failed to set VGA tuner gain.\");\n        }\n        r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, \"AMP\", 0.); // on or off\n        if (r != 0) {\n            print_log(LOG_WARNING, __func__, \"Failed to set AMP tuner gain.\");\n        }\n\n    }\n    SoapySDR_free(driver);\n    // otherwise leave unset, hopefully the driver has good defaults\n\n    return r;\n}\n\nstatic int soapysdr_gain_str_set(SoapySDRDevice *dev, char const *gain_str, int verbose)\n{\n    if (!gain_str || !*gain_str || strlen(gain_str) >= GAIN_STR_MAX_SIZE)\n        return -1;\n\n    int r = 0;\n\n    // Disable automatic gain\n    r = SoapySDRDevice_hasGainMode(dev, SOAPY_SDR_RX, 0);\n    if (r) {\n        r = SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, 0, 0);\n        if (r != 0) {\n            print_log(LOG_WARNING, __func__, \"Failed to disable automatic gain.\");\n        }\n        else {\n            if (verbose)\n                print_log(LOG_NOTICE, \"SDR\", \"Tuner set to manual gain.\");\n        }\n    }\n\n    if (strchr(gain_str, '=')) {\n        char gain_cpy[GAIN_STR_MAX_SIZE];\n        snprintf(gain_cpy, sizeof(gain_cpy), \"%s\", gain_str);\n        char *gain_p = gain_cpy;\n        // Set each gain individually (more control)\n        char *name;\n        char *value;\n        while (getkwargs(&gain_p, &name, &value)) {\n            double num = atof(value);\n            if (verbose)\n                print_logf(LOG_NOTICE, \"SDR\", \"Setting gain element %s: %f dB\", name, num);\n            r = SoapySDRDevice_setGainElement(dev, SOAPY_SDR_RX, 0, name, num);\n            if (r != 0) {\n                print_logf(LOG_WARNING, __func__, \"setGainElement(%s, %f) failed: %d\", name, num, r);\n            }\n        }\n    }\n    else {\n        // Set overall gain and let SoapySDR distribute amongst components\n        double value = atof(gain_str);\n        r = SoapySDRDevice_setGain(dev, SOAPY_SDR_RX, 0, value);\n        if (r != 0) {\n            print_log(LOG_WARNING, __func__, \"Failed to set tuner gain.\");\n        }\n        else {\n            if (verbose)\n                print_logf(LOG_NOTICE, __func__, \"Tuner gain set to %0.2f dB.\", value);\n        }\n        // read back and print each individual gain element\n        if (verbose) {\n            size_t len = 0;\n            char **gains = SoapySDRDevice_listGains(dev, SOAPY_SDR_RX, 0, &len);\n            fprintf(stderr, \"Gain elements: \");\n            for (size_t i = 0; i < len; ++i) {\n                double gain = SoapySDRDevice_getGain(dev, SOAPY_SDR_RX, 0);\n                fprintf(stderr, \"%s=%g \", gains[i], gain);\n            }\n            fprintf(stderr, \"\\n\");\n            SoapySDRStrings_clear(&gains, len);\n        }\n    }\n\n    return r;\n}\n\nstatic void soapysdr_show_device_info(SoapySDRDevice *dev)\n{\n    size_t len = 0, i = 0;\n    char *hwkey;\n    SoapySDRKwargs args;\n    char **antennas;\n    char **gains;\n    char **frequencies;\n    SoapySDRRange *frequencyRanges;\n    SoapySDRRange *rates;\n    SoapySDRRange *bandwidths;\n    double fullScale;\n    char **stream_formats;\n    char *native_stream_format;\n\n    int direction = SOAPY_SDR_RX;\n    size_t channel = 0;\n\n    hwkey = SoapySDRDevice_getHardwareKey(dev);\n    fprintf(stderr, \"Using device %s: \", hwkey);\n    SoapySDR_free(hwkey);\n\n    args = SoapySDRDevice_getHardwareInfo(dev);\n    for (i = 0; i < args.size; ++i) {\n        fprintf(stderr, \"%s=%s \", args.keys[i], args.vals[i]);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDRKwargs_clear(&args);\n\n    antennas = SoapySDRDevice_listAntennas(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu antenna(s): \", len);\n    for (i = 0; i < len; ++i) {\n        fprintf(stderr, \"%s \", antennas[i]);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDRStrings_clear(&antennas, len);\n\n    gains = SoapySDRDevice_listGains(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu gain(s): \", len);\n    for (i = 0; i < len; ++i) {\n        SoapySDRRange gainRange = SoapySDRDevice_getGainRange(dev, direction, channel);\n        fprintf(stderr, \"%s %.0f - %.0f (step %.0f) \", gains[i], gainRange.minimum, gainRange.maximum, gainRange.step);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDRStrings_clear(&gains, len);\n\n    frequencies = SoapySDRDevice_listFrequencies(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu frequencies: \", len);\n    for (i = 0; i < len; ++i) {\n        fprintf(stderr, \"%s \", frequencies[i]);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDRStrings_clear(&frequencies, len);\n\n    frequencyRanges = SoapySDRDevice_getFrequencyRange(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu frequency range(s): \", len);\n    for (i = 0; i < len; ++i) {\n        fprintf(stderr, \"%.0f - %.0f (step %.0f) \", frequencyRanges[i].minimum, frequencyRanges[i].maximum, frequencyRanges[i].step);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDR_free(frequencyRanges);\n\n    rates = SoapySDRDevice_getSampleRateRange(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu sample rate range(s): \", len);\n    for (i = 0; i < len; ++i) {\n        if (rates[i].minimum == rates[i].maximum)\n            fprintf(stderr, \"%.0f \", rates[i].minimum);\n        else\n            fprintf(stderr, \"%.0f - %.0f (step %.0f) \", rates[i].minimum, rates[i].maximum, rates[i].step);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDR_free(rates);\n\n    bandwidths = SoapySDRDevice_getBandwidthRange(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu bandwidth range(s): \", len);\n    for (i = 0; i < len; ++i) {\n        fprintf(stderr, \"%.0f - %.0f (step %.0f) \", bandwidths[i].minimum, bandwidths[i].maximum, bandwidths[i].step);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDR_free(bandwidths);\n\n    double bandwidth = SoapySDRDevice_getBandwidth(dev, direction, channel);\n    fprintf(stderr, \"Found current bandwidth %.0f\\n\", bandwidth);\n\n    stream_formats = SoapySDRDevice_getStreamFormats(dev, direction, channel, &len);\n    fprintf(stderr, \"Found %zu stream format(s): \", len);\n    for (i = 0; i < len; ++i) {\n        fprintf(stderr, \"%s \", stream_formats[i]);\n    }\n    fprintf(stderr, \"\\n\");\n    SoapySDRStrings_clear(&stream_formats, len);\n\n    native_stream_format = SoapySDRDevice_getNativeStreamFormat(dev, direction, channel, &fullScale);\n    fprintf(stderr, \"Found native stream format: %s (full scale: %.1f)\\n\", native_stream_format, fullScale);\n    SoapySDR_free(native_stream_format);\n}\n\nstatic int sdr_open_soapy(sdr_dev_t **out_dev, char const *dev_query, int verbose)\n{\n    if (verbose)\n        SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);\n\n    sdr_dev_t *dev = calloc(1, sizeof(sdr_dev_t));\n    if (!dev) {\n        WARN_CALLOC(\"sdr_open_soapy()\");\n        return -1; // NOTE: returns error on alloc failure.\n    }\n#ifdef THREADS\n    pthread_mutex_init(&dev->lock, NULL);\n#endif\n\n    dev->soapy_dev = SoapySDRDevice_makeStrArgs(dev_query);\n    if (!dev->soapy_dev) {\n        if (verbose)\n            print_logf(LOG_ERROR, __func__, \"Failed to open sdr device matching '%s'.\", dev_query);\n        free(dev);\n        return -1;\n    }\n\n    if (verbose)\n        soapysdr_show_device_info(dev->soapy_dev);\n\n    // select a stream format, in preference order: native CU8, CS8, CS16, forced CS16\n    // stream_formats = SoapySDRDevice_getStreamFormats(dev->soapy_dev, SOAPY_SDR_RX, 0, &len);\n    char *native_format = SoapySDRDevice_getNativeStreamFormat(dev->soapy_dev, SOAPY_SDR_RX, 0, &dev->fullScale);\n    char const *selected_format;\n    if (!strcmp(SOAPY_SDR_CU8, native_format)) {\n        // actually not supported by SoapySDR\n        selected_format = SOAPY_SDR_CU8;\n        dev->sample_size = sizeof(uint8_t); // CU8\n        dev->sample_signed = 0;\n    }\n//    else if (!strcmp(SOAPY_SDR_CS8, native_format)) {\n//        // TODO: CS8 needs conversion to CU8\n//        // e.g. RTL-SDR (8 bit), scale is 128.0\n//        selected_format = SOAPY_SDR_CS8;\n//        dev->sample_size = sizeof(int8_t) * 2; // CS8\n//        dev->sample_signed = 1;\n//    }\n    else if (!strcmp(SOAPY_SDR_CS16, native_format)) {\n        // e.g. LimeSDR-mini (12 bit), native scale is 2048.0\n        // e.g. SDRplay RSP1A (14 bit), native scale is 32767.0\n        selected_format = SOAPY_SDR_CS16;\n        dev->sample_size = sizeof(int16_t) * 2; // CS16\n        dev->sample_signed = 1;\n    }\n    else {\n        // force CS16\n        selected_format = SOAPY_SDR_CS16;\n        dev->sample_size = sizeof(int16_t) * 2; // CS16\n        dev->sample_signed = 1;\n        dev->fullScale = 32768.0; // assume max for SOAPY_SDR_CS16\n    }\n    SoapySDR_free(native_format);\n\n    SoapySDRKwargs args = SoapySDRDevice_getHardwareInfo(dev->soapy_dev);\n    size_t info_len     = 2;\n    for (size_t i = 0; i < args.size; ++i) {\n        info_len += strlen(args.keys[i]) + strlen(args.vals[i]) + 6;\n    }\n    char *p = dev->dev_info = malloc(info_len);\n    if (!dev->dev_info)\n        FATAL_MALLOC(\"sdr_open_soapy\");\n    for (size_t i = 0; i < args.size; ++i) {\n        p += sprintf(p, \"%s\\\"%s\\\":\\\"%s\\\"\", i ? \",\" : \"{\", args.keys[i], args.vals[i]);\n    }\n    sprintf(p, \"}\");\n    SoapySDRKwargs_clear(&args);\n\n    SoapySDRKwargs stream_args = {0};\n    int r;\n#if SOAPY_SDR_API_VERSION >= 0x00080000\n    // API version 0.8\n#undef SoapySDRDevice_setupStream\n    dev->soapy_stream = SoapySDRDevice_setupStream(dev->soapy_dev, SOAPY_SDR_RX, selected_format, NULL, 0, &stream_args);\n    r = dev->soapy_stream == NULL;\n#else\n    // API version 0.7\n    r = SoapySDRDevice_setupStream(dev->soapy_dev, &dev->soapy_stream, SOAPY_SDR_RX, selected_format, NULL, 0, &stream_args);\n#endif\n    if (r != 0) {\n        if (verbose)\n            print_log(LOG_ERROR, __func__, \"Failed to setup sdr device\");\n        free(dev->dev_info);\n        free(dev);\n        return -3;\n    }\n\n    *out_dev = dev;\n    return 0;\n}\n\n// the buffer sizes can't be proven to be correct\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wunknown-warning-option\"\n#pragma GCC diagnostic ignored \"-Wanalyzer-allocation-size\"\n\nstatic int soapysdr_read_loop(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)\n{\n    size_t buffer_size = (size_t)buf_num * buf_len;\n    if (dev->buffer_size != buffer_size) {\n        free(dev->buffer);\n        dev->buffer = malloc(buffer_size);\n        if (!dev->buffer) {\n            WARN_MALLOC(\"soapysdr_read_loop()\");\n            return -1; // NOTE: returns error on alloc failure.\n        }\n        dev->buffer_size = buffer_size;\n        dev->buffer_pos = 0;\n    }\n\n    size_t buf_elems = buf_len / dev->sample_size;\n\n    dev->running = 1;\n    do {\n        if (dev->buffer_pos + buf_len > buffer_size)\n            dev->buffer_pos = 0;\n        int16_t *buffer = (void *)&dev->buffer[dev->buffer_pos];\n        dev->buffer_pos += buf_len;\n\n        void *buffs[]    = {buffer};\n        int flags        = 0;\n        long long timeNs = 0;\n        long timeoutUs   = 1000000; // 1 second\n        unsigned n_read  = 0, i;\n        int r;\n\n        do {\n            buffs[0] = &buffer[n_read * 2];\n            r  = SoapySDRDevice_readStream(dev->soapy_dev, dev->soapy_stream, buffs, buf_elems - n_read, &flags, &timeNs, timeoutUs);\n            if (r < 0)\n                break;\n            n_read += r; // r is number of elements read, elements=complex pairs, so buffer length is twice\n            //fprintf(stderr, \"readStream ret=%d, flags=%d, timeNs=%lld (%zu - %u)\\n\", r, flags, timeNs, buf_elems, n_read);\n        } while (n_read < buf_elems);\n        //fprintf(stderr, \"readStream ret=%u (%u), flags=%d, timeNs=%lld\\n\", n_read, buf_len, flags, timeNs);\n        if (r < 0) {\n            if (r == SOAPY_SDR_OVERFLOW) {\n                fprintf(stderr, \"O\");\n                fflush(stderr);\n                continue;\n            }\n            print_logf(LOG_WARNING, __func__, \"sync read failed. %d\", r);\n        }\n\n        // convert to CS16 or CU8 if needed\n        // if converting CS8 to CU8 -- vectorized with -O3\n        //for (i = 0; i < n_read * 2; ++i)\n        //    cu8buf[i] = (int8_t)cu8buf[i] + 128;\n\n        // TODO: SoapyRemote doesn't scale properly when reading (local) CS16 from (remote) CS8\n        // rescale cs16 buffer\n        if (dev->fullScale >= 2047.0 && dev->fullScale <= 2048.0) {\n            for (i = 0; i < n_read * 2; ++i)\n                buffer[i] *= 16; // prevent left shift of negative value\n        }\n        else if (dev->fullScale < 32767.0) {\n            int upscale = 32768 / dev->fullScale;\n            for (i = 0; i < n_read * 2; ++i)\n                buffer[i] *= upscale;\n        }\n\n#ifdef THREADS\n        pthread_mutex_lock(&dev->lock);\n#endif\n        uint32_t sample_rate      = dev->sample_rate;\n        uint32_t center_frequency = dev->center_frequency;\n#ifdef THREADS\n        pthread_mutex_unlock(&dev->lock);\n#endif\n        sdr_event_t ev = {\n                .ev               = SDR_EV_DATA,\n                .sample_rate      = sample_rate,\n                .center_frequency = center_frequency,\n                .buf              = buffer,\n                .len              = n_read * dev->sample_size,\n        };\n#ifdef THREADS\n        pthread_mutex_lock(&dev->lock);\n        int exit_acquire = dev->exit_acquire;\n        pthread_mutex_unlock(&dev->lock);\n        if (exit_acquire) {\n            break; // do not deliver any more events\n        }\n#endif\n        if (n_read > 0) // prevent a crash in callback\n            cb(&ev, ctx);\n\n    } while (dev->running);\n\n    return 0;\n}\n\n#pragma GCC diagnostic pop\n\n#endif\n\n/* Public API */\n\nint sdr_open(sdr_dev_t **out_dev, char const *dev_query, int verbose)\n{\n    if (dev_query && !strncmp(dev_query, \"rtl_tcp\", 7))\n        return rtltcp_open(out_dev, dev_query, verbose);\n\n#if !defined(RTLSDR) && !defined(SOAPYSDR)\n    if (verbose)\n        print_log(LOG_ERROR, __func__, \"No input drivers (RTL-SDR or SoapySDR) compiled in.\");\n    return -1;\n#endif\n\n    /* Open RTLSDR by default or if index or serial given, if available */\n    if (!dev_query || *dev_query == ':' || (*dev_query >= '0' && *dev_query <= '9')) {\n#ifdef RTLSDR\n        return sdr_open_rtl(out_dev, dev_query, verbose);\n#else\n        print_log(LOG_ERROR, __func__, \"No input driver for RTL-SDR compiled in.\");\n        return -1;\n#endif\n    }\n\n#ifdef SOAPYSDR\n    UNUSED(soapysdr_set_bandwidth);\n    UNUSED(soapysdr_direct_sampling);\n    UNUSED(soapysdr_offset_tuning);\n\n    /* Open SoapySDR otherwise, if available */\n    return sdr_open_soapy(out_dev, dev_query, verbose);\n#endif\n    print_log(LOG_ERROR, __func__, \"No input driver for SoapySDR compiled in.\");\n\n    return -1;\n}\n\nint sdr_close(sdr_dev_t *dev)\n{\n    if (!dev)\n        return -1;\n\n    int ret = sdr_stop(dev);\n\n    if (dev->rtl_tcp)\n        ret = rtltcp_close(dev->rtl_tcp);\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        ret = SoapySDRDevice_unmake(dev->soapy_dev);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        ret = rtlsdr_close(dev->rtlsdr_dev);\n#endif\n\n#ifdef THREADS\n    pthread_mutex_destroy(&dev->lock);\n#endif\n\n    free(dev->dev_info);\n    free(dev->buffer);\n    free(dev);\n    return ret;\n}\n\nchar const *sdr_get_dev_info(sdr_dev_t *dev)\n{\n    if (!dev)\n        return NULL;\n\n    return dev->dev_info;\n}\n\nint sdr_get_sample_size(sdr_dev_t *dev)\n{\n    if (!dev)\n        return 0;\n\n    return dev->sample_size;\n}\n\nint sdr_get_sample_signed(sdr_dev_t *dev)\n{\n    if (!dev)\n        return 0;\n\n    return dev->sample_signed;\n}\n\nint sdr_set_center_freq(sdr_dev_t *dev, uint32_t freq, int verbose)\n{\n    if (!dev)\n        return -1;\n\n#ifdef THREADS\n    if (pthread_equal(dev->thread, pthread_self())) {\n        fprintf(stderr, \"%s: must not be called from acquire callback!\\n\", __func__);\n        return -1;\n    }\n#endif\n\n    int r = -1;\n\n    if (dev->rtl_tcp) {\n        dev->rtl_tcp_freq = freq;\n        r = rtltcp_command(dev, RTLTCP_SET_FREQ, freq);\n    }\n\n#ifdef SOAPYSDR\n    SoapySDRKwargs args = {0};\n    if (dev->soapy_dev) {\n        r = SoapySDRDevice_setFrequency(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)freq, &args);\n    }\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev) {\n        r = rtlsdr_set_center_freq(dev->rtlsdr_dev, freq);\n        print_logf(LOG_DEBUG, \"SDR\", \"rtlsdr_set_center_freq %u = %d\", freq, r);\n    }\n#endif\n\n    if (verbose) {\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to set center freq.\");\n        else\n            print_logf(LOG_NOTICE, \"SDR\", \"Tuned to %s.\", nice_freq(sdr_get_center_freq(dev)));\n    }\n\n#ifdef THREADS\n    pthread_mutex_lock(&dev->lock);\n#endif\n    dev->center_frequency = freq;\n#ifdef THREADS\n    pthread_mutex_unlock(&dev->lock);\n#endif\n\n    return r;\n}\n\nuint32_t sdr_get_center_freq(sdr_dev_t *dev)\n{\n    if (!dev)\n        return 0;\n\n    if (dev->rtl_tcp)\n        return dev->rtl_tcp_freq;\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        return (uint32_t)SoapySDRDevice_getFrequency(dev->soapy_dev, SOAPY_SDR_RX, 0);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        return rtlsdr_get_center_freq(dev->rtlsdr_dev);\n#endif\n\n    return 0;\n}\n\nint sdr_set_freq_correction(sdr_dev_t *dev, int ppm, int verbose)\n{\n    if (!dev)\n        return -1;\n\n#ifdef THREADS\n    if (pthread_equal(dev->thread, pthread_self())) {\n        fprintf(stderr, \"%s: must not be called from acquire callback!\\n\", __func__);\n        return -1;\n    }\n#endif\n\n    int r = -1;\n\n    if (dev->rtl_tcp)\n        r = rtltcp_command(dev, RTLTCP_SET_FREQ_CORRECTION, ppm);\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        r = SoapySDRDevice_setFrequencyComponent(dev->soapy_dev, SOAPY_SDR_RX, 0, \"CORR\", (double)ppm, NULL);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev) {\n        r = rtlsdr_set_freq_correction(dev->rtlsdr_dev, ppm);\n        if (r == -2)\n            r = 0; // -2 is not an error code\n    }\n#endif\n\n    if (verbose) {\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to set frequency correction.\");\n        else\n            print_logf(LOG_NOTICE, \"SDR\", \"Frequency correction set to %d ppm.\", ppm);\n    }\n    return r;\n}\n\nint sdr_set_auto_gain(sdr_dev_t *dev, int verbose)\n{\n    if (!dev)\n        return -1;\n\n#ifdef THREADS\n    if (pthread_equal(dev->thread, pthread_self())) {\n        fprintf(stderr, \"%s: must not be called from acquire callback!\\n\", __func__);\n        return -1;\n    }\n#endif\n\n    int r = -1;\n\n    if (dev->rtl_tcp)\n        r = rtltcp_command(dev, RTLTCP_SET_GAIN_MODE, 0);\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        r = soapysdr_auto_gain(dev->soapy_dev, verbose);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 0);\n#endif\n\n    if (verbose) {\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to enable automatic gain.\");\n        else\n            print_log(LOG_NOTICE, \"SDR\", \"Tuner gain set to Auto.\");\n    }\n    return r;\n}\n\nint sdr_set_tuner_gain(sdr_dev_t *dev, char const *gain_str, int verbose)\n{\n    if (!dev)\n        return -1;\n\n#ifdef THREADS\n    if (pthread_equal(dev->thread, pthread_self())) {\n        fprintf(stderr, \"%s: must not be called from acquire callback!\\n\", __func__);\n        return -1;\n    }\n#endif\n\n    int r = -1;\n\n    if (!gain_str || !*gain_str) {\n        /* Enable automatic gain */\n        return sdr_set_auto_gain(dev, verbose);\n    }\n\n#ifdef SOAPYSDR\n    /* Enable manual gain */\n    if (dev->soapy_dev)\n        return soapysdr_gain_str_set(dev->soapy_dev, gain_str, verbose);\n#endif\n\n    int gain = (int)(atof(gain_str) * 10); /* tenths of a dB */\n    if (gain == 0) {\n        /* Enable automatic gain */\n        return sdr_set_auto_gain(dev, verbose);\n    }\n\n    if (dev->rtl_tcp) {\n        return rtltcp_command(dev, RTLTCP_SET_GAIN_MODE, 1)\n                || rtltcp_command(dev, RTLTCP_SET_GAIN, gain);\n    }\n\n#ifdef RTLSDR\n    /* Enable manual gain */\n    r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr_dev, 1);\n    if (verbose)\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to enable manual gain.\");\n\n    /* Set the tuner gain */\n    gain = rtlsdr_find_tuner_gain(dev, gain, verbose);\n\n    /* Fix for FitiPower FC0012: set gain to minimum before desired value */\n    if (rtlsdr_get_tuner_type(dev->rtlsdr_dev) == RTLSDR_TUNER_FC0012) {\n        int minGain = -99;\n        minGain = rtlsdr_find_tuner_gain(dev, minGain, verbose);\n\n        r = rtlsdr_set_tuner_gain(dev->rtlsdr_dev, minGain);\n        if (verbose) {\n            if (r < 0)\n                print_log(LOG_WARNING, __func__, \"Failed to set initial gain.\");\n            else\n                print_logf(LOG_NOTICE, \"SDR\", \"Set initial gain for FC0012 to %f dB.\", minGain / 10.0);\n        }\n    }\n\n    r = rtlsdr_set_tuner_gain(dev->rtlsdr_dev, gain);\n    if (verbose) {\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to set tuner gain.\");\n        else\n            print_logf(LOG_NOTICE, \"SDR\", \"Tuner gain set to %f dB.\", gain / 10.0);\n    }\n#endif\n\n    return r;\n}\n\nint sdr_set_antenna(sdr_dev_t *dev, char const *antenna_str, int verbose)\n{\n    if (!dev)\n        return -1;\n\n    POSSIBLY_UNUSED(verbose);\n    int r = -1;\n\n    if (!antenna_str)\n        return 0;\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev) {\n        r = SoapySDRDevice_setAntenna(dev->soapy_dev, SOAPY_SDR_RX, 0, antenna_str);\n\n        if (verbose) {\n            if (r < 0)\n                print_log(LOG_WARNING, __func__, \"Failed to set antenna.\");\n\n            // report the antenna that is actually used\n            char *antenna = SoapySDRDevice_getAntenna(dev->soapy_dev, SOAPY_SDR_RX, 0);\n            print_logf(LOG_NOTICE, \"SDR\", \"Antenna set to '%s'.\", antenna);\n            free(antenna);\n        }\n        return r;\n    }\n#endif\n\n  // currently only SoapySDR supports devices with multiple antennas\n  print_log(LOG_WARNING, __func__, \"Antenna selection only available for SoapySDR devices\");\n\n  return r;\n}\n\nint sdr_set_sample_rate(sdr_dev_t *dev, uint32_t rate, int verbose)\n{\n    if (!dev)\n        return -1;\n\n#ifdef THREADS\n    if (pthread_equal(dev->thread, pthread_self())) {\n        fprintf(stderr, \"%s: must not be called from acquire callback!\\n\", __func__);\n        return -1;\n    }\n#endif\n\n    int r = -1;\n\n    if (dev->rtl_tcp) {\n        dev->rtl_tcp_rate = rate;\n        r = rtltcp_command(dev, RTLTCP_SET_SAMPLE_RATE, rate);\n    }\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        r = SoapySDRDevice_setSampleRate(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)rate);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        r = rtlsdr_set_sample_rate(dev->rtlsdr_dev, rate);\n#endif\n\n    if (verbose) {\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to set sample rate.\");\n        else\n            print_logf(LOG_NOTICE, \"SDR\", \"Sample rate set to %u S/s.\", sdr_get_sample_rate(dev)); // Unfortunately, doesn't return real rate\n    }\n\n#ifdef THREADS\n    pthread_mutex_lock(&dev->lock);\n#endif\n    dev->sample_rate = rate;\n#ifdef THREADS\n    pthread_mutex_unlock(&dev->lock);\n#endif\n\n    return r;\n}\n\nuint32_t sdr_get_sample_rate(sdr_dev_t *dev)\n{\n    if (!dev)\n        return 0;\n\n    if (dev->rtl_tcp)\n        return dev->rtl_tcp_rate;\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        return (uint32_t)SoapySDRDevice_getSampleRate(dev->soapy_dev, SOAPY_SDR_RX, 0);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        return rtlsdr_get_sample_rate(dev->rtlsdr_dev);\n#endif\n\n    return 0;\n}\n\nint sdr_apply_settings(sdr_dev_t *dev, char const *sdr_settings, int verbose)\n{\n    if (!dev)\n        return -1;\n\n    POSSIBLY_UNUSED(verbose);\n    int r = 0;\n\n    if (!sdr_settings || !*sdr_settings)\n        return 0;\n\n    if (dev->rtl_tcp) {\n        while (sdr_settings && *sdr_settings) {\n            char const *val = NULL;\n            // This mirrors the settings of SoapyRTLSDR\n            if (kwargs_match(sdr_settings, \"direct_samp\", &val)) {\n                int direct_sampling = atoiv(val, 1);\n                r = rtltcp_command(dev, RTLTCP_SET_DIRECT_SAMPLING, direct_sampling);\n            }\n            else if (kwargs_match(sdr_settings, \"offset_tune\", &val)) {\n                int offset_tuning = atobv(val, 1);\n                r = rtltcp_command(dev, RTLTCP_SET_OFFSET_TUNING, offset_tuning);\n            }\n            else if (kwargs_match(sdr_settings, \"digital_agc\", &val)) {\n                int digital_agc = atobv(val, 1);\n                r = rtltcp_command(dev, RTLTCP_SET_AGC_MODE, digital_agc);\n            }\n            else if (kwargs_match(sdr_settings, \"biastee\", &val)) {\n                int biastee = atobv(val, 1);\n                r = rtltcp_command(dev, RTLTCP_SET_BIAS_TEE, biastee);\n            }\n            else {\n                print_logf(LOG_ERROR, __func__, \"Unknown rtl_tcp setting: %s\", sdr_settings);\n                return -1;\n            }\n            sdr_settings = kwargs_skip(sdr_settings);\n        }\n        return r;\n    }\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev) {\n        SoapySDRKwargs settings = SoapySDRKwargs_fromString(sdr_settings);\n        for (size_t i = 0; i < settings.size; ++i) {\n            const char *key   = settings.keys[i];\n            const char *value = settings.vals[i];\n            if (!key) {\n                continue;\n            }\n            if (verbose) {\n                print_logf(LOG_NOTICE, \"SDR\", \"Setting %s to %s\", key, value);\n            }\n            if (!strcmp(key, \"antenna\")) {\n                if (SoapySDRDevice_setAntenna(dev->soapy_dev, SOAPY_SDR_RX, 0, value) != 0) {\n                    r = -1;\n                    print_logf(LOG_WARNING, __func__, \"Antenna setting failed: %s\", SoapySDRDevice_lastError());\n                }\n            }\n            else if (!strcmp(key, \"bandwidth\")) {\n                uint32_t f_value = atouint32_metric(value, \"-t bandwidth= \");\n                if (SoapySDRDevice_setBandwidth(dev->soapy_dev, SOAPY_SDR_RX, 0, (double)f_value) != 0) {\n                    r = -1;\n                    print_logf(LOG_WARNING, __func__, \"Bandwidth setting failed: %s\", SoapySDRDevice_lastError());\n                }\n            }\n            else {\n                if (SoapySDRDevice_writeSetting(dev->soapy_dev, key, value) != 0) {\n                    r = -1;\n                    print_logf(LOG_WARNING, __func__, \"sdr setting failed: %s\", SoapySDRDevice_lastError());\n                }\n            }\n        }\n        SoapySDRKwargs_clear(&settings);\n        return r;\n    }\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev) {\n        while (sdr_settings && *sdr_settings) {\n            char const *val = NULL;\n            // This mirrors the settings of SoapyRTLSDR\n            if (kwargs_match(sdr_settings, \"direct_samp\", &val)) {\n                int direct_sampling = atoiv(val, 1);\n                r = rtlsdr_set_direct_sampling(dev->rtlsdr_dev, direct_sampling);\n            }\n            else if (kwargs_match(sdr_settings, \"offset_tune\", &val)) {\n                int offset_tuning = atobv(val, 1);\n                r = rtlsdr_set_offset_tuning(dev->rtlsdr_dev, offset_tuning);\n            }\n            else if (kwargs_match(sdr_settings, \"digital_agc\", &val)) {\n                int digital_agc = atobv(val, 1);\n                r = rtlsdr_set_agc_mode(dev->rtlsdr_dev, digital_agc);\n            }\n            else if (kwargs_match(sdr_settings, \"biastee\", &val)) {\n#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__))\n                // check weak link for Linux with older rtlsdr\n                if (!rtlsdr_set_bias_tee) {\n                    print_log(LOG_ERROR, __func__, \"This librtlsdr version does not support biastee setting\");\n                    return -1;\n                }\n#endif\n                int biastee = atobv(val, 1);\n                r = rtlsdr_set_bias_tee(dev->rtlsdr_dev, biastee);\n            }\n            else {\n                print_logf(LOG_ERROR, __func__, \"Unknown RTLSDR setting: %s\", sdr_settings);\n                return -1;\n            }\n            sdr_settings = kwargs_skip(sdr_settings);\n        }\n        return r;\n    }\n#endif\n\n    print_log(LOG_WARNING, __func__, \"sdr settings not available.\"); // no open device\n\n    return -1;\n}\n\nint sdr_activate(sdr_dev_t *dev)\n{\n    if (!dev)\n        return -1;\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev) {\n        if (SoapySDRDevice_activateStream(dev->soapy_dev, dev->soapy_stream, 0, 0, 0) != 0) {\n            print_log(LOG_ERROR, __func__, \"Failed to activate stream\");\n            exit(1);\n        }\n    }\n#endif\n\n    return 0;\n}\n\nint sdr_deactivate(sdr_dev_t *dev)\n{\n    if (!dev)\n        return -1;\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev) {\n        SoapySDRDevice_deactivateStream(dev->soapy_dev, dev->soapy_stream, 0, 0);\n        SoapySDRDevice_closeStream(dev->soapy_dev, dev->soapy_stream);\n    }\n#endif\n\n    return 0;\n}\n\nint sdr_reset(sdr_dev_t *dev, int verbose)\n{\n    if (!dev)\n        return -1;\n\n    int r = 0;\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        r = rtlsdr_reset_buffer(dev->rtlsdr_dev);\n#endif\n\n    if (verbose) {\n        if (r < 0)\n            print_log(LOG_WARNING, __func__, \"Failed to reset buffers.\");\n    }\n    return r;\n}\n\nint sdr_start_sync(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)\n{\n    if (!dev)\n        return -1;\n\n    if (buf_num == 0)\n        buf_num = SDR_DEFAULT_BUF_NUMBER;\n    if (buf_len == 0)\n        buf_len = SDR_DEFAULT_BUF_LENGTH;\n\n    if (dev->rtl_tcp)\n        return rtltcp_read_loop(dev, cb, ctx, buf_num, buf_len);\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev)\n        return soapysdr_read_loop(dev, cb, ctx, buf_num, buf_len);\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev)\n        return rtlsdr_read_loop(dev, cb, ctx, buf_num, buf_len);\n#endif\n\n    return -1;\n}\n\nint sdr_stop_sync(sdr_dev_t *dev)\n{\n    if (!dev)\n        return -1;\n\n    if (dev->rtl_tcp) {\n        dev->running = 0;\n        return 0;\n    }\n\n#ifdef SOAPYSDR\n    if (dev->soapy_dev) {\n        dev->running = 0;\n        return 0;\n    }\n#endif\n\n#ifdef RTLSDR\n    if (dev->rtlsdr_dev) {\n        dev->running = 0;\n        return rtlsdr_cancel_async(dev->rtlsdr_dev);\n    }\n#endif\n\n    return -1;\n}\n\n#ifdef SOAPYSDR\nstatic void soapysdr_log_handler(const SoapySDRLogLevel level, const char *message)\n{\n    // Our log levels are compatible with SoapySDR.\n    print_log((log_level_t)level, \"SoapySDR\", message);\n}\n#endif\n\nvoid sdr_redirect_logging(void)\n{\n#ifdef SOAPYSDR\n    SoapySDR_registerLogHandler(soapysdr_log_handler);\n#endif\n}\n\n/* threading */\n\n#ifdef THREADS\nstatic THREAD_RETURN THREAD_CALL acquire_thread(void *arg)\n{\n    sdr_dev_t *dev = arg;\n    print_log(LOG_DEBUG, __func__, \"acquire_thread enter...\");\n\n    int r = sdr_start_sync(dev, dev->async_cb, dev->async_ctx, dev->buf_num, dev->buf_len);\n    // if (cfg->verbosity > 1)\n    print_log(LOG_DEBUG, __func__, \"acquire_thread async stop...\");\n\n    if (r < 0) {\n        print_logf(LOG_ERROR, \"SDR\", \"async read failed (%d).\", r);\n    }\n\n//    sdr_event_t ev = {\n//            .ev  = SDR_EV_QUIT,\n//    };\n//    dev->async_cb(&ev, dev->async_ctx);\n\n    print_log(LOG_DEBUG, __func__, \"acquire_thread done...\");\n    return (THREAD_RETURN)(intptr_t)r;\n}\n\nint sdr_start(sdr_dev_t *dev, sdr_event_cb_t async_cb, void *async_ctx, uint32_t buf_num, uint32_t buf_len)\n{\n    if (!dev)\n        return -1;\n\n    dev->async_cb = async_cb;\n    dev->async_ctx = async_ctx;\n    dev->buf_num = buf_num;\n    dev->buf_len = buf_len;\n\n#ifndef _WIN32\n    // Block all signals from the worker thread\n    sigset_t sigset;\n    sigset_t oldset;\n    sigfillset(&sigset);\n    pthread_sigmask(SIG_SETMASK, &sigset, &oldset);\n#endif\n    int r = pthread_create(&dev->thread, NULL, acquire_thread, dev);\n#ifndef _WIN32\n    pthread_sigmask(SIG_SETMASK, &oldset, NULL);\n#endif\n    if (r) {\n        fprintf(stderr, \"%s: error in pthread_create, rc: %d\\n\", __func__, r);\n    }\n    return r;\n}\n\nint sdr_stop(sdr_dev_t *dev)\n{\n    if (!dev)\n        return -1;\n\n    if (pthread_equal(dev->thread, pthread_self())) {\n        fprintf(stderr, \"%s: must not be called from acquire callback!\\n\", __func__);\n        return -1;\n    }\n\n    print_log(LOG_DEBUG, __func__, \"EXITING...\");\n    pthread_mutex_lock(&dev->lock);\n    if (dev->exit_acquire) {\n        pthread_mutex_unlock(&dev->lock);\n        print_log(LOG_DEBUG, __func__, \"Already exiting.\");\n        return 0;\n    }\n    dev->exit_acquire = 1; // for rtl_tcp and SoapySDR\n    sdr_stop_sync(dev); // for rtlsdr\n    pthread_mutex_unlock(&dev->lock);\n\n    print_log(LOG_DEBUG, __func__, \"JOINING...\");\n    int r = pthread_join(dev->thread, NULL);\n    if (r) {\n        fprintf(stderr, \"%s: error in pthread_join, rc: %d\\n\", __func__, r);\n    }\n\n    print_log(LOG_DEBUG, __func__, \"EXITED.\");\n    return r;\n}\n#else\nint sdr_start(sdr_dev_t *dev, sdr_event_cb_t cb, void *ctx, uint32_t buf_num, uint32_t buf_len)\n{\n    UNUSED(dev);\n    UNUSED(cb);\n    UNUSED(ctx);\n    UNUSED(buf_num);\n    UNUSED(buf_len);\n    print_log(LOG_ERROR, __func__, \"rtl_433 compiled without thread support, SDR inputs not available.\");\n    return -1;\n}\nint sdr_stop(sdr_dev_t *dev)\n{\n    UNUSED(dev);\n    return -1;\n}\n#endif\n"
  },
  {
    "path": "src/term_ctl.c",
    "content": "/** @file\n    Terminal control utility functions.\n\n    Copyright (C) 2018 Christian Zuckschwerdt\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n#include <stdlib.h>\n#ifndef _WIN32\n#include <unistd.h>\n#include <sys/ioctl.h>\n#endif\n\n#include \"term_ctl.h\"\n\n#ifdef _WIN32\n#include <io.h>\n#include <limits.h>\n#include <windows.h>\n#include <lm.h>\n\n#ifndef STDOUT_FILENO\n#define STDOUT_FILENO   1\n#endif\n\n#ifndef STDERR_FILENO\n#define STDERR_FILENO   2\n#endif\n\n#ifndef UNUSED\n#define UNUSED(x) (void)(x)\n#endif\n\ntypedef struct console {\n    CONSOLE_SCREEN_BUFFER_INFO info;\n    BOOL                       redirected;\n    BOOL                       ansi;\n    HANDLE                     hnd;\n    FILE                      *file;\n    WORD                       fg, bg;\n} console_t;\n\nstatic WORD _term_get_win_color(BOOL fore, term_color_t color)\n{\n    // applies intensity only on foreground,\n    // i.e. background will fallback to dim\n    switch (color) {\n       case TERM_COLOR_RESET: /* Cannot occur; just to suppress a warning */\n            break;\n       case TERM_COLOR_BLACK:\n            return (0);\n       case TERM_COLOR_BLUE:\n            return (1);\n       case TERM_COLOR_GREEN:\n            return (2);\n       case TERM_COLOR_CYAN:\n            return (3);\n       case TERM_COLOR_RED:\n            return (4);\n       case TERM_COLOR_MAGENTA:\n            return (5);\n       case TERM_COLOR_YELLOW:\n            return (6);\n       case TERM_COLOR_WHITE:\n            return (7);\n       case TERM_COLOR_BRIGHT_BLACK:\n            return (fore ? 0 + FOREGROUND_INTENSITY : 0);\n       case TERM_COLOR_BRIGHT_BLUE:\n            return (fore ? 1 + FOREGROUND_INTENSITY : 1);\n       case TERM_COLOR_BRIGHT_GREEN:\n            return (fore ? 2 + FOREGROUND_INTENSITY : 2);\n       case TERM_COLOR_BRIGHT_CYAN:\n            return (fore ? 3 + FOREGROUND_INTENSITY : 3);\n       case TERM_COLOR_BRIGHT_RED:\n            return (fore ? 4 + FOREGROUND_INTENSITY : 4);\n       case TERM_COLOR_BRIGHT_MAGENTA:\n            return (fore ? 5 + FOREGROUND_INTENSITY : 5);\n       case TERM_COLOR_BRIGHT_YELLOW:\n            return (fore ? 6 + FOREGROUND_INTENSITY : 6);\n       case TERM_COLOR_BRIGHT_WHITE:\n            return (fore ? 7 + FOREGROUND_INTENSITY : 7);\n    }\n    fprintf(stderr,\"FATAL: No mapping for TERM_COLOR_x=%d (fore: %d)\\n\", color, fore);\n    return (0);\n}\n\nstatic void _term_set_color(console_t *console, BOOL fore, term_color_t color)\n{\n    WORD win_color;\n\n    if (!console->file)\n        return;\n\n    if (color == TERM_COLOR_RESET) {\n        console->fg = (console->info.wAttributes & 7);\n        console->bg = (console->info.wAttributes & ~7);\n    }\n    else if (fore) {\n        console->fg = _term_get_win_color(TRUE, color);\n    }\n    else if (color <= TERM_COLOR_WHITE ||\n            (color >= TERM_COLOR_BRIGHT_BLACK && color <= TERM_COLOR_BRIGHT_WHITE)) {\n        console->bg = 16 * _term_get_win_color(FALSE, color);\n    }\n    else\n        return;\n\n    win_color = console->bg + console->fg;\n\n    /* Hack: as WinCon does not have color-themes (as Linux have) and no 'TERM_COLOR_BRIGHT_x'\n     * codes are used, always use a high-intensity foreground color. This look best in\n     * CMD with the default black background color.\n     *\n     * Note: do not do this for \"BLACK\" as that would turn it into \"GREY\".\n     */\n    if (fore && color != TERM_COLOR_RESET && color != TERM_COLOR_BLACK)\n        win_color |= FOREGROUND_INTENSITY;\n\n    fflush(console->file);\n    SetConsoleTextAttribute(console->hnd, win_color);\n}\n\n/*\n * Cleanup in case we got a SIGINT signal in the middle of a\n * non-default colour output.\n */\nstatic void _term_free(console_t *console)\n{\n    if (console->hnd && console->hnd != INVALID_HANDLE_VALUE) {\n        fflush(console->file);\n        SetConsoleTextAttribute(console->hnd, console->info.wAttributes);\n    }\n    free(console);\n}\n\nstatic BOOL _term_has_color(console_t *console)\n{\n    return console->hnd && !console->redirected;\n}\n\nstatic void *_term_init(FILE *fp)\n{\n    console_t *console = calloc(1, sizeof(*console));\n    if (!console) {\n        fprintf(stderr, \"term_init failed\\n\");\n        return NULL; // NOTE: return NULL on alloc failure.\n    }\n\n    int fd = fileno(fp);\n    if (fd == STDOUT_FILENO) {\n        console->hnd = GetStdHandle(STD_OUTPUT_HANDLE);\n        console->file = fp;\n    }\n    else if (fd == STDERR_FILENO) {\n        console->hnd = GetStdHandle(STD_ERROR_HANDLE);\n        console->file = fp;\n    }\n    console->redirected = (console->hnd == INVALID_HANDLE_VALUE) ||\n                         (!GetConsoleScreenBufferInfo(console->hnd, &console->info)) ||\n                         (GetFileType(console->hnd) != FILE_TYPE_CHAR);\n\n    // Test for Windows 10 to enable ANSI output, needs netapi32.dll\n    LPWKSTA_INFO_100 pBuf = NULL;\n    NET_API_STATUS nStatus;\n    nStatus = NetWkstaGetInfo(NULL, 100, (LPBYTE *)&pBuf);\n    if (nStatus == NERR_Success) {\n        console->ansi = (pBuf->wki100_platform_id == 500) && (pBuf->wki100_ver_major >= 10);\n    }\n    if (pBuf != NULL) {\n        NetApiBufferFree(pBuf);\n    }\n\n    // Windows 10 version 1511 added ANSI filters to cmd and terminal.\n    // To use ANSI colors in Windows versions 1511 to 1903 requires setting VirtualTerminalLevel\n    // by calling the SetConsoleMode API with the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.\n    // ANSI colors are available by default in Windows version 1909 or newer\n    if (console->ansi) {\n        DWORD dwMode = 0;\n        GetConsoleMode(console->hnd, &dwMode);\n        dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;\n        SetConsoleMode(console->hnd, dwMode);\n        // Check if it worked, it will fail for Legacy console-mode\n        GetConsoleMode (console->hnd, &dwMode);\n        if (!(dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)) {\n            console->ansi = 0;\n        }\n    }\n\n    _term_set_color(console, FALSE, TERM_COLOR_RESET); /* Set 'console->fg' and 'console->bg' */\n\n    return console;\n}\n#endif /* _WIN32 */\n\nint term_get_columns(void *ctx)\n{\n#ifdef _WIN32\n    console_t *console = (console_t *)ctx;\n    /*\n     * Call this again as the screen dimensions could have changes since\n     * we called '_term_init()'.\n     */\n    CONSOLE_SCREEN_BUFFER_INFO c_info;\n\n    if (!console->hnd || console->hnd == INVALID_HANDLE_VALUE)\n       return (80);\n\n    if (!GetConsoleScreenBufferInfo(console->hnd, &c_info))\n       return (80);\n    return (c_info.srWindow.Right - c_info.srWindow.Left + 1);\n#else\n    FILE *fp = (FILE *)ctx;\n    struct winsize w;\n    ioctl(fileno(fp), TIOCGWINSZ, &w);\n    return w.ws_col;\n#endif\n}\n\n/**\nReturns wether the environment suggests a dark or light\nterminal background.\n\nOur default color theme is for dark backgrounds.\n\nCheck the COLORFGBG environment variable, which should\nbe 15;0 for a dark theme and 0;15 for a light theme.\n\nIf the last value is 7 or 9 to 15 then assume a light theme.\n\n@return 1 if a light background was detected, 0 for a dark background otherwise\n*/\nstatic int term_get_bg(void)\n{\n    char const *colorfgbg = getenv(\"COLORFGBG\");\n    if (!colorfgbg) {\n        return 0; // default dark theme\n    }\n    char *p = strrchr(colorfgbg, ';');\n    if (!p) {\n        return 0; // default dark theme\n    }\n\n    // Check if the last value is 7 or 9 to 15.\n    if (p[1] == '7' || p[1] == '9'\n            || (p[1] == '1' && p[2] != '\\0')) {\n        return 1; // light theme\n    }\n    return 0; // dark theme\n}\n\nint term_has_color(void *ctx)\n{\n#ifdef _WIN32\n    return _term_has_color(ctx);\n#else\n    char const *color = getenv(\"RTL433_COLOR\");\n    if (color && strcmp(color, \"always\") == 0) {\n        return 1;\n    }\n    if (color && strcmp(color, \"never\") == 0) {\n        return 0;\n    }\n\n    char const *no_color = getenv(\"NO_COLOR\");\n    if (no_color && no_color[0] != '\\0') {\n        return 0;\n    }\n\n    FILE *fp = (FILE *)ctx;\n    return isatty(fileno(fp));\n#endif\n}\n\nvoid *term_init(FILE *fp)\n{\n#ifdef _WIN32\n    return _term_init(fp);\n#else\n    return fp;\n#endif\n}\n\nvoid term_free(void *ctx)\n{\n    if (!ctx)\n        return;\n#ifdef _WIN32\n    _term_free(ctx);\n#else\n    FILE *fp = (FILE *)ctx;\n    if (term_has_color(ctx))\n        fprintf(fp, \"\\033[0m\");\n#endif\n}\n\nvoid term_ring_bell(void *ctx)\n{\n#ifdef _WIN32\n    Beep(800, 20);\n    UNUSED(ctx);\n#else\n    FILE *fp = (FILE *)ctx;\n    fprintf(fp, \"\\a\");\n#endif\n}\n\nvoid term_set_fg(void *ctx, term_color_t color)\n{\n    // Cache the detected terminal background color\n    static int light_bg = -1;\n    if (light_bg == -1) {\n        light_bg = term_get_bg();\n    }\n\n#ifdef _WIN32\n    console_t *console = (console_t *)ctx;\n    if (!console->ansi) {\n        _term_set_color(ctx, TRUE, color);\n        return;\n    }\n    FILE *fp = console->file;\n#else\n    FILE *fp = (FILE *)ctx;\n#endif\n    if (color == TERM_COLOR_RESET) {\n        fprintf(fp, \"\\033[0m\");\n    }\n    else if (light_bg) {\n        fprintf(fp, \"\\033[%dm\", color); // normal colors on light backgrounds\n    }\n    else {\n        fprintf(fp, \"\\033[%d;1m\", color); // bright/bold colors on dark backgrounds\n    }\n}\n\nvoid term_set_bg(void *ctx, term_color_t bg, term_color_t fg)\n{\n    // Cache the detected terminal background color\n    static int light_bg = -1;\n    if (light_bg == -1) {\n        light_bg = term_get_bg();\n    }\n    if (light_bg && fg >= TERM_COLOR_BRIGHT_BLACK && fg <= TERM_COLOR_BRIGHT_WHITE) {\n        fg -= 60; // remove bright/bold foreground on light backgrounds\n    }\n\n    if (bg < TERM_COLOR_BLACK\n            || (bg > TERM_COLOR_WHITE && bg < TERM_COLOR_BRIGHT_BLACK)\n            || bg > TERM_COLOR_BRIGHT_WHITE) {\n        bg = 0;\n    }\n    if (fg < TERM_COLOR_BLACK\n            || (fg > TERM_COLOR_WHITE && fg < TERM_COLOR_BRIGHT_BLACK)\n            || fg > TERM_COLOR_BRIGHT_WHITE) {\n        fg = 0;\n    }\n\n#ifdef _WIN32\n    console_t *console = (console_t *)ctx;\n    if (!console->ansi) {\n        if (bg)\n            _term_set_color(ctx, FALSE, bg);\n        if (fg)\n            _term_set_color(ctx, TRUE, fg);\n        return;\n    }\n    FILE *fp = console->file;\n#else\n    FILE *fp = (FILE *)ctx;\n#endif\n    if (bg && fg)\n        fprintf(fp, \"\\033[%d;%dm\", bg + 10, fg);\n    else if (bg)\n        fprintf(fp, \"\\033[%dm\", bg + 10);\n    else if (fg)\n        fprintf(fp, \"\\033[%dm\", fg);\n}\n\n#define DIM(array) (int) (sizeof(array) / sizeof(array[0]))\n\nstatic term_color_t color_map[] = {\n                    TERM_COLOR_RESET,     /* \"~0\" */\n                    TERM_COLOR_GREEN,\n                    TERM_COLOR_WHITE,     /* \"~2\" */\n                    TERM_COLOR_BLUE,\n                    TERM_COLOR_CYAN,      /* \"~4\" */\n                    TERM_COLOR_MAGENTA,\n                    TERM_COLOR_YELLOW,    /* \"~6\" */\n                    TERM_COLOR_BLACK,\n                    TERM_COLOR_RED,       /* \"~8\" */\n                  };\n\nint term_set_color_map(int ascii_idx, term_color_t color)\n{\n    ascii_idx -= '0';\n    if (ascii_idx < 0 || ascii_idx >= DIM(color_map))\n        return -1;\n    color_map[ascii_idx] = color;\n    return ascii_idx;\n}\n\nint term_get_color_map(int ascii_idx)\n{\n    int i;\n\n    ascii_idx -= '0';\n    for (i = 0; ascii_idx >= 0 && i < DIM(color_map); i++)\n        if (i == ascii_idx)\n           return (int)color_map[i];\n    return -1;\n}\n\nint term_puts(void *ctx, char const *buf)\n{\n    char const *p = buf;\n    int i, len, buf_len, color;\n    FILE *fp;\n\n    if (!ctx)\n        return fprintf(stderr, \"%s\", buf);\n\n#ifdef _WIN32\n    console_t *console = (console_t *)ctx;\n    fp = console->file;\n#else\n    fp = (FILE *)ctx;\n#endif\n\n    if (!fp)\n        fp = stderr;\n\n    buf_len = (int)strlen(buf);\n    for (i = len = 0; *p && i < buf_len; i++, p++) {\n        if (*p == '~') {\n            p++;\n            color = ctx ? term_get_color_map(*p) : -1;\n            if (color >= 0)\n                term_set_fg(ctx, (term_color_t)color);\n        }\n        else {\n            fputc(*p, fp);\n            len++;\n        }\n    }\n    return len;\n}\n\nint term_printf(void *ctx, _Printf_format_string_ char const *format, ...)\n{\n    int len;\n    va_list args;\n    char buf[4000];\n\n    va_start(args, format);\n\n    // Terminate first in case a buggy '_MSC_VER < 1900' is used.\n    buf[sizeof(buf)-1] = '\\0';\n    vsnprintf(buf, sizeof(buf)-1, format, args);\n    len = term_puts(ctx, buf);\n    va_end (args);\n    return len;\n}\n\nint term_help_fputs(void *ctx, char const *buf, FILE *fp)\n{\n    char const *p = buf;\n    int i, len, buf_len, color, state = 0, set_color = -1, next_color = -1;\n    if (!fp) {\n        fp = stderr;\n    }\n\n    if (!ctx) {\n        return fprintf(fp, \"%s\", buf);\n    }\n\n#ifdef _WIN32\n    console_t *console = (console_t *)ctx;\n    fp = console->file;\n#else\n    fp = (FILE *)ctx;\n#endif\n\n    buf_len = (int)strlen(buf);\n    for (i = len = 0; *p && i < buf_len; i++, p++) {\n        if (*p == '~') {\n            p++;\n            color = ctx ? term_get_color_map(*p) : -1;\n            if (color >= 0)\n                term_set_fg(ctx, (term_color_t)color);\n            continue;\n        }\n\n        if (state == 0 && *p == '[') {\n            state = 1;\n            next_color = 5;\n        }\n        else if ((state == 1 || state == 2) && *p == ']' && (p[1] == ',' || (p[1] == ' ' && p[2] != '|') || p[1] == '\\n' || p[1] == '\\0')) {\n            state = 0;\n            set_color = 0;\n        }\n        else if (state == 1 && *p == ' ') {\n            state = 2;\n            next_color = 6;\n        }\n        else if (state == 2 && *p == '|') {\n            set_color = 0;\n            next_color = 6;\n        }\n\n        else if (state == 0 && *p == '=' && p[1] == ' ') {\n            state = 3;\n            set_color = 1;\n        }\n        else if (state == 3 && *p == '=') {\n            state = 0;\n            next_color = 0;\n        }\n\n        else if (state == 0 && *p == '\\'') {\n            state = 4;\n            next_color = 4;\n        }\n        else if (state == 4 && *p == '\\'') {\n            state = 0;\n            set_color = 0;\n        }\n\n        else if (state == 0 && *p == '\\\"') {\n            state = 5;\n            set_color = 4;\n        }\n        else if (state == 5 && *p == '\\\"') {\n            state = 0;\n            next_color = 0;\n        }\n\n        if (set_color >= 0) {\n            color = ctx ? (int)color_map[set_color] : -1;\n            if (color >= 0)\n                term_set_fg(ctx, (term_color_t)color);\n        }\n        set_color = next_color;\n        next_color = -1;\n\n        fputc(*p, fp);\n        len++;\n    }\n    return len;\n}\n\nint term_help_fprintf(FILE *fp, _Printf_format_string_ char const *format, ...)\n{\n    int len;\n    va_list args;\n    char buf[4000];\n\n    va_start(args, format);\n\n    void *term = term_init(fp);\n    if (!term_has_color(term)) {\n        term_free(term);\n        term = NULL;\n    }\n\n    // Terminate first in case a buggy '_MSC_VER < 1900' is used.\n    buf[sizeof(buf) - 1] = '\\0';\n    vsnprintf(buf, sizeof(buf) - 1, format, args);\n    len = term_help_fputs(term, buf, fp);\n\n    term_free(term);\n\n    va_end(args);\n    return len;\n}\n"
  },
  {
    "path": "src/write_sigrok.c",
    "content": "/** @file\n    Sigrok Pulseview format writer.\n\n    Copyright (C) 2020 by Christian Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n#ifndef _WIN32\n#include <sys/types.h>\n#include <sys/wait.h>\n#endif\n#ifdef _WIN32\n#include <windows.h>\n#endif\n\n#include \"fatal.h\"\n#include \"write_sigrok.h\"\n\nvoid write_sigrok(char const *filename, unsigned samplerate, unsigned probes, unsigned analogs, char const *labels[])\n{\n    // e.g. uses channels\n    // U8:LOGIC:logic-1-1\n    // F32:I:analog-1-4-1\n    // F32:Q:analog-1-5-1\n    // F32:AM:analog-1-6-1\n    // F32:FM:analog-1-7-1\n\n    // probe1=FRAME\n    // probe2=ASK\n    // probe3=FSK\n    // analog4=I\n    // analog5=Q\n    // analog6=AM\n    // analog7=FM\n\n    // create version tag\n    FILE *fp = fopen(\"version\", \"w\");\n    if (!fp) {\n        perror(\"creating Sigrok \\\"version\\\" file\");\n        return;\n    }\n    fprintf(fp, \"2\");\n    fclose(fp);\n\n    // create meta data\n    fp = fopen(\"metadata\", \"w\");\n    if (!fp) {\n        perror(\"creating Sigrok \\\"metadata\\\" file\");\n        return;\n    }\n    fprintf(fp,\n            \"[device 1]\\n\"\n            \"samplerate=%u kHz\\n\"\n            \"capturefile=logic-1\\n\"\n            \"unitsize=1\\n\"\n            \"total probes=%u\\n\"\n            \"total analog=%u\\n\",\n            samplerate / 1000, probes, analogs);\n    if (labels) {\n        char const **label = labels;\n        for (unsigned i = 1; i <= probes; ++i)\n            fprintf(fp, \"probe%u=%s\\n\", i, *label++);\n        for (unsigned i = probes + 1; i <= probes + analogs; ++i)\n            fprintf(fp, \"analog%u=%s\\n\", i, *label++);\n    }\n    else {\n        for (unsigned i = 1; i <= probes; ++i)\n            fprintf(fp, \"probe%u=L%u\\n\", i, i);\n        for (unsigned i = probes + 1; i <= probes + analogs; ++i)\n            fprintf(fp, \"analog%u=A%u\\n\", i, i);\n    }\n\n    // EOF\n    fclose(fp);\n\n#ifdef _WIN32\n    STARTUPINFO si;\n    PROCESS_INFORMATION pi;\n\n    ZeroMemory(&si, sizeof(si));\n    si.cb = sizeof(si);\n    ZeroMemory(&pi, sizeof(pi));\n\n    char cmd_line[MAX_PATH] = \"\";\n    strcat_s(cmd_line, MAX_PATH, \"7z.exe\");\n    strcat_s(cmd_line, MAX_PATH, \" a\");\n    strcat_s(cmd_line, MAX_PATH, \" -bb0 \");\n    strcat_s(cmd_line, MAX_PATH, \" -sdel \");\n    strcat_s(cmd_line, MAX_PATH, \" -tzip \");\n    strcat_s(cmd_line, MAX_PATH, filename);\n    strcat_s(cmd_line, MAX_PATH, \" version\");\n    strcat_s(cmd_line, MAX_PATH, \" metadata\");\n\n    if (probes) {\n        strcat_s(cmd_line, MAX_PATH, \" logic-1-1\");\n    }\n\n    char str_buf[64];\n    for (unsigned i = probes + 1; i <= probes + analogs; ++i) {\n        snprintf(str_buf, sizeof(str_buf), \" analog-1-%u-1\", i);\n        strcat_s(cmd_line, MAX_PATH, str_buf);\n    }\n\n    // Start the child process.\n    if (CreateProcess(NULL,  // No module name (use command line)\n                cmd_line,     // Command line\n                NULL,        // Process handle not inheritable\n                NULL,        // Thread handle not inheritable\n                FALSE,       // Set handle inheritance to FALSE\n                0,           // No creation flags\n                NULL,        // Use parent's environment block\n                NULL,        // Use parent's starting directory\n                &si,         // Pointer to STARTUPINFO structure\n                &pi)         // Pointer to PROCESS_INFORMATION structure\n    ) {\n        // Wait until child process exits.\n        WaitForSingleObject(pi.hProcess, INFINITE);\n\n        DWORD exit_code;\n\n        if (FALSE == GetExitCodeProcess(pi.hProcess, &exit_code)) {\n            perror(\"GetExitCodeProcess() failure\");\n        }\n\n        // Close process and thread handles.\n        CloseHandle(pi.hProcess);\n        CloseHandle(pi.hThread);\n\n        if (exit_code != 0) {\n            perror(\"7z.exe execution failed\");\n            return;\n        }\n    }\n    else {\n        perror(\"CreateProcess for 7z.exe failed.\");\n        return;\n    }\n\n#else\n    char *argv[30] = {0};\n    int arg        = 0;\n    argv[arg++]    = \"zip\";\n    argv[arg++]    = (char *)filename; // \"out.sr\"\n    argv[arg++]    = \"version\";\n    argv[arg++]    = \"metadata\";\n\n    if (probes) {\n        argv[arg++] = \"logic-1-1\";\n    }\n\n    char *argv_dups[30] = {0}; // store only dups to help the checker match the free()\n    char **argv_analog = &argv[arg];\n    char str_buf[64];\n    for (unsigned i = probes + 1; i <= probes + analogs; ++i) {\n        snprintf(str_buf, sizeof(str_buf), \"analog-1-%u-1\", i);\n        char* dup = strdup(str_buf);\n        if (!dup) {\n            FATAL_STRDUP(\"write_sigrok()\");\n        }\n        argv_dups[arg] = dup;\n        argv[arg++] = dup;\n    }\n\n    int status = 0;\n    pid_t pid = fork();\n    if (pid < 0) {\n        perror(\"forking zip\");\n    }\n    else if (pid == 0) {\n        // child process because return value zero\n        execvp(argv[0], argv);\n        // execvp() returns only on error\n        for (int i = 0; i < arg; ++i) {\n            fprintf(stderr, \"%s \", argv[i]);\n        }\n        fprintf(stderr, \"\\n\");\n        perror(\"execvp\");\n        exit(1);\n    }\n    else {\n        // parent process because return value non-zero\n        wait(&status);\n        if (WIFEXITED(status)) {\n            if (WEXITSTATUS(status)) {\n                fprintf(stderr, \"zip exited with status: %d\\n\", WEXITSTATUS(status));\n            }\n        }\n    }\n\n    // rm version metadata logic-1-1 analog-1-4-1 analog-1-5-1 analog-1-6-1 analog-1-7-1\n    if (unlink(\"version\")) {\n        perror(\"unlinking Sigrok \\\"version\\\" file\");\n    }\n    if (unlink(\"metadata\")) {\n        perror(\"unlinking Sigrok \\\"metadata\\\" file\");\n    }\n\n    if (probes) {\n        if (unlink(\"logic-1-1\")) {\n            perror(\"unlinking Sigrok \\\"logic-1-1\\\" file\");\n        }\n    }\n    for (unsigned i = 0; i < analogs && argv_analog[i]; ++i) {\n        if (unlink(argv_analog[i])) {\n            perror(\"unlinking Sigrok \\\"analog-1-N-1\\\" file\");\n        }\n    }\n    for (int i = 0; i < arg; ++i) {\n        free(argv_dups[i]);\n    }\n\n#endif // !_WIN32\n}\n\nvoid open_pulseview(char const *filename)\n{\n#ifdef _WIN32\n    fprintf(stderr, \"Opening Pulseview not implemented for win32\\n\");\n#else\n    char *argv[9] = {0};\n    int arg       = 0;\n    char *abspath = realpath(filename, NULL);\n#ifdef __APPLE__\n    argv[arg++] = \"open\";\n    argv[arg++] = \"-b\";\n    argv[arg++] = \"org.sigrok.PulseView\";\n    argv[arg++] = \"--fresh\";\n    argv[arg++] = \"--new\";\n    argv[arg++] = \"--args\";\n    argv[arg++] = \"-i\";\n    argv[arg++] = (char *)abspath;\n#else\n    argv[arg++] = \"pulseview\";\n    argv[arg++] = \"-i\";\n    argv[arg++] = abspath;\n#endif\n\n    fprintf(stderr, \"Opening Pulseview...\\n\");\n    int status = 0;\n    pid_t pid = fork();\n    if (pid < 0) {\n        perror(\"forking pulseview\");\n        return;\n    }\n    else if (pid == 0) {\n        // child process because return value zero\n        execvp(argv[0], argv);\n        // execvp() returns only on error\n        for (int i = 0; i < arg; ++i)\n            fprintf(stderr, \"%s \", argv[i]);\n        fprintf(stderr, \"\\n\");\n        perror(\"execvp\");\n        exit(1);\n    }\n    else {\n        // parent process because return value non-zero\n        wait(&status);\n        if (WIFEXITED(status)) {\n            if (WEXITSTATUS(status)) {\n                fprintf(stderr, \"pulseview open exited with status: %d\\n\", WEXITSTATUS(status));\n            }\n        }\n    }\n    free(abspath);\n#endif\n}\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "########################################################################\n# Compile test cases\n########################################################################\nadd_executable(data-test data-test.c ../src/output_file.c ../src/term_ctl.c)\n\ntarget_link_libraries(data-test data)\n\nadd_test(data-test data-test)\n\nadd_executable(baseband-test baseband-test.c ../src/baseband.c ../src/logger.c)\n\nif(UNIX)\ntarget_link_libraries(baseband-test m)\nendif()\n\n#add_test(baseband-test baseband-test)\n\n########################################################################\n# Define and build all unit tests\n########################################################################\n# target_compile_definitions was only added in CMake 2.8.11\nadd_definitions(-D_TEST)\nforeach(testSrc bitbuffer.c fileformat.c optparse.c bit_util.c r_util.c)\n    get_filename_component(testName ${testSrc} NAME_WE)\n\n    # Note that r_util.c needs compat_time.c shims\n    add_executable(test_${testName} ../src/${testSrc} ../src/compat_time.c)\n\n    add_test(${testName}_test test_${testName})\nendforeach(testSrc)\n\n########################################################################\n# Define integration tests\n########################################################################\nadd_test(rtl_433_help ../src/rtl_433 -h)\n\n########################################################################\n# Define style checks\n########################################################################\nadd_executable(style-check style-check.c)\nfile(GLOB STYLE_CHECK_FILES  ../include/*.h ../src/*.c ../src/devices/*.c ../CMakeLists.txt ../*/CMakeLists.txt)\nlist(REMOVE_ITEM STYLE_CHECK_FILES\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include/jsmn.h\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../src/jsmn.c\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include/mongoose.h\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../src/mongoose.c\")\nadd_test(style-check style-check ${STYLE_CHECK_FILES})\n\n########################################################################\n# Define clang static analyzer checks\n########################################################################\nif(BUILD_TESTING_ANALYZER)\nfile(GLOB ANALYZER_CHECK_FILES  ../include/*.h ../src/*.c ../src/devices/*.c)\nlist(REMOVE_ITEM ANALYZER_CHECK_FILES\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include/jsmn.h\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../src/jsmn.c\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../include/mongoose.h\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/../src/mongoose.c\")\nadd_test(clang-analyzer\n    ${CMAKE_CURRENT_SOURCE_DIR}/exitcode-for-output.sh\n    clang\n    -I${CMAKE_CURRENT_SOURCE_DIR}/../include\n    --analyze\n    -Xanalyzer\n    -analyzer-output=text\n    -Xanalyzer\n    ${ANALYZER_CHECK_FILES})\nendif()\n"
  },
  {
    "path": "tests/baseband-test.c",
    "content": "/*\n * Baseband Evaluation\n *\n * Functional and speed test for various baseband functions.\n *\n * Copyright (C) 2018 by Christian Zuckschwerdt <zany@triq.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#ifdef _MSC_VER\n#include <BaseTsd.h>\ntypedef SSIZE_T ssize_t;\n#endif\n\n#ifdef _WIN32\n#include <io.h>\n#include <fcntl.h>\n#ifdef _MSC_VER\n#define F_OK 0\n#define R_OK (1 << 2)\n#endif\n#endif\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include <time.h>\n\n#include \"fatal.h\"\n#include \"baseband.h\"\n\n#define MEASURE(label, block)                                              \\\n    do {                                                                   \\\n        clock_t start = clock();                                           \\\n        block;                                                             \\\n        clock_t stop   = clock();                                          \\\n        double elapsed = (double)(stop - start) * 1000.0 / CLOCKS_PER_SEC; \\\n        printf(\"Time elapsed in ms: %f for: %s\\n\", elapsed, label);        \\\n    } while (0)\n\nstatic int read_buf(const char *filename, void *buf, size_t nbyte)\n{\n    int fd = open(filename, O_RDONLY);\n    if (fd < 0) {\n        fprintf(stderr, \"Failed to open %s\\n\", filename);\n        return -1;\n    }\n    ssize_t ret = read(fd, buf, nbyte);\n    close(fd);\n    return ret;\n}\n\nstatic int write_buf(const char *filename, const void *buf, size_t nbyte)\n{\n    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);\n    if (fd < 0) {\n        fprintf(stderr, \"Failed to open %s\\n\", filename);\n        return -1;\n    }\n    ssize_t ret = write(fd, buf, nbyte);\n    close(fd);\n    return ret;\n}\n\nint main(int argc, char *argv[])\n{\n    baseband_init();\n\n    uint8_t *cu8_buf;\n    uint16_t *y16_buf;\n    int16_t *cs16_buf;\n    uint32_t *y32_buf;\n    uint16_t *u16_buf;\n    uint32_t *u32_buf;\n    int16_t *s16_buf;\n    int32_t *s32_buf;\n    char *filename;\n    long n_read;\n    unsigned long n_samples;\n    int max_block_size = 4096000;\n    filter_state_t state;\n    demodfm_state_t fm_state;\n\n    if (argc <= 1) {\n        return 1;\n    }\n    filename = argv[1];\n\n    cu8_buf  = malloc(sizeof(uint8_t) * 2 * max_block_size);\n    if (!cu8_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    n_read = read_buf(filename, cu8_buf, sizeof(uint8_t) * 2 * max_block_size);\n    if (n_read < 1) {\n        free(cu8_buf);\n        return 1;\n    }\n\n    y16_buf  = malloc(sizeof(uint16_t) * max_block_size);\n    if (!y16_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    cs16_buf = malloc(sizeof(int16_t) * 2 * max_block_size);\n    if (!cs16_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    y32_buf  = malloc(sizeof(uint32_t) * max_block_size);\n    if (!y32_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    u16_buf  = malloc(sizeof(uint16_t) * max_block_size);\n    if (!u16_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    u32_buf  = malloc(sizeof(uint32_t) * max_block_size);\n    if (!u32_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    s16_buf  = malloc(sizeof(int16_t) * max_block_size);\n    if (!s16_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n    s32_buf  = malloc(sizeof(int32_t) * max_block_size);\n    if (!s32_buf) {\n        FATAL_MALLOC(\"main()\");\n    }\n\n    n_samples = n_read / (sizeof(uint8_t) * 2);\n\n    for (unsigned long i = 0; i < n_samples * 2; i++) {\n        //cs16_buf[i] = 127 - cu8_buf[i];\n        //cs16_buf[i] = (int16_t)cu8_buf[i] * 16 - 2040;\n        cs16_buf[i] = (int16_t)cu8_buf[i] * 128 - 16320;\n        //cs16_buf[i] = (int16_t)cu8_buf[i] * 256 - 32640;\n    }\n\n    MEASURE(\"envelope_detect\",\n        envelope_detect(cu8_buf, y16_buf, n_samples);\n    );\n    MEASURE(\"envelope_detect_nolut\",\n        envelope_detect_nolut(cu8_buf, y16_buf, n_samples);\n    );\n    MEASURE(\"magnitude_est_cu8\",\n        magnitude_est_cu8(cu8_buf, y16_buf, n_samples);\n    );\n    MEASURE(\"magnitude_true_cu8\",\n        magnitude_true_cu8(cu8_buf, y16_buf, n_samples);\n    );\n    write_buf(\"bb.am.s16\", y16_buf, sizeof(uint16_t) * n_samples);\n    MEASURE(\"baseband_low_pass_filter\",\n        baseband_low_pass_filter(&state, y16_buf, (int16_t *)u16_buf, n_samples);\n    );\n    write_buf(\"bb.lp.am.s16\", u16_buf, sizeof(int16_t) * n_samples);\n    MEASURE(\"baseband_demod_FM\",\n        baseband_demod_FM(&fm_state, cu8_buf, s16_buf, n_samples, 250000, 0.1f);\n    );\n    write_buf(\"bb.fm.s16\", s16_buf, sizeof(int16_t) * n_samples);\n\n    write_buf(\"bb.cs16\", cs16_buf, sizeof(int16_t) * 2 * n_samples);\n    //envelope_detect_cs16(cs16_buf, y32_buf, n_samples);\n    //write_buf(\"bb.am.u32\", y32_buf, sizeof(uint32_t) * n_samples);\n    //baseband_low_pass_filter_u32(&state, y32_buf, u32_buf, n_samples);\n    //write_buf(\"bb.lp.am.u32\", u32_buf, sizeof(uint32_t) * n_samples);\n\n    MEASURE(\"magnitude_est_cs16\",\n        magnitude_est_cs16(cs16_buf, y16_buf, n_samples);\n    );\n    MEASURE(\"magnitude_true_cs16\",\n        magnitude_true_cs16(cs16_buf, y16_buf, n_samples);\n    );\n    write_buf(\"bb.mag.s16\", y16_buf, sizeof(uint16_t) * n_samples);\n    MEASURE(\"baseband_low_pass_filter\",\n        baseband_low_pass_filter(&state, y16_buf, (int16_t *)u16_buf, n_samples);\n    );\n    write_buf(\"bb.mag.lp.s16\", u16_buf, sizeof(int16_t) * n_samples);\n\n    //baseband_demod_FM_cs16(&fm_state, cs16_buf, s32_buf, n_samples);\n    //write_buf(\"bb.fm.s32\", s32_buf, sizeof(int32_t) * n_samples);\n\n    MEASURE(\"baseband_demod_FM_cs16\",\n        baseband_demod_FM_cs16(&fm_state, cs16_buf, s16_buf, n_samples, 250000, 0.1f);\n    );\n    write_buf(\"bb.cs16.fm.s16\", s16_buf, sizeof(int16_t) * n_samples);\n\n    free(cu8_buf);\n    free(y16_buf);\n    free(cs16_buf);\n    free(y32_buf);\n    free(u16_buf);\n    free(u32_buf);\n    free(s16_buf);\n    free(s32_buf);\n}\n"
  },
  {
    "path": "tests/data-test.c",
    "content": "/*\n * A general structure for extracting hierarchical data from the devices;\n * typically key-value pairs, but allows for more rich data as well\n *\n * Copyright (C) 2015 by Erkki Seppälä <flux@modeemi.fi>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n\n#include \"data.h\"\n#include \"output_file.h\"\n\nint main(void)\n{\n    /* clang-format off */\n    data_t *data = data_make(\n            \"label\",        \"\",             DATA_STRING, \"1.2.3\",\n            \"house_code\",   \"House Code\",   DATA_INT,    42,\n            \"temp\",         \"Temperature\",  DATA_DOUBLE, 99.9,\n            \"array\",        \"Array\",        DATA_ARRAY, data_array(2, DATA_STRING, (char*[2]){\"hello\", \"world\"}),\n            \"array2\",       \"Array 2\",      DATA_ARRAY, data_array(2, DATA_INT, (int[2]){4, 2}),\n            \"array3\",       \"Array 3\",      DATA_ARRAY, data_array(2, DATA_ARRAY, (data_array_t*[2]){\n                                                            data_array(2, DATA_INT, (int[2]){4, 2}),\n                                                            data_array(2, DATA_INT, (int[2]){5, 5}) }),\n            \"data\",         \"Data\",        DATA_DATA, data_make(\"Hello\", \"hello\", DATA_STRING, \"world\", NULL),\n            NULL);\n    /* clang-format on */\n    const char *fields[] = { \"label\", \"house_code\", \"temp\", \"array\", \"array2\", \"array3\", \"data\", \"house_code\" };\n\n    void *json_output = data_output_json_create(0, stdout);\n    void *kv_output = data_output_kv_create(0, stdout);\n    void *csv_output = data_output_csv_create(0, stdout);\n    data_output_start(csv_output, fields, sizeof fields / sizeof *fields);\n\n    data_output_print(json_output, data); fprintf(stdout, \"\\n\");\n    data_output_print(kv_output, data);\n    data_output_print(csv_output, data);\n\n    data_output_free(json_output);\n    data_output_free(kv_output);\n    data_output_free(csv_output);\n\n    data_free(data);\n}\n"
  },
  {
    "path": "tests/exitcode-for-output.sh",
    "content": "#!/bin/sh\n\n# execute command and set exit code 1 if there is output on stdout or stderr\nout=$(\"$@\" 2>&1)\necho \"$out\"\n[ -z \"$out\" ]\n"
  },
  {
    "path": "tests/pulse-eval.c",
    "content": "/** @file\n    Pulse Evaluation.\n\n    Functional and speed test for various pulse functions.\n\n    Copyright (C) 2018 by Christian Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n// gcc -Wall -I ../include -o pulse-eval ../src/baseband.c ../src/write_sigrok.c ../tests/pulse-eval.c && ./pulse-eval FILE\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#ifdef _MSC_VER\n#include <BaseTsd.h>\ntypedef SSIZE_T ssize_t;\n#endif\n\n#ifdef _WIN32\n#include <io.h>\n#include <fcntl.h>\n#ifdef _MSC_VER\n#define F_OK 0\n#define R_OK (1 << 2)\n#endif\n#endif\n#ifndef _MSC_VER\n#include <unistd.h>\n#endif\n\n#include <time.h>\n#include <assert.h>\n\n#include \"baseband.h\"\n#include \"write_sigrok.h\"\n\nstatic int read_buf(char const *filename, void *buf, size_t nbyte)\n{\n    int fd = open(filename, O_RDONLY);\n    if (fd < 0) {\n        fprintf(stderr, \"Failed to open %s\\n\", filename);\n        return -1;\n    }\n    ssize_t ret = read(fd, buf, nbyte);\n    close(fd);\n    return ret;\n}\n\nstatic int write_buf(char const *filename, void const *buf, size_t nbyte)\n{\n    int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);\n    if (fd < 0) {\n        fprintf(stderr, \"Failed to open %s\\n\", filename);\n        return -1;\n    }\n    ssize_t ret = write(fd, buf, nbyte);\n    close(fd);\n    return ret;\n}\n\nstatic int write_u16_to_f32(char const *filename, uint16_t const *u16, size_t len)\n{\n    float *f32 = malloc(sizeof(float) * len);\n    assert(f32);\n    for (size_t i = 0; i < len; ++i) {\n        f32[i] = u16[i] / 65536.0;\n    }\n    int ret = write_buf(filename, f32, sizeof(float) * len);\n    free(f32);\n    return ret;\n}\n\nstatic int write_s16_to_f32(char const *filename, uint16_t const *s16, size_t len)\n{\n    float *f32 = malloc(sizeof(float) * len);\n    assert(f32);\n    for (size_t i = 0; i < len; ++i) {\n        f32[i] = s16[i] / 32768.0;\n    }\n    int ret = write_buf(filename, f32, sizeof(float) * len);\n    free(f32);\n    return ret;\n}\n\n// ---\n\n#define MAVG_WIDTH 8\ntypedef struct mavg {\n    int idx;\n    int avg;\n    int vs[MAVG_WIDTH];       // values\n} mavg_t;\n\nstatic void mavg_push(mavg_t *a, int val)\n{\n    a->avg          = a->avg - a->vs[a->idx] + val;\n    a->vs[a->idx++] = val;\n    a->idx          = a->idx % MAVG_WIDTH;\n}\n\nstatic int mavg_avg(mavg_t *a)\n{\n    return a->avg / MAVG_WIDTH;\n}\n\n// ---\n\n#define MAVGW_WIDTH 512\ntypedef struct mavgw {\n    int idx;\n    int avg;\n    int vs[MAVGW_WIDTH]; // values\n} mavgw_t;\n\nstatic void mavgw_push(mavgw_t *a, int val)\n{\n    a->avg          = a->avg - a->vs[a->idx] + val;\n    a->vs[a->idx++] = val;\n    a->idx          = a->idx % MAVGW_WIDTH;\n}\n\nstatic int mavgw_avg(mavgw_t *a)\n{\n    return a->avg / MAVGW_WIDTH;\n}\n\n// ---\n\n#define MAVGDEV_WIDTH 8\ntypedef struct mavgdev {\n    int idx;\n    int avg;\n    unsigned dev;\n    int vs[MAVGDEV_WIDTH]; // values\n    unsigned msq[MAVGDEV_WIDTH]; // mean squares\n} mavgdev_t;\n\nstatic void mavgdev_push(mavgdev_t *a, int val)\n{\n    a->avg        = a->avg - a->vs[a->idx] + val;\n    a->vs[a->idx] = val;\n\n    int valc         = val - a->avg / MAVGDEV_WIDTH;\n    //unsigned val2    = abs(valc);\n    unsigned val2    = (valc * valc) >> 15;\n    a->dev           = a->dev - a->msq[a->idx] + val2;\n    a->msq[a->idx++] = val2;\n\n    a->idx = a->idx % MAVGDEV_WIDTH;\n}\n\nstatic int mavgdev_avg(mavgdev_t *a)\n{\n    return a->avg / MAVGDEV_WIDTH;\n}\n\nstatic int mavgdev_dev(mavgdev_t *a)\n{\n    return a->dev / MAVGDEV_WIDTH;\n}\n\n// ---\n\nint main(int argc, char *argv[])\n{\n    //baseband_init();\n\n    char *filename;\n    long n_read;\n    size_t n_samples;\n    int block_size = 4096000;\n    unsigned sample_rate = 250000;\n\n    int argi = 1;\n    for (; argi < argc; ++argi) {\n        if (*argv[argi] != '-')\n            break;\n        if (argv[argi][1] == 'b')\n            block_size = atoi(argv[++argi]);\n        else if (argv[argi][1] == 's')\n            sample_rate = atoi(argv[++argi]);\n        else {\n            fprintf(stderr, \"Wrong argument (%s).\\n\", argv[argi]);\n            return 1;\n        }\n    }\n    if (argc <= argi) {\n        fprintf(stderr, \"%s [-s samplerate] [-b blocksize] file\", argv[0]);\n        return 1;\n    }\n    filename = argv[argi];\n\n    uint8_t *cu8_buf = malloc(sizeof(uint8_t) * 2 * block_size);\n    assert(cu8_buf);\n    uint8_t *cs8_buf = malloc(sizeof(uint8_t) * 2 * block_size);\n    assert(cs8_buf);\n    uint16_t *y16_buf = malloc(sizeof(uint16_t) * block_size);\n    assert(y16_buf);\n    uint16_t *am16_buf = malloc(sizeof(uint16_t) * block_size);\n    assert(am16_buf);\n    int16_t *fm16_buf = malloc(sizeof(int16_t) * block_size);\n    assert(fm16_buf);\n    uint8_t *u8_buf = calloc(block_size, sizeof(uint8_t));\n    assert(u8_buf);\n\n    uint16_t *mavgl = calloc(block_size, sizeof(uint16_t));\n    assert(mavgl);\n    uint16_t *mavgr = calloc(block_size, sizeof(uint16_t));\n    assert(mavgr);\n    uint16_t *mavgw = calloc(block_size, sizeof(uint16_t));\n    assert(mavgw);\n\n    uint16_t *mdevl = calloc(block_size, sizeof(uint16_t));\n    assert(mdevl);\n    uint16_t *mdevr = calloc(block_size, sizeof(uint16_t));\n    assert(mdevr);\n\n    uint16_t *davgl = calloc(block_size, sizeof(uint16_t));\n    assert(davgl);\n    uint16_t *davgr = calloc(block_size, sizeof(uint16_t));\n    assert(davgr);\n\n    n_read = read_buf(filename, cu8_buf, sizeof(uint8_t) * 2 * block_size);\n    if (n_read < 1) {\n        goto out;\n    }\n    n_samples = n_read / (sizeof(uint8_t) * 2);\n\n    for (size_t i = 0; i < n_samples * 2; ++i) {\n        cs8_buf[i] = cu8_buf[i] - 128;\n    }\n\n    magnitude_est_cu8(cu8_buf, y16_buf, n_samples);\n    envelope_detect_nolut(cu8_buf, am16_buf, n_samples);\n    demodfm_state_t fm_state;\n    baseband_demod_FM(&fm_state, cu8_buf, fm16_buf, n_samples, 250000, 0.1f);\n    // envelope_detect(cu8_buf, y16_buf, n_samples);\n    // magnitude_est_cu8(cu8_buf, y16_buf, n_samples);\n    // baseband_low_pass_filter(&state, y16_buf, (int16_t *)u16_buf, n_samples);\n    // baseband_demod_FM(&fm_state, cu8_buf, s16_buf, n_samples, 250000, 0.1f);\n\n    // moving avgs (AM)\n    mavg_t ml = {0};\n    for (size_t i = 0; i < n_samples; ++i) {\n        mavgl[i] = mavg_avg(&ml);\n        mavg_push(&ml, y16_buf[i]);\n    }\n\n    mavg_t mr = {0};\n    for (size_t i = 0; i < n_samples - 8; ++i) {\n        mavgr[i] = mavg_avg(&mr);\n        mavg_push(&mr, y16_buf[i + 8]);\n    }\n\n    mavgw_t mw = {0};\n    for (size_t i = 0; i < n_samples; ++i) {\n        mavgw[i] = mavgw_avg(&mw);\n        mavgw_push(&mw, y16_buf[i]);\n    }\n\n    // slice mavgl by mavgw\n    for (size_t i = 0; i < n_samples; ++i) {\n        u8_buf[i] = mavgw[i] < 1000 ? 0 : mavgl[i] > mavgw[i] ? 0xff : 0;\n    }\n\n    // moving devs (FM)\n    mavgdev_t vl = {0};\n    for (size_t i = 0; i < n_samples; ++i) {\n        mdevl[i] = mavgdev_dev(&vl);\n        mavgdev_push(&vl, fm16_buf[i]);\n    }\n\n    mavgdev_t vr = {0};\n    for (size_t i = 0; i < n_samples - 8; ++i) {\n        mdevr[i] = mavgdev_dev(&vr);\n        mavgdev_push(&vr, fm16_buf[i + 8]);\n    }\n\n    // decaying avgs\n    int dl = y16_buf[0];\n    for (size_t i = 0; i < n_samples; ++i) {\n        davgl[i] = dl;\n        dl = (dl + y16_buf[i]) / 2;\n    }\n\n    int dr = y16_buf[0];\n    for (size_t i = 0; i < n_samples - 7; ++i) {\n        davgr[i] = dr / 128;\n        dr = 2 * dr - y16_buf[i] * 128 + y16_buf[i + 7];\n    }\n\n    // tests\n    for (size_t i = 0; i < n_samples; ++i) {\n        y16_buf[i] -= mdevr[i] / 16;\n    }\n\n    write_buf(\"logic-1-1\", u8_buf, n_samples);\n    write_s16_to_f32(\"analog-1-2-1\", am16_buf, n_samples);\n    write_s16_to_f32(\"analog-1-3-1\", y16_buf, n_samples);\n    write_s16_to_f32(\"analog-1-4-1\", (uint16_t *)fm16_buf, n_samples);\n    write_s16_to_f32(\"analog-1-5-1\", mavgl, n_samples);\n    write_s16_to_f32(\"analog-1-6-1\", mavgr, n_samples);\n    write_s16_to_f32(\"analog-1-7-1\", mavgw, n_samples);\n    write_s16_to_f32(\"analog-1-8-1\", mdevl, n_samples);\n    write_s16_to_f32(\"analog-1-9-1\", mdevr, n_samples);\n    write_s16_to_f32(\"analog-1-10-1\", davgl, n_samples);\n    write_s16_to_f32(\"analog-1-11-1\", davgr, n_samples);\n\n    char const *labels[] = {\n            \"logic\",\n            \"am16\",\n            \"y16\",\n            \"fm16\",\n            \"mavgl\",\n            \"mavgr\",\n            \"mavgw\",\n            \"mdevl\",\n            \"mdevr\",\n            \"davgl\",\n            \"davgr\",\n    };\n    write_sigrok(\"out.sr\", sample_rate, 1, 9, labels);\n    open_pulseview(\"out.sr\");\n\nout:\n    free(cu8_buf);\n    free(cs8_buf);\n    free(y16_buf);\n    free(am16_buf);\n    free(fm16_buf);\n    free(u8_buf);\n    free(mavgl);\n    free(mavgr);\n    free(mavgw);\n    free(mdevl);\n    free(mdevr);\n    free(davgl);\n    free(davgr);\n}\n"
  },
  {
    "path": "tests/style-check.c",
    "content": "/** @file\n    Source code style checks.\n\n    Copyright (C) 2019 by Christian Zuckschwerdt <zany@triq.net>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n*/\n\n// Run with: make CTEST_OUTPUT_ON_FAILURE=1 test\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\n#define MAX_LEN 1024\n\n/// Source code file style checks.\n/// Check that there are no long lines.\n/// Check that there are no CRLF endings.\n/// Check that there are no mixed tabs/spaces.\nstatic int style_check(char *path)\n{\n    char *strict = strstr(path, \"/devices/\");\n\n    FILE *fp = fopen(path, \"r\");\n    assert(fp);\n    if (!fp)\n        exit(EXIT_FAILURE);\n\n    int read_errors = 0;\n    int long_errors = 0;\n    int crlf_errors = 0;\n    int tabs_errors = 0;\n    int trailing_errors = 0;\n    int memc_errors = 0;\n    int funbrace_errors = 0;\n\n    int leading_tabs = 0;\n    int leading_spcs = 0;\n\n    int use_stdout = 0;\n    int use_printf = 0;\n\n    int need_cond = 0;\n\n    char str[MAX_LEN];\n    while (fgets(str, MAX_LEN, fp)) {\n        int len = strlen(str);\n        if (len <= 0) {\n            read_errors++;\n            continue;\n        }\n        if (len >= MAX_LEN - 1) {\n            long_errors++;\n        }\n        if (str[len - 1] == '\\r' || (len > 1 && str[len - 2] == '\\r')) {\n            crlf_errors++;\n        }\n\n        if (str[0] == '\\t') {\n            leading_tabs++;\n        }\n        if (len >= 4 && str[0] == ' ' && str[1] == ' ' && str[2] == ' ' && str[3] == ' ') {\n            leading_spcs++;\n        }\n        if (len > 1 && (str[len - 2] == ' ' || str[len - 2] == '\\t')) {\n            trailing_errors++;\n        }\n        if (strstr(str, \"(r_device *decoder\") && str[len - 2] == '{') {\n            funbrace_errors++;\n        }\n\n        if (strstr(str, \"stdout\")) {\n            use_stdout++;\n        }\n        char *p;\n        if ((p = strstr(str, \"printf\"))) {\n            if (p == str || p[-1] < '_'|| p[-1] > 'z') {\n                use_printf++;\n            }\n        }\n        if (need_cond && !strstr(str, \"if (!\")) {\n            // we had an alloc but no check on the following line\n            memc_errors++;\n        }\n        need_cond = 0;\n        if (strstr(str, \"alloc(\") && !strstr(str, \"alloc()\")) {\n            need_cond++;\n        }\n        if (strstr(str, \"strdup(\") && !strstr(str, \"strdup()\")) {\n            need_cond++;\n        }\n    }\n    fclose(fp);\n    if (leading_tabs && leading_spcs) {\n        tabs_errors = leading_tabs > leading_spcs ? leading_spcs : leading_tabs;\n    }\n\n    if (read_errors)\n        printf(\"File \\\"%s\\\" has %d READ errors.\\n\", path, read_errors);\n    if (long_errors)\n        printf(\"File \\\"%s\\\" has %d LONG line errors.\\n\", path, long_errors);\n    if (crlf_errors)\n        printf(\"File \\\"%s\\\" has %d CRLF errors.\\n\", path, crlf_errors);\n    if (tabs_errors)\n        printf(\"File \\\"%s\\\" has %d MIXED tab/spaces errors.\\n\", path, tabs_errors);\n    if (trailing_errors)\n        printf(\"File \\\"%s\\\" has %d TRAILING whitespace errors.\\n\", path, trailing_errors);\n    if (memc_errors)\n        printf(\"File \\\"%s\\\" has %d ALLOC check errors.\\n\", path, memc_errors);\n    if (funbrace_errors)\n        printf(\"File \\\"%s\\\" has %d BRACE function on newline error.\\n\", path, funbrace_errors);\n    if (leading_tabs)\n        printf(\"File \\\"%s\\\" has %d TAB indented lines.\\n\", path, leading_tabs);\n    if (strict && use_stdout)\n        printf(\"File \\\"%s\\\" has %d STDOUT lines.\\n\", path, use_stdout);\n    if (strict && use_printf)\n        printf(\"File \\\"%s\\\" has %d PRINTF lines.\\n\", path, use_printf);\n\n    return read_errors + long_errors + crlf_errors + tabs_errors + leading_tabs + trailing_errors\n            + funbrace_errors + (strict ? use_stdout + use_printf : 0) + memc_errors;\n}\n\nint main(int argc, char *argv[])\n{\n    int failed = 0;\n    for (int i = 1; i < argc; ++i) {\n        failed += style_check(argv[i]);\n    }\n    exit(!!failed);\n}\n"
  },
  {
    "path": "tests/symbolizer.py",
    "content": "#!/usr/bin/env python3\n\n\"\"\"Report all symbols and docs in decoders of rtl_433 as json.\"\"\"\n\n# from ../include/rtl_433_devices.h\n#     DECL(silvercrest) \\\n#\n# static char const *const output_fields_EG53MA4[] = {\n#         \"model\",\n#         \"type\",\n#         \"id\",\n#         \"flags\",\n#         \"pressure_kPa\",\n#         \"temperature_F\",\n#         \"mic\",\n#         NULL,\n# };\n#\n# r_device const schraeder = {\n#         .name        = \"Schrader TPMS\",\n#         .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n#         .short_width = 120,\n#         .long_width  = 0,\n#         .sync_width  = 0,\n#         .gap_limit   = 0,\n#         .reset_limit = 480,\n#         .decode_fn   = &schraeder_callback,\n#         .disabled    = 0,\n#         .fields      = output_fields,\n# };\n\nimport sys\nimport os\nfrom os import listdir\nfrom os.path import join, isfile, isdir, getsize\nimport fnmatch\nimport json\nimport datetime\nimport re\n\nerrout = sys.stderr\nhaserr = False\n\n\ndef log(s):\n    print(s, file=errout)\n\n\ndef err(s):\n    global haserr\n    haserr = True\n    print(s, file=errout)\n\n\ndef process_protocols(path):\n    \"\"\"Extract protocol numbers from a decl file.\"\"\"\n    protocols = []\n\n    with open(path, encoding='utf-8', errors='replace') as f:\n        for line in f.readlines():\n            #    DECL(prologue)\n            m = re.match(r'\\s*DECL\\s*\\(\\s*([^\\)]*)\\s*\\)', line)\n            if m:\n                pName = m.group(1)\n                protocols.append(pName)\n\n    return protocols\n\n\ndef update_links(links, rName, name, i, key, param):\n    if not rName:\n        err(f\"::error file={name},line={i}::Key without r_device ({key}: {param})\")\n    links[rName].update({key: param})\n\n\ndef process_source(path, name):\n    \"\"\"Extract symbols and documentation from a decoder file.\"\"\"\n    links = {}\n    links[name] = {\"src\": name, \"line\": 1, \"type\": \"file\"}\n    with open(join(path, name), encoding='utf-8', errors='replace') as f:\n        fName = None\n        fLine = None\n        rName = None\n        captureDoc = False\n        fileDoc = False\n        dLine = None\n        dSee = None\n        dSee2 = None\n        doc = None\n        for i, line in enumerate(f):\n            # look for documentation comments:\n            # /** @file ... */\n            # /** @fn ... */\n            m = re.match(r'\\s*\\*/', line)\n            if captureDoc and m:\n                captureDoc = False\n                if fileDoc:\n                    links[name].update({\"doc_line\": dLine, \"doc\": doc})\n                    fileDoc = False\n                    doc = None\n                if fName:\n                    if fName not in links:\n                        links[fName] = {\"src\": name, \"type\": \"func\"}\n                    if dSee:\n                        links[fName].update({\"doc_line\": dLine, \"doc_see\": dSee})\n                        dSee = None\n                    links[fName].update({\"doc_line\": dLine, \"doc\": doc})\n                    doc = None\n                    fName = None\n                continue\n            if captureDoc:\n                doc += line\n                # TODO: we should use a list\n                m = re.match(r'\\s*\\@sa\\s+(.*?)\\(\\)\\s*(?:(.*?)\\(\\))?', line)\n                if m:\n                    dSee = m.group(1)\n                    if m.group(2):\n                        dSee2 = m.group(2)\n                continue\n            # inline link /** @sa func() */\n            m = re.match(r'\\s*/\\*\\*\\s*\\@sa\\s+(.*?)\\(\\)\\s*\\*/', line)\n            if m:\n                dLine = i + 1\n                dSee = m.group(1)\n                continue\n            # inline /** ... */\n            m = re.match(r'\\s*/\\*\\*\\s*(.*?)\\s*\\*/', line)\n            if m:\n                dLine = i + 1\n                doc = m.group(1)\n                continue\n            # copyright /** @file ... */\n            m = re.match(r'\\s*/\\*\\*\\s*@file', line)\n            if m:\n                captureDoc = True\n                fileDoc = True\n                dLine = i + 1\n                doc = ''\n                continue\n            # /** @fn ... */\n            m = re.match(\n                r'\\s*/\\*\\*\\s*@fn\\s+(?:\\s*static\\s*)?(?:\\s*int\\s*)?([a-zA-Z0-9_]+)\\(\\s*r_device\\s+\\*\\s*[a-z]+\\s*,\\s*bitbuffer_t\\s+\\*\\s*[a-z]+', line)\n            if m:\n                fName = m.group(1)\n                captureDoc = True\n                dLine = i + 1\n                doc = ''\n                continue\n            m = re.match(r'\\s*/\\*\\*', line)\n            if m:\n                fName = None\n                captureDoc = True\n                dLine = i + 1\n                doc = ''\n                continue\n\n            # look for char * variable = \"\" declarations that should really be char const *\n            m = re.match(r'(?:static)?\\s?char\\s?\\*\\s?[^\\*\\s]+\\s?=\\s?(?:{|\")', line)\n            if m:\n                err(f\"::error file={name},line={i}::char * variable should be char const * when being given a constant value\")\n\n            # look for output fields declarations that must be static char const *const\n            m = re.match(r'static\\s?char\\s+const\\s*\\*\\s*[^\\*\\s]+\\[\\]\\s*=\\s*\\{', line)\n            if m:\n                err(f\"::error file={name},line={i}::output fields static variable should be 'static char const *const'\")\n\n            # look for r_device with decode_fn\n            m = re.match(r'\\s*r_device\\s+const\\s+([^\\*]*?)\\s*=', line)\n            if m:\n                rName = m.group(1)\n                if rName in links:\n                    err(f\"::error file={name},line={i}::Duplicate r_device ({rName})\")\n                links[rName] = {\"src\": name, \"line\": i + 1, \"type\": \"r_device\"}\n                if dSee:\n                    links[rName].update({\"doc_line\": dLine, \"doc_see\": dSee})\n                    dSee = None\n                if doc:\n                    links[rName].update({\"doc_line\": dLine, \"doc\": doc})\n                    doc = None\n                continue\n            # .name        = \"The Name\",\n            m = re.match(r'\\s*\\.name\\s*=\\s*\"([^\"]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'name', m.group(1))\n                continue\n            # .modulation  = OOK_PULSE_MANCHESTER_ZEROBIT,\n            m = re.match(r'\\s*\\.modulation\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'modulation', m.group(1))\n                continue\n            # .short_width = 120,\n            m = re.match(r'\\s*\\.short_width\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'short_width', m.group(1))\n                continue\n            # .long_width  = 0,\n            m = re.match(r'\\s*\\.long_width\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'long_width', m.group(1))\n                continue\n            # .sync_width  = 0,\n            m = re.match(r'\\s*\\.sync_width\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'sync_width', m.group(1))\n                continue\n            # .gap_limit   = 0,\n            m = re.match(r'\\s*\\.gap_limit\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'gap_limit', m.group(1))\n                continue\n            # .reset_limit = 480,\n            m = re.match(r'\\s*\\.reset_limit\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'reset_limit', m.group(1))\n                continue\n            # .decode_fn   = &the_callback,\n            m = re.match(r'\\s*\\.decode_fn\\s*=\\s*&([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'decode_fn', m.group(1))\n                continue\n            # .disabled    = 0,\n            m = re.match(r'\\s*\\.disabled\\s*=\\s*([^,\\s]*)', line)\n            if m:\n                update_links(links, rName, name, i, 'disabled', m.group(1))\n                continue\n\n            # static int foo_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n            # static int foo_callback(r_device *decoder, bitbuffer_t *bitbuffer, ...\n            # static int\n            # foo_callback(r_device *decoder, bitbuffer_t *bitbuffer)\n            m = re.match(\n                r'(?:\\s*static\\s*int\\s*)?([a-zA-Z0-9_]+)\\(\\s*r_device\\s+\\*\\s*[a-z]+\\s*,\\s*bitbuffer_t\\s+\\*\\s*[a-z]+', line)\n            if m:\n                # print(m.group(1))\n                fName = m.group(1)\n                fLine = i + 1\n                if fName not in links:\n                    links[fName] = {}\n                links[fName].update({\"src\": name, \"line\": fLine, \"type\": \"func\"})\n                if dSee:\n                    links[fName].update({\"doc_line\": dLine, \"doc_see\": dSee})\n                    dSee = None\n                if dSee2:\n                    links[fName].update({\"doc_line\": dLine, \"doc_see2\": dSee2})\n                    dSee2 = None\n                if doc:\n                    links[fName].update({\"doc_line\": dLine, \"doc\": doc})\n                    doc = None\n                continue\n\n            # Match the data outputs\n            # \"volume_m3\", \"Volume\", DATA_COND, foo, DATA_FORMAT, \"%.1f m3\", DATA_DOUBLE, wread,\n            # DATA_DATA | DATA_INT | DATA_DOUBLE | DATA_STRING | DATA_ARRAY\n            # data_int | data_dbl | data_str | data_ary | data_dat | data_hex\n            if re.match(r'\\s*//*', line):\n                # skip comment\n                pass\n            elif fName and re.match(r'.*\\b(?:data_int|data_dbl|data_str|data_ary|data_dat|data_hex)\\(.*', line):\n                m = re.match(r'.*\\bdata_(int|dbl|str|ary|dat|hex)\\(.*?,\\s*\"(.*?)\"\\s*,\\s*\"(.*?)\"\\s*,\\s*(?:NULL|\"(.*?)\")\\s*,\\s*(?:\"(.*?)\")?.*\\);.*', line)\n                if m:\n                    # type, subtype, and model should be fixed string values\n                    d_type = m.group(1)\n                    d_key = m.group(2)\n                    d_pretty = m.group(3)\n                    d_cond = None\n                    d_format = m.group(4)\n                    d_value = m.group(5)\n                    if d_key == 'type' or d_key == 'model':\n                        if d_type != 'str' or d_value is None:\n                            log(f\"::notice file={name},line={i + 1}::DATA line bad format\")\n\n                    if \"outputs\" not in links[fName]:\n                        links[fName][\"outputs\"] = []\n\n                    links[fName][\"outputs\"].append({\n                        \"type\": d_type,\n                        \"key\": d_key,\n                        \"pretty\": d_pretty,\n                        \"cond\": d_cond,\n                        \"format\": d_format,\n                        \"value\": d_value, # capture value for \"model\"\n                    })\n                else:\n                    # Complain if a line could not be parsed\n                    log(f\"::notice file={name},line={i + 1}::DATA line did not parse\")\n            elif re.match(r'.*\\b(DATA_COND|DATA_FORMAT|DATA_DATA|DATA_INT|DATA_DOUBLE|DATA_STRING|DATA_ARRAY)\\b.*', line):\n                typemap = {\n                    \"DATA_DATA\": \"dat\",\n                    \"DATA_INT\": \"int\",\n                    \"DATA_DOUBLE\": \"dbl\",\n                    \"DATA_STRING\": \"str\",\n                    \"DATA_ARRAY\": \"ary\",\n                }\n                m = re.match(r'\\s*\"(.*?)\"\\s*,\\s*\"(.*?)\"\\s*,\\s*(?:DATA_COND\\s*,\\s*(.*?)\\s*,\\s*)?(?:DATA_FORMAT\\s*,\\s*(.*?)\\s*,\\s*)?(DATA_DATA|DATA_INT|DATA_DOUBLE|DATA_STRING|DATA_ARRAY)\\s*,\\s*(?:\"(.*?)\")?.*', line)\n                if m:\n                    # type, subtype, and model should be fixed string values\n                    d_key = m.group(1)\n                    d_pretty = m.group(2)\n                    d_cond = m.group(3)\n                    d_format = m.group(4)\n                    d_type = typemap[m.group(5)]\n                    d_value = m.group(6)\n                    if d_key == 'type' or d_key == 'model':\n                        if d_type != 'str' or d_value is None:\n                            log(f\"::notice file={name},line={i + 1}::DATA line bad format\")\n\n                    if \"outputs\" not in links[fName]:\n                        links[fName][\"outputs\"] = []\n\n                    links[fName][\"outputs\"].append({\n                        \"key\": d_key,\n                        \"pretty\": d_pretty,\n                        \"cond\": d_cond,\n                        \"format\": d_format,\n                        \"type\": d_type,\n                        \"value\": d_value, # capture value for \"model\"\n                    })\n                else:\n                    # Complain if a line could not be parsed\n                    log(f\"::notice file={name},line={i + 1}::DATA line did not parse\")\n\n            # Match the prefix string up to \"TheModelName\"\n            # \"model\", \"\", DATA_STRING, \"Schrader\",\n            m = re.match(r'\\s*\"model\"\\s*,.*DATA_STRING', line)\n            if not m:\n                # data_t *data = data_str(NULL, \"model\", \"\", NULL, \"Honeywell-CM921\");\n                m = re.match(r'.*data_str\\([^,]*\\s*,\\s*\"model\"\\s*,[^,]*,[^,]*,', line)\n            if m:\n                prefix = m.group(0)\n                s = line[len(prefix):]\n                models = re.findall(r'\"([^\"]+)\"', s)\n                if len(models) == 0:\n                    err(f\"::error file={name},line={i + 1}::No models\")\n                if not fName:\n                    err(f\"::error file={name},line={i + 1}::No func\")\n                for model in models:\n                    if not re.match(r'^[A-Za-z][0-9A-Za-z\"]+(-[0-9A-Za-z\"]+)?$', model):\n                        log(f\"::error file={name},line={i + 1}::Bad model name \\\"{model}\\\"\")\n                    if model in links and links[model][\"func\"] != fName:\n                        log(f\"::notice file={name},line={i + 1}::Reused model\")\n                    elif model in links:\n                        log(f\"::notice file={name},line={i + 1}::Duplicate model\")\n                    links[model] = {\"src\": name, \"line\": i + 1, \"type\": \"model\", \"func\": fName}\n\n    if captureDoc:\n        err(f\"::error file={name},line={dLine}::Unclosed doc comment\")\n    if dSee:\n        err(f\"::error file={name},line={dLine}::Unattached doc sa\")\n    if doc:\n        err(f\"::error file={name},line={dLine}::Unattached doc comment\")\n\n    return links\n\n\ndef check_symbols(symbols):\n    \"\"\"Check link integrity.\"\"\"\n    models_by_func = {}\n    for f in symbols:\n        d = symbols[f]\n\n        if f == \"protocols\":\n            continue\n\n        if d[\"type\"] == \"file\":\n            if \"doc\" not in d:\n                log(f\"::notice file={f},line=1::file doc missing\")\n                pass\n\n        if d[\"type\"] == \"r_device\":\n            if \"decode_fn\" not in d:\n                err(f\"::error file={d['src']},line={d['line']}::device missing ({json.dumps(d)}) for {f}\")\n            elif d[\"decode_fn\"] not in symbols:\n                err(f\"::error file={d['src']},line={d['line']}::decoder missing ({d['decode_fn']}) for {f}\")\n\n        if d[\"type\"] == \"func\":\n            if \"line\" not in d:\n                err(f\"::error file={d['src']}::func missing ({f})\")\n            if \"doc\" not in d or not d[\"doc\"]:\n                if \"doc_see\" not in d or not d[\"doc_see\"]:\n                    err(f\"::warning file={d['src']},line={d['line']}::doc missing for {f}\")\n                pass\n\n        if d[\"type\"] == \"model\":\n            func = d[\"func\"]\n            if func not in models_by_func:\n                models_by_func[func] = []\n            models_by_func[func].append(f)\n\n    for f in symbols:\n        d = symbols[f]\n\n        if f == \"protocols\":\n            continue\n\n        if d[\"type\"] == \"r_device\":\n            if \"decode_fn\" not in d:\n                err(f\"::error file={d['src']},line={d['line']}::no decode_fn found for {f}\")\n                continue\n            decode_fn = d[\"decode_fn\"]\n            func = {}\n            if decode_fn in symbols:\n                func = symbols[decode_fn]\n            else:\n                err(f\"::error file={d['src']},line={d['line']}::decode_fn not found ({decode_fn}) for {f}\")\n            see = None\n            if \"doc_see\" in func:\n                see = func[\"doc_see\"]\n                if see not in symbols:\n                    err(f\"::error file={d['src']},line={d['line']}::broken link @sa ({see}) for {f}\")\n\n            if see and see in models_by_func:\n                # err(f\"::error file={d['src']},line={d['line']}::models on sa link ({see}) for {f}\")\n                pass\n            elif decode_fn not in models_by_func:\n                err(f\"::error file={d['src']},line={d['line']}::models not found for {f}\")\n                if see:\n                    err(f\"::error file={d['src']},line={d['line']}::but @sa ({func['doc_see']})\")\n\n\ndef main(args):\n    \"\"\"Scan basedir for all groups, devices, sets, and content.\"\"\"\n\n    # ../include/rtl_433_devices.h\n    #    DECL(prologue)\n\n    check = \"check\" in args\n    if check:\n        args.remove(\"check\")\n        errout = sys.stdout\n    root = (['.'] + args)[-1]\n    basedir = root + '/src/devices/'\n    declpath = root + '/include/rtl_433_devices.h'\n\n    symbols = {}\n\n    symbols['protocols'] = process_protocols(declpath)\n\n    for f in listdir(basedir):\n        if f.endswith('.c'):\n            symbols.update(process_source(basedir, f))\n\n    check_symbols(symbols)\n    if check:\n        return haserr\n    else:\n        # print(symbols)\n        # print(json.dumps(symbols, indent=2))\n        print(json.dumps(symbols))\n\n\nif __name__ == '__main__':\n    sys.exit(main(sys.argv[1:]))\n"
  }
]