diff --git a/.github/workflows/python-package.yml b/.github/workflows/lint_and_test.yml similarity index 77% rename from .github/workflows/python-package.yml rename to .github/workflows/lint_and_test.yml index 892f484..2884ccf 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/lint_and_test.yml @@ -1,33 +1,34 @@ # This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Python package +name: Linting and testing on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: test: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: ['3.5', '3.6', '3.7', '3.8', '3.9'] + python-version: ['3.9', '3.10','3.11', '3.12', '3.13.0-rc.1'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 nose coverage - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install flake8 nose2 coverage + python -m pip install . - name: Lint with flake8 run: | # stop the test if there are Python syntax errors or undefined names @@ -36,7 +37,7 @@ jobs: flake8 enocean --count --exit-zero --max-complexity=15 --max-line-length=127 --statistics - name: Test with nose run: | - nosetests -s -q --with-coverage --cover-package=enocean + python -m nose2 -s . --quiet --log-level 100 --with-coverage - name: Coveralls uses: AndreMiras/coveralls-python-action@develop with: diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4e1ef42..5402bda 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -8,24 +8,29 @@ on: types: [created] jobs: - deploy: - + testpypi-publish: + name: Upload release to PyPI + # name: Upload release to TestPyPI runs-on: ubuntu-latest - + environment: + name: pypi + url: https://pypi.org/p/enocean4ha + # name: testpypi + # url: https://test.pypi.org/p/enocean4ha + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install setuptools wheel twine build + - name: Build + run: | + python -m build + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + # with: + # verbose: true + # repository-url: https://test.pypi.org/legacy/ diff --git a/.gitignore b/.gitignore index 2bb1c65..17110e2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ dist pip-selfcheck.json .vscode/ +.idea/ diff --git a/README.md b/README.md index 0395414..7c02bb4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,20 @@ # Python EnOcean # -[![Build Status](https://travis-ci.org/kipe/enocean.svg?branch=master)](https://travis-ci.org/kipe/enocean) -[![Coverage Status](https://coveralls.io/repos/github/kipe/enocean/badge.svg?branch=master)](https://coveralls.io/github/kipe/enocean?branch=master) +[![Linting And Testing Status](https://github.com/topic2k/enocean4ha/actions/workflows/lint_and_test.yml/badge.svg?branch=main)](https://github.com/topic2k/enocean4ha/actions/workflows/lint_and_test.yml) +[![Coverage Status](https://coveralls.io/repos/github/topic2k/enocean4ha/badge.svg?branch=main)](https://coveralls.io/github/topic2k/enocean4ha?branch=main) +[![PyPi](https://img.shields.io/pypi/v/enocean4ha?logo=pypi&logoColor=959DA5)](https://pypi.org/project/enocean4ha/) + A Python library for reading and controlling [EnOcean](http://www.enocean.com/) devices. -Started as a part of [Forget Me Not](http://www.element14.com/community/community/design-challenges/forget-me-not) +It started as a part of the [Forget Me Not](http://www.element14.com/community/community/design-challenges/forget-me-not) design challenge @ [element14](http://www.element14.com/). +This fork was created, because the [original repo](https://github.com/kipe/enocean) seems to be inactive, and i needed +to add EEPs to make some devices usable in [Home Assistant](https://www.home-assistant.io/). + + + ## Install ## If not installed already, install [pip](https://pypi.python.org/pypi/pip) by running @@ -16,7 +23,7 @@ If not installed already, install [pip](https://pypi.python.org/pypi/pip) by run After pip is installed, install the module by running -`sudo pip install enocean` (or `sudo pip install git+https://github.com/kipe/enocean.git` if you want the "bleeding edge"). +`sudo pip install enocean4ha` (or `sudo pip install git+https://github.com/topic2k/enocean4ha.git` if you want the "bleeding edge"). After this, it's just a matter of running `enocean_example.py` and pressing the learn button on magnetic contact or temperature switch or pressing the rocker switch. @@ -25,3 +32,6 @@ You should be displayed with a log of the presses, as well as parsed values (assuming the sensors are the ones provided in the [EnOcean Starter Kit](https://www.enocean.com/en/enocean_modules/esk-300)). The example script can be stopped by pressing `CTRL+C` + +--- + diff --git a/SUPPORTED_PROFILES.md b/SUPPORTED_PROFILES.md index 1ca36d8..f92aaf0 100644 --- a/SUPPORTED_PROFILES.md +++ b/SUPPORTED_PROFILES.md @@ -1,7 +1,98 @@ # Supported profiles -All profiles (should) correspond to the official [EEP](http://www.enocean-alliance.org/eep/) by EnOcean. +All profiles (should) correspond to the official [EEP](https://www.enocean-alliance.org/eep/) by EnOcean. + +
RPS Telegram (0xF6) + +- [FUNC 0x01 - TYPE 0x01 - Push Button](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xF6---func-0x01---type-0x01---push-button) +- [FUNC 0x02 - TYPE 0x01 - Light and Blind Control - Application Style 1](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xF6---func-0x02---type-0x01---light-and-blind-control---application-style-1) +- [FUNC 0x02 - TYPE 0x02 - Light and Blind Control - Application Style 2](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xF6---func-0x02---type-0x02---light-and-blind-control---application-style-2) +- [FUNC 0x05 - TYPE 0x01 - Liquid Leakage Sensor (mechanic harvester)](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xF6---func-0x05---type-0x01---liquid-leakage-sensor-(mechanic-harvester)) +- [FUNC 0x05 - TYPE 0x02 - Smoke Detector](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xF6---func-0x05---type-0x02---smoke-detector) +- [FUNC 0x10 - TYPE 0x00 - Window Handle](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xF6---func-0x10---type-0x00---window-handle) + +
+ +
1BS Telegram (0xD5) + +- [FUNC 0x00 - TYPE 0x01 - Single Input Contact](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD5---func-0x00---type-0x01---single-input-contact) + +
+ +
4BS Telegram (0xA5) + +- [FUNC 0x02 - TYPE 0x01 - Temperature Sensor Range -40°C to 0°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x01---temperature-sensor-range--40°c-to-0°c) +- [FUNC 0x02 - TYPE 0x02 - Temperature Sensor Range -30°C to +10°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x02---temperature-sensor-range--30°c-to-+10°c) +- [FUNC 0x02 - TYPE 0x03 - Temperature Sensor Range -20°C to +20°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x03---temperature-sensor-range--20°c-to-+20°c) +- [FUNC 0x02 - TYPE 0x04 - Temperature Sensor Range -10°C to +30°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x04---temperature-sensor-range--10°c-to-+30°c) +- [FUNC 0x02 - TYPE 0x05 - Temperature Sensor Range 0°C to +40°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x05---temperature-sensor-range-0°c-to-+40°c) +- [FUNC 0x02 - TYPE 0x06 - Temperature Sensor Range +10°C to +50°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x06---temperature-sensor-range-+10°c-to-+50°c) +- [FUNC 0x02 - TYPE 0x07 - Temperature Sensor Range +20°C to +60°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x07---temperature-sensor-range-+20°c-to-+60°c) +- [FUNC 0x02 - TYPE 0x08 - Temperature Sensor Range +30°C to +70°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x08---temperature-sensor-range-+30°c-to-+70°c) +- [FUNC 0x02 - TYPE 0x09 - Temperature Sensor Range +40°C to +80°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x09---temperature-sensor-range-+40°c-to-+80°c) +- [FUNC 0x02 - TYPE 0x0A - Temperature Sensor Range +50°C to +90°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x0A---temperature-sensor-range-+50°c-to-+90°c) +- [FUNC 0x02 - TYPE 0x0B - Temperature Sensor Range +60°C to +100°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x0B---temperature-sensor-range-+60°c-to-+100°c) +- [FUNC 0x02 - TYPE 0x10 - Temperature Sensor Range -60°C to +20°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x10---temperature-sensor-range--60°c-to-+20°c) +- [FUNC 0x02 - TYPE 0x11 - Temperature Sensor Range -50°C to +30°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x11---temperature-sensor-range--50°c-to-+30°c) +- [FUNC 0x02 - TYPE 0x12 - Temperature Sensor Range -40°C to +40°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x12---temperature-sensor-range--40°c-to-+40°c) +- [FUNC 0x02 - TYPE 0x13 - Temperature Sensor Range -30°C to +50°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x13---temperature-sensor-range--30°c-to-+50°c) +- [FUNC 0x02 - TYPE 0x14 - Temperature Sensor Range -20°C to +60°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x14---temperature-sensor-range--20°c-to-+60°c) +- [FUNC 0x02 - TYPE 0x15 - Temperature Sensor Range -10°C to +70°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x15---temperature-sensor-range--10°c-to-+70°c) +- [FUNC 0x02 - TYPE 0x16 - Temperature Sensor Range 0°C to +80°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x16---temperature-sensor-range-0°c-to-+80°c) +- [FUNC 0x02 - TYPE 0x17 - Temperature Sensor Range +10°C to +90°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x17---temperature-sensor-range-+10°c-to-+90°c) +- [FUNC 0x02 - TYPE 0x18 - Temperature Sensor Range +20°C to +100°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x18---temperature-sensor-range-+20°c-to-+100°c) +- [FUNC 0x02 - TYPE 0x19 - Temperature Sensor Range +30°C to +110°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x19---temperature-sensor-range-+30°c-to-+110°c) +- [FUNC 0x02 - TYPE 0x1A - Temperature Sensor Range +40°C to +120°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x1A---temperature-sensor-range-+40°c-to-+120°c) +- [FUNC 0x02 - TYPE 0x1B - Temperature Sensor Range +50°C to +130°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x1B---temperature-sensor-range-+50°c-to-+130°c) +- [FUNC 0x02 - TYPE 0x20 - 10 Bit Temperature Sensor Range -10°C to +41.2°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x20---10-bit-temperature-sensor-range--10°c-to-+41.2°c) +- [FUNC 0x02 - TYPE 0x30 - 10 Bit Temperature Sensor Range -40°C to +62.3°C](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x02---type-0x30---10-bit-temperature-sensor-range--40°c-to-+62.3°c) +- [FUNC 0x04 - TYPE 0x01 - Range 0°C to +40°C and 0% to 100%](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x04---type-0x01---range-0°c-to-+40°c-and-0%-to-100%) +- [FUNC 0x04 - TYPE 0x02 - Range -20°C to +60°C and 0% to 100%](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x04---type-0x02---range--20°c-to-+60°c-and-0%-to-100%) +- [FUNC 0x04 - TYPE 0x03 - Range -20°C to +60°C 10bit-measurement and 0% to 100%](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x04---type-0x03---range--20°c-to-+60°c-10bit-measurement-and-0%-to-100%) +- [FUNC 0x04 - TYPE 0x04 - Range -40°C to +120°C 12bit-measurement and 0% to 100%](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x04---type-0x04---range--40°c-to-+120°c-12bit-measurement-and-0%-to-100%) +- [FUNC 0x06 - TYPE 0x01 - Range 300lx to 60.000lx](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x06---type-0x01---range-300lx-to-60.000lx) +- [FUNC 0x06 - TYPE 0x02 - Range 0lx to 1.020lx](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x06---type-0x02---range-0lx-to-1.020lx) +- [FUNC 0x07 - TYPE 0x01 - Occupancy with Supply voltage monitor](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x07---type-0x01---occupancy-with-supply-voltage-monitor) +- [FUNC 0x07 - TYPE 0x03 - Occupancy with Supply voltage monitor and 10-bit illumination measurement](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x07---type-0x03---occupancy-with-supply-voltage-monitor-and-10-bit-illumination-measurement) +- [FUNC 0x08 - TYPE 0x01 - Range 0lx to 510lx, 0°C to +51°C and Occupancy Button](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x08---type-0x01---range-0lx-to-510lx,-0°c-to-+51°c-and-occupancy-button) +- [FUNC 0x08 - TYPE 0x02 - Range 0lx to 1020lx, 0°C to +51°C and Occupancy Button](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x08---type-0x02---range-0lx-to-1020lx,-0°c-to-+51°c-and-occupancy-button) +- [FUNC 0x08 - TYPE 0x03 - Range 0lx to 1020lx, 0°C to +51°C and Occupancy Button](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x08---type-0x03---range-0lx-to-1020lx,-0°c-to-+51°c-and-occupancy-button) +- [FUNC 0x09 - TYPE 0x04 - CO2 Sensor](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x09---type-0x04---co2-sensor) +- [FUNC 0x09 - TYPE 0x05 - VOC Sensor](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x09---type-0x05---voc-sensor) +- [FUNC 0x09 - TYPE 0x09 - Gas Sensor](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x09---type-0x09---gas-sensor) +- [FUNC 0x10 - TYPE 0x03 - Temperature Sensor and Set Point](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x10---type-0x03---temperature-sensor-and-set-point) +- [FUNC 0x10 - TYPE 0x05 - Temperature Sensor, Set Point and Occupancy Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x10---type-0x05---temperature-sensor,-set-point-and-occupancy-control) +- [FUNC 0x10 - TYPE 0x06 - Temperature Sensor, Set Point and Day/Night Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x10---type-0x06---temperature-sensor,-set-point-and-day/night-control) +- [FUNC 0x10 - TYPE 0x10 - Temperature and Humidity Sensor, Set Point and Occupancy Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x10---type-0x10---temperature-and-humidity-sensor,-set-point-and-occupancy-control) +- [FUNC 0x10 - TYPE 0x12 - Temperature and Humidity Sensor and Set Point](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x10---type-0x12---temperature-and-humidity-sensor-and-set-point) +- [FUNC 0x11 - TYPE 0x02 - Temperature Controller Output](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x11---type-0x02---temperature-controller-output) +- [FUNC 0x11 - TYPE 0x03 - Blind Status](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x11---type-0x03---blind-status) +- [FUNC 0x13 - TYPE 0x01 - Weather Station](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x13---type-0x01---weather-station) +- [FUNC 0x14 - TYPE 0x01 - Single Input Contact (Window/Door), Supply voltage monitor](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x14---type-0x01---single-input-contact-(window/door),-supply-voltage-monitor) +- [FUNC 0x20 - TYPE 0x01 - Battery Powered Actuator (BI-DIR)](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x20---type-0x01---battery-powered-actuator-(bi-dir)) +- [FUNC 0x20 - TYPE 0x06 - HVAC Components,Harvesting-powered actuator with local temperature offset control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x20---type-0x06---hvac-components,harvesting-powered-actuator-with-local-temperature-offset-control) +- [FUNC 0x12 - TYPE 0x01 - Electricity](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x12---type-0x01---electricity) +- [FUNC 0x30 - TYPE 0x03 - Digital Inputs, Wake and Temperature](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x30---type-0x03---digital-inputs,-wake-and-temperature) +- [FUNC 0x38 - TYPE 0x08 - Gateway](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xA5---func-0x38---type-0x08---gateway) + +
+ +
VLD Telegram (0xD2) + +- [FUNC 0x01 - TYPE 0x01 - Electronic switch with Local Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD2---func-0x01---type-0x01---electronic-switch-with-local-control) +- [FUNC 0x01 - TYPE 0x0E - Electronic switch with Local Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD2---func-0x01---type-0x0E---electronic-switch-with-local-control) +- [FUNC 0x01 - TYPE 0x0F - Electronic switch with Local Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD2---func-0x01---type-0x0F---electronic-switch-with-local-control) +- [FUNC 0x01 - TYPE 0x12 - Electronic switch with Local Control](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD2---func-0x01---type-0x12---electronic-switch-with-local-control) +- [FUNC 0x05 - TYPE 0x00 - Type 0x00](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD2---func-0x05---type-0x00---type-0x00) +- [FUNC 0x14 - TYPE 0x41 - Indoor -Temperature, Humidity XYZ Acceleration, Illumination Sensor](https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#rorg-0xD2---func-0x14---type-0x41---indoor--temperature,-humidity-xyz-acceleration,-illumination-sensor) + +
+ + + +--- ### RPS Telegram (0xF6) + ##### RORG 0xF6 - FUNC 0x01 - TYPE 0x01 - Push Button |shortcut|description |type |values | @@ -10,7 +101,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |1 - Pressed | - ##### RORG 0xF6 - FUNC 0x02 - TYPE 0x01 - Light and Blind Control - Application Style 1 |shortcut|description |type |values | @@ -30,7 +120,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |T21 |T21 |status | | |NU |NU |status | | - ##### RORG 0xF6 - FUNC 0x02 - TYPE 0x02 - Light and Blind Control - Application Style 2 |shortcut|description |type |values | @@ -51,7 +140,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |NU |NU |status | | - ##### RORG 0xF6 - FUNC 0x05 - TYPE 0x01 - Liquid Leakage Sensor (mechanic harvester) |shortcut|description |type |values | @@ -62,7 +150,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |T21 |T21 |status | | |NU |NU |status | | - ##### RORG 0xF6 - FUNC 0x05 - TYPE 0x02 - Smoke Detector |shortcut|description |type |values | @@ -72,7 +159,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |48 - Energy LOW | - ##### RORG 0xF6 - FUNC 0x10 - TYPE 0x00 - Window Handle |shortcut|description |type |values | @@ -85,8 +171,8 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |NU |NU |status | | - ### 1BS Telegram (0xD5) + ##### RORG 0xD5 - FUNC 0x00 - TYPE 0x01 - Single Input Contact |shortcut|description |type |values | @@ -95,176 +181,152 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |1 - closed | - ### 4BS Telegram (0xA5) + ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x01 - Temperature Sensor Range -40°C to 0°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -40.0-0.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x02 - Temperature Sensor Range -30°C to +10°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -30.0-10.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x03 - Temperature Sensor Range -20°C to +20°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -20.0-20.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x04 - Temperature Sensor Range -10°C to +30°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -10.0-30.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x05 - Temperature Sensor Range 0°C to +40°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 0.0-40.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x06 - Temperature Sensor Range +10°C to +50°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 10.0-50.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x07 - Temperature Sensor Range +20°C to +60°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 20.0-60.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x08 - Temperature Sensor Range +30°C to +70°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 30.0-70.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x09 - Temperature Sensor Range +40°C to +80°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 40.0-80.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x0A - Temperature Sensor Range +50°C to +90°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 50.0-90.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x0B - Temperature Sensor Range +60°C to +100°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 60.0-100.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x10 - Temperature Sensor Range -60°C to +20°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -60.0-20.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x11 - Temperature Sensor Range -50°C to +30°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -50.0-30.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x12 - Temperature Sensor Range -40°C to +40°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -40.0-40.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x13 - Temperature Sensor Range -30°C to +50°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -30.0-50.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x14 - Temperature Sensor Range -20°C to +60°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -20.0-60.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x15 - Temperature Sensor Range -10°C to +70°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ -10.0-70.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x16 - Temperature Sensor Range 0°C to +80°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 0.0-80.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x17 - Temperature Sensor Range +10°C to +90°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 10.0-90.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x18 - Temperature Sensor Range +20°C to +100°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 20.0-100.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x19 - Temperature Sensor Range +30°C to +110°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 30.0-110.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x1A - Temperature Sensor Range +40°C to +120°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 40.0-120.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x1B - Temperature Sensor Range +50°C to +130°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 50.0-130.0 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x20 - 10 Bit Temperature Sensor Range -10°C to +41.2°C |shortcut|description |type |values | |--------|--------------------------------------------------|--------|---- | |TMP |Temperature (linear) |value |1023.0-0.0 ↔ -10.0-41.2 °C | - ##### RORG 0xA5 - FUNC 0x02 - TYPE 0x30 - 10 Bit Temperature Sensor Range -40°C to +62.3°C |shortcut|description |type |values | @@ -272,7 +334,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |TMP |Temperature (linear) |value |1023.0-0.0 ↔ -40.0-62.3 °C | - ##### RORG 0xA5 - FUNC 0x04 - TYPE 0x01 - Range 0°C to +40°C and 0% to 100% |shortcut|description |type |values | @@ -282,6 +343,14 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |TSN |Availability of the Temperature Sensor |enum |0 - not available | | | | |1 - available | +##### RORG 0xA5 - FUNC 0x04 - TYPE 0x02 - Range -20°C to +60°C and 0% to 100% + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|HUM |Rel. Humidity (linear) |value |0.0-250.0 ↔ 0.0-100.0 % | +|TMP |Temperature (linear) |value |0.0-250.0 ↔ -20.0-60.0 °C | +|TSN |Availability of the Temperature Sensor |enum |0 - not available | +| | | |1 - available | ##### RORG 0xA5 - FUNC 0x04 - TYPE 0x03 - Range -20°C to +60°C 10bit-measurement and 0% to 100% @@ -292,6 +361,12 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |TTP |Telegram Type |enum |0 - Heartbeat | | | | |1 - Event triggered | +##### RORG 0xA5 - FUNC 0x04 - TYPE 0x04 - Range -40°C to +120°C 12bit-measurement and 0% to 100% + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|HUM |Rel. Humidity (linear) |value |0.0-199.0 ↔ 0.0-100.0 % | +|TMP |Temperature (linear) |value |0.0-1599.0 ↔ -40.0-120.0 °C | ##### RORG 0xA5 - FUNC 0x06 - TYPE 0x01 - Range 300lx to 60.000lx @@ -304,7 +379,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |RS |Range select |enum |0 - Range acc. to DB_1 (ILL1) | | | | |1 - Range acc. to DB_2 (ILL2) | - ##### RORG 0xA5 - FUNC 0x06 - TYPE 0x02 - Range 0lx to 1.020lx |shortcut|description |type |values | @@ -316,7 +390,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |1 - Range acc. to DB_2 (ILL2) | - ##### RORG 0xA5 - FUNC 0x07 - TYPE 0x01 - Occupancy with Supply voltage monitor |shortcut|description |type |values | @@ -325,6 +398,14 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |PIR |PIR Status |enum |0 - off | | | | |1 - on | +##### RORG 0xA5 - FUNC 0x07 - TYPE 0x03 - Occupancy with Supply voltage monitor and 10-bit illumination measurement + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|SVC |Supply voltage (OPTIONAL) |value |0.0-250.0 ↔ 0.0-5.0 V | +|ILL |Illumination (linear) |value |0.0-1000.0 ↔ 0.0-1000.0 lx | +|PIR |PIR Status |enum |0 - Uncertain of occupancy status | +| | | |1 - Motion detected | ##### RORG 0xA5 - FUNC 0x08 - TYPE 0x01 - Range 0lx to 510lx, 0°C to +51°C and Occupancy Button @@ -339,6 +420,29 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |OCC |Occupancy Button |enum |0 - Button pressed | | | | |1 - Button released | +##### RORG 0xA5 - FUNC 0x08 - TYPE 0x02 - Range 0lx to 1020lx, 0°C to +51°C and Occupancy Button + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|SVC |Supply voltage (linear) |value |0.0-255.0 ↔ 0.0-5.1 V | +|ILL |Illumination (linear) |value |0.0-255.0 ↔ 0.0-1020.0 lx | +|TMP |Temperature (linear) |value |0.0-255.0 ↔ 0.0-51.0 °C | +|PIRS |PIR Status |enum |0 - PIR on | +| | | |1 - PIR off | +|OCC |Occupancy Button |enum |0 - Button pressed | +| | | |1 - Button released | + +##### RORG 0xA5 - FUNC 0x08 - TYPE 0x03 - Range 0lx to 1020lx, 0°C to +51°C and Occupancy Button + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|SVC |Supply voltage (linear) |value |0.0-255.0 ↔ 0.0-5.1 V | +|ILL |Illumination (linear) |value |0.0-255.0 ↔ 0.0-1530.0 lx | +|TMP |Temperature (linear) |value |0.0-255.0 ↔ -30.0-50.0 °C | +|PIRS |PIR Status |enum |0 - PIR on | +| | | |1 - PIR off | +|OCC |Occupancy Button |enum |0 - Button pressed | +| | | |1 - Button released | ##### RORG 0xA5 - FUNC 0x09 - TYPE 0x04 - CO2 Sensor @@ -349,7 +453,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |Conc |Concentration (linear) |value |0.0-255.0 ↔ 0.0-2550.0 ppm | |TMP |Temperature (linear) |value |0.0-255.0 ↔ 0.0-51.0 °C | - ##### RORG 0xA5 - FUNC 0x09 - TYPE 0x05 - VOC Sensor |shortcut|description |type |values | @@ -383,6 +486,11 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |26 - Diethyl ether | | | | |255 - ozone | +##### RORG 0xA5 - FUNC 0x09 - TYPE 0x09 - Gas Sensor + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CO2 |CO2 Measurement |value |0.0-255.0 ↔ 0.0-2000.0 ppm | ##### RORG 0xA5 - FUNC 0x10 - TYPE 0x03 - Temperature Sensor and Set Point @@ -392,7 +500,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |SP |Set Point (linear) |value |0.0-255.0 ↔ 0.0-255.0 % | |TMP |Temperature (linear) |value |255.0-0.0 ↔ 0.0-40.0 °C | - ##### RORG 0xA5 - FUNC 0x10 - TYPE 0x05 - Temperature Sensor, Set Point and Occupancy Control |shortcut|description |type |values | @@ -402,7 +509,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |OCC |Occupancy Button |enum |0 - Button pressed | | | | |1 - Button released | - ##### RORG 0xA5 - FUNC 0x10 - TYPE 0x06 - Temperature Sensor, Set Point and Day/Night Control |shortcut|description |type |values | @@ -412,7 +518,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |SLSW |Slide switch |enum |0 - Position I / Night / Off | | | | |1 - Position O / Day / On | - ##### RORG 0xA5 - FUNC 0x10 - TYPE 0x10 - Temperature and Humidity Sensor, Set Point and Occupancy Control |shortcut|description |type |values | @@ -423,7 +528,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |OCC |Occupancy Button |enum |0 - Button pressed | | | | |1 - Button released | - ##### RORG 0xA5 - FUNC 0x10 - TYPE 0x12 - Temperature and Humidity Sensor and Set Point |shortcut|description |type |values | @@ -433,7 +537,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |TMP |Temperature (linear) |value |0.0-250.0 ↔ 0.0-40.0 °C | - ##### RORG 0xA5 - FUNC 0x11 - TYPE 0x02 - Temperature Controller Output |shortcut|description |type |values | @@ -463,7 +566,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |2 - StandBy | | | | |3 - Frost | - ##### RORG 0xA5 - FUNC 0x11 - TYPE 0x03 - Blind Status |shortcut|description |type |values | @@ -494,6 +596,71 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |1 - Inverse ode | +##### RORG 0xA5 - FUNC 0x13 - TYPE 0x01 - Weather Station + +###### command: 1 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|DWS |Dawn sensor |value |0.0-255.0 ↔ 0.0-999.0 lx | +|TMP |Outdoor Temp |value |0.0-255.0 ↔ -40.0-80.0 °C | +|WND |Wind speed |value |0.0-255.0 ↔ 0.0-70.0 m/s | +|D/N |Day / Night |enum |0 - day | +| | | |1 - night | +|RAN |Rain Indication |enum |0 - no rain | +| | | |1 - rain | + +###### command: 2 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|SNW |Sun - West |value |0.0-255.0 ↔ 0.0-150.0 klx | +|SNS |Sun - South |value |0.0-255.0 ↔ 0.0-150.0 klx | +|SNE |Sun - East |value |0.0-255.0 ↔ 0.0-150.0 klx | +|HEM |Hemisphere |enum |0 - North | +| | | |1 - South | + +###### command: 3 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|DY |Day |value |1.0-31.0 ↔ 1.0-31.0 | +|MTH |Month |value |1.0-12.0 ↔ 1.0-12.0 | +|YR |Year |value |0.0-99.0 ↔ 2000.0-2099.0 | +|SRC |Source |enum |0 - Real Time Clock | +| | | |1 - GPS or equivalent | + +###### command: 4 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|WDY |Weekday |enum |1 - Monday | +| | | |2 - Tuesday | +| | | |3 - Wednesday | +| | | |4 - Thursday | +| | | |5 - Friday | +| | | |6 - Saturday | +| | | |7 - Sunday | +|HR |Hour |value |0.0-23.0 ↔ 0.0-23.0 | +|MIN |Minute |value |0.0-59.0 ↔ 0.0-59.0 | +|SEC |Second |value |0.0-59.0 ↔ 0.0-59.0 | +|TMF |Time Format |enum |0 - 24 Hours | +| | | |1 - 12 Hours | +|A/PM |AM/PM |enum |0 - AM | +| | | |1 - PM | +|SRC |Source |enum |0 - Real Time Clock | +| | | |1 - GPS or equivalent | + +###### command: 5 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|ELV |Elevation |value |0.0-180.0 ↔ -90.0-90.0 ° | +|AZM |Azimut |value |0.0-359.0 ↔ 0.0-359.0 ° | + +###### command: 6 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +| |Latitude(MSB) |value |0.0-15.0 ↔ 0.0-15.0 | +|LOT(MSB)|Longitude(MSB) |value |0.0-15.0 ↔ 0.0-15.0 | +|LAT(LSB)|Latitude(LSB) |value |0.0-255.0 ↔ 0.0-255.0 | +|LOT(LSB)|Longitude(LSB) |value |0.0-255.0 ↔ 0.0-255.0 | + ##### RORG 0xA5 - FUNC 0x14 - TYPE 0x01 - Single Input Contact (Window/Door), Supply voltage monitor @@ -504,7 +671,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |0 - closed | - ##### RORG 0xA5 - FUNC 0x20 - TYPE 0x01 - Battery Powered Actuator (BI-DIR) ###### direction: 1 @@ -551,6 +717,65 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |RCU |Select function |enum |0 - RCU | | | | |1 - service on | +##### RORG 0xA5 - FUNC 0x20 - TYPE 0x06 - HVAC Components,Harvesting-powered actuator with local temperature offset control + +###### direction: 1 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CV |Current Valve |value |0.0-100.0 ↔ 0.0-100.0 % | +|LOM |Local Offset Mode |enum |0 - LO is relative - Offsettemperature | +| | | |1 - LO is absolute - Absoluttemperature | +|LO |Local Offset |enum |0-80 - Temperature setpoint °C +/- local offset °C | +| | | |0x0 - Local Offset 0C | +| | | |1 - Local Offset 1C | +| | | |2 - Local Offset 2C | +| | | |3 - Local Offset 3C | +| | | |4 - Local Offset 4C | +| | | |5 - Local Offset 5C | +| | | |123 - Local Offset -5C | +| | | |124 - Local Offset -4C | +| | | |125 - Local Offset -3C | +| | | |126 - Local Offset -2C | +| | | |127 - Local Offset -1C | +|TMP |Temperature |enum |0-160 - Local Ambient or Feed temperature (Selected by Direction 2, DB1.1)| +|TSL |Tempertature Selection |enum |0 - Ambient Sensor Temperature | +| | | |1 - Feed Sensor Temperature | +|ENIE |Energy Input Enabled |enum |0 - Not Harvesting | +| | | |1 - Harvesting active | +|ES |Energy Storage |enum |0 - Low - almost discharged | +| | | |1 - Sufficently charged | +|DWO |Window open detection |enum |0 - NO window open detected | +| | | |1 - Window open detected | +|RCE |Radio Com Error |enum |0 - Radio communication is stable | +| | | |1 - 6 or more consecutive communication erros have occured | +|RSS |Radio Signal strength |enum |0 - Radio signal is strong | +| | | |1 - Radio signal is weak under -77dBm | +|ACO |Actuator obstructed |enum |0 - Actuator working correctly | +| | | |1 - Actuator blocked | + +###### direction: 2 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|SP |Setpoint |enum | | +|TMP |Room temperature from control unit |value |0.0-160.0 ↔ 0.0-40.0 % | +|REF |Reference run |enum |0 - Normal operation | +| | | |1 - Reference run | +|RFC |RF Communication intervall |enum |0 - Auto | +| | | |1 - 2 minutes | +| | | |2 - 5 minutes | +| | | |3 - 10 minutes | +| | | |4 - 20 minutes | +| | | |5 - 30 minutes | +| | | |6 - 60 minutes | +| | | |7 - 120 minutes | +|SB |Initiate summer mode Bit |enum |0 - Normal operation | +| | | |1 - Summer mode with 8hours radio duty cycle | +|SPS |Set point selection |enum |0 - Valve position mode | +| | | |1 - Temperature setpoint | +|TSL |Temperature Selection |enum |0 - Request ambient temperature | +| | | |1 - Request feedtemperature | +|SBY |Standbye |enum |0 - Normal operation | +| | | |1 - Enter standbye | ##### RORG 0xA5 - FUNC 0x12 - TYPE 0x01 - Electricity @@ -567,7 +792,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |3 - x/1000 | - ##### RORG 0xA5 - FUNC 0x30 - TYPE 0x03 - Digital Inputs, Wake and Temperature |shortcut|description |type |values | @@ -585,7 +809,6 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |1 - High | - ##### RORG 0xA5 - FUNC 0x38 - TYPE 0x08 - Gateway ###### command: 1 @@ -614,8 +837,8 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |1 - On | - ### VLD Telegram (0xD2) + ##### RORG 0xD2 - FUNC 0x01 - TYPE 0x01 - Electronic switch with Local Control ###### command: 4 @@ -659,6 +882,210 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian | | | |101-126 - Not used | | | | |127 - output value not valid / not set | +##### RORG 0xD2 - FUNC 0x01 - TYPE 0x0E - Electronic switch with Local Control + +###### command: 4 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|PF |Power Failure |enum |0 - Power Failure Detection disabled/not supported | +| | | |1 - Power Failure Detection enabled | +|PFD |Power Failure Detection |enum |0 - Power Failure Detection not detected/not supported/disabled | +| | | |1 - Power Failure Detection Detected | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|OC |Over current switch off |enum |0 - Over current switch off: ready / not supported | +| | | |1 - Over current switch off: executed | +|EL |Error level |enum |0 - Error level 0: hardware OK | +| | | |1 - Error level 1: hardware warning | +| | | |2 - Error level 2: hardware failure | +| | | |3 - Error level not supported | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - Not applicable, do not use | +| | | |31 - Input channel (from mains supply) | +|LC |Local control |enum |0 - Local control disabled / not supported | +| | | |1 - Local control enabled | +|OV |Output value |enum |0 - Output value 0% or OFF | +| | | |1-100 - Output value {value}% or ON | +| | | |101-126 - Not used | +| | | |127 - output value not valid / not set | + +###### command: 1 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|DV |Dim value |enum |0 - Switch to new output value | +| | | |1 - Dim to new output level - dim timer 1 | +| | | |2 - Dim to new output level - dim timer 2 | +| | | |3 - Dim to new output level - dim timer 3 | +| | | |4 - Stop dimming | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - All output channels supported by the device | +| | | |31 - Input channel (from mains supply) | +|OV |Output value |enum |0 - Output value 0% or OFF | +| | | |1-100 - Output value {value}% or ON | +| | | |101-126 - Not used | +| | | |127 - output value not valid / not set | + +###### command: 6 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|qu |Measurement to query |enum |0 - Query energy | +| | | |1 - Query power | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - All output channels supported by the device | +| | | |31 - Input channel (from mains supply) | + +###### command: 7 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|UN |Unit |enum |0 - Ws | +| | | |1 - Wh | +| | | |2 - kWh | +| | | |3 - W | +| | | |4 - kW | +| | | |5-7 - Not used | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - Not applicable, do not use | +| | | |31 - Input channel (from mains supply) | +|MV |Measurement value |value |1.0-4294967295.0 ↔ 1.0-4294967295.0 None | + +##### RORG 0xD2 - FUNC 0x01 - TYPE 0x0F - Electronic switch with Local Control + +###### command: 4 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|PF |Power Failure |enum |0 - Power Failure Detection disabled/not supported | +| | | |1 - Power Failure Detection enabled | +|PFD |Power Failure Detection |enum |0 - Power Failure Detection not detected/not supported/disabled | +| | | |1 - Power Failure Detection Detected | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|OC |Over current switch off |enum |0 - Over current switch off: ready / not supported | +| | | |1 - Over current switch off: executed | +|EL |Error level |enum |0 - Error level 0: hardware OK | +| | | |1 - Error level 1: hardware warning | +| | | |2 - Error level 2: hardware failure | +| | | |3 - Error level not supported | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - Not applicable, do not use | +| | | |31 - Input channel (from mains supply) | +|LC |Local control |enum |0 - Local control disabled / not supported | +| | | |1 - Local control enabled | +|OV |Output value |enum |0 - Output value 0% or OFF | +| | | |1-100 - Output value {value}% or ON | +| | | |101-126 - Not used | +| | | |127 - output value not valid / not set | + +###### command: 1 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|DV |Dim value |enum |0 - Switch to new output value | +| | | |1 - Dim to new output level - dim timer 1 | +| | | |2 - Dim to new output level - dim timer 2 | +| | | |3 - Dim to new output level - dim timer 3 | +| | | |4 - Stop dimming | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - All output channels supported by the device | +| | | |31 - Input channel (from mains supply) | +|OV |Output value |enum |0 - Output value 0% or OFF | +| | | |1-100 - Output value {value}% or ON | +| | | |101-126 - Not used | +| | | |127 - output value not valid / not set | + +###### command: 6 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|qu |Measurement to query |enum |0 - Query energy | +| | | |1 - Query power | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - All output channels supported by the device | +| | | |31 - Input channel (from mains supply) | + +###### command: 7 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|UN |Unit |enum |0 - Ws | +| | | |1 - Wh | +| | | |2 - kWh | +| | | |3 - W | +| | | |4 - kW | +| | | |5-7 - Not used | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - Not applicable, do not use | +| | | |31 - Input channel (from mains supply) | +|MV |Measurement value |value |1.0-4294967295.0 ↔ 1.0-4294967295.0 None | + +##### RORG 0xD2 - FUNC 0x01 - TYPE 0x12 - Electronic switch with Local Control + +###### command: 4 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|PF |Power Failure |enum |0 - Power Failure Detection disabled/not supported | +| | | |1 - Power Failure Detection enabled | +|PFD |Power Failure Detection |enum |0 - Power Failure Detection not detected/not supported/disabled | +| | | |1 - Power Failure Detection Detected | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|OC |Over current switch off |enum |0 - Over current switch off: ready / not supported | +| | | |1 - Over current switch off: executed | +|EL |Error level |enum |0 - Error level 0: hardware OK | +| | | |1 - Error level 1: hardware warning | +| | | |2 - Error level 2: hardware failure | +| | | |3 - Error level not supported | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - Not applicable, do not use | +| | | |31 - Input channel (from mains supply) | +|LC |Local control |enum |0 - Local control disabled / not supported | +| | | |1 - Local control enabled | +|OV |Output value |enum |0 - Output value 0% or OFF | +| | | |1-100 - Output value {value}% or ON | +| | | |101-126 - Not used | +| | | |127 - output value not valid / not set | + +###### command: 1 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|DV |Dim value |enum |0 - Switch to new output value | +| | | |1 - Dim to new output level - dim timer 1 | +| | | |2 - Dim to new output level - dim timer 2 | +| | | |3 - Dim to new output level - dim timer 3 | +| | | |4 - Stop dimming | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - All output channels supported by the device | +| | | |31 - Input channel (from mains supply) | +| | | |32-127 - Output channel {value} (to load) | +|OV |Output value |enum |0 - Output value 0% or OFF | +| | | |1-100 - Output value {value}% or ON | +| | | |101-126 - Not used | +| | | |127 - output value not valid / not set | + +###### command: 6 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|qu |Measurement to query |enum |0 - Query energy | +| | | |1 - Query power | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - All output channels supported by the device | +| | | |31 - Input channel (from mains supply) | + +###### command: 7 +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|CMD |Command indentifier |enum |0-13 - Command ID {value} | +|UN |Unit |enum |0 - Ws | +| | | |1 - Wh | +| | | |2 - kWh | +| | | |3 - W | +| | | |4 - kW | +| | | |5-7 - Not used | +|IO |I/O channel |enum |0-29 - Output channel {value} (to load) | +| | | |30 - Not applicable, do not use | +| | | |31 - Input channel (from mains supply) | +|MV |Measurement value |value |1.0-4294967295.0 ↔ 1.0-4294967295.0 None | ##### RORG 0xD2 - FUNC 0x05 - TYPE 0x00 - Type 0x00 @@ -720,4 +1147,20 @@ All profiles (should) correspond to the official [EEP](http://www.enocean-allian |CMD |Command Id |enum |0-5 - Command ID {value} | +##### RORG 0xD2 - FUNC 0x14 - TYPE 0x41 - Indoor -Temperature, Humidity XYZ Acceleration, Illumination Sensor + +|shortcut|description |type |values | +|--------|--------------------------------------------------|--------|---- | +|TMP |Temperature 10 |value |0.0-1000.0 ↔ -40.0-60.0 °C | +|HUM |Rel. Humidity linear) |value |0.0-200.0 ↔ 0.0-100.0 % | +|ILL |Illumination linear) |value |0.0-100000.0 ↔ 0.0-100000.0 lx | +|ACC |Acceleration Status |enum |0 - Periodic Update | +| | | |1 - Threshold 1 exceeded | +| | | |2 - Threshold 2 exceeded | +|ACX |Absolute Acceleration on X axis |value |0.0-1000.0 ↔ -2.5-2.5 g | +|ACY |Absolute Acceleration on Y axis |value |0.0-1000.0 ↔ -2.5-2.5 g | +|ACZ |Absolute Acceleration on Z axis |value |0.0-1000.0 ↔ -2.5-2.5 g | +|CO |Contact |enum |0 - Open | +| | | |1 - Closed | + diff --git a/enocean/decorators.py b/decorators.py similarity index 86% rename from enocean/decorators.py rename to decorators.py index e953709..82c95bd 100644 --- a/enocean/decorators.py +++ b/decorators.py @@ -1,16 +1,16 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division -import time + import functools +import time from os import environ def timing(rounds=1, limit=None): - ''' + """ Wrapper to implement simple timing of tests. Allows running multiple rounds to calculate average time. Limit (in milliseconds) can be set to assert, if (average) duration is too high. - ''' + """ def decorator(method): @functools.wraps(method) def f(): @@ -26,7 +26,7 @@ def f(): # Use milliseconds for duration counter duration = duration * 1e3 - print('Test "%s.%s" took %.06f ms.' % (method.__module__, method.__name__, duration)) + print(f'Test "{method.__module__}.{method.__name__}" took {duration:.06f} ms.') if limit is not None: assert limit > duration, 'Timing failure: %.06f > %.06f' % (duration, limit) diff --git a/enocean/__init__.py b/enocean/__init__.py index e69de29..ea28939 100644 --- a/enocean/__init__.py +++ b/enocean/__init__.py @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- + +__version__ = '0.71.0' diff --git a/enocean/communicators/__init__.py b/enocean/communicators/__init__.py index 7408bbe..91a5b90 100644 --- a/enocean/communicators/__init__.py +++ b/enocean/communicators/__init__.py @@ -1,4 +1,5 @@ -''' Provider for different Communicator -classes for EnOcean. ''' -from enocean.communicators.communicator import Communicator -from enocean.communicators.serialcommunicator import SerialCommunicator -from enocean.communicators.tcpcommunicator import TCPCommunicator +""" Provider for different Communicator -classes for EnOcean. """ + +from .communicator import Communicator +from .serialcommunicator import SerialCommunicator +from .tcpcommunicator import TCPCommunicator diff --git a/enocean/communicators/communicator.py b/enocean/communicators/communicator.py index 9aff7c0..1d3951f 100644 --- a/enocean/communicators/communicator.py +++ b/enocean/communicators/communicator.py @@ -1,26 +1,27 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -import logging -import datetime +import datetime +import logging +import queue import threading -try: - import queue -except ImportError: - import Queue as queue -from enocean.protocol.packet import Packet, UTETeachInPacket -from enocean.protocol.constants import PACKET, PARSE_RESULT, RETURN_CODE +from typing import Union + +from ..protocol.constants import COMMON_COMMAND, PACKET, PARSE_RESULT, RETURN_CODE +from ..protocol.packet import Packet, UTETeachInPacket +from ..protocol.version_info import VersionInfo + +LOGGER = logging.getLogger('enocean.communicators.Communicator') class Communicator(threading.Thread): - ''' + """ Communicator base-class for EnOcean. Not to be used directly, only serves as base class for SerialCommunicator etc. - ''' - logger = logging.getLogger('enocean.communicators.Communicator') + """ - def __init__(self, callback=None, teach_in=True): - super(Communicator, self).__init__() + def __init__(self, callback: callable = None, teach_in: bool = True, loglevel=logging.NOTSET) -> None: + super().__init__() + LOGGER.setLevel(loglevel) # Create an event to stop the thread self._stop_flag = threading.Event() # Input buffer @@ -32,33 +33,34 @@ def __init__(self, callback=None, teach_in=True): self.__callback = callback # Internal variable for the Base ID of the module. self._base_id = None + # Internal variable for the version info of the module. + self._version_info = None # Should new messages be learned automatically? Defaults to True. # TODO: Not sure if we should use CO_WR_LEARNMODE?? self.teach_in = teach_in - def _get_from_send_queue(self): - ''' Get message from send queue, if one exists ''' + def _get_from_send_queue(self) -> Union[Packet, None]: + """ Get message from send queue, if one exists """ try: packet = self.transmit.get(block=False) - self.logger.info('Sending packet') - self.logger.debug(packet) return packet except queue.Empty: pass return None - def send(self, packet): + def send(self, packet: Packet) -> bool: + LOGGER.debug(f'sending: {packet}') if not isinstance(packet, Packet): - self.logger.error('Object to send must be an instance of Packet') + LOGGER.error('Object to send must be an instance of Packet') return False self.transmit.put(packet) return True - def stop(self): + def stop(self) -> None: self._stop_flag.set() - def parse(self): - ''' Parses messages and puts them to receive queue ''' + def parse(self) -> Union[None, PARSE_RESULT]: + """ Parses messages and puts them to receive queue """ # Loop while we get new messages while True: status, self._buffer, packet = Packet.parse_msg(self._buffer) @@ -72,32 +74,49 @@ def parse(self): if isinstance(packet, UTETeachInPacket) and self.teach_in: response_packet = packet.create_response_packet(self.base_id) - self.logger.info('Sending response to UTE teach-in.') + LOGGER.info('Sending response to UTE teach-in.') self.send(response_packet) + LOGGER.debug(f"received: {packet}") if self.__callback is None: self.receive.put(packet) else: self.__callback(packet) - self.logger.debug(packet) + + @property # getter + def callback(self): + return self.__callback + + @callback.setter + def callback(self, callback): + self.__callback = callback @property - def base_id(self): - ''' Fetches Base ID from the transmitter, if required. Otherwise returns the currently set Base ID. ''' + def base_id(self) -> Union[None, list[int, int, int, int]]: + """ Fetches Base ID from the transmitter, if required. Otherwise, returns the currently set Base ID. """ # If base id is already set, return it. if self._base_id is not None: return self._base_id + start = datetime.datetime.now() + # Send COMMON_COMMAND 0x08, CO_RD_IDBASE request to the module - self.send(Packet(PACKET.COMMON_COMMAND, data=[0x08])) - # Loop over 10 times, to make sure we catch the response. - # Thanks to timeout, shouldn't take more than a second. - # Unfortunately, all other messages received during this time are ignored. - for i in range(0, 10): + self.send(Packet(PACKET.COMMON_COMMAND, data=[COMMON_COMMAND.CO_RD_IDBASE.value], optional=[])) + + # wait at most 1 second for the response + while True: + seconds_elapsed = (datetime.datetime.now() - start).total_seconds() + if seconds_elapsed > 1: + self.logger.error("Could not obtain base id from module within 1 second (timeout).") + break try: packet = self.receive.get(block=True, timeout=0.1) # We're only interested in responses to the request in question. - if packet.packet_type == PACKET.RESPONSE and packet.response == RETURN_CODE.OK and len(packet.response_data) == 4: # noqa: E501 + if ( + packet.packet_type == PACKET.RESPONSE + and packet.response == RETURN_CODE.OK + and len(packet.response_data) == 4 + ): # Base ID is set in the response data. self._base_id = packet.response_data # Put packet back to the Queue, so the user can also react to it if required... @@ -111,6 +130,69 @@ def base_id(self): return self._base_id @base_id.setter - def base_id(self, base_id): - ''' Sets the Base ID manually, only for testing purposes. ''' + def base_id(self, base_id: list[int, int, int, int]): + """ Sets the Base ID manually, only for testing purposes. """ self._base_id = base_id + + @property + def chip_id(self): + ''' Fetches Chip ID from the transmitter, if required. Otherwise returns the currently set Chip ID. ''' + if self.version_info is not None: + return self.version_info.chip_id + + return None + + @property + def version_info(self): + ''' Fetches version info from the transmitter, if required. Otherwise returns the currently set version info. ''' + + # If version info is already set, return it. + if self._version_info is not None: + return self._version_info + + start = datetime.datetime.now() + + # Send COMMON_COMMAND 0x03, CO_RD_VERSION request to the module + self.send(Packet(PACKET.COMMON_COMMAND, data=[COMMON_COMMAND.CO_RD_VERSION.value], optional=[])) + + # wait at most 1 second for the response + while True: + seconds_elapsed = (datetime.datetime.now() - start).total_seconds() + if seconds_elapsed > 1: + LOGGER.warning("Could not obtain version info from module within 1 second (timeout).") + break + + try: + packet = self.receive.get(block=True, timeout=0.1) + if packet.packet_type == PACKET.RESPONSE and packet.response == RETURN_CODE.OK and len(packet.response_data) == 32: + # interpret the version info + self._version_info: VersionInfo = VersionInfo() + res = packet.response_data + + self._version_info.app_version.main = res[0] + self._version_info.app_version.beta = res[1] + self._version_info.app_version.alpha = res[2] + self._version_info.app_version.build = res[3] + + self._version_info.api_version.main = res[4] + self._version_info.api_version.beta = res[5] + self._version_info.api_version.alpha = res[6] + self._version_info.api_version.build = res[7] + + self._version_info.chip_id = [ + res[8], res[9], res[10], res[11] + ] + self._version_info.chip_version = int.from_bytes(res[12:15], 'big') + + self._version_info.app_description = bytearray(res[16:32]).decode('utf8').strip() + + # Put packet back to the Queue, so the user can also react to it if required... + self.receive.put(packet) + break + # Put other packets back to the Queue. + self.receive.put(packet) + except queue.Empty: + continue + # Return the current version info (might be None). + return self._version_info + diff --git a/enocean/communicators/serialcommunicator.py b/enocean/communicators/serialcommunicator.py index ffe4201..6a1829d 100644 --- a/enocean/communicators/serialcommunicator.py +++ b/enocean/communicators/serialcommunicator.py @@ -1,23 +1,26 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + import logging -import serial import time -from enocean.communicators.communicator import Communicator +import serial + +from .communicator import Communicator + +LOGGER = logging.getLogger('enocean.communicators.SerialCommunicator') class SerialCommunicator(Communicator): - ''' Serial port communicator class for EnOcean radio ''' - logger = logging.getLogger('enocean.communicators.SerialCommunicator') + """ Serial port communicator class for EnOcean radio """ - def __init__(self, port='/dev/ttyAMA0', callback=None): - super(SerialCommunicator, self).__init__(callback) + def __init__(self, port: str = '/dev/ttyAMA0', callback: callable = None, loglevel=logging.NOTSET) -> None: + super().__init__(callback, loglevel=loglevel) + LOGGER.setLevel(loglevel) # Initialize serial port self.__ser = serial.Serial(port, 57600, timeout=0.1) - def run(self): - self.logger.info('SerialCommunicator started') + def run(self) -> None: + LOGGER.info('SerialCommunicator started') while not self._stop_flag.is_set(): # If there's messages in transmit queue # send them @@ -34,10 +37,11 @@ def run(self): try: self._buffer.extend(bytearray(self.__ser.read(16))) except serial.SerialException: - self.logger.error('Serial port exception! (device disconnected or multiple access on port?)') + LOGGER.error('Serial port exception! (device disconnected or multiple access on port?)') self.stop() + continue self.parse() time.sleep(0) self.__ser.close() - self.logger.info('SerialCommunicator stopped') + LOGGER.info('SerialCommunicator stopped') diff --git a/enocean/communicators/tcpcommunicator.py b/enocean/communicators/tcpcommunicator.py index 239012d..fecc846 100644 --- a/enocean/communicators/tcpcommunicator.py +++ b/enocean/communicators/tcpcommunicator.py @@ -1,22 +1,24 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + import logging import socket -from enocean.communicators.communicator import Communicator +from .communicator import Communicator + +LOGGER = logging.getLogger('enocean.communicators.TCPCommunicator') class TCPCommunicator(Communicator): - ''' Socket communicator class for EnOcean radio ''' - logger = logging.getLogger('enocean.communicators.TCPCommunicator') + """ Socket communicator class for EnOcean radio """ - def __init__(self, host='', port=9637): - super(TCPCommunicator, self).__init__() + def __init__(self, host: str = '', port: int = 9637, loglevel=logging.NOTSET) -> None: + super().__init__(loglevel=loglevel) + LOGGER.setLevel(loglevel) self.host = host self.port = port - def run(self): - self.logger.info('TCPCommunicator started') + def run(self) -> None: + LOGGER.info('TCPCommunicator started') sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((self.host, self.port)) sock.listen(5) @@ -27,7 +29,7 @@ def run(self): (client, addr) = sock.accept() except socket.timeout: continue - self.logger.debug('Client "%s" connected' % (addr)) + LOGGER.debug(f'Client "{addr}" connected') client.settimeout(0.5) while True and not self._stop_flag.is_set(): try: @@ -39,6 +41,6 @@ def run(self): self._buffer.extend(bytearray(data)) self.parse() client.close() - self.logger.debug('Client disconnected') + LOGGER.debug('Client disconnected') sock.close() - self.logger.info('TCPCommunicator stopped') + LOGGER.info('TCPCommunicator stopped') diff --git a/enocean/communicators/utils.py b/enocean/communicators/utils.py index 646841d..604aaad 100644 --- a/enocean/communicators/utils.py +++ b/enocean/communicators/utils.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + import socket diff --git a/enocean/consolelogger.py b/enocean/consolelogger.py index de3e01d..0557c15 100644 --- a/enocean/consolelogger.py +++ b/enocean/consolelogger.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + import logging import logging.handlers diff --git a/enocean/protocol/EEP.xml b/enocean/protocol/EEP.xml index b7d79e2..c909268 100644 --- a/enocean/protocol/EEP.xml +++ b/enocean/protocol/EEP.xml @@ -496,6 +496,34 @@ + + + + + 0 + 250 + + + 0 + 100 + + + + + 0 + 250 + + + -20 + 60 + + + + + + + + @@ -524,6 +552,30 @@ + + + + + 0 + 199 + + + 0 + 100 + + + + + 0 + 1599 + + + -40 + +120 + + + + @@ -622,9 +674,37 @@ + + + + + 0 + 250 + + + 0 + 5.000000 + + + + + 0 + 1000 + + + 0 + 1000 + + + + + + + + - + @@ -666,6 +746,90 @@ + + + + + 0 + 255 + + + 0 + 5.100000 + + + + + 0 + 255 + + + 0 + 1020 + + + + + 0 + 255 + + + 0 + 51 + + + + + + + + + + + + + + + + + 0 + 255 + + + 0 + 5.100000 + + + + + 0 + 255 + + + 0 + 1530 + + + + + 0 + 255 + + + -30 + 50 + + + + + + + + + + + + @@ -745,6 +909,20 @@ + + + + + 0 + 255 + + + 0 + 2000 + + + + @@ -771,7 +949,7 @@ - + @@ -799,7 +977,7 @@ - + @@ -827,7 +1005,7 @@ - + @@ -865,7 +1043,7 @@ - + @@ -1394,6 +1572,133 @@ + + + + + 0 + 100 + + + 0 + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 100 + + + 0 + 100 + + + + + 0 + 80 + + + 0 + 40 + + + + + + 0 + 160 + + + 0 + 40 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1621,6 +1926,364 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 4294967295 + + + 1 + 4294967295 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 4294967295 + + + 1 + 4294967295 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 4294967295 + + + 1 + 4294967295 + + + diff --git a/enocean/protocol/constants.py b/enocean/protocol/constants.py index 8dd3e74..1f75624 100644 --- a/enocean/protocol/constants.py +++ b/enocean/protocol/constants.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + from enum import IntEnum @@ -26,7 +26,15 @@ class PACKET(IntEnum): COMMAND_2_4 = 0x11 +# EnOceanSerialProtocol3-1-1.pdf / 33 +# noinspection PyPep8Naming +class COMMON_COMMAND(IntEnum): + CO_RD_VERSION = 0x03 + CO_RD_IDBASE = 0x08 + + # EnOceanSerialProtocol3.pdf / 18 +# noinspection PyPep8Naming class RETURN_CODE(IntEnum): OK = 0x00 ERROR = 0x01 @@ -36,6 +44,7 @@ class RETURN_CODE(IntEnum): # EnOceanSerialProtocol3.pdf / 20 +# noinspection PyPep8Naming class EVENT_CODE(IntEnum): SA_RECLAIM_NOT_SUCCESFUL = 0x01 SA_CONFIRM_LEARN = 0x02 @@ -45,6 +54,7 @@ class EVENT_CODE(IntEnum): # EnOcean_Equipment_Profiles_EEP_V2.61_public.pdf / 8 +# noinspection PyPep8Naming class RORG(IntEnum): UNDEFINED = 0x00 RPS = 0xF6 @@ -59,10 +69,13 @@ class RORG(IntEnum): SYS_EX = 0xC5 SEC = 0x30 SEC_ENCAPS = 0x31 + SEC_MAN = 0x34 + SIGNAL = 0xD0 UTE = 0xD4 # Results for message parsing +# noinspection PyPep8Naming class PARSE_RESULT(IntEnum): OK = 0x00 INCOMPLETE = 0x01 @@ -71,7 +84,7 @@ class PARSE_RESULT(IntEnum): # Data byte indexing # Starts from the end, so works on messages of all length. -class DB0(object): +class DB0: BIT_0 = -1 BIT_1 = -2 BIT_2 = -3 @@ -82,7 +95,7 @@ class DB0(object): BIT_7 = -8 -class DB1(object): +class DB1: BIT_0 = -9 BIT_1 = -10 BIT_2 = -11 @@ -93,7 +106,7 @@ class DB1(object): BIT_7 = -16 -class DB2(object): +class DB2: BIT_0 = -17 BIT_1 = -18 BIT_2 = -19 @@ -104,7 +117,7 @@ class DB2(object): BIT_7 = -24 -class DB3(object): +class DB3: BIT_0 = -25 BIT_1 = -26 BIT_2 = -27 @@ -115,7 +128,7 @@ class DB3(object): BIT_7 = -32 -class DB4(object): +class DB4: BIT_0 = -33 BIT_1 = -34 BIT_2 = -35 @@ -126,7 +139,7 @@ class DB4(object): BIT_7 = -40 -class DB5(object): +class DB5: BIT_0 = -41 BIT_1 = -42 BIT_2 = -43 @@ -137,7 +150,7 @@ class DB5(object): BIT_7 = -48 -class DB6(object): +class DB6: BIT_0 = -49 BIT_1 = -50 BIT_2 = -51 @@ -146,3 +159,7 @@ class DB6(object): BIT_5 = -54 BIT_6 = -55 BIT_7 = -56 + +class COMMON_COMMAND_CODE(IntEnum): + CO_RD_VERSION = 0x03 + CO_RD_IDBASE = 0x08 \ No newline at end of file diff --git a/enocean/protocol/crc8.py b/enocean/protocol/crc8.py index 5dd9b9d..c4a061b 100644 --- a/enocean/protocol/crc8.py +++ b/enocean/protocol/crc8.py @@ -1,5 +1,4 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import # https://gist.github.com/hypebeast/3833758 CRC_TABLE = ( diff --git a/enocean/protocol/eep.py b/enocean/protocol/eep.py index 8da3156..a74d8f7 100644 --- a/enocean/protocol/eep.py +++ b/enocean/protocol/eep.py @@ -1,46 +1,40 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -import os + import logging -from sys import version_info from collections import OrderedDict -from bs4 import BeautifulSoup +from importlib.resources import files +from typing import Union + +from bs4 import BeautifulSoup, Tag -import enocean.utils -# Left as a helper -from enocean.protocol.constants import RORG # noqa: F401 +from .. import utils -class EEP(object): +class EEP: logger = logging.getLogger('enocean.protocol.eep') - def __init__(self): + def __init__(self) -> None: self.init_ok = False self.telegrams = {} - eep_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'EEP.xml') try: - if version_info[0] > 2: - with open(eep_path, 'r', encoding='UTF-8') as xml_file: - self.soup = BeautifulSoup(xml_file.read(), "html.parser") - else: - with open(eep_path, 'r') as xml_file: - self.soup = BeautifulSoup(xml_file.read(), "html.parser") + xml_content = files('enocean.protocol').joinpath('EEP.xml').read_text(encoding='utf-8') + self.soup = BeautifulSoup(xml_content, features="lxml-xml") + self.__xml_to_dict() self.init_ok = True - self.__load_xml() except IOError: # Impossible to test with the current structure? # To be honest, as the XML is included with the library, # there should be no possibility of ever reaching this... - self.logger.warn('Cannot load protocol file!') + self.logger.warning('Cannot load protocol file!') self.init_ok = False - def __load_xml(self): + def __xml_to_dict(self) -> None: self.telegrams = { - enocean.utils.from_hex_string(telegram['rorg']): { - enocean.utils.from_hex_string(function['func']): { - enocean.utils.from_hex_string(type['type'], ): type - for type in function.find_all('profile') + utils.from_hex_string(telegram['rorg']): { + utils.from_hex_string(function['func']): { + utils.from_hex_string(typ['type'], ): typ + for typ in function.find_all('profile') } for function in telegram.find_all('profiles') } @@ -48,15 +42,15 @@ def __load_xml(self): } @staticmethod - def _get_raw(source, bitarray): - ''' Get raw data as integer, based on offset and size ''' + def _get_raw(source, bitarray) -> int: + """ Get raw data as integer, based on offset and size """ offset = int(source['offset']) size = int(source['size']) return int(''.join(['1' if digit else '0' for digit in bitarray[offset:offset + size]]), 2) @staticmethod - def _set_raw(target, raw_value, bitarray): - ''' put value into bit array ''' + def _set_raw(target: Tag, raw_value: int, bitarray: list) -> list: + """ put value into bit array """ offset = int(target['offset']) size = int(target['size']) for digit in range(size): @@ -64,13 +58,13 @@ def _set_raw(target, raw_value, bitarray): return bitarray @staticmethod - def _get_rangeitem(source, raw_value): + def _get_rangeitem(source: Tag, raw_value: int) -> Tag: for rangeitem in source.find_all('rangeitem'): if raw_value in range(int(rangeitem.get('start', -1)), int(rangeitem.get('end', -1)) + 1): return rangeitem - def _get_value(self, source, bitarray): - ''' Get value, based on the data in XML ''' + def _get_value(self, source: Tag, bitarray: list) -> dict: + """ Get value, based on the data in XML """ raw_value = self._get_raw(source, bitarray) rng = source.find('range') @@ -84,14 +78,14 @@ def _get_value(self, source, bitarray): return { source['shortcut']: { 'description': source.get('description'), - 'unit': source['unit'], + 'unit': source.get('unit'), 'value': (scl_max - scl_min) / (rng_max - rng_min) * (raw_value - rng_min) + scl_min, 'raw_value': raw_value, } } - def _get_enum(self, source, bitarray): - ''' Get enum value, based on the data in XML ''' + def _get_enum(self, source: Tag, bitarray: list) -> dict: + """ Get enum value, based on the data in XML """ raw_value = self._get_raw(source, bitarray) # Find value description. @@ -106,8 +100,8 @@ def _get_enum(self, source, bitarray): } } - def _get_boolean(self, source, bitarray): - ''' Get boolean value, based on the data in XML ''' + def _get_boolean(self, source: Tag, bitarray: list) -> dict: + """ Get boolean value, based on the data in XML """ raw_value = self._get_raw(source, bitarray) return { source['shortcut']: { @@ -118,8 +112,8 @@ def _get_boolean(self, source, bitarray): } } - def _set_value(self, target, value, bitarray): - ''' set given numeric value to target field in bitarray ''' + def _set_value(self, target: Tag, value: Union[int, float], bitarray: list) -> list: + """ set given numeric value to target field in bitarray """ # derive raw value rng = target.find('range') rng_min = float(rng.find('min').text) @@ -131,8 +125,8 @@ def _set_value(self, target, value, bitarray): # store value in bitfield return self._set_raw(target, int(raw_value), bitarray) - def _set_enum(self, target, value, bitarray): - ''' set given enum value (by string or integer value) to target field in bitarray ''' + def _set_enum(self, target: Tag, value: Union[int, str], bitarray: list) -> list: + """ set given enum value (by string or integer value) to target field in bitarray """ # derive raw value if isinstance(value, int): # check whether this value exists @@ -140,39 +134,42 @@ def _set_enum(self, target, value, bitarray): # set integer values directly raw_value = value else: - raise ValueError('Enum value "%s" not found in EEP.' % (value)) + raise ValueError(f'Enum value "{value}" not found in EEP.') else: value_item = target.find('item', {'description': value}) if value_item is None: - raise ValueError('Enum description for value "%s" not found in EEP.' % (value)) + raise ValueError(f'Enum description for value "{value}" not found in EEP.') raw_value = int(value_item['value']) return self._set_raw(target, raw_value, bitarray) @staticmethod - def _set_boolean(target, data, bitarray): - ''' set given value to target bit in bitarray ''' + def _set_boolean(target: Tag, data: bool, bitarray: list) -> list: + """ set given value to target bit in bitarray """ bitarray[int(target['offset'])] = data return bitarray - def find_profile(self, bitarray, eep_rorg, rorg_func, rorg_type, direction=None, command=None): - ''' Find profile and data description, matching RORG, FUNC and TYPE ''' + def find_profile(self, eep_rorg: int, rorg_func: int, rorg_type: int, + direction=None, command=None) -> Union[None, Tag]: + """ Find profile and data description, matching RORG, FUNC and TYPE """ if not self.init_ok: - self.logger.warn('EEP.xml not loaded!') + self.logger.warning('EEP.xml not loaded!') return None if eep_rorg not in self.telegrams.keys(): - self.logger.warn('Cannot find rorg %s in EEP!', hex(eep_rorg)) + self.logger.warning(f'Cannot find rorg {hex(eep_rorg)} in EEP!') return None if rorg_func not in self.telegrams[eep_rorg].keys(): - self.logger.warn('Cannot find rorg %s func %s in EEP!', hex(eep_rorg), hex(rorg_func)) + self.logger.warning(f'Cannot find rorg {hex(eep_rorg)} func {hex(rorg_func)} in EEP!') return None if rorg_type not in self.telegrams[eep_rorg][rorg_func].keys(): - self.logger.warn('Cannot find rorg %s func %s type %s in EEP!', hex(eep_rorg), hex(rorg_func), hex(rorg_type)) + self.logger.warning( + f'Cannot find rorg {hex(eep_rorg)} func {hex(rorg_func)} type {hex(rorg_type)} in EEP!' + ) return None - profile = self.telegrams[eep_rorg][rorg_func][rorg_type] + profile: Tag = self.telegrams[eep_rorg][rorg_func][rorg_type] if command: # multiple commands can be defined, with the command id always in same location (per RORG-FUNC-TYPE). @@ -191,8 +188,8 @@ def find_profile(self, bitarray, eep_rorg, rorg_func, rorg_type, direction=None, return profile.find('data', recursive=False) return profile.find('data', {'direction': direction}, recursive=False) - def get_values(self, profile, bitarray, status): - ''' Get keys and values from bitarray ''' + def get_values(self, profile: Tag, bitarray: list, status: list) -> tuple: + """ Get keys and values from bitarray """ if not self.init_ok or profile is None: return [], {} @@ -200,16 +197,20 @@ def get_values(self, profile, bitarray, status): for source in profile.contents: if not source.name: continue - if source.name == 'value': + elif source.name == 'value': output.update(self._get_value(source, bitarray)) - if source.name == 'enum': - output.update(self._get_enum(source, bitarray)) - if source.name == 'status': + elif source.name == 'enum': + try: + output.update(self._get_enum(source, bitarray)) + except (ValueError, TypeError): + pass + elif source.name == 'status': output.update(self._get_boolean(source, status)) + return output.keys(), output - def set_values(self, profile, data, status, properties): - ''' Update data based on data contained in properties ''' + def set_values(self, profile: Tag, data: list, status: list, properties: dict) -> tuple: + """ Update data based on data contained in properties """ if not self.init_ok or profile is None: return data, status @@ -218,7 +219,7 @@ def set_values(self, profile, data, status, properties): target = profile.find(shortcut=shortcut) if not target: # TODO: Should we raise an error? - self.logger.warning('Cannot find data description for shortcut %s', shortcut) + self.logger.warning(f"Cannot find data description for shortcut '{shortcut}'") continue # update bit_data diff --git a/enocean/protocol/packet.py b/enocean/protocol/packet.py index 14fdc34..3c6d96b 100644 --- a/enocean/protocol/packet.py +++ b/enocean/protocol/packet.py @@ -1,25 +1,26 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + import logging from collections import OrderedDict +from typing import Any, Union -import enocean.utils -from enocean.protocol import crc8 -from enocean.protocol.eep import EEP -from enocean.protocol.constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 +from .crc8 import calc +from .constants import PACKET, RORG, PARSE_RESULT, DB0, DB2, DB3, DB4, DB6 +from .eep import EEP +from ..utils import combine_hex, from_bitarray, to_hex_string, to_bitarray -class Packet(object): - ''' +class Packet: + """ Base class for Packet. Mainly used for for packet generation and Packet.parse_msg(buf) for parsing message. parse_msg() returns subclass, if one is defined for the data type. - ''' + """ eep = EEP() logger = logging.getLogger('enocean.protocol.packet') - def __init__(self, packet_type, data=None, optional=None): + def __init__(self, packet_type: PACKET, data: Union[None, list] = None, optional: Union[None, list] = None) -> None: self.packet_type = packet_type self.rorg = RORG.UNDEFINED self.rorg_func = None @@ -47,35 +48,32 @@ def __init__(self, packet_type, data=None, optional=None): self.parse() - def __str__(self): - return '0x%02X %s %s %s' % ( - self.packet_type, - [hex(o) for o in self.data], - [hex(o) for o in self.optional], - self.parsed) + def __str__(self) -> str: + return (f'0x{self.packet_type:02X} {[hex(o) for o in self.data]}' + f' {[hex(o) for o in self.optional]} {self.parsed}') - def __unicode__(self): + def __unicode__(self) -> str: return self.__str__() - def __eq__(self, other): + def __eq__(self, other) -> bool: return self.packet_type == other.packet_type and self.rorg == other.rorg \ and self.data == other.data and self.optional == other.optional @property - def _bit_data(self): + def _bit_data(self) -> list[bool]: # First and last 5 bits are always defined, so the data we're modifying is between them... # TODO: This is valid for the packets we're currently manipulating. # Needs the redefinition of Packet.data -> Packet.message. # Packet.data would then only have the actual, documented data-bytes. # Packet.message would contain the whole message. - # See discussion in issue #14 - return enocean.utils.to_bitarray(self.data[1:len(self.data) - 5], (len(self.data) - 6) * 8) + # See discussion in issue https://github.com/kipe/enocean/issues/14 + return to_bitarray(self.data[1:len(self.data) - 5], (len(self.data) - 6) * 8) @_bit_data.setter - def _bit_data(self, value): + def _bit_data(self, value: list[bool]) -> None: # The same as getting the data, first and last 5 bits are ommitted, as they are defined... for byte in range(len(self.data) - 6): - self.data[byte+1] = enocean.utils.from_bitarray(value[byte*8:(byte+1)*8]) + self.data[byte + 1] = from_bitarray(value[byte * 8:(byte + 1) * 8]) # # COMMENTED OUT, AS NOTHING TOUCHES _bit_optional FOR NOW. # # Thus, this is also untested. @@ -92,22 +90,24 @@ def _bit_data(self, value): # self.data[byte+1] = enocean.utils.from_bitarray(value[byte*8:(byte+1)*8]) @property - def _bit_status(self): - return enocean.utils.to_bitarray(self.status) + def _bit_status(self) -> list[bool]: + return to_bitarray(self.status) @_bit_status.setter - def _bit_status(self, value): - self.status = enocean.utils.from_bitarray(value) + def _bit_status(self, value: list[bool]) -> None: + self.status = from_bitarray(value) @staticmethod - def parse_msg(buf): - ''' + def parse_msg(buf: Union[list, bytearray]) -> (PARSE_RESULT, list, Any): + # 'Any' in return type should be Union[None | UTETeachInPacket | ResponsePacket | EventPacket | Packet] + # how to realize that? + """ Parses message from buffer. returns: - PARSE_RESULT - remaining buffer - Packet -object (if message was valid, else None) - ''' + """ # If the buffer doesn't contain 0x55 (start char) # the message isn't needed -> ignore if 0x55 not in buf: @@ -137,12 +137,12 @@ def parse_msg(buf): opt_data = msg[6 + data_len:6 + data_len + opt_len] # Check CRCs for header and data - if msg[5] != crc8.calc(msg[1:5]): + if msg[5] != calc(msg[1:5]): # Fail if doesn't match message Packet.logger.error('Header CRC error!') # Return CRC_MISMATCH return PARSE_RESULT.CRC_MISMATCH, buf, None - if msg[6 + data_len + opt_len] != crc8.calc(msg[6:6 + data_len + opt_len]): + if msg[6 + data_len + opt_len] != calc(msg[6:6 + data_len + opt_len]): # Fail if doesn't match message Packet.logger.error('Data CRC error!') # Return CRC_MISMATCH @@ -165,11 +165,16 @@ def parse_msg(buf): return PARSE_RESULT.OK, buf, packet @staticmethod - def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None, - destination=None, - sender=None, - learn=False, **kwargs): - ''' + def create( + packet_type: PACKET, rorg: RORG, rorg_func: int, rorg_type: int, + direction=None, + command: Union[None, int] = None, + destination: Union[None, list] = None, + sender: Union[None, list] = None, + learn: bool = False, + **kwargs + ): + """ Creates an packet ready for sending. Uses rorg, rorg_func and rorg_type to determine the values set based on EEP. Additional arguments (**kwargs) are used for setting the values. @@ -182,7 +187,7 @@ def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None - Require sender to be set? Would force the "correct" sender to be set. - Do we need to set telegram control bits? Might be useful for acting as a repeater? - ''' + """ if packet_type != PACKET.RADIO_ERP1: # At least for now, only support PACKET.RADIO_ERP1. @@ -203,10 +208,10 @@ def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None sender = [0xDE, 0xAD, 0xBE, 0xEF] if not isinstance(destination, list) or len(destination) != 4: - raise ValueError('Destination must a list containing 4 (numeric) values.') + raise ValueError('Destination must be a list containing 4 (numeric) values.') if not isinstance(sender, list) or len(sender) != 4: - raise ValueError('Sender must a list containing 4 (numeric) values.') + raise ValueError('Sender must be a list containing 4 (numeric) values.') packet = Packet(packet_type, data=[], optional=[]) packet.rorg = rorg @@ -243,11 +248,12 @@ def create(packet_type, rorg, rorg_func, rorg_type, direction=None, command=None # For example, stuff like RadioPacket.learn should be set. packet = Packet.parse_msg(packet.build())[2] packet.rorg = rorg + # noinspection PyUnresolvedReferences packet.parse_eep(rorg_func, rorg_type, direction, command) return packet - def parse(self): - ''' Parse data from Packet ''' + def parse(self) -> OrderedDict: + """ Parse data from Packet """ # Parse status from messages if self.rorg in [RORG.RPS, RORG.BS1, RORG.BS4]: self.status = self.data[-1] @@ -256,19 +262,20 @@ def parse(self): if self.rorg in [RORG.RPS, RORG.BS1, RORG.BS4]: # These message types should have repeater count in the last for bits of status. - self.repeater_count = enocean.utils.from_bitarray(self._bit_status[4:]) + self.repeater_count = from_bitarray(self._bit_status[4:]) return self.parsed - def select_eep(self, rorg_func, rorg_type, direction=None, command=None): - ''' Set EEP based on FUNC and TYPE ''' + def select_eep(self, rorg_func: int, rorg_type: int, direction=None, command: Union[None, int] = None) -> bool: + """ Set EEP based on FUNC and TYPE """ # set EEP profile self.rorg_func = rorg_func self.rorg_type = rorg_type - self._profile = self.eep.find_profile(self._bit_data, self.rorg, rorg_func, rorg_type, direction, command) + self._profile = self.eep.find_profile(self.rorg, rorg_func, rorg_type, direction, command) return self._profile is not None - def parse_eep(self, rorg_func=None, rorg_type=None, direction=None, command=None): - ''' Parse EEP based on FUNC and TYPE ''' + def parse_eep(self, rorg_func: Union[None, int] = None, rorg_type: Union[None, int] = None, + direction=None, command: Union[None, int] = None) -> list: + """ Parse EEP based on FUNC and TYPE """ # set EEP profile, if demanded if rorg_func is not None and rorg_type is not None: self.select_eep(rorg_func, rorg_type, direction, command) @@ -277,18 +284,19 @@ def parse_eep(self, rorg_func=None, rorg_type=None, direction=None, command=None self.parsed.update(values) return list(provides) - def set_eep(self, data): - ''' Update packet data based on EEP. Input data is a dictionary with keys corresponding to the EEP. ''' + def set_eep(self, data: dict) -> None: + """ Update packet data based on EEP. Input data is a dictionary with keys corresponding to the EEP. """ self._bit_data, self._bit_status = self.eep.set_values(self._profile, self._bit_data, self._bit_status, data) - def build(self): - ''' Build Packet for sending to EnOcean controller ''' + def build(self) -> list: + """ Build Packet for sending to EnOcean controller """ data_length = len(self.data) + # noinspection PyListCreation ords = [0x55, (data_length >> 8) & 0xFF, data_length & 0xFF, len(self.optional), int(self.packet_type)] - ords.append(crc8.calc(ords[1:5])) + ords.append(calc(ords[1:5])) ords.extend(self.data) ords.extend(self.optional) - ords.append(crc8.calc(ords[6:])) + ords.append(calc(ords[6:])) return ords @@ -299,33 +307,34 @@ class RadioPacket(Packet): learn = True contains_eep = False - def __str__(self): + def __str__(self) -> str: packet_str = super(RadioPacket, self).__str__() - return '%s->%s (%d dBm): %s' % (self.sender_hex, self.destination_hex, self.dBm, packet_str) + return f'{self.sender_hex}->{self.destination_hex} ({self.dBm} dBm): {packet_str}' @staticmethod - def create(rorg, rorg_func, rorg_type, direction=None, command=None, - destination=None, sender=None, learn=False, **kwargs): + def create(rorg: RORG, rorg_func: int, rorg_type: int, direction=None, + command: Union[None, int] = None, destination: Union[None, list] = None, + sender: Union[None, list] = None, learn: bool = False, **kwargs) -> Packet: return Packet.create(PACKET.RADIO_ERP1, rorg, rorg_func, rorg_type, direction, command, destination, sender, learn, **kwargs) @property - def sender_int(self): - return enocean.utils.combine_hex(self.sender) + def sender_int(self) -> int: + return combine_hex(self.sender) @property - def sender_hex(self): - return enocean.utils.to_hex_string(self.sender) + def sender_hex(self) -> str: + return to_hex_string(self.sender) @property - def destination_int(self): - return enocean.utils.combine_hex(self.destination) + def destination_int(self) -> int: + return combine_hex(self.destination) @property - def destination_hex(self): - return enocean.utils.to_hex_string(self.destination) + def destination_hex(self) -> str: + return to_hex_string(self.destination) - def parse(self): + def parse(self) -> OrderedDict: self.destination = self.optional[1:5] self.dBm = -self.optional[5] self.sender = self.data[-5:-1] @@ -343,11 +352,13 @@ def parse(self): self.contains_eep = self._bit_data[DB0.BIT_7] if self.contains_eep: # Get rorg_func and rorg_type from an unidirectional learn packet - self.rorg_func = enocean.utils.from_bitarray(self._bit_data[DB3.BIT_7:DB3.BIT_1]) - self.rorg_type = enocean.utils.from_bitarray(self._bit_data[DB3.BIT_1:DB2.BIT_2]) - self.rorg_manufacturer = enocean.utils.from_bitarray(self._bit_data[DB2.BIT_2:DB0.BIT_7]) - self.logger.debug('learn received, EEP detected, RORG: 0x%02X, FUNC: 0x%02X, TYPE: 0x%02X, Manufacturer: 0x%02X' % (self.rorg, self.rorg_func, self.rorg_type, self.rorg_manufacturer)) # noqa: E501 - + self.rorg_func = from_bitarray(self._bit_data[DB3.BIT_7:DB3.BIT_1]) + self.rorg_type = from_bitarray(self._bit_data[DB3.BIT_1:DB2.BIT_2]) + self.rorg_manufacturer = from_bitarray(self._bit_data[DB2.BIT_2:DB0.BIT_7]) + self.logger.debug( + f'learn received, EEP detected, RORG: 0x{self.rorg:X}, FUNC: 0x{self.rorg_func:X}, ' + f'TYPE: 0x{self.rorg_type:X}, Manufacturer: 0x{self.rorg_manufacturer:X}' + ) return super(RadioPacket, self).parse() @@ -373,23 +384,24 @@ class UTETeachInPacket(RadioPacket): contains_eep = True @property - def bidirectional(self): + def bidirectional(self) -> bool: return not self.unidirectional @property - def teach_in(self): + def teach_in(self) -> bool: return self.request_type != self.DELETE @property - def delete(self): + def delete(self) -> bool: return self.request_type == self.DELETE - def parse(self): + def parse(self) -> OrderedDict: super(UTETeachInPacket, self).parse() self.unidirectional = not self._bit_data[DB6.BIT_7] self.response_expected = not self._bit_data[DB6.BIT_6] - self.request_type = enocean.utils.from_bitarray(self._bit_data[DB6.BIT_5:DB6.BIT_3]) - self.rorg_manufacturer = enocean.utils.from_bitarray(self._bit_data[DB3.BIT_2:DB2.BIT_7] + self._bit_data[DB4.BIT_7:DB3.BIT_7]) # noqa: E501 + self.request_type = from_bitarray(self._bit_data[DB6.BIT_5:DB6.BIT_3]) + self.rorg_manufacturer = from_bitarray( + self._bit_data[DB3.BIT_2:DB2.BIT_7] + self._bit_data[DB4.BIT_7:DB3.BIT_7]) # noqa: E501 self.channel = self.data[2] self.rorg_type = self.data[5] self.rorg_func = self.data[6] @@ -398,16 +410,21 @@ def parse(self): self.learn = True return self.parsed - def create_response_packet(self, sender_id, response=TEACHIN_ACCEPTED): + def create_response_packet(self, sender_id: list, response: Union[None, list] = None) -> RadioPacket: + if response is None: + response = self.TEACHIN_ACCEPTED + # Create data: # - Respond with same RORG (UTE Teach-in) # - Always use bidirectional communication, set response code, set command identifier. # - Databytes 5 to 0 are copied from the original message # - Set sender id and status - data = [self.rorg] + \ - [enocean.utils.from_bitarray([True, False] + response + [False, False, False, True])] + \ - self.data[2:8] + \ - sender_id + [0] + data = ( + [self.rorg] + + [from_bitarray([True, False] + response + [False, False, False, True])] + + self.data[2:8] + + sender_id + [0] + ) # Always use 0x03 to indicate sending, attach sender ID, dBm, and security level optional = [0x03] + self.sender + [0xFF, 0x00] @@ -419,7 +436,7 @@ class ResponsePacket(Packet): response = 0 response_data = [] - def parse(self): + def parse(self) -> OrderedDict: self.response = self.data[0] self.response_data = self.data[1:] return super(ResponsePacket, self).parse() @@ -429,7 +446,7 @@ class EventPacket(Packet): event = 0 event_data = [] - def parse(self): + def parse(self) -> OrderedDict: self.event = self.data[0] self.event_data = self.data[1:] return super(EventPacket, self).parse() diff --git a/enocean/protocol/version_info.py b/enocean/protocol/version_info.py new file mode 100644 index 0000000..af98c4b --- /dev/null +++ b/enocean/protocol/version_info.py @@ -0,0 +1,15 @@ +# -*- encoding: utf-8 -*- +from __future__ import print_function, unicode_literals, division, absolute_import + +class VersionIdentifier(object): + main = 0 + beta = 0 + alpha = 0 + build = 0 + +class VersionInfo(object): + app_version: VersionIdentifier = VersionIdentifier() + api_version: VersionIdentifier = VersionIdentifier() + chip_id: 0 + chip_version = 0 + app_description = '' \ No newline at end of file diff --git a/enocean/tests/test_utils.py b/enocean/tests/test_utils.py deleted file mode 100644 index 9112d7d..0000000 --- a/enocean/tests/test_utils.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -import enocean.utils - - -def test_get_bit(): - assert enocean.utils.get_bit(1, 0) == 1 - assert enocean.utils.get_bit(8, 3) == 1 - assert enocean.utils.get_bit(6, 2) == 1 - assert enocean.utils.get_bit(6, 1) == 1 - - -def test_to_hex_string(): - assert enocean.utils.to_hex_string(0) == '00' - assert enocean.utils.to_hex_string(15) == '0F' - assert enocean.utils.to_hex_string(16) == '10' - assert enocean.utils.to_hex_string(22) == '16' - - assert enocean.utils.to_hex_string([0, 15, 16, 22]) == '00:0F:10:16' - assert enocean.utils.to_hex_string([0x00, 0x0F, 0x10, 0x16]) == '00:0F:10:16' - - -def test_from_hex_string(): - assert enocean.utils.from_hex_string('00') == 0 - assert enocean.utils.from_hex_string('0F') == 15 - assert enocean.utils.from_hex_string('10') == 16 - assert enocean.utils.from_hex_string('16') == 22 - - assert enocean.utils.from_hex_string('00:0F:10:16') == [0, 15, 16, 22] - assert enocean.utils.from_hex_string('00:0F:10:16') == [0x00, 0x0F, 0x10, 0x16] diff --git a/enocean/utils.py b/enocean/utils.py index 528b5af..e4edfb1 100644 --- a/enocean/utils.py +++ b/enocean/utils.py @@ -1,40 +1,40 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import +from typing import Union -def get_bit(byte, bit): - ''' Get bit value from byte ''' +def get_bit(byte: int, bit: int) -> int: + """ Get bit value from byte """ return (byte >> bit) & 0x01 -def combine_hex(data): - ''' Combine list of integer values to one big integer ''' +def combine_hex(data: Union[list, bytearray]) -> int: + """ Combine list of integer values to one big integer """ output = 0x00 for i, value in enumerate(reversed(data)): output |= (value << i * 8) return output -def to_bitarray(data, width=8): - ''' Convert data (list of integers, bytearray or integer) to bitarray ''' +def to_bitarray(data: Union[list, bytearray], width=8) -> list[bool]: + """ Convert data (list of integers, bytearray or integer) to bitarray """ if isinstance(data, list) or isinstance(data, bytearray): data = combine_hex(data) return [True if digit == '1' else False for digit in bin(data)[2:].zfill(width)] -def from_bitarray(data): - ''' Convert bit array back to integer ''' +def from_bitarray(data: list[bool]) -> int: + """ Convert bit array back to integer """ return int(''.join(['1' if x else '0' for x in data]), 2) -def to_hex_string(data): - ''' Convert list of integers to a hex string, separated by ":" ''' +def to_hex_string(data: Union[list, int]) -> str: + """ Convert list of integers to a hex string, separated by ":" """ if isinstance(data, int): - return '%02X' % data - return ':'.join([('%02X' % o) for o in data]) + return f'{data:02X}' + return ':'.join([f'{o:02X}' for o in data]) -def from_hex_string(hex_string): +def from_hex_string(hex_string: str) -> Union[int, list]: reval = [int(x, 16) for x in hex_string.split(':')] if len(reval) == 1: return reval[0] diff --git a/examples/co_rd_version_example.py b/examples/co_rd_version_example.py index 7ff2d42..68014d5 100755 --- a/examples/co_rd_version_example.py +++ b/examples/co_rd_version_example.py @@ -10,18 +10,15 @@ in the ESP3 document. """ -from enocean.consolelogger import init_logging -from enocean.communicators.serialcommunicator import SerialCommunicator -from enocean.protocol.packet import Packet -from enocean.protocol.constants import PACKET -from enocean import utils -import traceback +import queue import sys +import traceback -try: - import queue -except ImportError: - import Queue as queue +from enocean import utils +from enocean.communicators.serialcommunicator import SerialCommunicator +from enocean.consolelogger import init_logging +from enocean.protocol.constants import PACKET +from enocean.protocol.packet import Packet init_logging() """ diff --git a/examples/enocean_example.py b/examples/enocean_example.py index be9eb0c..f8729ed 100755 --- a/examples/enocean_example.py +++ b/examples/enocean_example.py @@ -1,17 +1,15 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from enocean.consolelogger import init_logging -import enocean.utils -from enocean.communicators.serialcommunicator import SerialCommunicator -from enocean.protocol.packet import RadioPacket -from enocean.protocol.constants import PACKET, RORG + +import queue import sys import traceback -try: - import queue -except ImportError: - import Queue as queue +import enocean.utils +from enocean.communicators.serialcommunicator import SerialCommunicator +from enocean.consolelogger import init_logging +from enocean.protocol.constants import PACKET, RORG +from enocean.protocol.packet import RadioPacket def assemble_radio_packet(transmitter_id): @@ -25,6 +23,7 @@ def assemble_radio_packet(transmitter_id): init_logging() communicator = SerialCommunicator() communicator.start() +print('The Chip ID of your module is %s.' % enocean.utils.to_hex_string(communicator.chip_id)) print('The Base ID of your module is %s.' % enocean.utils.to_hex_string(communicator.base_id)) if communicator.base_id is not None: diff --git a/examples/example_D2-05-00.py b/examples/example_D2-05-00.py index 20f655d..5b87593 100644 --- a/examples/example_D2-05-00.py +++ b/examples/example_D2-05-00.py @@ -7,18 +7,14 @@ Waits for UTE Teach-ins, sends the response automatically and prints the ID of new device. ''' +import queue import sys -import time import traceback + import enocean.utils from enocean.communicators import SerialCommunicator -from enocean.protocol.packet import RadioPacket, UTETeachInPacket from enocean.protocol.constants import RORG - -try: - import queue -except ImportError: - import Queue as queue +from enocean.protocol.packet import RadioPacket, UTETeachInPacket def set_position(destination, percentage): diff --git a/examples/example_DO21-11B-E.py b/examples/example_DO21-11B-E.py index 52464ec..0015532 100644 --- a/examples/example_DO21-11B-E.py +++ b/examples/example_DO21-11B-E.py @@ -7,18 +7,15 @@ Waits for UTE Teach-ins, sends the response automatically and prints the ID of new device. ''' +import queue import sys import time import traceback + import enocean.utils from enocean.communicators import SerialCommunicator -from enocean.protocol.packet import RadioPacket, UTETeachInPacket from enocean.protocol.constants import RORG - -try: - import queue -except ImportError: - import Queue as queue +from enocean.protocol.packet import RadioPacket, UTETeachInPacket def send_command(destination, output_value): diff --git a/examples/serial_to_tcp.py b/examples/serial_to_tcp.py index e68d19c..899fda6 100755 --- a/examples/serial_to_tcp.py +++ b/examples/serial_to_tcp.py @@ -1,15 +1,13 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from enocean.consolelogger import init_logging -from enocean.communicators.serialcommunicator import SerialCommunicator -from enocean.communicators.utils import send_to_tcp_socket + +import queue import sys import traceback -try: - import queue -except ImportError: - import Queue as queue +from enocean.communicators.serialcommunicator import SerialCommunicator +from enocean.communicators.utils import send_to_tcp_socket +from enocean.consolelogger import init_logging init_logging() communicator = SerialCommunicator() diff --git a/examples/tcp_server.py b/examples/tcp_server.py index d7fa94d..5d6b2e4 100755 --- a/examples/tcp_server.py +++ b/examples/tcp_server.py @@ -1,15 +1,13 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from enocean.consolelogger import init_logging -from enocean.communicators.tcpcommunicator import TCPCommunicator -from enocean.protocol.constants import PACKET, RORG + +import queue import sys import traceback -try: - import queue -except ImportError: - import Queue as queue +from enocean.communicators.tcpcommunicator import TCPCommunicator +from enocean.consolelogger import init_logging +from enocean.protocol.constants import PACKET, RORG init_logging() communicator = TCPCommunicator() diff --git a/generate_supported_profiles.py b/generate_supported_profiles.py index 30ee887..962802b 100755 --- a/generate_supported_profiles.py +++ b/generate_supported_profiles.py @@ -1,38 +1,60 @@ #!/usr/bin/env python # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import + import codecs + from enocean.protocol.eep import EEP ROW_FORMAT = '|{:8s}|{:50s}|{:8s}|{:70s}|\n' - +BASE_URL = "https://github.com/topic2k/enocean4ha/blob/topix/SUPPORTED_PROFILES.md#" eep = EEP() with codecs.open('SUPPORTED_PROFILES.md', 'w', 'utf-8') as f_handle: - f_handle.write('# Supported profiles\n') - f_handle.write('All profiles (should) correspond to the official [EEP](http://www.enocean-alliance.org/eep/) by EnOcean.\n\n') + write = f_handle.write + write("# Supported profiles\n") + write( + "All profiles (should) correspond to the official" + " [EEP](https://www.enocean-alliance.org/eep/) by EnOcean.\n\n" + ) + # table of contents for telegram in eep.soup.find_all('telegram'): - f_handle.write('### %s (%s)\n' % (telegram['description'], telegram['rorg'])) + write("
") + write(f" {telegram['description']} ({telegram['rorg']}) \n\n") for func in telegram.find_all('profiles'): - # f_handle.write('##### FUNC %s - %s\n' % (func['func'], func['description'])) for profile in func.find_all('profile'): - f_handle.write('##### RORG %s - FUNC %s - TYPE %s - %s\n\n' % (telegram['rorg'], func['func'], profile['type'], profile['description'])) + write( + f"- [FUNC {func['func']} - TYPE {profile['type']} - {profile['description']}]" + f"({BASE_URL}rorg-{telegram['rorg']}---func-{func['func']}---type-{profile['type']}-" + f"--{profile['description'].lower().replace(' ', '-')})\n" + ) + + write("\n
\n\n") + write('\n\n---\n\n') + + # contents + for telegram in eep.soup.find_all('telegram'): + write(f"### {telegram['description']} ({telegram['rorg']})\n\n") + for func in telegram.find_all('profiles'): + for profile in func.find_all('profile'): + write( + f"##### RORG {telegram['rorg']} - FUNC {func['func']}" + f" - TYPE {profile['type']} - {profile['description']}\n\n" + ) for data in profile.find_all('data'): header = [] if data.get('direction'): - header.append('direction: %s' % (data.get('direction'))) + header.append(f"direction: {data.get('direction')}") if data.get('command'): - header.append('command: %s' % (data.get('command'))) - + header.append(f"command: {data.get('command')}") if header: - f_handle.write('###### %s\n' % ' '.join(header)) + write(f"###### {' '.join(header)}\n") - f_handle.write(ROW_FORMAT.format('shortcut', 'description', 'type', 'values')) - f_handle.write(ROW_FORMAT.format('--------', '--------------------------------------------------', '--------', '----')) + write(ROW_FORMAT.format('shortcut', 'description', 'type', 'values')) + write(ROW_FORMAT.format('-'*8, '-'*50, '-'*8, '-'*4)) for child in data.children: if child.name is None: continue @@ -43,27 +65,35 @@ continue if item.name == 'rangeitem': - values.append('%s-%s - %s' % (item['start'], item['end'], item['description'])) + values.append(f"{item['start']}-{item['end']} - {item['description']}") elif item.name == 'item': - values.append('%s - %s' % (item['value'], item['description'])) + values.append(f"{item['value']} - {item['description']}") elif item.name == 'range': parent = item.parent range_min = float(item.find('min').text) range_max = float(item.find('max').text) scale = parent.find('scale') - scale_min = float(scale.find('min').text) - scale_max = float(scale.find('max').text) + if scale: + scale_min = float(scale.find('min').text) + scale_max = float(scale.find('max').text) + else: + scale_min = '' + scale_max = '' + unit = item.get('unit') or parent.get('unit') + + values.append(f"{range_min}-{range_max} ↔ {scale_min}-{scale_max} {unit}") - values.append('%s-%s ↔ %s-%s %s' % (range_min, range_max, scale_min, scale_max, parent['unit'])) if not values: - f_handle.write(ROW_FORMAT.format(child['shortcut'], child['description'], child.name, '')) + write(ROW_FORMAT.format(child['shortcut'], child['description'], child.name, '')) continue - f_handle.write(ROW_FORMAT.format(child['shortcut'], child['description'], child.name, values[0])) + write(ROW_FORMAT.format( + child.get('shortcut', ''), + child.get('description', ''), + child.name, values[0] + )) for i in range(1, len(values)): - f_handle.write(ROW_FORMAT.format('', '', '', values[i])) - f_handle.write('\n') - f_handle.write('\n') - - f_handle.write('\n') + write(ROW_FORMAT.format('', '', '', values[i])) + write('\n') + write('\n') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0dc143e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +[build-system] +requires = [ "setuptools>=74.0", "build>=1.2"] +build-backend = "setuptools.build_meta" + +[project] +name = "enocean4ha" +dynamic = [ "version", "readme"] +authors = [ + { name="Kimmo Huoman", email="kipenroskaposti@gmail.com" }, +] +maintainers = [ + { name="Torsten Pieper", email="topic2k@atlogger.de" }, +] +description = "EnOcean serial protocol implementation" +keywords = [ "EnOcean", "HomeAssistant", ] +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Topic :: Home Automation", +] +dependencies = [ + 'pyserial>=3.5', + 'beautifulsoup4>=4.12', + 'lxml>=5.3', +] + +[project.urls] +Homepage = "https://github.com/topic2k/enocean4ha" +Issues = "https://github.com/topic2k/enocean4ha/issues" + +[project.optional-dependencies] +tests = [ "nose2>=0.15.1", ] + +[tool.setuptools.dynamic] +version = { attr = "enocean.__version__" } +readme = { file = [ "README.md", "SUPPORTED_PROFILES.md" ], content-type = "text/markdown" } + +[tool.setuptools.packages.find] +where = [ "." ] +include = [ "enocean*" ] + +[tool.setuptools.package-data] +"*" = [ "*.xml", ] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index fb7ca9a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -enum-compat>=0.0.2 -pyserial>=3.0 -beautifulsoup4>=4.3.2 diff --git a/run_tests_with_timing.sh b/run_tests_with_timing.sh deleted file mode 100755 index 4cd310e..0000000 --- a/run_tests_with_timing.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -WITH_TIMINGS=1 nosetests -s -q diff --git a/setup.py b/setup.py deleted file mode 100644 index 3fea09c..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -try: - from setuptools import setup -except ImportError: - from distutils.core import setup - -setup( - name='enocean', - version='0.60.1', - description='EnOcean serial protocol implementation', - author='Kimmo Huoman', - author_email='kipenroskaposti@gmail.com', - url='https://github.com/kipe/enocean', - packages=[ - 'enocean', - 'enocean.protocol', - 'enocean.communicators', - ], - scripts=[ - 'examples/enocean_example.py', - ], - package_data={ - '': ['EEP.xml'] - }, - install_requires=[ - 'enum-compat>=0.0.2', - 'pyserial>=3.0', - 'beautifulsoup4>=4.3.2', - ]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/enocean/communicators/tests/test_communicator.py b/tests/test_communicator.py similarity index 94% rename from enocean/communicators/tests/test_communicator.py rename to tests/test_communicator.py index c4d1d54..05fe2a5 100644 --- a/enocean/communicators/tests/test_communicator.py +++ b/tests/test_communicator.py @@ -1,10 +1,9 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import +from decorators import timing from enocean.communicators.communicator import Communicator -from enocean.protocol.packet import Packet, RadioPacket from enocean.protocol.constants import PACKET -from enocean.decorators import timing +from enocean.protocol.packet import Packet, RadioPacket @timing(1000) diff --git a/enocean/protocol/tests/test_eep.py b/tests/test_protocol_eep.py similarity index 91% rename from enocean/protocol/tests/test_eep.py rename to tests/test_protocol_eep.py index acdecdf..3f08b1d 100644 --- a/enocean/protocol/tests/test_eep.py +++ b/tests/test_protocol_eep.py @@ -1,15 +1,14 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.packet import Packet -from enocean.protocol.eep import EEP +from decorators import timing from enocean.protocol.constants import RORG -from enocean.decorators import timing +from enocean.protocol.eep import EEP +from enocean.protocol.packet import Packet @timing(1000) def test_temperature(): - ''' Tests RADIO message for EEP -profile 0xA5 0x02 0x05 ''' + """ Tests RADIO message for EEP -profile 0xA5 0x02 0x05 """ status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x0A, 0x07, 0x01, @@ -35,7 +34,7 @@ def test_temperature(): @timing(1000) def test_magnetic_switch(): - ''' Tests RADIO message for EEP -profile 0xD5 0x00 0x01 ''' + """ Tests RADIO message for EEP -profile 0xD5 0x00 0x01 """ status, buf, packet = Packet.parse_msg(bytearray([ 0x55, 0x00, 0x07, 0x07, 0x01, @@ -245,16 +244,16 @@ def test_fails(): eep = EEP() # Mock initialization failure eep.init_ok = False - assert eep.find_profile(packet._bit_data, 0xD5, 0x00, 0x01) is None + assert eep.find_profile(0xD5, 0x00, 0x01) is None # TODO: Needs better test. A much better. assert eep.set_values(profile=None, data=[True], status=[False, False], properties={'CV': False}) eep.init_ok = True - profile = eep.find_profile(packet._bit_data, 0xD5, 0x00, 0x01) + profile = eep.find_profile(0xD5, 0x00, 0x01) assert eep.set_values(profile, packet._bit_data, packet.status, {'ASD': 1}) - assert eep.find_profile(packet._bit_data, 0xFF, 0x00, 0x01) is None - assert eep.find_profile(packet._bit_data, 0xD5, 0xFF, 0x01) is None - assert eep.find_profile(packet._bit_data, 0xD5, 0x00, 0xFF) is None + assert eep.find_profile(0xFF, 0x00, 0x01) is None + assert eep.find_profile(0xD5, 0xFF, 0x01) is None + assert eep.find_profile(0xD5, 0x00, 0xFF) is None status, buf, packet = Packet.parse_msg(bytearray([ 0x55, @@ -264,5 +263,5 @@ def test_fails(): 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0xBF ])) - assert eep.find_profile(packet._bit_data, 0xD2, 0x01, 0x01) is not None - assert eep.find_profile(packet._bit_data, 0xD2, 0x01, 0x01, command=-1) is None + assert eep.find_profile(0xD2, 0x01, 0x01) is not None + assert eep.find_profile(0xD2, 0x01, 0x01, command=-1) is None diff --git a/enocean/protocol/tests/test_packet.py b/tests/test_protocol_packet.py similarity index 98% rename from enocean/protocol/tests/test_packet.py rename to tests/test_protocol_packet.py index 96fa4c3..43c5a1e 100644 --- a/enocean/protocol/tests/test_packet.py +++ b/tests/test_protocol_packet.py @@ -1,9 +1,8 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -from enocean.protocol.packet import Packet, EventPacket +from decorators import timing from enocean.protocol.constants import PACKET, PARSE_RESULT, EVENT_CODE -from enocean.decorators import timing +from enocean.protocol.packet import Packet, EventPacket @timing(1000) diff --git a/enocean/protocol/tests/test_packet_creation.py b/tests/test_protocol_packet_creation.py similarity index 95% rename from enocean/protocol/tests/test_packet_creation.py rename to tests/test_protocol_packet_creation.py index 03a6a8b..a097bd1 100644 --- a/enocean/protocol/tests/test_packet_creation.py +++ b/tests/test_protocol_packet_creation.py @@ -1,10 +1,10 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import -from nose.tools import raises -from enocean.protocol.packet import Packet, RadioPacket +from unittest.case import TestCase + +from decorators import timing from enocean.protocol.constants import PACKET, RORG -from enocean.decorators import timing +from enocean.protocol.packet import Packet, RadioPacket @timing(1000) @@ -242,15 +242,17 @@ def test_switch(): @timing(1000) -@raises(ValueError) -def test_illegal_eep_enum1(): - RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB='inexisting') +class TestIllegalEEPEnum1(TestCase): + def test_illegal_eep_enum1(self): + with self.assertRaises(ValueError): + RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB='inexisting') -@raises(ValueError) @timing(1000) -def test_illegal_eep_enum2(): - RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB=2) +class TestIllegalEEPEnum2(TestCase): + def test_illegal_eep_enum2(self): + with self.assertRaises(ValueError): + RadioPacket.create(rorg=RORG.RPS, rorg_func=0x02, rorg_type=0x02, sender=[0x00, 0x29, 0x89, 0x79], EB=2) # Corresponds to the tests done in test_eep diff --git a/enocean/protocol/tests/test_teachin.py b/tests/test_protocol_teachin.py similarity index 92% rename from enocean/protocol/tests/test_teachin.py rename to tests/test_protocol_teachin.py index d908a94..b197645 100644 --- a/enocean/protocol/tests/test_teachin.py +++ b/tests/test_protocol_teachin.py @@ -1,10 +1,9 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import +from decorators import timing from enocean.communicators import Communicator -from enocean.protocol.packet import Packet from enocean.protocol.constants import RORG, DB6 -from enocean.decorators import timing +from enocean.protocol.packet import Packet @timing(rounds=100, limit=750) diff --git a/enocean/protocol/tests/test_temperature_sensors.py b/tests/test_protocol_temperature_sensors.py similarity index 80% rename from enocean/protocol/tests/test_temperature_sensors.py rename to tests/test_protocol_temperature_sensors.py index b5bba06..70991d9 100644 --- a/enocean/protocol/tests/test_temperature_sensors.py +++ b/tests/test_protocol_temperature_sensors.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- -from __future__ import print_function, unicode_literals, division, absolute_import from enocean.protocol.eep import EEP + eep = EEP() # profiles = eep. @@ -12,7 +12,7 @@ def test_first_range(): for i in range(len(values)): minimum = float(i * 10 + offset) maximum = minimum + 40 - profile = eep.find_profile([], 0xA5, 0x02, values[i]) + profile = eep.find_profile(0xA5, 0x02, values[i]) assert minimum == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('min').text) assert maximum == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('max').text) @@ -24,17 +24,17 @@ def test_second_range(): for i in range(len(values)): minimum = float(i * 10 + offset) maximum = minimum + 80 - profile = eep.find_profile([], 0xA5, 0x02, values[i]) + profile = eep.find_profile(0xA5, 0x02, values[i]) assert minimum == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('min').text) assert maximum == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('max').text) def test_rest(): - profile = eep.find_profile([], 0xA5, 0x02, 0x20) + profile = eep.find_profile(0xA5, 0x02, 0x20) assert -10 == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('min').text) assert +41.2 == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('max').text) - profile = eep.find_profile([], 0xA5, 0x02, 0x30) + profile = eep.find_profile(0xA5, 0x02, 0x30) assert -40 == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('min').text) assert +62.3 == float(profile.find('value', {'shortcut': 'TMP'}).find('scale').find('max').text) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..f49f531 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- + +from enocean.utils import from_hex_string, to_hex_string, get_bit + + +def test_get_bit(): + assert get_bit(1, 0) == 1 + assert get_bit(8, 3) == 1 + assert get_bit(6, 2) == 1 + assert get_bit(6, 1) == 1 + + +def test_to_hex_string(): + assert to_hex_string(0) == '00' + assert to_hex_string(15) == '0F' + assert to_hex_string(16) == '10' + assert to_hex_string(22) == '16' + + assert to_hex_string([0, 15, 16, 22]) == '00:0F:10:16' + assert to_hex_string([0x00, 0x0F, 0x10, 0x16]) == '00:0F:10:16' + + +def test_from_hex_string(): + assert from_hex_string('00') == 0 + assert from_hex_string('0F') == 15 + assert from_hex_string('10') == 16 + assert from_hex_string('16') == 22 + + assert from_hex_string('00:0F:10:16') == [0, 15, 16, 22] + assert from_hex_string('00:0F:10:16') == [0x00, 0x0F, 0x10, 0x16]