class Logger: def __init__(self, address, port): # connect to the cube self.cube = MaxCube(MaxCubeConnection(address, port)) def log(self, today): self.cube.update() now = time.strftime("%Y-%m-%d %H:%M") apartments = {} print(len(self.cube.devices), "devices found") for device in self.cube.devices: id = device.name.split("_")[0] if id not in apartments: apartments[id] = [] apartments[id].append(device) today_file = today + "-log.csv" if not Path(today_file).is_file(): # create a new file if it doesn't exist with open(today_file, "w") as file: file.write("time,apartment,thermostat,valve,temp\n") file.close() with open(today_file, "a") as file: row = ",".join([ now, id, device.name, str(device.valve_position), str(device.actual_temperature) ]) + "\n" file.write(row) file.close()
class TestMaxCube(TestCase): """ Test the Max! Cube. """ def init(self, ClassMock, responses): self.commander = ClassMock.return_value self.commander.update.return_value = responses self.cube = MaxCube("host", 1234, now=lambda: datetime(2012, 10, 22, 5, 30)) self.commander.update.assert_called_once() self.commander.update.reset_mock() self.commander.send_radio_msg.return_value = True def test_init(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.assertEqual("KEQ0566338", self.cube.serial) self.assertEqual("0b6475", self.cube.rf_address) self.assertEqual("Cube", self.cube.name) self.assertEqual("01.13", self.cube.firmware_version) self.assertEqual(4, len(self.cube.devices)) device = self.cube.devices[0] self.assertEqual(4.5, device.min_temperature) self.assertEqual(25.0, device.max_temperature) self.assertEqual("06BC53", device.rf_address) self.assertEqual("Kitchen", device.name) self.assertEqual("06BC5A", self.cube.devices[1].rf_address) self.assertEqual("Living", self.cube.devices[1].name) self.assertEqual("08AB82", self.cube.devices[2].rf_address) self.assertEqual("Sleeping", self.cube.devices[2].name) self.assertEqual("06BC5C", self.cube.devices[3].rf_address) self.assertEqual("Work", self.cube.devices[3].name) def __update(self, responses: List[Message]): self.commander.update.return_value = responses self.cube.update() self.commander.update.assert_called_once() def test_parse_auto_l_message(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.__update([LAST_STATE_MSG]) device = self.cube.devices[0] self.assertEqual(MAX_DEVICE_MODE_AUTOMATIC, device.mode) self.assertEqual(23.6, device.actual_temperature) self.assertEqual(17.0, device.target_temperature) def test_parse_manual_l_message(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.__update( [ Message.decode( b"L:Cwa8U/ESGQkhALMACwa8WgkSGQAhAMAACwa8XAkSGQUhALIACwirggMSGQUhAAAA" ) ] ) device = self.cube.devices[0] self.assertEqual(MAX_DEVICE_MODE_MANUAL, device.mode) self.assertEqual(17.9, device.actual_temperature) self.assertEqual(16.5, device.target_temperature) def test_disconnect(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.cube.disconnect() self.commander.disconnect.assert_called_once() def test_use_persistent_connection(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.commander.use_persistent_connection = True self.assertTrue(self.cube.use_persistent_connection) self.cube.use_persistent_connection = False self.assertFalse(self.commander.use_persistent_connection) def test_is_thermostat(self, _): device = MaxDevice() device.type = MAX_CUBE self.assertFalse(device.is_thermostat()) device.type = MAX_THERMOSTAT self.assertTrue(device.is_thermostat()) device.type = MAX_THERMOSTAT_PLUS self.assertTrue(device.is_thermostat()) device.type = MAX_WALL_THERMOSTAT self.assertFalse(device.is_thermostat()) device.type = MAX_WINDOW_SHUTTER self.assertFalse(device.is_thermostat()) def test_is_wall_thermostat(self, _): device = MaxDevice() device.type = MAX_CUBE self.assertFalse(device.is_wallthermostat()) device.type = MAX_THERMOSTAT self.assertFalse(device.is_wallthermostat()) device.type = MAX_THERMOSTAT_PLUS self.assertFalse(device.is_wallthermostat()) device.type = MAX_WALL_THERMOSTAT self.assertTrue(device.is_wallthermostat()) device.type = MAX_WINDOW_SHUTTER self.assertFalse(device.is_wallthermostat()) def test_is_window_shutter(self, _): device = MaxDevice() device.type = MAX_CUBE self.assertFalse(device.is_windowshutter()) device.type = MAX_THERMOSTAT self.assertFalse(device.is_windowshutter()) device.type = MAX_THERMOSTAT_PLUS self.assertFalse(device.is_windowshutter()) device.type = MAX_WALL_THERMOSTAT self.assertFalse(device.is_windowshutter()) device.type = MAX_WINDOW_SHUTTER self.assertTrue(device.is_windowshutter()) def test_set_target_temperature(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.assertTrue(self.cube.set_target_temperature(self.cube.devices[0], 24.5)) self.assertEqual(24.5, self.cube.devices[0].target_temperature) self.commander.send_radio_msg.assert_called_once() self.commander.send_radio_msg.assert_called_with("00044000000006BC530131") def test_do_not_update_if_set_target_temperature_fails(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.commander.send_radio_msg.return_value = False self.assertFalse(self.cube.set_target_temperature(self.cube.devices[0], 24.5)) self.assertEqual(21, self.cube.devices[0].target_temperature) self.commander.send_radio_msg.assert_called_once() self.commander.send_radio_msg.assert_called_with("00044000000006BC530131") def test_set_target_temperature_should_round_temperature(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) self.cube.set_target_temperature(self.cube.devices[0], 24.6) self.assertEqual(24.5, self.cube.devices[0].target_temperature) self.commander.send_radio_msg.assert_called_once() self.commander.send_radio_msg.assert_called_with("00044000000006BC530131") def test_set_target_temperature_is_ignored_by_windowshutter(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) self.cube.set_target_temperature(self.cube.devices[2], 24.5) self.commander.send_radio_msg.assert_not_called() def test_set_mode_thermostat(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_1) device = self.cube.devices[0] self.assertEqual(21.0, device.target_temperature) self.cube.set_mode(device, MAX_DEVICE_MODE_MANUAL) self.assertEqual(MAX_DEVICE_MODE_MANUAL, device.mode) self.commander.send_radio_msg.assert_called_once() self.commander.send_radio_msg.assert_called_with("00044000000006BC53016A") def test_init_2(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) self.assertEqual("JEQ0341267", self.cube.serial) self.assertEqual("015d2a", self.cube.rf_address) self.assertEqual("Cube", self.cube.name) self.assertEqual("01.13", self.cube.firmware_version) self.assertEqual(3, len(self.cube.devices)) device = self.cube.devices[0] self.assertEqual(21.5, device.comfort_temperature) self.assertEqual(16.5, device.eco_temperature) self.assertEqual(4.5, device.min_temperature) self.assertEqual(30.5, device.max_temperature) device = self.cube.devices[1] self.assertEqual(21.5, device.comfort_temperature) self.assertEqual(16.5, device.eco_temperature) self.assertEqual(4.5, device.min_temperature) self.assertEqual(30.5, device.max_temperature) device = self.cube.devices[2] self.assertEqual(1, device.initialized) device = self.cube.devices[0] self.assertEqual(MAX_DEVICE_MODE_AUTOMATIC, device.mode) self.assertIsNone(device.actual_temperature) self.assertEqual(8.0, device.target_temperature) device = self.cube.devices[1] self.assertEqual(MAX_DEVICE_MODE_AUTOMATIC, device.mode) self.assertEqual(22.9, device.actual_temperature) self.assertEqual(8.0, device.target_temperature) device = self.cube.devices[2] self.assertFalse(device.is_open) self.assertTrue(device.battery == MAX_DEVICE_BATTERY_LOW) def test_parse_m_message(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) self.__update( [ Message.decode( b"M:00,01,VgIEAQdLaXRjaGVuBrxTAgZMaXZpbmcGvFoDCFNsZWVwaW5nCKuCBARXb3JrBrxcBAEGvF" b"NLRVEwMzM2MTA4B0tpdGNoZW4BAQa8WktFUTAzMzYxMDAGTGl2aW5nAgEIq4JLRVEwMzM1NjYyCFNs" b"ZWVwaW5nAwEGvFxLRVEwMzM2MTA0BFdvcmsEAQ==" ), INIT_RESPONSE_2[-1], ] ) self.assertEqual("0E2EBA", self.cube.devices[0].rf_address) self.assertEqual("Thermostat", self.cube.devices[0].name) self.assertEqual(MAX_THERMOSTAT, self.cube.devices[0].type) self.assertEqual("KEQ1086437", self.cube.devices[0].serial) self.assertEqual(1, self.cube.devices[0].room_id) self.assertEqual("0A0881", self.cube.devices[1].rf_address) self.assertEqual("Wandthermostat", self.cube.devices[1].name) self.assertEqual(MAX_WALL_THERMOSTAT, self.cube.devices[1].type) self.assertEqual("KEQ0655743", self.cube.devices[1].serial) self.assertEqual(2, self.cube.devices[1].room_id) self.assertEqual("0CA2B2", self.cube.devices[2].rf_address) self.assertEqual("Fensterkontakt", self.cube.devices[2].name) self.assertEqual(MAX_WINDOW_SHUTTER, self.cube.devices[2].type) self.assertEqual("KEQ0839778", self.cube.devices[2].serial) self.assertEqual(1, self.cube.devices[2].room_id) self.assertEqual("Kitchen", self.cube.rooms[0].name) self.assertEqual(1, self.cube.rooms[0].id) self.assertEqual("Living", self.cube.rooms[1].name) self.assertEqual(2, self.cube.rooms[1].id) def test_get_devices(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) devices = self.cube.get_devices() self.assertEqual(3, len(devices)) def test_device_by_rf(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) device = self.cube.device_by_rf("0CA2B2") self.assertEqual("0CA2B2", device.rf_address) self.assertEqual("Fensterkontakt", device.name) self.assertEqual(MAX_WINDOW_SHUTTER, device.type) self.assertEqual("KEQ0839778", device.serial) self.assertEqual(1, device.room_id) def test_device_by_rf_negative(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) device = self.cube.device_by_rf("DEADBEEF") self.assertIsNone(device) def test_devices_by_room(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) room = MaxRoom() room.id = 1 devices = self.cube.devices_by_room(room) self.assertEqual(2, len(devices)) def test_devices_by_room_negative(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) room = MaxRoom() room.id = 3 devices = self.cube.devices_by_room(room) self.assertEqual(0, len(devices)) def test_get_rooms(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) rooms = self.cube.get_rooms() self.assertEqual("Badezimmer", rooms[0].name) self.assertEqual(1, rooms[0].id) self.assertEqual("Wohnzimmer", rooms[1].name) self.assertEqual(2, rooms[1].id) def test_room_by_id(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) room = self.cube.room_by_id(1) self.assertEqual("Badezimmer", room.name) self.assertEqual(1, room.id) def test_room_by_id_negative(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) room = self.cube.room_by_id(3) self.assertIsNone(room) def test_set_programme(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) self.commander.send_radio_msg.return_value = True result = self.cube.set_programme( self.cube.devices[0], "saturday", [{"temp": 20.5, "until": "13:30"}, {"temp": 18, "until": "24:00"}], ) self.assertTrue(result) self.commander.send_radio_msg.assert_called_once() self.commander.send_radio_msg.assert_called_with( "0000100000000E2EBA010052A249200000000000" ) def test_set_programme_already_existing_does_nothing(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) result = self.cube.set_programme( self.cube.devices[0], "saturday", INIT_PROGRAMME_1["saturday"] ) self.assertIsNone(result) self.commander.send_radio_msg.assert_not_called() def test_get_device_as_dict(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) device = self.cube.devices[0] result = device.to_dict() self.assertEqual(result["name"], "Thermostat") self.assertEqual(result["comfort_temperature"], 21.5) self.assertEqual( result["programme"]["monday"], [ {"until": "05:30", "temp": 8}, {"until": "06:30", "temp": 21}, {"until": "23:55", "temp": 8}, {"until": "24:00", "temp": 8}, ], ) def test_set_auto_mode_read_temp_from_program(self, ClassMock): self.init(ClassMock, INIT_RESPONSE_2) device = self.cube.devices[0] self.assertEqual(8.0, device.target_temperature) self.cube.set_mode(device, MAX_DEVICE_MODE_AUTOMATIC) self.assertEqual(21.0, device.target_temperature) self.assertEqual(MAX_DEVICE_MODE_AUTOMATIC, device.mode) self.commander.send_radio_msg.assert_called_once() self.commander.send_radio_msg.assert_called_with("0004400000000E2EBA0100")
class Controller: def __init__(self, address, port): # connect to the cube self.cube = MaxCube(MaxCubeConnection(address, port)) # setup the output mode on all the pins wiringpi.wiringPiSetup() wiringpi.pinMode(PIN1, 1) wiringpi.pinMode(PIN2, 1) wiringpi.pinMode(PIN3, 1) # pin_status dict contains the current status of the output pins # status 0 means that the pump is operating normally (turned on) # status 1 means that the pump is turned off (1 on the GPIO pin opens the relay) self.pin_status = {PIN1: 0, PIN2: 0, PIN3: 0} # map apartment IDs to the output pins self.pin_mapping = {"10": PIN1, "11": PIN2} def update_pins(self): """ update the GPIO pins to the status set in the self.pin_status dictionary """ for pin in [PIN1, PIN2, PIN3]: wiringpi.digitalWrite(pin, self.pin_status[pin]) def scan_and_update_pins(self): """ Scan for all the connected thermostats. They are named "ID_name", where ID is the id of the apartment. If valves on all the thermostats in an apartment are set to 0, turn off the corresponding pin """ self.cube.update() apartments = {} print(len(self.cube.devices), "devices found") for device in self.cube.devices: id = device.name.split("_")[0] if id not in apartments: apartments[id] = [] apartments[id].append(device) for id in apartments.keys(): all_off = True for device in apartments[id]: all_off &= device.valve_position == 0 if all_off: if self.pin_status[self.pin_mapping[id]] == 0: print("The pump for apartment", id, "will be turned off.") self.pin_status[self.pin_mapping[id]] = 1 else: if self.pin_status[self.pin_mapping[id]] == 1: print("The pump for apartment", id, "will be turned back on.") self.pin_status[self.pin_mapping[id]] = 0 self.update_pins() def run_forever(self, interval=5): while True: try: self.scan_and_update_pins() sleep(interval * 60) except Exception: print("Background sleep was terminated, exiting the loop") break # turn everything to 0 and exit wiringpi.wiringPiSetup() wiringpi.pinMode(PIN1, 1) wiringpi.pinMode(PIN2, 1) wiringpi.pinMode(PIN3, 1) wiringpi.digitalWrite(PIN1, 0) wiringpi.digitalWrite(PIN2, 0) wiringpi.digitalWrite(PIN3, 0) print("All the pumps are now on") print("Controller is shutting down")