Ejemplo n.º 1
0
async def test_VMB1TS_message(generate_sanic_request, module_address,
                              mock_velbus):
    mock_velbus.set_expected_conversation([
        VMB1TS_module_info_exchange(module_address),
    ])

    req = generate_sanic_request()
    await module_req(req, f'{module_address:02x}', '/type')
    mock_velbus.assert_conversation_happened_exactly()

    message(
        VelbusFrame(
            address=module_address,
            message=PushButtonStatus(just_pressed=Bitmap(
                8)([True, False, False, False, False, False, False, False])),
        ))

    resp = await module_req(req, f'{module_address:02x}', '/heater')
    assert resp.status == 200
    assert resp.body.decode('utf-8') == "true"

    message(
        VelbusFrame(
            address=module_address,
            message=PushButtonStatus(just_released=Bitmap(
                8)([True, False, False, False, False, False, False, False])),
        ))

    resp = await module_req(req, f'{module_address:02x}', '/heater')
    assert resp.status == 200
    assert resp.body.decode('utf-8') == "false"
Ejemplo n.º 2
0
async def test_put_dimvalue_list_cancel(generate_sanic_request, mock_velbus,
                                        module_address, channel):
    mock_velbus.set_expected_conversation([
        VMB4DC_module_info_exchange(module_address),
        (VelbusFrame(address=module_address,
                     message=SetDimvalue(
                         channel=channel,
                         dimvalue=100,
                         dimspeed=0,
                     )).to_bytes(),
         VelbusFrame(address=module_address,
                     message=DimmercontrollerStatus(
                         channel=channel,
                         dimvalue=100,
                     )).to_bytes()),
    ])

    sanic_req = generate_sanic_request(method='PUT',
                                       body=json.dumps([{
                                           "dimvalue": 100
                                       }, {
                                           "dimvalue":
                                           20,
                                           "when":
                                           "2000-01-01 00:00:00.1"
                                       }]))
    resp = await HttpApi.module_req(sanic_req, f'{module_address:02x}',
                                    f"/{channel}/e_dimvalue")
    assert 202 == resp.status

    await asyncio.sleep(0.05)
    mock_velbus.assert_conversation_happened_exactly()

    # Now interrupt this sleep with a new HTTP-call
    sanic_req = generate_sanic_request(method='PUT', body='42')
    mock_velbus.set_expected_conversation([
        (VelbusFrame(
            address=module_address,
            message=SetDimvalue(
                channel=channel,
                dimvalue=42,
                dimspeed=0,
            ),
        ).to_bytes(),
         VelbusFrame(address=module_address,
                     message=DimmercontrollerStatus(
                         channel=channel,
                         dimvalue=42,
                     )).to_bytes()),
    ])

    resp = await HttpApi.module_req(sanic_req, f'{module_address:02x}',
                                    f"/{channel}/e_dimvalue")
    assert resp.status // 100 == 2

    await asyncio.sleep(0.1)  # until 0.15
    mock_velbus.assert_conversation_happened_exactly()

    assert len(HttpApi.modules[module_address].result().submodules[channel].
               delayed_calls) == 0
Ejemplo n.º 3
0
async def test_put_dimvalue_int(generate_sanic_request, mock_velbus,
                                module_address, channel):
    mock_velbus.set_expected_conversation([
        VMB4DC_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=SetDimvalue(
                channel=channel,
                dimvalue=100,
                dimspeed=0,
            ),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=DimmercontrollerStatus(
                 channel=channel,
                 dimvalue=100,
             ),
         ).to_bytes()),
    ])

    sanic_req = generate_sanic_request(method='PUT', body='100')
    resp = await HttpApi.module_req(sanic_req, f'{module_address:02x}',
                                    f"/{channel}/dimvalue")
    assert resp.status // 100 == 2

    mock_velbus.assert_conversation_happened_exactly()
Ejemplo n.º 4
0
async def test_put_relay(generate_sanic_request, mock_velbus, module_address,
                         channel, true_false):
    mock_velbus.set_expected_conversation([
        VMB4RYNO_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=SwitchRelay(
                command=SwitchRelay.Command.SwitchRelayOn
                if true_false else SwitchRelay.Command.SwitchRelayOff,
                channel=Index(8)(channel),
            ),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=RelayStatus(
                 channel=channel,
                 relay_status=RelayStatus.RelayStatus.On
                 if true_false else RelayStatus.RelayStatus.Off,
             ),
         ).to_bytes())
    ])

    sanic_req = generate_sanic_request(method='PUT',
                                       body=json.dumps(true_false))
    resp = await HttpApi.module_req(sanic_req, f"{module_address:02x}",
                                    f"/{channel}/relay")
    assert resp.status == 200
    assert resp.body.decode('utf-8') == json.dumps(true_false)

    await asyncio.sleep(0.05)  # allow time to process the queue
    assert mock_velbus.assert_conversation_happened_exactly()
Ejemplo n.º 5
0
async def test_put_relay_timer(generate_sanic_request, mock_velbus,
                               module_address, channel):
    timer = 42

    mock_velbus.set_expected_conversation([
        VMB4RYNO_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=StartRelayTimer(
                channel=channel,
                delay_time=timer,
            ),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=RelayStatus(
                 channel=channel,
                 relay_status=RelayStatus.RelayStatus.On,
             ),
         ).to_bytes())
    ])

    sanic_req = generate_sanic_request(method='PUT', body=str(timer))
    resp = await HttpApi.module_req(sanic_req, f"{module_address:02x}",
                                    f"/{channel}/relay")
    assert resp.status == 200
    assert resp.body.decode('utf-8') == 'true'

    await asyncio.sleep(0.05)  # allow time to process the queue
    assert mock_velbus.assert_conversation_happened_exactly()
Ejemplo n.º 6
0
async def test_ws(generate_sanic_request, mock_velbus):
    module_address = 0x11  # TODO: check for "all" addresses
    channel = 4  # TODO: check all channels

    mock_velbus.set_expected_conversation([
        VMB4RYNO_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=ModuleStatusRequest(channel=Index(8)(channel).to_int(), ),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=RelayStatus(
                 channel=channel,
                 relay_status=RelayStatus.RelayStatus.Off,
             ),
         ).to_bytes())
    ])
    # Initialize the module
    sanic_req = generate_sanic_request()
    resp = await HttpApi.module_req(sanic_req, f"{module_address:02x}",
                                    f"/{channel}/relay")

    ws = Mock()
    ws.subscribed_modules = {module_address}
    ws.send = Mock(return_value=make_awaitable(None))

    HttpApi.ws_clients.add(ws)
    sanic_req = generate_sanic_request()
    await HttpApi.ws_client_listen_module(VelbusHttpProtocol(sanic_req),
                                          module_address, ws)
    ws.send.assert_called_once_with('[{"op": "add", "path": "/11", "value": {'
                                    '"1": {}, '
                                    '"2": {}, '
                                    '"3": {}, '
                                    '"4": {"relay": false}, '
                                    '"5": {}'
                                    '}}]')
    ws.send.reset_mock()

    HttpApi.message(
        VelbusFrame(address=module_address,
                    message=RelayStatus(
                        channel=channel,
                        relay_status=RelayStatus.RelayStatus.On,
                    )))
    ws.send.assert_called_once_with(
        '[{"op": "add", "path": "/11/4/relay", "value": true}]')
    ws.send.reset_mock()

    HttpApi.message(
        VelbusFrame(address=module_address,
                    message=RelayStatus(
                        channel=channel,
                        relay_status=RelayStatus.RelayStatus.Off,
                    )))
    ws.send.assert_called_once_with(
        '[{"op": "add", "path": "/11/4/relay", "value": false}]')
    ws.send.reset_mock()
Ejemplo n.º 7
0
def VMB4RYNO_module_info_exchange(module_address):
    return (VelbusFrame(
        address=module_address,
        message=ModuleTypeRequest(),
    ).to_bytes(),
            VelbusFrame(
                address=module_address,
                message=ModuleType(module_info=VMB4RYNO_mi(), ),
            ).to_bytes())
Ejemplo n.º 8
0
 def velbus_query(self,
                  question: VelbusFrame,
                  response_type: type,
                  response_address: int = None,
                  timeout: int = 2,
                  additional_check=(lambda vbm: True)):
     assert question == VelbusFrame(address=1, message=ModuleTypeRequest())
     return make_awaitable(
         VelbusFrame(address=1, message=ModuleType(module_info=VMB4RYNO_mi()))
     )
Ejemplo n.º 9
0
async def test_ws(generate_sanic_request, mock_velbus, module_address,
                  channel):
    mock_velbus.set_expected_conversation([
        VMB2BLE_module_info_exchange(module_address),
        VMB2BLE_module_status_exchange(module_address, channel, 7),
    ])
    # Initialize the module
    sanic_req = generate_sanic_request()
    resp = await module_req(sanic_req, f"{module_address:02x}",
                            f"/{channel}/position")
    assert 200 == resp.status

    client_state = dict()

    def receive(ops: str):
        patch = jsonpatch.JsonPatch(json.loads(ops))
        nonlocal client_state
        client_state = patch.apply(client_state)
        return make_awaitable(None)

    ws = Mock()
    ws.subscribed_modules = {module_address}
    ws.send = receive
    HttpApi.ws_clients.add(ws)

    sanic_req = generate_sanic_request()
    await HttpApi.ws_client_listen_module(VelbusHttpProtocol(sanic_req),
                                          module_address, ws)

    assert "off" == client_state[f"{module_address:02x}"][str(
        channel)]["status"]
    assert 7 == client_state[f"{module_address:02x}"][str(channel)]["position"]

    HttpApi.message(
        VelbusFrame(address=module_address,
                    message=BlindStatusV2(
                        channel=channel,
                        blind_status=BlindStatusV2.BlindStatus.Up,
                        blind_position=7,
                    )))

    assert "up" == client_state[f"{module_address:02x}"][str(
        channel)]["status"]

    HttpApi.message(
        VelbusFrame(address=module_address,
                    message=BlindStatusV2(
                        channel=channel,
                        blind_status=BlindStatusV2.BlindStatus.Off,
                        blind_position=0,
                    )))

    assert "off" == client_state[f"{module_address:02x}"][str(
        channel)]["status"]
    assert 0 == client_state[f"{module_address:02x}"][str(channel)]["position"]
Ejemplo n.º 10
0
def VMB2BLE_module_status_exchange(module_address, channel, position):
    return (VelbusFrame(address=module_address,
                        message=ModuleStatusRequest(
                            channel=channel, )).to_bytes(),
            VelbusFrame(
                address=module_address,
                message=BlindStatusV2(
                    channel=channel,
                    blind_status=BlindStatusV2.BlindStatus.Off,
                    blind_position=position,
                ),
            ).to_bytes())
Ejemplo n.º 11
0
async def test_VMB2BL_instantiation(generate_sanic_request, module_address,
                                    mock_velbus):
    mock_velbus.set_expected_conversation([
        (VelbusFrame(
            address=module_address,
            message=ModuleTypeRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=ModuleType(module_info=VMB2BL_mi(), ),
         ).to_bytes()),
    ])

    req = generate_sanic_request()
    resp = await module_req(req, f'{module_address:02x}', '/type')
    mock_velbus.assert_conversation_happened_exactly()

    assert 200 == resp.status
    assert f'VMB2BL at 0x{module_address:02x}\r\n' == resp.body.decode('utf-8')
Ejemplo n.º 12
0
async def test_reply(mock_velbus, module_address):
    mock_velbus.set_expected_conversation([
        (VelbusFrame(
            address=module_address,
            message=ModuleTypeRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=ModuleType(module_info=VMB4RYNO(), ),
         ).to_bytes())
    ])
    bus = VelbusProtocol(client_id="INTERNAL")
    await bus.velbus_query(
        VelbusFrame(
            address=module_address,
            message=ModuleTypeRequest(),
        ),
        ModuleType,
    )
Ejemplo n.º 13
0
async def test_VMB1TS_heater(generate_sanic_request, module_address,
                             mock_velbus):
    mock_velbus.set_expected_conversation([
        VMB1TS_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=ModuleStatusRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=TemperatureSensorStatus(heater=True, ),
         ).to_bytes()),
    ])

    req = generate_sanic_request()
    resp = await module_req(req, f'{module_address:02x}', '/heater')
    mock_velbus.assert_conversation_happened_exactly()

    assert resp.status == 200
    assert resp.body.decode('utf-8') == 'true'
Ejemplo n.º 14
0
async def test_VMB1TS_temperature(generate_sanic_request, module_address,
                                  mock_velbus):
    mock_velbus.set_expected_conversation([
        VMB1TS_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=SensorTemperatureRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=SensorTemperature(current_temperature=22, ),
         ).to_bytes()),
    ])

    req = generate_sanic_request()
    resp = await module_req(req, f'{module_address:02x}', '/temperature')
    mock_velbus.assert_conversation_happened_exactly()

    assert 200 == resp.status
    assert f'22.0' == resp.body.decode('utf-8')
def test_decode():
    b = b'\x0f\xfb\x01\x40\xb5\x04'
    a = VelbusFrame.from_bytes(b)
    assert a.to_bytes() == b

    assert a == VelbusFrame(address=1, message=ModuleTypeRequest())

    assert json.loads(json.dumps(a.message.to_json_able())) == {
        'type': 'ModuleTypeRequest',
        'properties': {}
    }
Ejemplo n.º 16
0
async def test_get_edimvalue(generate_sanic_request, mock_velbus,
                             module_address, channel):
    sanic_req = generate_sanic_request(method='PUT',
                                       body=json.dumps([{
                                           "dimvalue": 100
                                       }, {
                                           "dimvalue":
                                           20,
                                           "when":
                                           datetime.datetime.now().isoformat()
                                       }]))
    mock_velbus.set_expected_conversation([
        VMB4DC_module_info_exchange(module_address),
        (VelbusFrame(address=module_address,
                     message=SetDimvalue(
                         channel=channel,
                         dimvalue=100,
                         dimspeed=0,
                     )).to_bytes(),
         VelbusFrame(address=module_address,
                     message=DimmercontrollerStatus(
                         channel=channel,
                         dimvalue=100,
                     )).to_bytes()),
    ])

    resp = await HttpApi.module_req(sanic_req, f'{module_address:02x}',
                                    f"/{channel}/e_dimvalue")
    assert resp.status // 100 == 2

    sanic_req = generate_sanic_request()
    resp = await HttpApi.module_req(sanic_req, f'{module_address:02x}',
                                    f"/{channel}/e_dimvalue")
    assert resp.status == 200
    resp = json.loads(resp.body)
    assert len(
        resp
    ) >= 1  # maybe the "now" action is still in the list, maybe it has already happened
    assert resp[-1]['dimvalue'] == 20
    assert isinstance(resp[-1]['when'], str)
Ejemplo n.º 17
0
async def test_no_reply(mock_velbus):
    mock_velbus.set_expected_conversation([
        (VelbusFrame(
            address=0x01,
            message=ModuleTypeRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=0x01,
             message=ModuleType(module_info=VMB4RYNO(), ),
         ).to_bytes()),
    ])
    bus = VelbusProtocol(client_id="INTERNAL")

    with pytest.raises(TimeoutError):
        await bus.velbus_query(
            VelbusFrame(
                address=0x02,
                message=ModuleTypeRequest(),
            ),
            ModuleType,
            timeout=0.01,
        )
Ejemplo n.º 18
0
async def test_get_relay(generate_sanic_request, mock_velbus, module_address,
                         channel):
    mock_velbus.set_expected_conversation([
        VMB4RYNO_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=ModuleStatusRequest(channel=Index(8)(channel).to_int(), ),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=RelayStatus(
                 channel=channel,
                 relay_status=RelayStatus.RelayStatus.On,
             ),
         ).to_bytes())
    ])

    sanic_req = generate_sanic_request()
    resp = await HttpApi.module_req(sanic_req, f"{module_address:02x}",
                                    f"/{channel}/relay")
    assert 200 == resp.status
    assert 'true' == resp.body.decode('utf-8')
    assert mock_velbus.assert_conversation_happened_exactly()
Ejemplo n.º 19
0
async def test_get_dimvalue(generate_sanic_request, mock_velbus,
                            module_address, channel):
    mock_velbus.set_expected_conversation([
        VMB4DC_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=ModuleStatusRequest(channel=Index(8)(channel).to_int(), ),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=DimmercontrollerStatus(
                 channel=channel,
                 dimvalue=channel * 10,
             ),
         ).to_bytes()),
    ])

    sanic_req = generate_sanic_request(method='GET')
    resp = await HttpApi.module_req(sanic_req, f'{module_address:02x}',
                                    f"/{channel}/dimvalue")
    assert 200 == resp.status
    assert str(channel * 10) == resp.body.decode('utf-8')
    mock_velbus.assert_conversation_happened_exactly()
Ejemplo n.º 20
0
async def test_get_module_timeout(generate_sanic_request):
    req = generate_sanic_request(path='/modules/aA/')
    give_request_ip(req)

    with patch('velbus.VelbusProtocol.VelbusHttpProtocol.process_message', new_callable=CoroMock) as velbus_pm:
        start_time = datetime.datetime.now()
        with pytest.raises(CachedTimeoutError):
            _ = await HttpApi.module_req(req, 'aA', '')
        duration = (datetime.datetime.now() - start_time).total_seconds()
        assert (2 * .9 < duration < 2 * 1.1)  # give 10% slack

        velbus_pm.assert_called_with(VelbusFrame(
            address=0xaa,
            message=ModuleTypeRequest(),
        ))

        start_time = datetime.datetime.now()
        with pytest.raises(CachedTimeoutError):
            _ = await HttpApi.module_req(req, 'Aa', '')
        duration = (datetime.datetime.now() - start_time).total_seconds()
        assert (duration < .1)  # Should return immediately
Ejemplo n.º 21
0
#!/usr/bin/env python3
import argparse
import socket
from datetime import datetime

from velbus.VelbusMessage.VelbusFrame import VelbusFrame
from velbus.VelbusMessage.RealTimeClockStatus import RealTimeClockStatus

parser = argparse.ArgumentParser(description='Send current time to Velbus')
parser.add_argument('--hostname', help="Hostname to connect to", default="localhost")
parser.add_argument('--port', help="Port to connect to", default=8445)
args = parser.parse_args()

s = socket.create_connection((args.hostname, args.port))

message = RealTimeClockStatus()
message.set_to(when=datetime.now())
frame = VelbusFrame(address=0x00, message=message)
pdu = frame.to_bytes()

s.send(pdu)
s.close()
Ejemplo n.º 22
0
import pytest
import json

from velbus.VelbusMessage._types import LedStatus
from velbus.VelbusMessage.VelbusFrame import VelbusFrame
from velbus.VelbusMessage.RelayStatus import RelayStatus


@pytest.mark.parametrize('binary,frame,json', [
    (b'\x0f\xfb\x20\x08\xfb\x02\x00\x01\x80\x00\x00\x00\x50\x04',
     VelbusFrame(
         address=0x20,
         message=RelayStatus(
             channel=2,
             relay_status=RelayStatus.RelayStatus.On,
             led_status=LedStatus.On,
         ),
     ), None),
])
def test_message(binary: bytes, frame: VelbusFrame, json: dict):
    b = binary
    a = VelbusFrame.from_bytes(b)

    # test roundtrip
    assert a.to_bytes() == b

    # test decode
    assert a == frame

    # test JSON
    if json is not None:
Ejemplo n.º 23
0
async def test_VMB2BL_status_position_estimation(generate_sanic_request,
                                                 mock_velbus):
    mock_velbus.set_expected_conversation([
        (VelbusFrame(
            address=0x11,
            message=ModuleTypeRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=0x11,
             message=ModuleType(module_info=VMB2BL_mi(), ),
         ).to_bytes()),
        (VelbusFrame(address=0x11,
                     message=ModuleStatusRequest(channel=1)).to_bytes(),
         VelbusFrame(
             address=0x11,
             message=BlindStatusV1(
                 channel=BlindNumber(1),
                 blind_status=BlindStatusV1.BlindStatus.Off,
             ),
         ).to_bytes()),
    ])

    with freeze_time() as frozen_datetime:
        req = generate_sanic_request()
        resp = await module_req(req, '11', '/1/position')

        mock_velbus.assert_conversation_happened_exactly()

        assert 200 == resp.status
        assert '50' == resp.body.decode('utf-8')

        message(
            VelbusFrame(
                address=0x11,
                message=BlindStatusV1(
                    channel=BlindNumber(1),
                    blind_status=BlindStatusV1.BlindStatus.Blind1Down,
                ),
            ))

        frozen_datetime.tick(delta=datetime.timedelta(seconds=15))

        message(
            VelbusFrame(
                address=0x11,
                message=BlindStatusV1(
                    channel=BlindNumber(1),
                    blind_status=BlindStatusV1.BlindStatus.Off,
                ),
            ))

        req = generate_sanic_request()
        resp = await module_req(req, '11', '/1/position')
        assert 200 == resp.status
        assert '100' == resp.body.decode('utf-8')

        message(
            VelbusFrame(
                address=0x11,
                message=BlindStatusV1(
                    channel=BlindNumber(1),
                    blind_status=BlindStatusV1.BlindStatus.Blind1Up,
                ),
            ))

        frozen_datetime.tick(delta=datetime.timedelta(seconds=3))

        message(
            VelbusFrame(
                address=0x11,
                message=BlindStatusV1(
                    channel=BlindNumber(1),
                    blind_status=BlindStatusV1.BlindStatus.Off,
                ),
            ))

        req = generate_sanic_request()
        resp = await module_req(req, '11', '/1/position')
        assert 200 == resp.status
        assert 80. == float(resp.body.decode('utf-8'))
Ejemplo n.º 24
0
async def test_VMB1TS_cached_temperature(generate_sanic_request,
                                         module_address, mock_velbus):
    mock_velbus.set_expected_conversation([
        VMB1TS_module_info_exchange(module_address),
        (VelbusFrame(
            address=module_address,
            message=SensorTemperatureRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=SensorTemperature(current_temperature=23, ),
         ).to_bytes()),
        (VelbusFrame(
            address=module_address,
            message=SensorTemperatureRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=SensorTemperature(current_temperature=24, ),
         ).to_bytes()),
        (VelbusFrame(
            address=module_address,
            message=SensorTemperatureRequest(),
        ).to_bytes(),
         VelbusFrame(
             address=module_address,
             message=SensorTemperature(current_temperature=25, ),
         ).to_bytes()),
    ])

    with freeze_time() as frozen_datetime:
        req = generate_sanic_request()
        await module_req(req, f'{module_address:02x}', '/temperature')

        resp = await module_req(req, f'{module_address:02x}', '/temperature')
        # Should *not* re-request the temperature
        # Should still return the "old" 23ºC
        assert 200 == resp.status
        assert f'23.0' == resp.body.decode('utf-8')

        frozen_datetime.tick(delta=datetime.timedelta(seconds=15))

        resp = await module_req(req, f'{module_address:02x}', '/temperature')
        # Should *not* re-request the temperature
        # Should still return the "old" 23ºC
        assert 200 == resp.status
        assert f'23.0' == resp.body.decode('utf-8')

        req.headers = {
            "Cache-Control": "max-age=30",
        }
        resp = await module_req(req, f'{module_address:02x}', '/temperature')
        # Should *not* re-request the temperature
        # Should still return the "old" 23ºC
        assert 200 == resp.status
        assert f'23.0' == resp.body.decode('utf-8')

        req.headers = {
            "Cache-Control": "max-age=10",
        }
        resp = await module_req(req, f'{module_address:02x}', '/temperature')
        # *Should* re-request the temperature
        assert 200 == resp.status
        assert f'24.0' == resp.body.decode('utf-8')

        frozen_datetime.tick(delta=datetime.timedelta(seconds=61))

        req.headers = {}
        resp = await module_req(req, f'{module_address:02x}', '/temperature')
        # *Should* re-request the temperature
        assert 200 == resp.status
        assert f'25.0' == resp.body.decode('utf-8')