Skip to content

Commit

Permalink
feat: implement utility functions for calculating system/unit code (#18)
Browse files Browse the repository at this point in the history
Merge pull request #18 from d-Rickyy-b/dev
  • Loading branch information
d-Rickyy-b authored Jan 5, 2021
2 parents 922f150 + 9486ebe commit ffcf34e
Show file tree
Hide file tree
Showing 47 changed files with 1,205 additions and 335 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/python-lint-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 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: Run tests and lint

on:
push:
branches: [ master, dev ]
pull_request:
branches: [ master ]

jobs:
test-and-lint:

runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.5', '3.6', '3.7', '3.8']

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest coveralls
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
coverage run --omit='*/virtualenv/*,*/site-packages/*' -m pytest
- name: Publish coverage
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
run: |
coveralls
12 changes: 11 additions & 1 deletion .github/workflows/python-pypi-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
pip install setuptools wheel twine flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
Expand Down
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.2.0] - 2021-01-05
### Added

- Implement CMR300, CMR500 and ITR300 devices ([1a9082a](https://github.com/d-Rickyy-b/pyBrematic/commit/1a9082a67e5c025c2dd7e0b8d2fc47108df204c6), [740ecf1](https://github.com/d-Rickyy-b/pyBrematic/commit/82ff04c5f69b778ea27f8f105f54d9fae43b03ce), [82ff04c](https://github.com/d-Rickyy-b/pyBrematic/commit/82ff04c5f69b778ea27f8f105f54d9fae43b03ce))
- Add methods to generate system and unit code for intertechno devices ([380ba42](https://github.com/d-Rickyy-b/pyBrematic/commit/380ba42d3d811c1aee74b93c83e74c88402b27c8))
- Implement ConnAir gateway ([40ac9e9](https://github.com/d-Rickyy-b/pyBrematic/commit/40ac9e90e998c55a74a4f7ad2688bda97ba92d66))

## [1.1.0] - 2020-12-27

### Added

- Implement support for ITR3500 ([3fc05b1](https://github.com/d-Rickyy-b/pyBrematic/commit/3fc05b1c9683c72dd1291dbb353f30bed3e040dd))
- Support for local storage needed for autopair ([ca29013](https://github.com/d-Rickyy-b/pyBrematic/commit/ca29013edd693855628231d05f0ef9c6aac8f4c8))
- Implement Intertechno autopair devices such as ITL500 ([0fd8123](https://github.com/d-Rickyy-b/pyBrematic/commit/0fd812356d573c7cd332c75071c3f53e5e8f59e1))
- Add Action enum to easily choose the action that should be executed ([c983641](https://github.com/d-Rickyy-b/pyBrematic/commit/c98364196b69230c6a8dd48c9d805ebfdf66f97e))

### CI

- Implement GitHub Actions workflow for building and deploying packages on each release (PS: might still be a bit buggy at the moment)

## [1.0.0] - 2018-07-07

### Added

- Implemented Intertechno gateway ([cb13362](https://github.com/d-Rickyy-b/pyBrematic/commit/cb1336296e82e0dbb34c0abb5f9fe4a7f2bf3c4f))
- Implement CMR1000 and PAR1500 ([40427d9](https://github.com/d-Rickyy-b/pyBrematic/commit/40427d9d70abb94832a62715ab885103f1a8b20a))
- Implement AB440SA ([7d61f49](https://github.com/d-Rickyy-b/pyBrematic/commit/7d61f49214938118486d3c20d52b489d2585e148))

### Fixed

- Fix get_signal function for RCR1000N device ([90001d7](https://github.com/d-Rickyy-b/pyBrematic/commit/90001d7bd62c15cca066343b8b649ea1f465daa7))

## [0.0.1] - 2018-05-10

Initial release with a single gateway and outlet.

[unreleased]: https://github.com/d-Rickyy-b/pyBrematic/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/d-Rickyy-b/pyBrematic/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/d-Rickyy-b/pyBrematic/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/d-Rickyy-b/pyBrematic/compare/v0.0.1...v1.0.0
[0.0.1]: https://github.com/d-Rickyy-b/pyBrematic/tree/v0.0.1
55 changes: 46 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,60 @@
# pyBrematic

[![Build Status](https://travis-ci.com/d-Rickyy-b/pyBrematic.svg?branch=master)](https://travis-ci.com/d-Rickyy-b/pyBrematic)
[![PyPI version](https://badge.fury.io/py/pyBrematic.svg)](https://pypi.org/project/pyBrematic)
[![Coverage Status](https://coveralls.io/repos/github/d-Rickyy-b/pyBrematic/badge.svg?branch=master)](https://coveralls.io/github/d-Rickyy-b/pyBrematic?branch=master)

# pyBrematic
This project offers a home for controlling your remote power outlets (and potentially other stuff) with the Python programming language. With the help of the community we might get other devices working aswell.
The topic "smart home" or "home automation" in particular has become increasingly popular throughout the last few years. While many manufacturers are relying
on cloud infrastructure, there are some that produce local-only devices, using the 433 MHz ISM band.

The python module "pyBrematic" enables you to control and automate your 433 MHz remote power outlets (and other switches/dimmers) with the Python programming
language. All you need for this is a supported 433 MHz network gateway, such as the `Intertechno ITGW-433`, the `Brematic GWY 433` (or `CONNAIR 433`, which is
basically the same as the Brematic one).

With the help of the community we might get other devices working as well.

### Installation
You can simply install the module via [pip](https://de.wikipedia.org/wiki/Pip_(Python)) like this:

This module is available on pypi and hence can be downloaded via pip like this:

`pip install pyBrematic`

If you have multiple versions of Python installed, make sure to use the Python 3 package manager:
And if you are having issues with installing the package, try to use the `--user` switch,
to [install it to your home directory](https://stackoverflow.com/questions/42988977/what-is-the-purpose-pip-install-user).
PyBrematic has no external dependencies. Only Python versions >= 3.5 are supported.

### Example usage

`pip3 install pyBrematic`
To check out how to use the module, go to the [example file](https://github.com/d-Rickyy-b/pyBrematic/blob/master/pyBrematic/example/example.py). There you'll
find an example configuration of how to use the module.

And if you are having issues with installing the package, try to use the `--user` switch, to [install it to your home directory](https://stackoverflow.com/questions/42988977/what-is-the-purpose-pip-install-user).
```python
from pyBrematic import Action
from pyBrematic.devices.brennenstuhl import RCS1000N
from pyBrematic.devices.intertechno import ITR3500, calc_system_and_unit_code
from pyBrematic.gateways import BrennenstuhlGateway

### Example usage
To check out how to use the module, go to the [example file](https://github.com/d-Rickyy-b/pyBrematic/blob/master/pyBrematic/example/example.py) where I wrote a little example script, to show how to use the module.
# Set your system and unit codes
system_code = "11110" # DIPs 1-4 are in the 'up' position, 5 is 'down'
unit_code = "10000" # DIP A is in the 'up' position, B-E are 'down'

# Create a new device with the specified codes
desk_lamp = RCS1000N(system_code, unit_code)

# For Intertechno devices you can also use the following methods to get the code
# via master/slave (letter/number) notation. Allowed values are "A-P" and "1-16".
led_system_code, led_unit_code = calc_system_and_unit_code("A3")
led_strip = ITR3500(led_system_code, led_unit_code)

# Create a new gateway located at the specified IP
gw = BrennenstuhlGateway("192.168.178.9")

# Send the request and pass it the device and the action (on/off)
gw.send_request(desk_lamp, Action.ON)
gw.send_request(desk_lamp, Action.OFF)
```

### Important notice
Since all data packets are sent to the gateways via [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol), it cannot be guaranteed, that all requests will be transmitted to the gateway. For critical purposes you cannot rely on sending the signal once.

Since all data packets are sent to the gateways via [UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol), it cannot be guaranteed, that all requests
will be received by the gateway. For critical purposes you cannot rely on sending the signal once.
5 changes: 2 additions & 3 deletions pyBrematic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .devices.device import Device
from .gateways.brennenstuhl_gateway import BrennenstuhlGateway
from .gateways.gateway import Gateway
from .action import Action

__all__ = ('Device', 'Gateway', 'BrennenstuhlGateway')
__all__ = ["Gateway", "Action"]
File renamed without changes.
6 changes: 4 additions & 2 deletions pyBrematic/devices/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-

from .action import Action
from .autopairdevice import AutoPairDevice
from .device import Device

__all__ = ['Device', 'AutoPairDevice', 'Action']
# Backwards compatibility
from pyBrematic.action import Action

__all__ = ["Device", "AutoPairDevice", "Action"]
14 changes: 12 additions & 2 deletions pyBrematic/devices/autopairdevice.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Device class representing a remote-controllable receiver"""
from pyBrematic.utils import Storage
from .action import Action
from pyBrematic.action import Action


class AutoPairDevice(object):
Expand All @@ -22,6 +22,16 @@ def __init__(self, device_id, seed=None):
storage = Storage()
self.seed = seed or storage.get_seed(device_id)

def get_signal(self, gateway, action):
def get_signal(self, action):
"""Returns a signal which triggers a device to execute the intended action"""
raise NotImplementedError("Subclasses must implement this method!")

@staticmethod
def join_list(lst, delimeter=","):
"""Join a list together with a certain delimeter
:param lst: The list to join
:param delimeter: The delimeter to use to join the list elements
:return:
"""
return delimeter.join(lst)
66 changes: 22 additions & 44 deletions pyBrematic/devices/brennenstuhl/RCS1000N.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-

from pyBrematic.devices import Device, Action
from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway
from pyBrematic.action import Action
from pyBrematic.devices import Device


# SYSTEM-CODE | unit code
Expand All @@ -17,71 +17,49 @@ class RCS1000N(Device):
# TXP:0,0,10,5600,350,25,<bit_low>,<enc_system_code>,<enc_unit_code>,<bit_high>,1,1,16;
# Check http://luckow.org/archive/brennenstuhl.html (german) for more info about this.
# Parameters for the requests. Only change if you have good reasons for it
sRepeat = 10
sPauseBS = 5600
sPauseIT = 11200
sTune = 350
sBaudBS = 25
sBaudIT = 26
sSpeedBS = 16
sSpeedIT = 32
repeat = 10
tune = 350
txversion = 1

headBSGW = "TXP:0,0,{},{},{},{},".format(sRepeat, sPauseBS, sTune, sBaudBS)
tailBSGW = "{},{};".format(txversion, sSpeedBS)
pause_BS = 5600
pause_IT = 11200

headITGW = "0,0,{},{},{},{},0,".format(sRepeat, sPauseIT, sTune, sBaudIT)
tailITGW = "{},{},0".format(txversion, sSpeedIT)
baud_BS = 25
baud_IT = 26

speed_BS = 16
speed_IT = 32

# Values of high and low bits (binary -> encoded)
# bit_low: 0 -> 1 | bit_high: 1 -> 3
lo = "1,"
hi = "3,"
lo = "1"
hi = "3"

seq_low = lo + hi + lo + hi
seq_high = lo + hi + hi + lo
seq_low = [lo, hi, lo, hi]
seq_high = [lo, hi, hi, lo]

# These sequences are the commands for switching a device on or off
# On: 01011 -> encoded: 13133
# Off: 10010 -> encoded: 31131
on = seq_low + seq_high
off = seq_high + seq_low
supported_actions = {Action.ON: on, Action.OFF: off}

def __init__(self, system_code, unit_code):
super().__init__(system_code, unit_code)

# Method for encoding the system_code or unit_code from binary to a gateway-readable format
@staticmethod
def encode(code, seq_low, seq_high):
encoded_msg = ""
for bit in code:
if bit == "0":
encoded_msg += seq_high
elif bit == "1":
encoded_msg += seq_low
else:
raise ValueError("Invalid value in system_code or unit_code!")
return encoded_msg

def get_signal(self, gateway, action):
def get_signal(self, action):
"""Returns a signal which triggers a device to execute the intended action"""
# Encoding the system_code and unit_code
system_msg = self.encode(self.system_code, self.seq_low, self.seq_high)
unit_msg = self.encode(self.unit_code, self.seq_low, self.seq_high)

if isinstance(gateway, BrennenstuhlGateway):
head = self.headBSGW
tail = self.tailBSGW
elif isinstance(gateway, IntertechnoGateway):
head = self.headITGW
tail = self.tailITGW
action_signal = self.supported_actions.get(action)

# Build the payload of the UDP package depending on the action.
if action == Action.ON:
data = head + system_msg + unit_msg + self.on + tail
elif action == Action.OFF:
data = head + system_msg + unit_msg + self.off + tail
else:
if not action_signal:
raise ValueError("Value of 'action' isn't valid!")

return data
data = system_msg + unit_msg + action_signal

return self.join_list(data)
8 changes: 8 additions & 0 deletions pyBrematic/devices/brennenstuhl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Supported Brennenstuhl devices
|Model|Type|IC|Tested|
|---|---|---|---|
|RCR1000N|Plug switch|HX2262 / HX2272|Yes|
|RCS1000N|Plug switch|HX2262 / HX2272|Yes|

# Useful references
- https://github.com/nicolashimmelmann/ESP_GWY433
2 changes: 1 addition & 1 deletion pyBrematic/devices/brennenstuhl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
from .RCR1000N import RCR1000N
from .RCS1000N import RCS1000N

__all__ = ['RCR1000N', 'RCS1000N']
__all__ = ["RCR1000N", "RCS1000N"]
Loading

0 comments on commit ffcf34e

Please sign in to comment.