From 1a9082a67e5c025c2dd7e0b8d2fc47108df204c6 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 13:27:21 +0100 Subject: [PATCH 01/45] feat: add CMR300 class The CMR300 works the same as the CMR1000 --- pyBrematic/devices/intertechno/CMR300.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 pyBrematic/devices/intertechno/CMR300.py diff --git a/pyBrematic/devices/intertechno/CMR300.py b/pyBrematic/devices/intertechno/CMR300.py new file mode 100644 index 0000000..b235919 --- /dev/null +++ b/pyBrematic/devices/intertechno/CMR300.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Reference: https://github.com/Power-Switch/PowerSwitch_Android/blob/master/Smartphone/src/main/java/eu/power_switch/obj/receiver/device/intertechno/CMR300.java +# Since this is a dimmer, I am not sure if this code also supports dimming or only switchig. + +from .CMR1000 import CMR1000 + + +class CMR300(CMR1000): + """Device class for the Intertechno CMR-300 dimmer""" + + def __init__(self, system_code, unit_code): + super().__init__(system_code, unit_code) From 740ecf1f48b90aaaf9e98425b4c68bdfd5c88a24 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 13:27:40 +0100 Subject: [PATCH 02/45] feat: add CMR500 class The CMR500 works the same as the CMR1000 --- pyBrematic/devices/intertechno/CMR500.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 pyBrematic/devices/intertechno/CMR500.py diff --git a/pyBrematic/devices/intertechno/CMR500.py b/pyBrematic/devices/intertechno/CMR500.py new file mode 100644 index 0000000..3f562bb --- /dev/null +++ b/pyBrematic/devices/intertechno/CMR500.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Reference: https://github.com/Power-Switch/PowerSwitch_Android/blob/master/Smartphone/src/main/java/eu/power_switch/obj/receiver/device/intertechno/CMR500.java +# Since this is a dimmer, I am not sure if this code also supports dimming or only switchig. + +from .CMR1000 import CMR1000 + + +class CMR500(CMR1000): + """Device class for the Intertechno CMR-500 switch""" + + def __init__(self, system_code, unit_code): + super().__init__(system_code, unit_code) From 82ff04c5f69b778ea27f8f105f54d9fae43b03ce Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 14:06:55 +0100 Subject: [PATCH 03/45] feat: add ITR300 class The ITR300 works the same as the CMR1000 --- pyBrematic/devices/intertechno/ITR300.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pyBrematic/devices/intertechno/ITR300.py diff --git a/pyBrematic/devices/intertechno/ITR300.py b/pyBrematic/devices/intertechno/ITR300.py new file mode 100644 index 0000000..a840e7c --- /dev/null +++ b/pyBrematic/devices/intertechno/ITR300.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from pyBrematic.devices.intertechno import CMR1000 + + +class ITR300(CMR1000): + """Device class for the Intertechno ITR-300 remote outlet""" + + def __init__(self, system_code, unit_code): + super().__init__(system_code, unit_code) From 975954834924b5e936a0c38cf3ade6407999beb7 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 14:16:22 +0100 Subject: [PATCH 04/45] test: implement two new tests for RCR1000N --- .../brennenstuhl/tests/brennenstuhl_test.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py index 27b413e..8f968fe 100644 --- a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py +++ b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py @@ -32,6 +32,33 @@ def test_RCR1000N(self): self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + def test_RCR1000N_10000_00000(self): + """Test to check the functionality of the RCR1000N class""" + # Taken from https://github.com/Power-Switch/PowerSwitch_Android/blob/54400f74230bb78f87b19cc89c0f174e080a3fa7/Smartphone/src/androidTest/java/eu/power_switch/obj/device/brennenstuhl/RCS1000NComfort_Test.java#L97 + dev = RCR1000N("10000", "00000") + + on_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" + off_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0" + + self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) + self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) + + on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;" + off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;" + + self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) + self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + + def test_RCR1000N_10000_10000(self): + """Test to check the functionality of the RCR1000N class""" + dev = RCR1000N("10000", "10000") + + on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;" + off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;" + + self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) + self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + def test_RCS1000N(self): """Test to check the functionality of the RCS1000N class""" dev = RCS1000N("10000", "00100") From 437befc56fbde50ce2f772db112153871b479acb Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 15:26:01 +0100 Subject: [PATCH 05/45] feat: implement intertechnodevice This class provides a better base class for the intertechno devices. It contains shared code. --- .../devices/intertechno/intertechnodevice.py | 23 ++++ .../tests/intertechnodevice_test.py | 111 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 pyBrematic/devices/intertechno/intertechnodevice.py create mode 100644 pyBrematic/devices/intertechno/tests/intertechnodevice_test.py diff --git a/pyBrematic/devices/intertechno/intertechnodevice.py b/pyBrematic/devices/intertechno/intertechnodevice.py new file mode 100644 index 0000000..c9260c9 --- /dev/null +++ b/pyBrematic/devices/intertechno/intertechnodevice.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +from pyBrematic.devices.device import Device +from .tools import calc_systemcode, calc_unitcode + + +class IntertechnoDevice(Device): + """Base class for Intertechno devices using system/unit code""" + + def __init__(self, system_code, unit_code): + super().__init__(system_code, unit_code) + + def get_signal(self, gateway, action): + """Returns a signal which triggers a device to execute the intended action""" + super().get_signal(gateway, action) + + @staticmethod + def calc_systemcode(master): + return calc_systemcode(master) + + @staticmethod + def calc_unitcode(slave): + return calc_unitcode(slave) diff --git a/pyBrematic/devices/intertechno/tests/intertechnodevice_test.py b/pyBrematic/devices/intertechno/tests/intertechnodevice_test.py new file mode 100644 index 0000000..8dbb60e --- /dev/null +++ b/pyBrematic/devices/intertechno/tests/intertechnodevice_test.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +import unittest + +from pyBrematic.devices.intertechno import IntertechnoDevice + + +class TestIntertechnoDevice(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_calc_unitcode_invalid_range(self): + """Test if exception raises on out of range values for calc_unitcode""" + with self.assertRaises(expected_exception=ValueError): + IntertechnoDevice.calc_unitcode(-1) + + with self.assertRaises(expected_exception=ValueError): + IntertechnoDevice.calc_unitcode(17) + + def test_calc_systemcode_invalid_range(self): + """Test if exception raises on out of range values for calc_systemcode""" + with self.assertRaises(expected_exception=ValueError): + IntertechnoDevice.calc_systemcode("Z") + + with self.assertRaises(expected_exception=ValueError): + IntertechnoDevice.calc_systemcode("-") + + with self.assertRaises(expected_exception=ValueError): + IntertechnoDevice.calc_systemcode("") + + def test_calc_systemcode(self): + """Test if the system code calculation returns correct codes""" + dev = IntertechnoDevice("0000", "0000") + + code = dev.calc_systemcode("A") + self.assertEqual(code, "0000") + code = dev.calc_systemcode("B") + self.assertEqual(code, "1000") + code = dev.calc_systemcode("C") + self.assertEqual(code, "0100") + code = dev.calc_systemcode("D") + self.assertEqual(code, "1100") + code = dev.calc_systemcode("E") + self.assertEqual(code, "0010") + code = dev.calc_systemcode("F") + self.assertEqual(code, "1010") + code = dev.calc_systemcode("G") + self.assertEqual(code, "0110") + code = dev.calc_systemcode("H") + self.assertEqual(code, "1110") + code = dev.calc_systemcode("I") + self.assertEqual(code, "0001") + code = dev.calc_systemcode("J") + self.assertEqual(code, "1001") + code = dev.calc_systemcode("K") + self.assertEqual(code, "0101") + code = dev.calc_systemcode("L") + self.assertEqual(code, "1101") + code = dev.calc_systemcode("M") + self.assertEqual(code, "0011") + code = dev.calc_systemcode("N") + self.assertEqual(code, "1011") + code = dev.calc_systemcode("O") + self.assertEqual(code, "0111") + code = dev.calc_systemcode("P") + self.assertEqual(code, "1111") + + def test_calc_unitcode(self): + """Test if the unit code calculation returns correct codes""" + dev = IntertechnoDevice("0000", "0000") + + code = dev.calc_unitcode(1) + self.assertEqual(code, "0000") + code = dev.calc_unitcode(2) + self.assertEqual(code, "1000") + code = dev.calc_unitcode(3) + self.assertEqual(code, "0100") + code = dev.calc_unitcode(4) + self.assertEqual(code, "1100") + code = dev.calc_unitcode(5) + self.assertEqual(code, "0010") + code = dev.calc_unitcode(6) + self.assertEqual(code, "1010") + code = dev.calc_unitcode(7) + self.assertEqual(code, "0110") + code = dev.calc_unitcode(8) + self.assertEqual(code, "1110") + code = dev.calc_unitcode(9) + self.assertEqual(code, "0001") + code = dev.calc_unitcode(10) + self.assertEqual(code, "1001") + code = dev.calc_unitcode(11) + self.assertEqual(code, "0101") + code = dev.calc_unitcode(12) + self.assertEqual(code, "1101") + code = dev.calc_unitcode(13) + self.assertEqual(code, "0011") + code = dev.calc_unitcode(14) + self.assertEqual(code, "1011") + code = dev.calc_unitcode(15) + self.assertEqual(code, "0111") + code = dev.calc_unitcode(16) + self.assertEqual(code, "1111") + + +if __name__ == "__main__": + unittest.main() From 380ba42d3d811c1aee74b93c83e74c88402b27c8 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 15:50:57 +0100 Subject: [PATCH 06/45] feat: implement tools to calculate system/unit code for intertechno Calculation methods to convert the system/unit code from human readable format (A5) into a binary string (00000010). fixes #14 --- .../devices/intertechno/tests/tools_test.py | 113 ++++++++++++++++++ pyBrematic/devices/intertechno/tools.py | 54 +++++++++ 2 files changed, 167 insertions(+) create mode 100644 pyBrematic/devices/intertechno/tests/tools_test.py create mode 100644 pyBrematic/devices/intertechno/tools.py diff --git a/pyBrematic/devices/intertechno/tests/tools_test.py b/pyBrematic/devices/intertechno/tests/tools_test.py new file mode 100644 index 0000000..e019bef --- /dev/null +++ b/pyBrematic/devices/intertechno/tests/tools_test.py @@ -0,0 +1,113 @@ +import unittest + +from pyBrematic.devices.intertechno.tools import calc_unitcode, calc_systemcode, calc_system_and_unit_code + + +class TestIntertechnoTools(unittest.TestCase): + + def test_calc_unitcode_invalid_range(self): + """Test if exception raises on out of range values for calc_unitcode""" + with self.assertRaises(expected_exception=ValueError): + calc_unitcode(-1) + + with self.assertRaises(expected_exception=ValueError): + calc_unitcode(17) + + def test_calc_systemcode_invalid_range(self): + """Test if exception raises on out of range values for calc_systemcode""" + with self.assertRaises(expected_exception=ValueError): + calc_systemcode("Z") + + with self.assertRaises(expected_exception=ValueError): + calc_systemcode("-") + + with self.assertRaises(expected_exception=ValueError): + calc_systemcode("") + + def test_calc_systemcode(self): + """Test if the system code calculation returns correct codes""" + code = calc_systemcode("A") + self.assertEqual(code, "0000") + code = calc_systemcode("B") + self.assertEqual(code, "1000") + code = calc_systemcode("C") + self.assertEqual(code, "0100") + code = calc_systemcode("D") + self.assertEqual(code, "1100") + code = calc_systemcode("E") + self.assertEqual(code, "0010") + code = calc_systemcode("F") + self.assertEqual(code, "1010") + code = calc_systemcode("G") + self.assertEqual(code, "0110") + code = calc_systemcode("H") + self.assertEqual(code, "1110") + code = calc_systemcode("I") + self.assertEqual(code, "0001") + code = calc_systemcode("J") + self.assertEqual(code, "1001") + code = calc_systemcode("K") + self.assertEqual(code, "0101") + code = calc_systemcode("L") + self.assertEqual(code, "1101") + code = calc_systemcode("M") + self.assertEqual(code, "0011") + code = calc_systemcode("N") + self.assertEqual(code, "1011") + code = calc_systemcode("O") + self.assertEqual(code, "0111") + code = calc_systemcode("P") + self.assertEqual(code, "1111") + + def test_calc_unitcode(self): + """Test if the unit code calculation returns correct codes""" + code = calc_unitcode(1) + self.assertEqual(code, "0000") + code = calc_unitcode(2) + self.assertEqual(code, "1000") + code = calc_unitcode(3) + self.assertEqual(code, "0100") + code = calc_unitcode(4) + self.assertEqual(code, "1100") + code = calc_unitcode(5) + self.assertEqual(code, "0010") + code = calc_unitcode(6) + self.assertEqual(code, "1010") + code = calc_unitcode(7) + self.assertEqual(code, "0110") + code = calc_unitcode(8) + self.assertEqual(code, "1110") + code = calc_unitcode(9) + self.assertEqual(code, "0001") + code = calc_unitcode(10) + self.assertEqual(code, "1001") + code = calc_unitcode(11) + self.assertEqual(code, "0101") + code = calc_unitcode(12) + self.assertEqual(code, "1101") + code = calc_unitcode(13) + self.assertEqual(code, "0011") + code = calc_unitcode(14) + self.assertEqual(code, "1011") + code = calc_unitcode(15) + self.assertEqual(code, "0111") + code = calc_unitcode(16) + self.assertEqual(code, "1111") + + def calc_system_and_unit_code(self): + """Test if combined method does exactly the same as the individual methods""" + system1, unit1 = calc_system_and_unit_code("A5") + self.assertEqual(system1, calc_systemcode("A")) + self.assertEqual(unit1, calc_systemcode(5)) + + system2, unit2 = calc_system_and_unit_code("B11") + self.assertEqual(system2, calc_systemcode("B")) + self.assertEqual(unit2, calc_systemcode(11)) + + system3, unit3 = calc_system_and_unit_code("F8") + self.assertEqual(system3, calc_systemcode("F")) + self.assertEqual(unit3, calc_systemcode(8)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pyBrematic/devices/intertechno/tools.py b/pyBrematic/devices/intertechno/tools.py new file mode 100644 index 0000000..055cb06 --- /dev/null +++ b/pyBrematic/devices/intertechno/tools.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +import re + + +def calc_system_and_unit_code(code): + """Calculates the (binary) system and unit code from a given string such as "A5", "B11", "F8" + + :param code: The alphanumeric code such as "D7" + :return: Tuple of string representation of binary values (system, unit) - e.g.: ("0100", "1000") + """ + pattern = r"([A-P])(\d+)" + match = re.match(pattern, code) + if not match: + raise ValueError("Code must start with a letter A-P and end in a number 1-16!") + + system_ascii = match.group(1) + unit_ascii = match.group(2) + + system_bin = calc_systemcode(system_ascii) + unit_bin = calc_unitcode(int(unit_ascii)) + return system_bin, unit_bin + + +def calc_systemcode(master): + """Calculates the (binary) system code from a given string such as "A", "B", "F" + + :param master: The system channel letter such as "D" + :return: String representation of binary value - e.g.: "0100" + """ + if master == "": + raise ValueError("Value must not be empty!") + + master = master.upper() + + # Check if master is in range [A-P] + number = ord(master) - 65 + if number not in range(0, 16 + 1): + raise ValueError("Master value '{}' is not ranged between [1, 16]".format(master)) + + systemcode = "{0:04b}".format(number) + return systemcode[::-1] + + +def calc_unitcode(slave): + """Calculates the (binary) system code from a given channel number such as 3, 8, 15 + + :param slave: The unit channel number such as 11 + :return: String representation of binary value - e.g.: "0100" + """ + if slave not in range(1, 16 + 1): + raise ValueError("Slave value '{}' is not ranged between [1, 16]".format(slave)) + + unitcode = "{0:04b}".format(slave - 1) + return unitcode[::-1] From f45b97ab23f2dbd9f779715870b80114b7eefae3 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 18:50:09 +0100 Subject: [PATCH 07/45] refactor: make CMR1000 inherit from Intertechnodevice --- pyBrematic/devices/intertechno/CMR1000.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyBrematic/devices/intertechno/CMR1000.py b/pyBrematic/devices/intertechno/CMR1000.py index cc93261..38b0ea7 100644 --- a/pyBrematic/devices/intertechno/CMR1000.py +++ b/pyBrematic/devices/intertechno/CMR1000.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -from pyBrematic.devices.device import Device +from pyBrematic.devices.intertechno import IntertechnoDevice from pyBrematic.exceptions import GatewayNotSupportedException from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway -class CMR1000(Device): +class CMR1000(IntertechnoDevice): """Device class for the Intertechno CMR-1000 remote outlet""" sRepeat = 6 sPause = 11125 From 601cf5407322f23e2bc1fde6ebe1758977a02fa2 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 18:50:52 +0100 Subject: [PATCH 08/45] feat: add imports to __init__ --- pyBrematic/devices/intertechno/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyBrematic/devices/intertechno/__init__.py b/pyBrematic/devices/intertechno/__init__.py index 2d6ca5f..c62f087 100644 --- a/pyBrematic/devices/intertechno/__init__.py +++ b/pyBrematic/devices/intertechno/__init__.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- +from .intertechnodevice import IntertechnoDevice from .CMR1000 import CMR1000 +from .CMR500 import CMR500 +from .CMR300 import CMR300 from .ITL500 import ITL500 from .ITR3500 import ITR3500 from .PAR1500 import PAR1500 +from .tools import calc_systemcode, calc_unitcode, calc_system_and_unit_code -__all__ = ['CMR1000', 'PAR1500', 'ITR3500', 'ITL500'] +__all__ = ['IntertechnoDevice', 'CMR1000', 'CMR500', 'CMR300', 'PAR1500', 'ITR3500', 'ITL500', 'calc_systemcode', 'calc_unitcode', 'calc_system_and_unit_code'] From 2abfec65465d7cbb7f43b6cd303e2ae2c5e3819d Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 18:52:19 +0100 Subject: [PATCH 09/45] test: add mew tests for intertechno code calculation --- .../intertechno/tests/intertechno_test.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pyBrematic/devices/intertechno/tests/intertechno_test.py b/pyBrematic/devices/intertechno/tests/intertechno_test.py index 387f569..09c97eb 100644 --- a/pyBrematic/devices/intertechno/tests/intertechno_test.py +++ b/pyBrematic/devices/intertechno/tests/intertechno_test.py @@ -45,6 +45,21 @@ def test_CMR1000(self): on_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,140;" off_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,140;" + system_code = dev.calc_systemcode("A") + unit_code = dev.calc_unitcode(5) + dev2 = CMR1000(system_code, unit_code) + + self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) + self.assertEqual(on_signal_BSGW, dev2.get_signal(self.bsgw, Action.ON)) + self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(off_signal_BSGW, dev2.get_signal(self.bsgw, Action.OFF)) + + def test_CMR1000_generation(self): + system_code = CMR1000.calc_systemcode("P") + unit_code = CMR1000.calc_unitcode(16) + dev = CMR1000(system_code, unit_code) + on_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,140;" + off_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,140;" self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) @@ -63,3 +78,21 @@ def test_ITR3500(self): self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + + def test_calc_unitcode(self): + with self.assertRaises(expected_exception=ValueError): + CMR1000.calc_unitcode(-1) + + with self.assertRaises(expected_exception=ValueError): + CMR1000.calc_unitcode(17) + + def test_calc_systemcode(self): + with self.assertRaises(expected_exception=ValueError): + CMR1000.calc_systemcode("Z") + + with self.assertRaises(expected_exception=ValueError): + CMR1000.calc_systemcode("-") + + +if __name__ == "__main__": + unittest.main() From 7cb93f5b7d73bb20c4a675a052690c7ad16f9037 Mon Sep 17 00:00:00 2001 From: Rico Date: Thu, 31 Dec 2020 18:53:34 +0100 Subject: [PATCH 10/45] feat: add code calculation to example --- pyBrematic/example/example.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyBrematic/example/example.py b/pyBrematic/example/example.py index a052595..2af6ba2 100644 --- a/pyBrematic/example/example.py +++ b/pyBrematic/example/example.py @@ -3,6 +3,8 @@ from pyBrematic.devices import Action from pyBrematic.devices.brennenstuhl import RCS1000N +from pyBrematic.devices.intertechno import ITR3500 +from pyBrematic.devices.intertechno import calc_unitcode, calc_systemcode from pyBrematic.gateways import BrennenstuhlGateway # Set your system and unit codes @@ -12,6 +14,11 @@ # Create a new device with the specified codes desk_lamp = RCS1000N(system_code, unit_code) +# You can also use the following methods to get the code via master/slave (letter/number) notation +led_system_code = calc_systemcode("A") +led_unit_code = calc_unitcode(3) +led_strip = ITR3500(led_system_code, led_unit_code) + # Create a new gateway located at the specified IP gw = BrennenstuhlGateway("192.168.178.9") From 280509270f863b17ee280f0e03f1e8b2372233b4 Mon Sep 17 00:00:00 2001 From: Rico Date: Fri, 1 Jan 2021 14:18:37 +0100 Subject: [PATCH 11/45] style: remove double whitespace --- pyBrematic/devices/elro/AB440SA.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyBrematic/devices/elro/AB440SA.py b/pyBrematic/devices/elro/AB440SA.py index 375dcea..61e1f09 100644 --- a/pyBrematic/devices/elro/AB440SA.py +++ b/pyBrematic/devices/elro/AB440SA.py @@ -5,7 +5,7 @@ class AB440SA(Device): - """Device class for the ELRO AB440SA remote outlet""" + """Device class for the ELRO AB440SA remote outlet""" lo = "1," hi = "3," seq_low = lo + hi + lo + hi From 940c873ab9839e83bb881748f22c73846b1d4adf Mon Sep 17 00:00:00 2001 From: Rico Date: Fri, 1 Jan 2021 23:24:26 +0100 Subject: [PATCH 12/45] docs: rewrite README --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1eb004c..d045d99 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,61 @@ +# 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 +from pyBrematic.devices.intertechno import calc_unitcode, calc_systemcode +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" # Switches 1-4 are in the 'up' position, 5 is 'down' +unit_code = "10000" # Switch 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) + +# You can also use the following methods to get the code via master/slave (letter/number) notation +led_system_code = calc_systemcode("A") +led_unit_code = calc_unitcode(3) +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. From 7127ca75ff1cac1d116f5c8e4ac7d177cb643b16 Mon Sep 17 00:00:00 2001 From: Rico Date: Fri, 1 Jan 2021 23:25:08 +0100 Subject: [PATCH 13/45] style: use snake case for function name --- pyBrematic/utils/singleton.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyBrematic/utils/singleton.py b/pyBrematic/utils/singleton.py index e760b28..a2abc5d 100644 --- a/pyBrematic/utils/singleton.py +++ b/pyBrematic/utils/singleton.py @@ -6,14 +6,14 @@ def singleton(cls): @wraps(cls) - def getInstance(*args, **kwargs): + def get_instance(*args, **kwargs): instance = __instances.get(cls, None) if not instance: instance = cls(*args, **kwargs) __instances[cls] = instance return instance - return getInstance + return get_instance def _delete_all_instances(): From 7a4e396f88495bcb33f9a5d847a153f441261f5f Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 15:21:24 +0100 Subject: [PATCH 14/45] refactor: move action class to pyBrematic root This change should still be backwards compatible. --- pyBrematic/__init__.py | 3 ++- pyBrematic/{devices => }/action.py | 0 pyBrematic/devices/__init__.py | 6 ++++-- pyBrematic/devices/autopairdevice.py | 2 +- pyBrematic/devices/brennenstuhl/RCS1000N.py | 3 ++- pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py | 2 +- pyBrematic/devices/device.py | 2 +- pyBrematic/devices/elro/AB440SA.py | 3 ++- pyBrematic/devices/elro/tests/elro_test.py | 2 +- pyBrematic/devices/intertechno/ITL500.py | 3 ++- pyBrematic/devices/intertechno/tests/intertechno_test.py | 2 +- pyBrematic/devices/tests/autopairdevice_test.py | 3 ++- pyBrematic/devices/tests/device_test.py | 3 ++- pyBrematic/example/example.py | 2 +- 14 files changed, 22 insertions(+), 14 deletions(-) rename pyBrematic/{devices => }/action.py (100%) diff --git a/pyBrematic/__init__.py b/pyBrematic/__init__.py index ee21da7..cd7c6a1 100644 --- a/pyBrematic/__init__.py +++ b/pyBrematic/__init__.py @@ -1,5 +1,6 @@ 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__ = ["Device", "Gateway", "BrennenstuhlGateway", "Action"] diff --git a/pyBrematic/devices/action.py b/pyBrematic/action.py similarity index 100% rename from pyBrematic/devices/action.py rename to pyBrematic/action.py diff --git a/pyBrematic/devices/__init__.py b/pyBrematic/devices/__init__.py index 48b1166..8f2fa25 100644 --- a/pyBrematic/devices/__init__.py +++ b/pyBrematic/devices/__init__.py @@ -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"] diff --git a/pyBrematic/devices/autopairdevice.py b/pyBrematic/devices/autopairdevice.py index a89458d..1fea6af 100644 --- a/pyBrematic/devices/autopairdevice.py +++ b/pyBrematic/devices/autopairdevice.py @@ -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): diff --git a/pyBrematic/devices/brennenstuhl/RCS1000N.py b/pyBrematic/devices/brennenstuhl/RCS1000N.py index 6d34a09..59557c2 100644 --- a/pyBrematic/devices/brennenstuhl/RCS1000N.py +++ b/pyBrematic/devices/brennenstuhl/RCS1000N.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from pyBrematic.devices import Device, Action +from pyBrematic.action import Action +from pyBrematic.devices import Device from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway diff --git a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py index 8f968fe..958fa86 100644 --- a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py +++ b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py @@ -2,7 +2,7 @@ import unittest -from pyBrematic.devices import Action +from pyBrematic.action import Action from pyBrematic.devices.brennenstuhl import RCR1000N, RCS1000N from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway diff --git a/pyBrematic/devices/device.py b/pyBrematic/devices/device.py index 8647a14..57ed2d0 100644 --- a/pyBrematic/devices/device.py +++ b/pyBrematic/devices/device.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Device class representing a remote-controlled receiver""" -from .action import Action +from pyBrematic.action import Action class Device(object): diff --git a/pyBrematic/devices/elro/AB440SA.py b/pyBrematic/devices/elro/AB440SA.py index 61e1f09..7cea9ad 100644 --- a/pyBrematic/devices/elro/AB440SA.py +++ b/pyBrematic/devices/elro/AB440SA.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from pyBrematic.devices import Device, Action +from pyBrematic.action import Action +from pyBrematic.devices import Device from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway diff --git a/pyBrematic/devices/elro/tests/elro_test.py b/pyBrematic/devices/elro/tests/elro_test.py index 73e242d..780fd2c 100644 --- a/pyBrematic/devices/elro/tests/elro_test.py +++ b/pyBrematic/devices/elro/tests/elro_test.py @@ -2,7 +2,7 @@ import unittest -from pyBrematic.devices import Action +from pyBrematic.action import Action from pyBrematic.devices.elro import AB440SA from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway diff --git a/pyBrematic/devices/intertechno/ITL500.py b/pyBrematic/devices/intertechno/ITL500.py index 1185481..bac821c 100644 --- a/pyBrematic/devices/intertechno/ITL500.py +++ b/pyBrematic/devices/intertechno/ITL500.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -from pyBrematic.devices import AutoPairDevice, Action +from pyBrematic.action import Action +from pyBrematic.devices import AutoPairDevice from pyBrematic.exceptions import GatewayNotSupportedException from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway from pyBrematic.utils import Rand diff --git a/pyBrematic/devices/intertechno/tests/intertechno_test.py b/pyBrematic/devices/intertechno/tests/intertechno_test.py index 09c97eb..2d8f830 100644 --- a/pyBrematic/devices/intertechno/tests/intertechno_test.py +++ b/pyBrematic/devices/intertechno/tests/intertechno_test.py @@ -2,7 +2,7 @@ import unittest -from pyBrematic.devices import Action +from pyBrematic.action import Action from pyBrematic.devices.intertechno import PAR1500, CMR1000, ITR3500 from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway diff --git a/pyBrematic/devices/tests/autopairdevice_test.py b/pyBrematic/devices/tests/autopairdevice_test.py index f0ca129..fbf8903 100644 --- a/pyBrematic/devices/tests/autopairdevice_test.py +++ b/pyBrematic/devices/tests/autopairdevice_test.py @@ -2,7 +2,8 @@ import unittest -from pyBrematic.devices import AutoPairDevice, Action +from pyBrematic.action import Action +from pyBrematic.devices import AutoPairDevice from pyBrematic.gateways import BrennenstuhlGateway diff --git a/pyBrematic/devices/tests/device_test.py b/pyBrematic/devices/tests/device_test.py index cd00094..c54a9f4 100644 --- a/pyBrematic/devices/tests/device_test.py +++ b/pyBrematic/devices/tests/device_test.py @@ -2,7 +2,8 @@ import unittest -from pyBrematic.devices import Device, Action +from pyBrematic.action import Action +from pyBrematic.devices import Device from pyBrematic.gateways import BrennenstuhlGateway diff --git a/pyBrematic/example/example.py b/pyBrematic/example/example.py index 2af6ba2..f756840 100644 --- a/pyBrematic/example/example.py +++ b/pyBrematic/example/example.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """This is an example usage file for the pyBrematic module""" -from pyBrematic.devices import Action +from pyBrematic import Action from pyBrematic.devices.brennenstuhl import RCS1000N from pyBrematic.devices.intertechno import ITR3500 from pyBrematic.devices.intertechno import calc_unitcode, calc_systemcode From d5ce290aeb208afd40fb0e053699aa1119697935 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 15:28:06 +0100 Subject: [PATCH 15/45] test: fix calc_system_and_unitcode test --- pyBrematic/devices/intertechno/tests/tools_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyBrematic/devices/intertechno/tests/tools_test.py b/pyBrematic/devices/intertechno/tests/tools_test.py index e019bef..2180451 100644 --- a/pyBrematic/devices/intertechno/tests/tools_test.py +++ b/pyBrematic/devices/intertechno/tests/tools_test.py @@ -94,19 +94,19 @@ def test_calc_unitcode(self): code = calc_unitcode(16) self.assertEqual(code, "1111") - def calc_system_and_unit_code(self): + def test_calc_system_and_unit_code(self): """Test if combined method does exactly the same as the individual methods""" system1, unit1 = calc_system_and_unit_code("A5") self.assertEqual(system1, calc_systemcode("A")) - self.assertEqual(unit1, calc_systemcode(5)) + self.assertEqual(unit1, calc_unitcode(5)) system2, unit2 = calc_system_and_unit_code("B11") self.assertEqual(system2, calc_systemcode("B")) - self.assertEqual(unit2, calc_systemcode(11)) + self.assertEqual(unit2, calc_unitcode(11)) system3, unit3 = calc_system_and_unit_code("F8") self.assertEqual(system3, calc_systemcode("F")) - self.assertEqual(unit3, calc_systemcode(8)) + self.assertEqual(unit3, calc_unitcode(8)) if __name__ == '__main__': From 395656523aa1ef3e7de923a023203e110673d3a8 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 15:28:30 +0100 Subject: [PATCH 16/45] test: implement test for error handling for code calculation --- .../devices/intertechno/tests/tools_test.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyBrematic/devices/intertechno/tests/tools_test.py b/pyBrematic/devices/intertechno/tests/tools_test.py index 2180451..30ff729 100644 --- a/pyBrematic/devices/intertechno/tests/tools_test.py +++ b/pyBrematic/devices/intertechno/tests/tools_test.py @@ -108,6 +108,25 @@ def test_calc_system_and_unit_code(self): self.assertEqual(system3, calc_systemcode("F")) self.assertEqual(unit3, calc_unitcode(8)) + def test_calc_system_and_unit_code_issue(self): + """Test if wrong input raises a ValueError""" + with self.assertRaises(ValueError): + _, _ = calc_system_and_unit_code("Q5") + + with self.assertRaises(ValueError): + _, _ = calc_system_and_unit_code("R18") + + with self.assertRaises(ValueError): + _, _ = calc_system_and_unit_code("A18") + + with self.assertRaises(ValueError): + _, _ = calc_system_and_unit_code("A0101110101") + + with self.assertRaises(ValueError): + _, _ = calc_system_and_unit_code("11B") + + with self.assertRaises(ValueError): + _, _ = calc_system_and_unit_code("0000100001") if __name__ == '__main__': unittest.main() From ba33f6cb2be43869c2fb0e3f7928074a59b74a5b Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 15:31:01 +0100 Subject: [PATCH 17/45] style: use double quotes --- pyBrematic/devices/brennenstuhl/__init__.py | 2 +- pyBrematic/devices/elro/__init__.py | 2 +- pyBrematic/devices/intertechno/__init__.py | 2 +- pyBrematic/devices/intertechno/tests/tools_test.py | 2 +- pyBrematic/exceptions/__init__.py | 2 +- pyBrematic/gateways/__init__.py | 2 +- pyBrematic/utils/__init__.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyBrematic/devices/brennenstuhl/__init__.py b/pyBrematic/devices/brennenstuhl/__init__.py index 42d41f1..1110584 100644 --- a/pyBrematic/devices/brennenstuhl/__init__.py +++ b/pyBrematic/devices/brennenstuhl/__init__.py @@ -3,4 +3,4 @@ from .RCR1000N import RCR1000N from .RCS1000N import RCS1000N -__all__ = ['RCR1000N', 'RCS1000N'] +__all__ = ["RCR1000N", "RCS1000N"] diff --git a/pyBrematic/devices/elro/__init__.py b/pyBrematic/devices/elro/__init__.py index d0fb93b..78ab670 100644 --- a/pyBrematic/devices/elro/__init__.py +++ b/pyBrematic/devices/elro/__init__.py @@ -2,4 +2,4 @@ from .AB440SA import AB440SA -__all__ = ['AB440SA'] +__all__ = ["AB440SA"] diff --git a/pyBrematic/devices/intertechno/__init__.py b/pyBrematic/devices/intertechno/__init__.py index c62f087..3f80e93 100644 --- a/pyBrematic/devices/intertechno/__init__.py +++ b/pyBrematic/devices/intertechno/__init__.py @@ -9,4 +9,4 @@ from .PAR1500 import PAR1500 from .tools import calc_systemcode, calc_unitcode, calc_system_and_unit_code -__all__ = ['IntertechnoDevice', 'CMR1000', 'CMR500', 'CMR300', 'PAR1500', 'ITR3500', 'ITL500', 'calc_systemcode', 'calc_unitcode', 'calc_system_and_unit_code'] +__all__ = ["IntertechnoDevice", "CMR1000", "CMR500", "CMR300", "PAR1500", "ITR3500", "ITL500", "calc_systemcode", "calc_unitcode", "calc_system_and_unit_code"] diff --git a/pyBrematic/devices/intertechno/tests/tools_test.py b/pyBrematic/devices/intertechno/tests/tools_test.py index 30ff729..69278a7 100644 --- a/pyBrematic/devices/intertechno/tests/tools_test.py +++ b/pyBrematic/devices/intertechno/tests/tools_test.py @@ -128,5 +128,5 @@ def test_calc_system_and_unit_code_issue(self): with self.assertRaises(ValueError): _, _ = calc_system_and_unit_code("0000100001") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/pyBrematic/exceptions/__init__.py b/pyBrematic/exceptions/__init__.py index 554bf62..ba24d27 100644 --- a/pyBrematic/exceptions/__init__.py +++ b/pyBrematic/exceptions/__init__.py @@ -2,4 +2,4 @@ from .GatewayNotSupportedException import GatewayNotSupportedException -__all__ = ['GatewayNotSupportedException'] +__all__ = ["GatewayNotSupportedException"] diff --git a/pyBrematic/gateways/__init__.py b/pyBrematic/gateways/__init__.py index 02e6a11..2d5682f 100644 --- a/pyBrematic/gateways/__init__.py +++ b/pyBrematic/gateways/__init__.py @@ -4,4 +4,4 @@ from .gateway import Gateway from .intertechno_gateway import IntertechnoGateway -__all__ = ['Gateway', 'IntertechnoGateway', 'BrennenstuhlGateway'] +__all__ = ["Gateway", "IntertechnoGateway", "BrennenstuhlGateway"] diff --git a/pyBrematic/utils/__init__.py b/pyBrematic/utils/__init__.py index 985c59f..f72c3c1 100644 --- a/pyBrematic/utils/__init__.py +++ b/pyBrematic/utils/__init__.py @@ -5,4 +5,4 @@ from .singleton import singleton from .storage import Storage -__all__ = ['singleton', 'Storage', 'DataEncoder', 'Rand'] +__all__ = ["singleton", "Storage", "DataEncoder", "Rand"] From a22026deb13ad6d7c4057e99198645643aa4f282 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 15:32:53 +0100 Subject: [PATCH 18/45] refactor: use combined code calc method in example file --- pyBrematic/example/example.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pyBrematic/example/example.py b/pyBrematic/example/example.py index f756840..5fe8375 100644 --- a/pyBrematic/example/example.py +++ b/pyBrematic/example/example.py @@ -3,20 +3,19 @@ from pyBrematic import Action from pyBrematic.devices.brennenstuhl import RCS1000N -from pyBrematic.devices.intertechno import ITR3500 -from pyBrematic.devices.intertechno import calc_unitcode, calc_systemcode +from pyBrematic.devices.intertechno import ITR3500, calc_system_and_unit_code from pyBrematic.gateways import BrennenstuhlGateway # Set your system and unit codes -system_code = "11110" # Switches 1-4 are in the 'up' position, 5 is 'down' -unit_code = "10000" # Switch A is in the 'up' position, B-E are 'down' +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) -# You can also use the following methods to get the code via master/slave (letter/number) notation -led_system_code = calc_systemcode("A") -led_unit_code = calc_unitcode(3) +# 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 From af6476b261b98bf9c4261b8c18957eb2fd0477d6 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 16:26:58 +0100 Subject: [PATCH 19/45] docs: add readme files for intertechno/brennenstuhl device modules --- pyBrematic/devices/brennenstuhl/README.md | 8 ++++++++ pyBrematic/devices/intertechno/README.md | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 pyBrematic/devices/brennenstuhl/README.md create mode 100644 pyBrematic/devices/intertechno/README.md diff --git a/pyBrematic/devices/brennenstuhl/README.md b/pyBrematic/devices/brennenstuhl/README.md new file mode 100644 index 0000000..bb7e7b6 --- /dev/null +++ b/pyBrematic/devices/brennenstuhl/README.md @@ -0,0 +1,8 @@ +# Supported Brennenstuhl devices +|Model|Type|Tested| +|---|---|---| +|RCR1000N|Plug switch|Yes| +|RCS1000N|Plug switch|Yes| + +# Useful references +- https://github.com/nicolashimmelmann/ESP_GWY433 diff --git a/pyBrematic/devices/intertechno/README.md b/pyBrematic/devices/intertechno/README.md new file mode 100644 index 0000000..f2f3c2f --- /dev/null +++ b/pyBrematic/devices/intertechno/README.md @@ -0,0 +1,13 @@ +# Supported Intertechno devices +|Model|Type|Tested| +|---|---|---| +|CMR300|Switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|CMR500|Dimmer|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|CMR1000|Switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|ITL500|Blind motor switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/5#issuecomment-491618075)| +|ITR300|Plug switch|No (but same as CMR1000)| +|ITR3500|Plug switch|No (but same as CMR1000)| +|PAR1500|Plug switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| + +# Useful references +- https://wiki.fhem.de/wiki/Intertechno_Code_Berechnung#Stellen_8-9_.28Festwert_0F.29 From 28760cc6b8c049b528fa0095fb1e7a94956f889d Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 17:27:25 +0100 Subject: [PATCH 20/45] chore: implement linter & test action (#17) --- .github/workflows/python-package.yml | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/python-package.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml new file mode 100644 index 0000000..ef93e7e --- /dev/null +++ b/.github/workflows/python-package.yml @@ -0,0 +1,39 @@ +# 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 + +on: + push: + branches: [ master, dev ] + pull_request: + branches: [ master ] + +jobs: + build: + + 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 pytest-cov + 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 --cov=./ --cov-report=xml From 027d8ec631106411e9b6edda761c5b8174209400 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 17:34:23 +0100 Subject: [PATCH 21/45] ci: rename GitHub lint workflow Renaming from python-package.yml to python-lint-test.yml to better represent that this workflow does linting and testing. --- .github/workflows/{python-package.yml => python-lint-test.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{python-package.yml => python-lint-test.yml} (100%) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-lint-test.yml similarity index 100% rename from .github/workflows/python-package.yml rename to .github/workflows/python-lint-test.yml From 54fd93f6267e606ed32b69bbb0fe433f30d76f2d Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 17:36:17 +0100 Subject: [PATCH 22/45] ci: rename GitHub workflow for testing and linting --- .github/workflows/python-lint-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-lint-test.yml b/.github/workflows/python-lint-test.yml index ef93e7e..c358a6c 100644 --- a/.github/workflows/python-lint-test.yml +++ b/.github/workflows/python-lint-test.yml @@ -1,7 +1,7 @@ # 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: Run tests and lint on: push: @@ -10,7 +10,7 @@ on: branches: [ master ] jobs: - build: + test-and-lint: runs-on: ubuntu-latest strategy: From ade15f859ecbc19f2ee035282e9b89444d3f096a Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 17:53:55 +0100 Subject: [PATCH 23/45] ci: add testing and linting to build action --- .github/workflows/python-pypi-publish.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-pypi-publish.yml b/.github/workflows/python-pypi-publish.yml index d4ba985..6688abd 100644 --- a/.github/workflows/python-pypi-publish.yml +++ b/.github/workflows/python-pypi-publish.yml @@ -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 --cov=./ --cov-report=xml - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} From ff59e5ce8370fe249eee6845e058e5ff8bb05fe2 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 17:54:14 +0100 Subject: [PATCH 24/45] chore: add flake8 config --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 224a779..a57e1d1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [metadata] -description-file = README.md \ No newline at end of file +description-file = README.md + +[flake8] +exclude = .git,__pycache__,build,dist,.env,.venv,venv From c271d7722bd24f34e4083bad75583a41a975c049 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 19:31:10 +0100 Subject: [PATCH 25/45] docs: improve example in README --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d045d99..48df7ca 100644 --- a/README.md +++ b/README.md @@ -31,20 +31,19 @@ find an example configuration of how to use the module. ```python from pyBrematic import Action from pyBrematic.devices.brennenstuhl import RCS1000N -from pyBrematic.devices.intertechno import ITR3500 -from pyBrematic.devices.intertechno import calc_unitcode, calc_systemcode +from pyBrematic.devices.intertechno import ITR3500, calc_system_and_unit_code from pyBrematic.gateways import BrennenstuhlGateway # Set your system and unit codes -system_code = "11110" # Switches 1-4 are in the 'up' position, 5 is 'down' -unit_code = "10000" # Switch A is in the 'up' position, B-E are 'down' +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) -# You can also use the following methods to get the code via master/slave (letter/number) notation -led_system_code = calc_systemcode("A") -led_unit_code = calc_unitcode(3) +# 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 From 5479796454f7fd6f44084af42768af5d9ec23ff1 Mon Sep 17 00:00:00 2001 From: Rico Date: Sat, 2 Jan 2021 19:31:31 +0100 Subject: [PATCH 26/45] chore: update version number --- pyBrematic/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyBrematic/version.py b/pyBrematic/version.py index 6849410..c68196d 100644 --- a/pyBrematic/version.py +++ b/pyBrematic/version.py @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "1.2.0" From 7ed0adde45144649c2235e0696c2b223c16d10ee Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 01:31:48 +0100 Subject: [PATCH 27/45] feat(device): implement join_list method --- pyBrematic/devices/device.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyBrematic/devices/device.py b/pyBrematic/devices/device.py index 57ed2d0..0c58cd8 100644 --- a/pyBrematic/devices/device.py +++ b/pyBrematic/devices/device.py @@ -14,3 +14,13 @@ def __init__(self, system_code, unit_code): def get_signal(self, gateway, 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) From 5764b8bd7746968ae8dc5f3b192dea759484a575 Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 01:47:21 +0100 Subject: [PATCH 28/45] refactor: move encode method to device class --- pyBrematic/devices/brennenstuhl/RCS1000N.py | 13 ------------- pyBrematic/devices/device.py | 13 +++++++++++++ pyBrematic/devices/elro/AB440SA.py | 13 ------------- pyBrematic/devices/intertechno/CMR1000.py | 17 ++--------------- 4 files changed, 15 insertions(+), 41 deletions(-) diff --git a/pyBrematic/devices/brennenstuhl/RCS1000N.py b/pyBrematic/devices/brennenstuhl/RCS1000N.py index 59557c2..75b894b 100644 --- a/pyBrematic/devices/brennenstuhl/RCS1000N.py +++ b/pyBrematic/devices/brennenstuhl/RCS1000N.py @@ -51,19 +51,6 @@ class RCS1000N(Device): 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): """Returns a signal which triggers a device to execute the intended action""" # Encoding the system_code and unit_code diff --git a/pyBrematic/devices/device.py b/pyBrematic/devices/device.py index 0c58cd8..d513172 100644 --- a/pyBrematic/devices/device.py +++ b/pyBrematic/devices/device.py @@ -15,6 +15,19 @@ def get_signal(self, gateway, action): """Returns a signal which triggers a device to execute the intended action""" raise NotImplementedError("Subclasses must implement this method!") + @staticmethod + def encode(code, seq_low, seq_high): + """Method for encoding the system_code or unit_code from binary to a gateway-readable format""" + 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 + @staticmethod def join_list(lst, delimeter=","): """Join a list together with a certain delimeter diff --git a/pyBrematic/devices/elro/AB440SA.py b/pyBrematic/devices/elro/AB440SA.py index 7cea9ad..ebc911c 100644 --- a/pyBrematic/devices/elro/AB440SA.py +++ b/pyBrematic/devices/elro/AB440SA.py @@ -34,19 +34,6 @@ class AB440SA(Device): def __init__(self, system_code, unit_code): super().__init__(system_code, unit_code) - @staticmethod - def encode(code, seq_low, seq_high): - """Method for encoding the system_code or unit_code from binary to a gateway-readable format""" - 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): """Returns a signal which triggers a device to execute the intended action""" system_msg = self.encode(self.system_code, self.seq_low, self.seq_high) diff --git a/pyBrematic/devices/intertechno/CMR1000.py b/pyBrematic/devices/intertechno/CMR1000.py index 38b0ea7..0572ec4 100644 --- a/pyBrematic/devices/intertechno/CMR1000.py +++ b/pyBrematic/devices/intertechno/CMR1000.py @@ -36,24 +36,11 @@ class CMR1000(IntertechnoDevice): 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_low - elif bit == "1": - encoded_msg += seq_high - else: - raise ValueError("Invalid value in system_code or unit_code!") - return encoded_msg - def get_signal(self, gateway, 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_fl) - unit_msg = self.encode(self.unit_code, self.seq_low, self.seq_fl) + system_msg = self.encode(self.system_code, self.seq_fl, self.seq_low) + unit_msg = self.encode(self.unit_code, self.seq_fl, self.seq_low) if isinstance(gateway, BrennenstuhlGateway): head = self.headBSGW From 40ac9e90e998c55a74a4f7ad2688bda97ba92d66 Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 01:58:14 +0100 Subject: [PATCH 29/45] feat: implement ConnAir gateway --- pyBrematic/gateways/connair_gateway.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pyBrematic/gateways/connair_gateway.py diff --git a/pyBrematic/gateways/connair_gateway.py b/pyBrematic/gateways/connair_gateway.py new file mode 100644 index 0000000..ef6fc88 --- /dev/null +++ b/pyBrematic/gateways/connair_gateway.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from .brennenstuhl_gateway import BrennenstuhlGateway + + +class ConnAirGateway(BrennenstuhlGateway): + def __init__(self, ip, port=49880): + super().__init__(ip, port) From 083ba9e6054fd2fd11cb3712a0649f856ee60254 Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 02:01:55 +0100 Subject: [PATCH 30/45] feat: add "supported_actions" to multiple devices --- pyBrematic/devices/brennenstuhl/RCS1000N.py | 1 + pyBrematic/devices/elro/AB440SA.py | 1 + pyBrematic/devices/intertechno/CMR1000.py | 1 + 3 files changed, 3 insertions(+) diff --git a/pyBrematic/devices/brennenstuhl/RCS1000N.py b/pyBrematic/devices/brennenstuhl/RCS1000N.py index 75b894b..069b3e5 100644 --- a/pyBrematic/devices/brennenstuhl/RCS1000N.py +++ b/pyBrematic/devices/brennenstuhl/RCS1000N.py @@ -47,6 +47,7 @@ class RCS1000N(Device): # 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) diff --git a/pyBrematic/devices/elro/AB440SA.py b/pyBrematic/devices/elro/AB440SA.py index ebc911c..5a30e6e 100644 --- a/pyBrematic/devices/elro/AB440SA.py +++ b/pyBrematic/devices/elro/AB440SA.py @@ -30,6 +30,7 @@ class AB440SA(Device): 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) diff --git a/pyBrematic/devices/intertechno/CMR1000.py b/pyBrematic/devices/intertechno/CMR1000.py index 0572ec4..0eb02e4 100644 --- a/pyBrematic/devices/intertechno/CMR1000.py +++ b/pyBrematic/devices/intertechno/CMR1000.py @@ -32,6 +32,7 @@ class CMR1000(IntertechnoDevice): on = seq_fl + seq_fl off = seq_fl + seq_low additional = seq_low + seq_fl + supported_actions = {Action.ON: on, Action.OFF: off} def __init__(self, system_code, unit_code): super().__init__(system_code, unit_code) From 02edc392911a2fb071e426046de1377fca4be26f Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 02:30:30 +0100 Subject: [PATCH 31/45] refactor: remove unnecessary 'gateway' parameter from get_signal This is a pretty big change internally. I replaced all the code where any gateway was referred to from within any device class. We still need to access device specific parameters from the code calculation (happening inside the gateway classes) but we don't need to pass any gateway to the device classes anymore. That also means that we don't need multiple tests for the "get_signal" method, because the method is actually gateway independent now. Each gateway has now their own "head_format" and "tail_format" which is filled from certain data from a device during runtime. Also changed the way the signal is generated (using lists now, joining them to a string before returning). fixes #15 --- pyBrematic/devices/autopairdevice.py | 12 ++- pyBrematic/devices/brennenstuhl/RCS1000N.py | 49 +++++------- .../brennenstuhl/tests/brennenstuhl_test.py | 50 +++++-------- pyBrematic/devices/device.py | 2 +- pyBrematic/devices/elro/AB440SA.py | 51 +++++-------- pyBrematic/devices/elro/tests/elro_test.py | 16 ++-- pyBrematic/devices/intertechno/CMR1000.py | 54 ++++++------- pyBrematic/devices/intertechno/ITL500.py | 75 +++++++------------ .../devices/intertechno/intertechnodevice.py | 4 +- .../devices/intertechno/tests/ITL500_test.py | 63 ++++------------ .../intertechno/tests/intertechno_test.py | 60 ++++++--------- .../devices/tests/autopairdevice_test.py | 2 +- pyBrematic/devices/tests/device_test.py | 5 +- pyBrematic/gateways/__init__.py | 5 +- pyBrematic/gateways/brennenstuhl_gateway.py | 18 ++++- pyBrematic/gateways/gateway.py | 12 +++ pyBrematic/gateways/intertechno_gateway.py | 18 ++++- pyBrematic/gateways/tests/__init__.py | 0 .../tests/brennenstuhl_gateway_test.py | 59 +++++++++++++++ .../gateways/tests/connair_gateway_test.py | 59 +++++++++++++++ .../tests/intertechno_gateway_test.py | 61 +++++++++++++++ 21 files changed, 395 insertions(+), 280 deletions(-) create mode 100644 pyBrematic/gateways/tests/__init__.py create mode 100644 pyBrematic/gateways/tests/brennenstuhl_gateway_test.py create mode 100644 pyBrematic/gateways/tests/connair_gateway_test.py create mode 100644 pyBrematic/gateways/tests/intertechno_gateway_test.py diff --git a/pyBrematic/devices/autopairdevice.py b/pyBrematic/devices/autopairdevice.py index 1fea6af..8731c7f 100644 --- a/pyBrematic/devices/autopairdevice.py +++ b/pyBrematic/devices/autopairdevice.py @@ -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) diff --git a/pyBrematic/devices/brennenstuhl/RCS1000N.py b/pyBrematic/devices/brennenstuhl/RCS1000N.py index 069b3e5..c4d4b3a 100644 --- a/pyBrematic/devices/brennenstuhl/RCS1000N.py +++ b/pyBrematic/devices/brennenstuhl/RCS1000N.py @@ -2,7 +2,6 @@ from pyBrematic.action import Action from pyBrematic.devices import Device -from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway # SYSTEM-CODE | unit code @@ -18,29 +17,26 @@ class RCS1000N(Device): # TXP:0,0,10,5600,350,25,,,,,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 @@ -52,25 +48,18 @@ class RCS1000N(Device): def __init__(self, system_code, unit_code): super().__init__(system_code, unit_code) - 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) diff --git a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py index 958fa86..eec8348 100644 --- a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py +++ b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py @@ -20,57 +20,43 @@ def test_RCR1000N(self): """Test to check the functionality of the RCR1000N class""" dev = RCR1000N("10000", "00100") - on_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" - off_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0" + on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" + off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) - self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) - - on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;" - off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;" - - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) def test_RCR1000N_10000_00000(self): """Test to check the functionality of the RCR1000N class""" # Taken from https://github.com/Power-Switch/PowerSwitch_Android/blob/54400f74230bb78f87b19cc89c0f174e080a3fa7/Smartphone/src/androidTest/java/eu/power_switch/obj/device/brennenstuhl/RCS1000NComfort_Test.java#L97 dev = RCR1000N("10000", "00000") - on_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" - off_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0" - - self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) - self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) - - on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;" - off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;" + on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" + off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) def test_RCR1000N_10000_10000(self): """Test to check the functionality of the RCR1000N class""" dev = RCR1000N("10000", "10000") - on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;" - off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;" + on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" + off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) def test_RCS1000N(self): """Test to check the functionality of the RCS1000N class""" dev = RCS1000N("10000", "00100") - on_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" - off_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0" + on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" + off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) - self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) - on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,16;" - off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,16;" - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) +if __name__ == "__main__": + unittest.main() diff --git a/pyBrematic/devices/device.py b/pyBrematic/devices/device.py index d513172..5c33fcd 100644 --- a/pyBrematic/devices/device.py +++ b/pyBrematic/devices/device.py @@ -11,7 +11,7 @@ def __init__(self, system_code, unit_code): self.system_code = system_code self.unit_code = unit_code - 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!") diff --git a/pyBrematic/devices/elro/AB440SA.py b/pyBrematic/devices/elro/AB440SA.py index 5a30e6e..29cecbd 100644 --- a/pyBrematic/devices/elro/AB440SA.py +++ b/pyBrematic/devices/elro/AB440SA.py @@ -2,31 +2,27 @@ from pyBrematic.action import Action from pyBrematic.devices import Device -from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway class AB440SA(Device): """Device class for the ELRO AB440SA remote outlet""" - lo = "1," - hi = "3," - seq_low = lo + hi + lo + hi - seq_high = lo + hi + hi + lo - - sRepeat = 10 - sPauseBS = 5600 - sPauseIT = 11200 - sTune = 350 - sBaudBS = 25 - sBaudIT = 26 - sSpeedBS = 14 - sSpeedIT = 32 + repeat = 10 + tune = 350 txversion = 1 - headITGW = "0,0,{},{},{},{},0,".format(sRepeat, sPauseIT, sTune, sBaudIT) - tailITGW = "{},{},0".format(txversion, sSpeedIT) + pause_BS = 5600 + pause_IT = 11200 - headBSGW = "TXP:0,0,{},{},{},{},".format(sRepeat, sPauseBS, sTune, sBaudBS) - tailBSGW = "{},{};".format(txversion, sSpeedBS) + baud_BS = 25 + baud_IT = 26 + + speed_BS = 14 + speed_IT = 32 + + lo = "1" + hi = "3" + seq_low = [lo, hi, lo, hi] + seq_high = [lo, hi, hi, lo] on = seq_low + seq_high off = seq_high + seq_low @@ -35,24 +31,17 @@ class AB440SA(Device): def __init__(self, system_code, unit_code): super().__init__(system_code, unit_code) - def get_signal(self, gateway, action): + def get_signal(self, action): """Returns a signal which triggers a device to execute the intended action""" 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) diff --git a/pyBrematic/devices/elro/tests/elro_test.py b/pyBrematic/devices/elro/tests/elro_test.py index 780fd2c..ff993ea 100644 --- a/pyBrematic/devices/elro/tests/elro_test.py +++ b/pyBrematic/devices/elro/tests/elro_test.py @@ -21,14 +21,14 @@ def test_AB440SA(self): unit_code = "00100" device = AB440SA(system_code=system_code, unit_code=unit_code) - on_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,32,0" - off_signal_ITGW = "0,0,10,11200,350,26,0,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,32,0" + on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" + off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(device.get_signal(self.itgw, Action.ON), on_signal_ITGW) - self.assertEqual(device.get_signal(self.itgw, Action.OFF), off_signal_ITGW) + self.assertEqual(device.get_signal(Action.ON), on_signal) + self.assertEqual(device.get_signal(Action.OFF), off_signal) - on_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,14;" - off_signal_BSGW = "TXP:0,0,10,5600,350,25,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,14;" + on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" + off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(device.get_signal(self.bsgw, Action.ON), on_signal_BSGW) - self.assertEqual(device.get_signal(self.bsgw, Action.OFF), off_signal_BSGW) + self.assertEqual(device.get_signal(Action.ON), on_signal) + self.assertEqual(device.get_signal(Action.OFF), off_signal) diff --git a/pyBrematic/devices/intertechno/CMR1000.py b/pyBrematic/devices/intertechno/CMR1000.py index 0eb02e4..2194579 100644 --- a/pyBrematic/devices/intertechno/CMR1000.py +++ b/pyBrematic/devices/intertechno/CMR1000.py @@ -1,33 +1,30 @@ # -*- coding: utf-8 -*- from pyBrematic.devices.intertechno import IntertechnoDevice -from pyBrematic.exceptions import GatewayNotSupportedException -from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway +from pyBrematic.action import Action class CMR1000(IntertechnoDevice): """Device class for the Intertechno CMR-1000 remote outlet""" - sRepeat = 6 - sPause = 11125 - sTune = 89 - sBaudBS = 25 - sBaudIT = 26 - sSpeedBS = 140 - sSpeedIT = 125 + repeat = 6 + tune = 89 txversion = 1 - headBSGW = "TXP:0,0,{},{},{},{},".format(sRepeat, sPause, sTune, sBaudBS) - tailBSGW = "{},{};".format(txversion, sSpeedBS) + pause_BS = 11125 + pause_IT = 11125 - headITGW = "0,0,{},{},{},{},0,".format(sRepeat, sPause, sTune, sBaudIT) - tailITGW = "{},{},0".format(txversion, sSpeedIT) + baud_BS = 25 + baud_IT = 26 + + speed_BS = 140 + speed_IT = 125 # Values of high and low bits (binary -> encoded) - # bit_low: 0 -> 1 | bit_high: 1 -> 3 - lo = "4," - hi = "12," + # bit_low: 0 -> 4 | bit_high: 1 -> 12 + lo = "4" + hi = "12" - seq_low = lo + hi + lo + hi - seq_fl = lo + hi + hi + lo + seq_low = [lo, hi, lo, hi] + seq_fl = [lo, hi, hi, lo] on = seq_fl + seq_fl off = seq_fl + seq_low @@ -37,27 +34,18 @@ class CMR1000(IntertechnoDevice): def __init__(self, system_code, unit_code): super().__init__(system_code, unit_code) - 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_fl, self.seq_low) unit_msg = self.encode(self.unit_code, self.seq_fl, self.seq_low) - if isinstance(gateway, BrennenstuhlGateway): - head = self.headBSGW - tail = self.tailBSGW - elif isinstance(gateway, IntertechnoGateway): - head = self.headITGW - tail = self.tailITGW - else: - raise GatewayNotSupportedException + action_signal = self.supported_actions.get(action) # Build the payload of the UDP package depending on the action. - if action == self.ACTION_ON: - data = head + system_msg + unit_msg + self.additional + self.on + tail - elif action == self.ACTION_OFF: - data = head + system_msg + unit_msg + self.additional + self.off + tail - else: + if not action_signal: raise ValueError("Value of 'action' isn't valid!") - return data + data = system_msg + unit_msg + self.additional + action_signal + + return self.join_list(data) diff --git a/pyBrematic/devices/intertechno/ITL500.py b/pyBrematic/devices/intertechno/ITL500.py index bac821c..00cc4c7 100644 --- a/pyBrematic/devices/intertechno/ITL500.py +++ b/pyBrematic/devices/intertechno/ITL500.py @@ -1,24 +1,29 @@ # -*- coding: utf-8 -*- from pyBrematic.action import Action from pyBrematic.devices import AutoPairDevice -from pyBrematic.exceptions import GatewayNotSupportedException -from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway from pyBrematic.utils import Rand class ITL500(AutoPairDevice): """Device class for the Intertechno ITL-500 sun blind switch""" - headBSGW = "TXP:0,0,5,10976,98,66,3,29," - tailBSGW = "3,126" + repeat = 5 + tune = 98 + txversion = 3 - headITGW = "0,0,5,10976,98,67,0,3,29," - tailITGW = "3,112,0" + pause_BS = 10976 + pause_IT = 10976 - lo = "3," - hi = "15," + baud_BS = 66 + baud_IT = 67 - seq_low = lo + lo + lo + hi - seq_fl = lo + hi + lo + lo + speed_IT = 112 + speed_BS = 126 + + lo = "3" + hi = "15" + + seq_low = [lo, lo, lo, hi] + seq_fl = [lo, hi, lo, lo] on = seq_low + seq_low + seq_fl off = seq_low + seq_low + seq_low @@ -27,75 +32,49 @@ class ITL500(AutoPairDevice): def __init__(self, device_id, seed=None): super().__init__(device_id, seed) - 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 prng = Rand(self.seed) - signal = "" - data = "" - - if isinstance(gateway, BrennenstuhlGateway): - head = self.headBSGW - tail = self.tailBSGW - elif isinstance(gateway, IntertechnoGateway): - head = self.headITGW - tail = self.tailITGW - else: - raise GatewayNotSupportedException + signal = ["3", "29"] # Build the payload of the UDP package depending on the action. if action == Action.UNPAIR_ALL: for i in range(24): - signal += self.lo - - signal += self.lo + self.lo + self.hi + self.lo - signal += self.lo + self.lo + self.lo + self.lo + signal.append(self.lo) - data = head + signal + tail + signal += [self.lo, self.lo, self.hi, self.lo] + signal += [self.lo, self.lo, self.lo, self.lo] elif action == Action.UP or action == Action.PAIR: # When the receiver is in pairing mode, the first ON signal sent to the device will be stored by it. # Using a prng and a stored seed to generate the same number sequence in each request - signal += self.hi + signal.append(self.hi) for i in range(24): - if prng.next_bool(): - signal += self.hi - else: - signal += self.lo + signal.append(self.hi if prng.next_bool() else self.lo) signal += self.on for i in range(2): - if prng.next_bool(): - signal += self.hi - else: - signal += self.lo + signal.append(self.hi if prng.next_bool() else self.lo) signal += self.additional - data = head + signal + tail elif action == Action.DOWN or action == Action.UNPAIR: - signal += self.hi + signal.append(self.hi) for i in range(24): - if prng.next_bool(): - signal += self.hi - else: - signal += self.lo + signal.append(self.hi if prng.next_bool() else self.lo) signal += self.off for i in range(2): - if prng.next_bool(): - signal += self.hi - else: - signal += self.lo + signal.append(self.hi if prng.next_bool() else self.lo) signal += self.additional - data = head + signal + tail else: raise ValueError("Value of 'action' isn't valid!") - return data + return self.join_list(signal) diff --git a/pyBrematic/devices/intertechno/intertechnodevice.py b/pyBrematic/devices/intertechno/intertechnodevice.py index c9260c9..ca6f038 100644 --- a/pyBrematic/devices/intertechno/intertechnodevice.py +++ b/pyBrematic/devices/intertechno/intertechnodevice.py @@ -10,9 +10,9 @@ class IntertechnoDevice(Device): def __init__(self, system_code, unit_code): super().__init__(system_code, unit_code) - def get_signal(self, gateway, action): + def get_signal(self, action): """Returns a signal which triggers a device to execute the intended action""" - super().get_signal(gateway, action) + super().get_signal(action) @staticmethod def calc_systemcode(master): diff --git a/pyBrematic/devices/intertechno/tests/ITL500_test.py b/pyBrematic/devices/intertechno/tests/ITL500_test.py index 8a8fb86..93a43de 100644 --- a/pyBrematic/devices/intertechno/tests/ITL500_test.py +++ b/pyBrematic/devices/intertechno/tests/ITL500_test.py @@ -1,69 +1,38 @@ # -*- coding: utf-8 -*- import unittest -from pyBrematic.devices import AutoPairDevice +from pyBrematic import Action from pyBrematic.devices.intertechno import ITL500 -from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway -from pyBrematic.exceptions import GatewayNotSupportedException from unittest.mock import Mock class TestITL500(unittest.TestCase): def setUp(self): - self.bsgw = BrennenstuhlGateway("192.168.178.2") - self.itgw = IntertechnoGateway("192.168.178.3") - seed = 8712387 self.dev = ITL500(123, seed) - def test_invalid_gateway(self): - with self.assertRaises(GatewayNotSupportedException): - fake_gateway = Mock() - self.dev.get_signal(fake_gateway, AutoPairDevice.ACTION_UP) - def test_invalid_action(self): with self.assertRaises(ValueError): fake_action = Mock() - self.dev.get_signal(self.itgw, fake_action) - - def test_up_intertechno(self): - up_signal_ITGW = "0,0,5,10976,98,67,0,3,29," + "15,3,15,15,3,15,15,3,15,15,3,3,15,3,3,15,15,15,3,15,15,15,15,3,15,3,3,3,15,3,3,3,15,3,15,3,3,3,3,3,3,3,15,3,3,3,15," + "3,112,0" - self.assertEqual(up_signal_ITGW, self.dev.get_signal(self.itgw, AutoPairDevice.ACTION_UP)) + self.dev.get_signal(fake_action) - def test_up_brennenstuhl(self): - up_signal_BSGW = "TXP:0,0,5,10976,98,66,3,29," + "15,3,15,15,3,15,15,3,15,15,3,3,15,3,3,15,15,15,3,15,15,15,15,3,15,3,3,3,15,3,3,3,15,3,15,3,3,3,3,3,3,3,15,3,3,3,15," + "3,126" - self.assertEqual(up_signal_BSGW, self.dev.get_signal(self.bsgw, AutoPairDevice.ACTION_UP)) + def test_up_signal(self): + up_signal = "3,29,15,3,15,15,3,15,15,3,15,15,3,3,15,3,3,15,15,15,3,15,15,15,15,3,15,3,3,3,15,3,3,3,15,3,15,3,3,3,3,3,3,3,15,3,3,3,15" + self.assertEqual(up_signal, self.dev.get_signal(Action.UP)) - def test_down_intertechno(self): - down_signal_ITGW = "0,0,5,10976,98,67,0,3,29," + "15,3,15,15,3,15,15,3,15,15,3,3,15,3,3,15,15,15,3,15,15,15,15,3,15,3,3,3,15,3,3,3,15,3,3,3,15,3,3,3,3,3,15,3,3,3,15," + "3,112,0" - self.assertEqual(down_signal_ITGW, self.dev.get_signal(self.itgw, AutoPairDevice.ACTION_DOWN)) + def test_down_signal(self): + down_signal = "3,29,15,3,15,15,3,15,15,3,15,15,3,3,15,3,3,15,15,15,3,15,15,15,15,3,15,3,3,3,15,3,3,3,15,3,3,3,15,3,3,3,3,3,15,3,3,3,15" + self.assertEqual(down_signal, self.dev.get_signal(Action.DOWN)) - def test_down_brennenstuhl(self): - down_signal_BSGW = "TXP:0,0,5,10976,98,66,3,29," + "15,3,15,15,3,15,15,3,15,15,3,3,15,3,3,15,15,15,3,15,15,15,15,3,15,3,3,3,15,3,3,3,15,3,3,3,15,3,3,3,3,3,15,3,3,3,15," + "3,126" - self.assertEqual(down_signal_BSGW, self.dev.get_signal(self.bsgw, AutoPairDevice.ACTION_DOWN)) - - def test_other_seed_intertechno(self): + def test_other_seed(self): seed2 = 89090823173454879234 dev_new = ITL500(1234, seed2) - up_signal_ITGW = "0,0,5,10976,98,67,0,3,29," + "15,3,15,15,3,3,3,3,3,15,3,3,15,15,3,3,15,15,3,3,15,15,15,3,15,3,3,3,15,3,3,3,15,3,15,3,3,3,3,3,3,3,15,3,3,3,15," + "3,112,0" - down_signal_ITGW = "0,0,5,10976,98,67,0,3,29," + "15,3,15,15,3,3,3,3,3,15,3,3,15,15,3,3,15,15,3,3,15,15,15,3,15,3,3,3,15,3,3,3,15,3,3,3,15,3,3,3,3,3,15,3,3,3,15," + "3,112,0" - self.assertEqual(up_signal_ITGW, dev_new.get_signal(self.itgw, AutoPairDevice.ACTION_UP)) - self.assertEqual(down_signal_ITGW, dev_new.get_signal(self.itgw, AutoPairDevice.ACTION_DOWN)) - - def test_other_seed_brennenstuhl(self): - seed2 = 89090823173454879234 - dev_new = ITL500(1234, seed2) - - up_signal_BSGW = "TXP:0,0,5,10976,98,66,3,29," + "15,3,15,15,3,3,3,3,3,15,3,3,15,15,3,3,15,15,3,3,15,15,15,3,15,3,3,3,15,3,3,3,15,3,15,3,3,3,3,3,3,3,15,3,3,3,15," + "3,126" - down_signal_BSGW = "TXP:0,0,5,10976,98,66,3,29," + "15,3,15,15,3,3,3,3,3,15,3,3,15,15,3,3,15,15,3,3,15,15,15,3,15,3,3,3,15,3,3,3,15,3,3,3,15,3,3,3,3,3,15,3,3,3,15," + "3,126" - self.assertEqual(up_signal_BSGW, dev_new.get_signal(self.bsgw, AutoPairDevice.ACTION_UP)) - self.assertEqual(down_signal_BSGW, dev_new.get_signal(self.bsgw, AutoPairDevice.ACTION_DOWN)) - - def test_unpair_all_intertechno(self): - unpair_signal_ITGW = "0,0,5,10976,98,67,0,3,29," + "3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,15,3,3,3,3,3," + "3,112,0" - self.assertEqual(unpair_signal_ITGW, self.dev.get_signal(self.itgw, AutoPairDevice.ACTION_UNPAIR_ALL)) + up_signal = "3,29," + "15,3,15,15,3,3,3,3,3,15,3,3,15,15,3,3,15,15,3,3,15,15,15,3,15,3,3,3,15,3,3,3,15,3,15,3,3,3,3,3,3,3,15,3,3,3,15" + down_signal = "3,29," + "15,3,15,15,3,3,3,3,3,15,3,3,15,15,3,3,15,15,3,3,15,15,15,3,15,3,3,3,15,3,3,3,15,3,3,3,15,3,3,3,3,3,15,3,3,3,15" + self.assertEqual(up_signal, dev_new.get_signal(Action.UP)) + self.assertEqual(down_signal, dev_new.get_signal(Action.DOWN)) - def test_unpair_all_brennenstuhl(self): - unpair_signal_BSGW = "TXP:0,0,5,10976,98,66,3,29," + "3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,15,3,3,3,3,3," + "3,126" - self.assertEqual(unpair_signal_BSGW, self.dev.get_signal(self.bsgw, AutoPairDevice.ACTION_UNPAIR_ALL)) + def test_unpair_all(self): + unpair_signal = "3,29," + "3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,15,3,3,3,3,3" + self.assertEqual(unpair_signal, self.dev.get_signal(Action.UNPAIR_ALL)) diff --git a/pyBrematic/devices/intertechno/tests/intertechno_test.py b/pyBrematic/devices/intertechno/tests/intertechno_test.py index 2d8f830..37f81f0 100644 --- a/pyBrematic/devices/intertechno/tests/intertechno_test.py +++ b/pyBrematic/devices/intertechno/tests/intertechno_test.py @@ -4,14 +4,12 @@ from pyBrematic.action import Action from pyBrematic.devices.intertechno import PAR1500, CMR1000, ITR3500 -from pyBrematic.gateways import BrennenstuhlGateway, IntertechnoGateway class TestIntertechno(unittest.TestCase): def setUp(self): - self.bsgw = BrennenstuhlGateway("192.168.178.2") - self.itgw = IntertechnoGateway("192.168.178.3") + pass def tearDown(self): pass @@ -20,64 +18,52 @@ def test_PAR1500(self): """Test to check the functionality of the PAR1500 class""" dev = PAR1500("1000", "1111") - on_signal_ITGW = "0,0,6,11125,89,26,0,4,12,12,4,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,125,0" - off_signal_ITGW = "0,0,6,11125,89,26,0,4,12,12,4,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,125,0" - - self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) - self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) - - on_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,12,4,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,140;" - off_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,12,4,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,140;" + on_signal = "4,12,12,4,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4" + off_signal = "4,12,12,4,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12" - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) def test_CMR1000(self): """Test to check the functionality of the CMR1000 class""" dev = CMR1000("0000", "0010") # binary representation of A5 - on_signal_ITGW = "0,0,6,11125,89,26,0,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,125,0" - off_signal_ITGW = "0,0,6,11125,89,26,0,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,125,0" + on_signal = "4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4" + off_signal = "4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12" - self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) - self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) - on_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,140;" - off_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,140;" + on_signal_2 = "4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4" + off_signal_2 = "4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12" system_code = dev.calc_systemcode("A") unit_code = dev.calc_unitcode(5) dev2 = CMR1000(system_code, unit_code) - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(on_signal_BSGW, dev2.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) - self.assertEqual(off_signal_BSGW, dev2.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(on_signal_2, dev.get_signal(Action.ON)) + self.assertEqual(on_signal_2, dev2.get_signal(Action.ON)) + self.assertEqual(off_signal_2, dev.get_signal(Action.OFF)) + self.assertEqual(off_signal_2, dev2.get_signal(Action.OFF)) def test_CMR1000_generation(self): system_code = CMR1000.calc_systemcode("P") unit_code = CMR1000.calc_unitcode(16) dev = CMR1000(system_code, unit_code) - on_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,140;" - off_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,140;" - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + on_signal = "4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4" + off_signal = "4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,12,4,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12" + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) def test_ITR3500(self): """Test to check the functionality of the ITR3500 class""" dev = ITR3500("0000", "0010") # binary representation of A5 - on_signal_ITGW = "0,0,6,11125,89,26,0,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,125,0" - off_signal_ITGW = "0,0,6,11125,89,26,0,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,125,0" - - self.assertEqual(on_signal_ITGW, dev.get_signal(self.itgw, Action.ON)) - self.assertEqual(off_signal_ITGW, dev.get_signal(self.itgw, Action.OFF)) - - on_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4,1,140;" - off_signal_BSGW = "TXP:0,0,6,11125,89,25,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12,1,140;" + on_signal = "4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,12,4" + off_signal = "4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,4,12,12,4,4,12,4,12,4,12,4,12,4,12,12,4,4,12,12,4,4,12,4,12" - self.assertEqual(on_signal_BSGW, dev.get_signal(self.bsgw, Action.ON)) - self.assertEqual(off_signal_BSGW, dev.get_signal(self.bsgw, Action.OFF)) + self.assertEqual(on_signal, dev.get_signal(Action.ON)) + self.assertEqual(off_signal, dev.get_signal(Action.OFF)) def test_calc_unitcode(self): with self.assertRaises(expected_exception=ValueError): diff --git a/pyBrematic/devices/tests/autopairdevice_test.py b/pyBrematic/devices/tests/autopairdevice_test.py index fbf8903..4547861 100644 --- a/pyBrematic/devices/tests/autopairdevice_test.py +++ b/pyBrematic/devices/tests/autopairdevice_test.py @@ -19,4 +19,4 @@ def test_Device(self): """Test to make sure that call to method raises an exception""" dev = AutoPairDevice("10000", "00100") with self.assertRaises(NotImplementedError): - dev.get_signal(self.gw, Action.ON) + dev.get_signal(Action.ON) diff --git a/pyBrematic/devices/tests/device_test.py b/pyBrematic/devices/tests/device_test.py index c54a9f4..31466b0 100644 --- a/pyBrematic/devices/tests/device_test.py +++ b/pyBrematic/devices/tests/device_test.py @@ -4,13 +4,12 @@ from pyBrematic.action import Action from pyBrematic.devices import Device -from pyBrematic.gateways import BrennenstuhlGateway class TestDevice(unittest.TestCase): def setUp(self): - self.gw = BrennenstuhlGateway("192.168.178.2") + pass def tearDown(self): pass @@ -19,4 +18,4 @@ def test_Device(self): """Test to make sure that call to method raises an exception""" dev = Device("10000", "00100") with self.assertRaises(NotImplementedError): - dev.get_signal(self.gw, Action.ON) + dev.get_signal(Action.ON) diff --git a/pyBrematic/gateways/__init__.py b/pyBrematic/gateways/__init__.py index 2d5682f..b86bcc1 100644 --- a/pyBrematic/gateways/__init__.py +++ b/pyBrematic/gateways/__init__.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from .brennenstuhl_gateway import BrennenstuhlGateway from .gateway import Gateway +from .brennenstuhl_gateway import BrennenstuhlGateway from .intertechno_gateway import IntertechnoGateway +from .connair_gateway import ConnAirGateway -__all__ = ["Gateway", "IntertechnoGateway", "BrennenstuhlGateway"] +__all__ = ["Gateway", "IntertechnoGateway", "BrennenstuhlGateway", "ConnAirGateway"] diff --git a/pyBrematic/gateways/brennenstuhl_gateway.py b/pyBrematic/gateways/brennenstuhl_gateway.py index e7d7b17..3ea409e 100644 --- a/pyBrematic/gateways/brennenstuhl_gateway.py +++ b/pyBrematic/gateways/brennenstuhl_gateway.py @@ -9,11 +9,25 @@ class BrennenstuhlGateway(Gateway): def __init__(self, ip, port=49880): super().__init__(ip, port) + # Each UDP package sent to the gateway must contain certain parameters set in a header and a tail. + # These are format strings used to insert those parameters. + # For the Brematic GWY 433 the parameters look like that: + # head: "TXP:0,0,,,," + # tail: ",;" + self.head_format = "TXP:0,0,{0},{1},{2},{3}" + self.tail_format = "{0},{1};" + + def build_udp_payload(self, device, action): + head = self.get_head(device.repeat, device.pause_BS, device.tune, device.baud) + tail = self.get_tail(device.txversion, device.speed_BS) + payload = device.get_signal(self, action) + + return ",".join([head, payload, tail]) def send_request(self, device, action): """Sends the UDP request to the gateway""" - data = device.get_signal(self, action) + payload = self.build_udp_payload() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.sendto(data.encode(), (self._ip, self._port)) + s.sendto(payload.encode(), (self._ip, self._port)) s.close() diff --git a/pyBrematic/gateways/gateway.py b/pyBrematic/gateways/gateway.py index 45e9a25..d70ef12 100644 --- a/pyBrematic/gateways/gateway.py +++ b/pyBrematic/gateways/gateway.py @@ -13,6 +13,18 @@ class Gateway(object): def __init__(self, ip, port): self._ip = ip self._port = port + self.head_format = None + self.tail_format = None + + def get_head(self, repeat, pause, tune, baud): + if not self.head_format: + raise ValueError("No head_format found for gateway") + return self.head_format.format(repeat, pause, tune, baud) + + def get_tail(self, txversion, speed): + if not self.tail_format: + raise ValueError("No tail_format found for gateway") + return self.tail_format.format(txversion, speed) def send_request(self, device, action): raise NotImplementedError("Subclasses must implement this method!") diff --git a/pyBrematic/gateways/intertechno_gateway.py b/pyBrematic/gateways/intertechno_gateway.py index 1f728c0..edc6a45 100644 --- a/pyBrematic/gateways/intertechno_gateway.py +++ b/pyBrematic/gateways/intertechno_gateway.py @@ -9,11 +9,25 @@ class IntertechnoGateway(Gateway): def __init__(self, ip, port=49880): super().__init__(ip, port) + # Each UDP package sent to the gateway must contain certain parameters set in a header and a tail. + # These are format strings used to insert those parameters. + # For the Intertechno GWY 433 the parameters look like that: + # head: "0,0,,,," + # tail: "," + self.head_format = "0,0,{0},{1},{2},{3}" + self.tail_format = "{0},{1}" + + def build_udp_payload(self, device, action): + head = self.get_head(device.repeat, device.pause_IT, device.tune, device.baud) + tail = self.get_tail(device.txversion, device.speed_IT) + payload = device.get_signal(self, action) + + return ",".join([head, payload, tail]) def send_request(self, device, action): """Sends the UDP request to the gateway""" - data = device.get_signal(self, action) + payload = self.build_udp_payload(device, action) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.sendto(data.encode(), (self._ip, self._port)) + s.sendto(payload.encode(), (self._ip, self._port)) s.close() diff --git a/pyBrematic/gateways/tests/__init__.py b/pyBrematic/gateways/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py b/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py new file mode 100644 index 0000000..ca1f9d9 --- /dev/null +++ b/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +import unittest +from unittest.mock import Mock + +from pyBrematic.gateways import BrennenstuhlGateway +from pyBrematic import Action + + +class TestBrennenstuhlGateway(unittest.TestCase): + + def setUp(self): + self.ip = "192.168.1.100" + self.port = "1234" + self.gw = BrennenstuhlGateway(self.ip, self.port) + + def test_build_udp_payload(self): + """Test if building the UDP payload for the Brennenstuhl GW works correctly""" + device = Mock() + device.repeat = 123 + device.pause_BS = 987 + device.tune = 567 + device.baud = 999 + device.txversion = 1 + device.speed_BS = 234 + + function = Mock() + function.return_value = "SIGNAL-A,B,B,A-SIGNAL" + device.get_signal = function + + payload = self.gw.build_udp_payload(device, Action.ON) + self.assertEqual("TXP:0,0,123,987,567,999,SIGNAL-A,B,B,A-SIGNAL,1,234;", payload) + + def test_get_head(self): + """Test if formatting the 'head' string works with random data""" + self.gw.head_format = "1{0}----{1}++++{2}ASDF{3}#24/" + head = self.gw.get_head(777, "ASDF1", 313, "-A-") + self.assertEqual("1777----ASDF1++++313ASDF-A-#24/", head) + + def test_get_tail(self): + """Test if formatting the 'tail' string works with random data""" + self.gw.tail_format = "1{0}---++---{1}++ASDF#24/" + tail = self.gw.get_tail("//-ASDF1", 313) + self.assertEqual("1//-ASDF1---++---313++ASDF#24/", tail) + + def test_missing_head_format(self): + """Test if missing 'head' format is handled correctly""" + self.gw.head_format = None + with self.assertRaises(ValueError): + _ = self.gw.get_head("1", "2", "3", "4") + + def test_invalid_tail_format(self): + """Test if missing 'tail' format is handled correctly""" + self.gw.tail_format = None + with self.assertRaises(ValueError): + _ = self.gw.get_tail("1", "2") + + +if __name__ == '__main__': + unittest.main() diff --git a/pyBrematic/gateways/tests/connair_gateway_test.py b/pyBrematic/gateways/tests/connair_gateway_test.py new file mode 100644 index 0000000..7e89f34 --- /dev/null +++ b/pyBrematic/gateways/tests/connair_gateway_test.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +import unittest +from unittest.mock import Mock + +from pyBrematic import Action +from pyBrematic.gateways import ConnAirGateway + + +class TestConnAirGateway(unittest.TestCase): + + def setUp(self): + self.ip = "192.168.1.100" + self.port = "1234" + self.gw = ConnAirGateway(self.ip, self.port) + + def test_build_udp_payload(self): + """Test if building the UDP payload for the ConnAir GW works correctly""" + device = Mock() + device.repeat = 2345 + device.pause_BS = 917 + device.tune = 557 + device.baud = 91919 + device.txversion = 1 + device.speed_BS = 234 + + function = Mock() + function.return_value = "A,SIGNAL-A,B,B,A-SIGNAL,C" + device.get_signal = function + + payload = self.gw.build_udp_payload(device, Action.ON) + self.assertEqual("TXP:0,0,2345,917,557,91919,A,SIGNAL-A,B,B,A-SIGNAL,C,1,234;", payload) + + def test_get_head(self): + """Test if formatting the 'head' string works with random data""" + self.gw.head_format = "1{0}----{1}++++{2}ASDF{3}#24/" + head = self.gw.get_head(777, "ASDF1", 313, "-A-") + self.assertEqual("1777----ASDF1++++313ASDF-A-#24/", head) + + def test_get_tail(self): + """Test if formatting the 'tail' string works with random data""" + self.gw.tail_format = "1{0}---++---{1}++ASDF#24/" + tail = self.gw.get_tail("//-ASDF1", 313) + self.assertEqual("1//-ASDF1---++---313++ASDF#24/", tail) + + def test_missing_head_format(self): + """Test if missing 'head' format is handled correctly""" + self.gw.head_format = None + with self.assertRaises(ValueError): + _ = self.gw.get_head("1", "2", "3", "4") + + def test_invalid_tail_format(self): + """Test if missing 'tail' format is handled correctly""" + self.gw.tail_format = None + with self.assertRaises(ValueError): + _ = self.gw.get_tail("1", "2") + + +if __name__ == '__main__': + unittest.main() diff --git a/pyBrematic/gateways/tests/intertechno_gateway_test.py b/pyBrematic/gateways/tests/intertechno_gateway_test.py new file mode 100644 index 0000000..ddb0fb5 --- /dev/null +++ b/pyBrematic/gateways/tests/intertechno_gateway_test.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +import unittest +from unittest.mock import Mock + +from pyBrematic import Action +from pyBrematic.gateways import IntertechnoGateway + + +class TestIntertechnoGateway(unittest.TestCase): + + def setUp(self): + self.ip = "192.168.1.100" + self.port = "1234" + self.gw = IntertechnoGateway(self.ip, self.port) + + def test_build_udp_payload(self): + """Test if building the UDP payload for the Intertechno GW works correctly""" + device = Mock() + device.repeat = 2345 + device.pause_BS = 917 + device.pause_IT = 111 + device.tune = 557 + device.baud = 91919 + device.txversion = 1 + device.speed_BS = 234 + device.speed_IT = 432 + + function = Mock() + function.return_value = "A,SIGNAL-A,B,B,A-SIGNAL,C" + device.get_signal = function + + payload = self.gw.build_udp_payload(device, Action.ON) + self.assertEqual("0,0,2345,111,557,91919,A,SIGNAL-A,B,B,A-SIGNAL,C,1,432", payload) + + def test_get_head(self): + """Test if formatting the 'head' string works with random data""" + self.gw.head_format = "1{0}----{1}++++{2}ASDF{3}#24/" + head = self.gw.get_head(777, "ASDF1", 313, "-A-") + self.assertEqual("1777----ASDF1++++313ASDF-A-#24/", head) + + def test_get_tail(self): + """Test if formatting the 'tail' string works with random data""" + self.gw.tail_format = "1{0}---++---{1}++ASDF#24/" + tail = self.gw.get_tail("//-ASDF1", 313) + self.assertEqual("1//-ASDF1---++---313++ASDF#24/", tail) + + def test_missing_head_format(self): + """Test if missing 'head' format is handled correctly""" + self.gw.head_format = None + with self.assertRaises(ValueError): + _ = self.gw.get_head("1", "2", "3", "4") + + def test_invalid_tail_format(self): + """Test if missing 'tail' format is handled correctly""" + self.gw.tail_format = None + with self.assertRaises(ValueError): + _ = self.gw.get_tail("1", "2") + + +if __name__ == '__main__': + unittest.main() From e76e808988ad5d92b3ad4221b7b1297f6f673b06 Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 02:37:51 +0100 Subject: [PATCH 32/45] chore: improve flake8 config --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index a57e1d1..660561d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,5 @@ description-file = README.md [flake8] exclude = .git,__pycache__,build,dist,.env,.venv,venv +max-line-length = 160 +ignore = E501 From 5dd84022fc3aacd9a2f8f76efab2cb3b8b66866d Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 02:38:19 +0100 Subject: [PATCH 33/45] refactor: remove unused imports --- pyBrematic/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyBrematic/__init__.py b/pyBrematic/__init__.py index cd7c6a1..42d7f2e 100644 --- a/pyBrematic/__init__.py +++ b/pyBrematic/__init__.py @@ -1,6 +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", "Action"] +__all__ = ["Gateway", "Action"] From 059fe89b27b461adbb3baf406979ce602594dc27 Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 02:42:33 +0100 Subject: [PATCH 34/45] docs: add IC column to device README files --- pyBrematic/devices/brennenstuhl/README.md | 8 ++++---- pyBrematic/devices/intertechno/README.md | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyBrematic/devices/brennenstuhl/README.md b/pyBrematic/devices/brennenstuhl/README.md index bb7e7b6..59f180d 100644 --- a/pyBrematic/devices/brennenstuhl/README.md +++ b/pyBrematic/devices/brennenstuhl/README.md @@ -1,8 +1,8 @@ # Supported Brennenstuhl devices -|Model|Type|Tested| -|---|---|---| -|RCR1000N|Plug switch|Yes| -|RCS1000N|Plug switch|Yes| +|Model|Type|IC|Tested| +|---|---|---|---| +|RCR1000N|Plug switch|HX2262 / HX2272|Yes| +|RCS1000N|Plug switch|HX2262 / HX2272|Yes| # Useful references - https://github.com/nicolashimmelmann/ESP_GWY433 diff --git a/pyBrematic/devices/intertechno/README.md b/pyBrematic/devices/intertechno/README.md index f2f3c2f..30db167 100644 --- a/pyBrematic/devices/intertechno/README.md +++ b/pyBrematic/devices/intertechno/README.md @@ -1,13 +1,13 @@ # Supported Intertechno devices -|Model|Type|Tested| -|---|---|---| -|CMR300|Switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| -|CMR500|Dimmer|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| -|CMR1000|Switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| -|ITL500|Blind motor switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/5#issuecomment-491618075)| -|ITR300|Plug switch|No (but same as CMR1000)| -|ITR3500|Plug switch|No (but same as CMR1000)| -|PAR1500|Plug switch|[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|Model|Type|IC|Tested| +|---|---|---|---| +|CMR300|Switch||[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|CMR500|Dimmer||[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|CMR1000|Switch||[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| +|ITL500|Blind motor switch||[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/5#issuecomment-491618075)| +|ITR300|Plug switch||No (but same as CMR1000)| +|ITR3500|Plug switch||No (but same as CMR1000)| +|PAR1500|Plug switch||[Yes](https://github.com/d-Rickyy-b/pyBrematic/issues/14#issuecomment-752923204)| # Useful references - https://wiki.fhem.de/wiki/Intertechno_Code_Berechnung#Stellen_8-9_.28Festwert_0F.29 From 240f98d9aa3772f6bd408ad9953274bd273e821f Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 16:33:11 +0100 Subject: [PATCH 35/45] fix: add missing parameters device and action --- pyBrematic/gateways/brennenstuhl_gateway.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyBrematic/gateways/brennenstuhl_gateway.py b/pyBrematic/gateways/brennenstuhl_gateway.py index 3ea409e..142a454 100644 --- a/pyBrematic/gateways/brennenstuhl_gateway.py +++ b/pyBrematic/gateways/brennenstuhl_gateway.py @@ -26,7 +26,7 @@ def build_udp_payload(self, device, action): def send_request(self, device, action): """Sends the UDP request to the gateway""" - payload = self.build_udp_payload() + payload = self.build_udp_payload(device, action) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.sendto(payload.encode(), (self._ip, self._port)) From ac42c009cddf30552998be5ee0068e52c9dea7a9 Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 16:33:39 +0100 Subject: [PATCH 36/45] test: add test for checking default port in gateway --- pyBrematic/gateways/tests/brennenstuhl_gateway_test.py | 5 +++++ pyBrematic/gateways/tests/connair_gateway_test.py | 5 +++++ pyBrematic/gateways/tests/intertechno_gateway_test.py | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py b/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py index ca1f9d9..62fdda3 100644 --- a/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py +++ b/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py @@ -13,6 +13,11 @@ def setUp(self): self.port = "1234" self.gw = BrennenstuhlGateway(self.ip, self.port) + def test_default_port(self): + """Test if default port is correctly set""" + self.gw = BrennenstuhlGateway(self.ip) + self.assertEqual(49880, self.gw._port) + def test_build_udp_payload(self): """Test if building the UDP payload for the Brennenstuhl GW works correctly""" device = Mock() diff --git a/pyBrematic/gateways/tests/connair_gateway_test.py b/pyBrematic/gateways/tests/connair_gateway_test.py index 7e89f34..613b3f8 100644 --- a/pyBrematic/gateways/tests/connair_gateway_test.py +++ b/pyBrematic/gateways/tests/connair_gateway_test.py @@ -13,6 +13,11 @@ def setUp(self): self.port = "1234" self.gw = ConnAirGateway(self.ip, self.port) + def test_default_port(self): + """Test if default port is correctly set""" + self.gw = ConnAirGateway(self.ip) + self.assertEqual(49880, self.gw._port) + def test_build_udp_payload(self): """Test if building the UDP payload for the ConnAir GW works correctly""" device = Mock() diff --git a/pyBrematic/gateways/tests/intertechno_gateway_test.py b/pyBrematic/gateways/tests/intertechno_gateway_test.py index ddb0fb5..aa7be4a 100644 --- a/pyBrematic/gateways/tests/intertechno_gateway_test.py +++ b/pyBrematic/gateways/tests/intertechno_gateway_test.py @@ -13,6 +13,11 @@ def setUp(self): self.port = "1234" self.gw = IntertechnoGateway(self.ip, self.port) + def test_default_port(self): + """Test if default port is correctly set""" + self.gw = IntertechnoGateway(self.ip) + self.assertEqual(49880, self.gw._port) + def test_build_udp_payload(self): """Test if building the UDP payload for the Intertechno GW works correctly""" device = Mock() From ba0870007d7e711ee038a09b46d85a1266373ece Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 16:35:02 +0100 Subject: [PATCH 37/45] test: implement test for send_request for gateways --- .../tests/brennenstuhl_gateway_test.py | 27 +++++++++++++++++-- .../gateways/tests/connair_gateway_test.py | 25 ++++++++++++++++- .../tests/intertechno_gateway_test.py | 24 ++++++++++++++++- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py b/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py index 62fdda3..6eb9d84 100644 --- a/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py +++ b/pyBrematic/gateways/tests/brennenstuhl_gateway_test.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- +import socket import unittest -from unittest.mock import Mock +from unittest.mock import Mock, patch -from pyBrematic.gateways import BrennenstuhlGateway from pyBrematic import Action +from pyBrematic.gateways import BrennenstuhlGateway class TestBrennenstuhlGateway(unittest.TestCase): @@ -59,6 +60,28 @@ def test_invalid_tail_format(self): with self.assertRaises(ValueError): _ = self.gw.get_tail("1", "2") + @patch("socket.socket") + def test_send_request(self, socket_class_mock): + device_mock = Mock() + # Mock method to prevent issues with that + build_udp_payload_mock = Mock() + payload_mock = Mock() + # Since "encode" is being called on the payload object later on, we need to set a return value + payload_mock.encode.return_value = "Test Payload" + build_udp_payload_mock.return_value = payload_mock + self.gw.build_udp_payload = build_udp_payload_mock + action_mock = Mock() + + socket_instance_mock = Mock() + socket_class_mock.return_value = socket_instance_mock + + self.gw.send_request(device_mock, action_mock) + + build_udp_payload_mock.assert_called_once_with(device_mock, action_mock) + + socket_class_mock.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM) + socket_instance_mock.sendto.assert_called_once_with("Test Payload", (self.ip, self.port)) + if __name__ == '__main__': unittest.main() diff --git a/pyBrematic/gateways/tests/connair_gateway_test.py b/pyBrematic/gateways/tests/connair_gateway_test.py index 613b3f8..30f29e7 100644 --- a/pyBrematic/gateways/tests/connair_gateway_test.py +++ b/pyBrematic/gateways/tests/connair_gateway_test.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- +import socket import unittest -from unittest.mock import Mock +from unittest.mock import Mock, patch from pyBrematic import Action from pyBrematic.gateways import ConnAirGateway @@ -59,6 +60,28 @@ def test_invalid_tail_format(self): with self.assertRaises(ValueError): _ = self.gw.get_tail("1", "2") + @patch("socket.socket") + def test_send_request(self, socket_class_mock): + device_mock = Mock() + # Mock method to prevent issues with that + build_udp_payload_mock = Mock() + payload_mock = Mock() + # Since "encode" is being called on the payload object later on, we need to set a return value + payload_mock.encode.return_value = "Test Payload" + build_udp_payload_mock.return_value = payload_mock + self.gw.build_udp_payload = build_udp_payload_mock + action_mock = Mock() + + socket_instance_mock = Mock() + socket_class_mock.return_value = socket_instance_mock + + self.gw.send_request(device_mock, action_mock) + + build_udp_payload_mock.assert_called_once_with(device_mock, action_mock) + + socket_class_mock.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM) + socket_instance_mock.sendto.assert_called_once_with("Test Payload", (self.ip, self.port)) + if __name__ == '__main__': unittest.main() diff --git a/pyBrematic/gateways/tests/intertechno_gateway_test.py b/pyBrematic/gateways/tests/intertechno_gateway_test.py index aa7be4a..759c7cc 100644 --- a/pyBrematic/gateways/tests/intertechno_gateway_test.py +++ b/pyBrematic/gateways/tests/intertechno_gateway_test.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- +import socket import unittest -from unittest.mock import Mock +from unittest.mock import Mock, patch from pyBrematic import Action from pyBrematic.gateways import IntertechnoGateway @@ -61,6 +62,27 @@ def test_invalid_tail_format(self): with self.assertRaises(ValueError): _ = self.gw.get_tail("1", "2") + @patch("socket.socket") + def test_send_request(self, socket_class_mock): + device_mock = Mock() + # Mock method to prevent issues with that + build_udp_payload_mock = Mock() + payload_mock = Mock() + # Since "encode" is being called on the payload object later on, we need to set a return value + payload_mock.encode.return_value = "Test Payload" + build_udp_payload_mock.return_value = payload_mock + self.gw.build_udp_payload = build_udp_payload_mock + action_mock = Mock() + + socket_instance_mock = Mock() + socket_class_mock.return_value = socket_instance_mock + + self.gw.send_request(device_mock, action_mock) + + build_udp_payload_mock.assert_called_once_with(device_mock, action_mock) + + socket_class_mock.assert_called_once_with(socket.AF_INET, socket.SOCK_DGRAM) + socket_instance_mock.sendto.assert_called_once_with("Test Payload", (self.ip, self.port)) if __name__ == '__main__': unittest.main() From 9686bd17352c8e73bed7aefe113803bdf2dd71ea Mon Sep 17 00:00:00 2001 From: Rico Date: Sun, 3 Jan 2021 16:35:20 +0100 Subject: [PATCH 38/45] style: add missing empty line --- pyBrematic/devices/intertechno/tests/tools_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyBrematic/devices/intertechno/tests/tools_test.py b/pyBrematic/devices/intertechno/tests/tools_test.py index 69278a7..fca6597 100644 --- a/pyBrematic/devices/intertechno/tests/tools_test.py +++ b/pyBrematic/devices/intertechno/tests/tools_test.py @@ -128,5 +128,6 @@ def test_calc_system_and_unit_code_issue(self): with self.assertRaises(ValueError): _, _ = calc_system_and_unit_code("0000100001") + if __name__ == "__main__": unittest.main() From 4e0b470f56bbfbc06abe83c29f44765fa9c3e316 Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 19:47:18 +0100 Subject: [PATCH 39/45] test: add test for invalid and valid actions for get_signal --- .../brennenstuhl/tests/brennenstuhl_test.py | 20 +++++++++++++++ pyBrematic/devices/elro/tests/elro_test.py | 25 +++++++++++++++++++ .../intertechno/tests/intertechno_test.py | 17 +++++++++++++ 3 files changed, 62 insertions(+) diff --git a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py index eec8348..11deffd 100644 --- a/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py +++ b/pyBrematic/devices/brennenstuhl/tests/brennenstuhl_test.py @@ -57,6 +57,26 @@ def test_RCS1000N(self): self.assertEqual(on_signal, dev.get_signal(Action.ON)) self.assertEqual(off_signal, dev.get_signal(Action.OFF)) + def test_invalid_action(self): + """Test to check if invalid actions raise an exception""" + system_code = "10000" + unit_code = "00100" + device = RCS1000N(system_code=system_code, unit_code=unit_code) + + with self.assertRaises(ValueError): + _ = device.get_signal("WrongAction!") + + def test_valid_action(self): + """Test to check if valid actions are set up correctly""" + system_code = "10000" + unit_code = "00100" + device = RCS1000N(system_code=system_code, unit_code=unit_code) + + self.assertIsNotNone(device.supported_actions.get(Action.ON)) + self.assertIsNotNone(device.supported_actions.get(Action.OFF)) + + self.assertEqual(device.on, device.supported_actions.get(Action.ON)) + self.assertEqual(device.off, device.supported_actions.get(Action.OFF)) if __name__ == "__main__": unittest.main() diff --git a/pyBrematic/devices/elro/tests/elro_test.py b/pyBrematic/devices/elro/tests/elro_test.py index ff993ea..ecde30b 100644 --- a/pyBrematic/devices/elro/tests/elro_test.py +++ b/pyBrematic/devices/elro/tests/elro_test.py @@ -32,3 +32,28 @@ def test_AB440SA(self): self.assertEqual(device.get_signal(Action.ON), on_signal) self.assertEqual(device.get_signal(Action.OFF), off_signal) + + def test_invalid_action(self): + """Test to check if invalid actions raise an exception""" + system_code = "10000" + unit_code = "00100" + device = AB440SA(system_code=system_code, unit_code=unit_code) + + with self.assertRaises(ValueError): + _ = device.get_signal("WrongAction!") + + def test_valid_action(self): + """Test to check if valid actions are set up correctly""" + system_code = "10000" + unit_code = "00100" + device = AB440SA(system_code=system_code, unit_code=unit_code) + + self.assertIsNotNone(device.supported_actions.get(Action.ON)) + self.assertIsNotNone(device.supported_actions.get(Action.OFF)) + + self.assertEqual(device.on, device.supported_actions.get(Action.ON)) + self.assertEqual(device.off, device.supported_actions.get(Action.OFF)) + + +if __name__ == "__main__": + unittest.main() diff --git a/pyBrematic/devices/intertechno/tests/intertechno_test.py b/pyBrematic/devices/intertechno/tests/intertechno_test.py index 37f81f0..20a190d 100644 --- a/pyBrematic/devices/intertechno/tests/intertechno_test.py +++ b/pyBrematic/devices/intertechno/tests/intertechno_test.py @@ -55,6 +55,23 @@ def test_CMR1000_generation(self): self.assertEqual(on_signal, dev.get_signal(Action.ON)) self.assertEqual(off_signal, dev.get_signal(Action.OFF)) + def test_invalid_action(self): + """Test to check if invalid actions raise an exception""" + device = CMR1000("0000", "0010") + + with self.assertRaises(ValueError): + _ = device.get_signal("WrongAction!") + + def test_valid_action(self): + """Test to check if valid actions are set up correctly""" + device = CMR1000("0000", "0010") + + self.assertIsNotNone(device.supported_actions.get(Action.ON)) + self.assertIsNotNone(device.supported_actions.get(Action.OFF)) + + self.assertEqual(device.on, device.supported_actions.get(Action.ON)) + self.assertEqual(device.off, device.supported_actions.get(Action.OFF)) + def test_ITR3500(self): """Test to check the functionality of the ITR3500 class""" dev = ITR3500("0000", "0010") # binary representation of A5 From b1dd0a190307e124e881622d677627f1f9b1485a Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 19:47:41 +0100 Subject: [PATCH 40/45] test: add check for device "encode" method --- pyBrematic/devices/tests/device_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyBrematic/devices/tests/device_test.py b/pyBrematic/devices/tests/device_test.py index 31466b0..1b62305 100644 --- a/pyBrematic/devices/tests/device_test.py +++ b/pyBrematic/devices/tests/device_test.py @@ -19,3 +19,18 @@ def test_Device(self): dev = Device("10000", "00100") with self.assertRaises(NotImplementedError): dev.get_signal(Action.ON) + + def test_encode_error(self): + dev = Device("10000", "00100") + with self.assertRaises(ValueError): + dev.encode("000123000", "A", "B") + + def test_encode(self): + dev = Device("10000", "00100") + encoded = dev.encode("000111", "A", "B") + self.assertEqual(list, type(encoded)) + self.assertEqual(["B", "B", "B", "A", "A", "A"], encoded) + + +if __name__ == "__main__": + unittest.main() From 9e2159ece570cd22489d514de2f735f574f9ee7c Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 19:48:09 +0100 Subject: [PATCH 41/45] test: set loglevel to error to remove logging during test execution --- pyBrematic/utils/tests/storage_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyBrematic/utils/tests/storage_test.py b/pyBrematic/utils/tests/storage_test.py index 28426e2..7835583 100644 --- a/pyBrematic/utils/tests/storage_test.py +++ b/pyBrematic/utils/tests/storage_test.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import json +import logging import unittest from pyBrematic.utils import Storage @@ -99,6 +100,8 @@ def test_add_device(self): self.assertEqual(dev.seed, seed) def test_duplicate_storage(self): + # Set log-level to "ERROR" to prevent text output during test execution + logging.getLogger().setLevel(logging.ERROR) self.storage.add_device(1337) self.assertEqual(1, len(self.storage.devices)) From 8d4479b1936ef688398d9077d4562a246ebdad40 Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 19:48:33 +0100 Subject: [PATCH 42/45] test: use correct order in test for expected and actual values --- pyBrematic/devices/elro/tests/elro_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyBrematic/devices/elro/tests/elro_test.py b/pyBrematic/devices/elro/tests/elro_test.py index ecde30b..a94c457 100644 --- a/pyBrematic/devices/elro/tests/elro_test.py +++ b/pyBrematic/devices/elro/tests/elro_test.py @@ -24,14 +24,14 @@ def test_AB440SA(self): on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(device.get_signal(Action.ON), on_signal) - self.assertEqual(device.get_signal(Action.OFF), off_signal) + self.assertEqual(on_signal, device.get_signal(Action.ON)) + self.assertEqual(off_signal, device.get_signal(Action.OFF)) on_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1" off_signal = "1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3,1,3,3,1,1,3,3,1,1,3,3,1,1,3,1,3" - self.assertEqual(device.get_signal(Action.ON), on_signal) - self.assertEqual(device.get_signal(Action.OFF), off_signal) + self.assertEqual(on_signal, device.get_signal(Action.ON)) + self.assertEqual(off_signal, device.get_signal(Action.OFF)) def test_invalid_action(self): """Test to check if invalid actions raise an exception""" From 97daf51e90fbda7cae78eef47f9457de1aae78a6 Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 19:49:22 +0100 Subject: [PATCH 43/45] CI: implement coveralls on GH actions (#19) * CI: implement coveralls on GH actions * CI: remove testing branch from GitHub action --- .github/workflows/python-lint-test.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-lint-test.yml b/.github/workflows/python-lint-test.yml index c358a6c..64a89ab 100644 --- a/.github/workflows/python-lint-test.yml +++ b/.github/workflows/python-lint-test.yml @@ -26,7 +26,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 pytest pytest-cov + python -m pip install flake8 pytest coveralls if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | @@ -36,4 +36,9 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest --cov=./ --cov-report=xml + coverage run --omit='*/virtualenv/*,*/site-packages/*' -m pytest + - name: Publish coverage + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + coveralls From 3d2661f26ea2b160e234b915e2ad34ba600d72ef Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 21:00:11 +0100 Subject: [PATCH 44/45] ci: remove coverage from pypi-package build action --- .github/workflows/python-pypi-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-pypi-publish.yml b/.github/workflows/python-pypi-publish.yml index 6688abd..cec8a13 100644 --- a/.github/workflows/python-pypi-publish.yml +++ b/.github/workflows/python-pypi-publish.yml @@ -31,7 +31,7 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest --cov=./ --cov-report=xml + pytest - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} From 9486ebeb78ffa9492133344eaab94d15eaed09ff Mon Sep 17 00:00:00 2001 From: Rico Date: Tue, 5 Jan 2021 21:00:27 +0100 Subject: [PATCH 45/45] docs: add changelog --- CHANGELOG.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..db4d465 --- /dev/null +++ b/CHANGELOG.md @@ -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