From 0d699bb768b5f35bbf34b9346166072066b30a29 Mon Sep 17 00:00:00 2001 From: Tobias Sauerwein Date: Wed, 24 Mar 2021 07:17:51 +0100 Subject: [PATCH] Add tests for Netatmo sensor (#46393) * Add tests for Netatmo sensor * Fix coveragerc * Remove freezegun dependency * Use f-strings instead of string concatenation * Update tests/components/netatmo/test_sensor.py Co-authored-by: Erik Montnemery * Address comment on config options test * Replace deprecated call to async_get_registry() * Fix public weather sensor update test * Clean up * Prevent division by zero Co-authored-by: Erik Montnemery Co-authored-by: Martin Hjelmare --- .coveragerc | 2 +- homeassistant/components/netatmo/sensor.py | 12 +- tests/components/netatmo/common.py | 2 + tests/components/netatmo/conftest.py | 13 +- tests/components/netatmo/test_sensor.py | 235 +++++++++++ tests/fixtures/netatmo/gethomecoachsdata.json | 202 +++++++++ tests/fixtures/netatmo/getpublicdata.json | 392 ++++++++++++++++++ 7 files changed, 844 insertions(+), 14 deletions(-) create mode 100644 tests/components/netatmo/test_sensor.py create mode 100644 tests/fixtures/netatmo/gethomecoachsdata.json create mode 100644 tests/fixtures/netatmo/getpublicdata.json diff --git a/.coveragerc b/.coveragerc index 972831c4bd4..10eac76421c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -644,7 +644,7 @@ omit = homeassistant/components/nello/lock.py homeassistant/components/nest/legacy/* homeassistant/components/netatmo/data_handler.py - homeassistant/components/netatmo/sensor.py + homeassistant/components/netatmo/helper.py homeassistant/components/netdata/sensor.py homeassistant/components/netgear/device_tracker.py homeassistant/components/netgear_lte/* diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index cccd3865e54..4c6facb3eca 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -641,7 +641,7 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): elif self.type == "guststrength": data = self._data.get_latest_gust_strengths() - if not data: + if data is None: if self._state is None: return _LOGGER.debug( @@ -650,8 +650,8 @@ class NetatmoPublicSensor(NetatmoBase, SensorEntity): self._state = None return - values = [x for x in data.values() if x is not None] - if self._mode == "avg": - self._state = round(sum(values) / len(values), 1) - elif self._mode == "max": - self._state = max(values) + if values := [x for x in data.values() if x is not None]: + if self._mode == "avg": + self._state = round(sum(values) / len(values), 1) + elif self._mode == "max": + self._state = max(values) diff --git a/tests/components/netatmo/common.py b/tests/components/netatmo/common.py index b952d6fe790..6bafd12acea 100644 --- a/tests/components/netatmo/common.py +++ b/tests/components/netatmo/common.py @@ -29,6 +29,8 @@ COMMON_RESPONSE = { "user": {"id": "91763b24c43d3e344f424e8b", "email": "john@doe.com"}, } +TEST_TIME = 1559347200.0 + def fake_post_request(**args): """Return fake data.""" diff --git a/tests/components/netatmo/conftest.py b/tests/components/netatmo/conftest.py index e0138dcc4d7..9a16391d2a4 100644 --- a/tests/components/netatmo/conftest.py +++ b/tests/components/netatmo/conftest.py @@ -5,7 +5,7 @@ from unittest.mock import patch import pytest -from .common import ALL_SCOPES, fake_post_request, fake_post_request_no_data +from .common import ALL_SCOPES, TEST_TIME, fake_post_request, fake_post_request_no_data from tests.common import MockConfigEntry @@ -81,11 +81,10 @@ async def mock_entry_fixture(hass, config_entry): @pytest.fixture(name="sensor_entry") async def mock_sensor_entry_fixture(hass, config_entry): """Mock setup of sensor platform.""" - with selected_platforms(["sensor"]): + with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): await hass.config_entries.async_setup(config_entry.entry_id) - - await hass.async_block_till_done() - return config_entry + await hass.async_block_till_done() + yield config_entry @pytest.fixture(name="camera_entry") @@ -131,5 +130,5 @@ async def mock_entry_error_fixture(hass, config_entry): mock_auth.return_value.post_request.side_effect = fake_post_request_no_data await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() - return config_entry + await hass.async_block_till_done() + yield config_entry diff --git a/tests/components/netatmo/test_sensor.py b/tests/components/netatmo/test_sensor.py new file mode 100644 index 00000000000..fcb2ce454df --- /dev/null +++ b/tests/components/netatmo/test_sensor.py @@ -0,0 +1,235 @@ +"""The tests for the Netatmo sensor platform.""" +from datetime import timedelta +from unittest.mock import patch + +import pytest + +from homeassistant.components.netatmo import sensor +from homeassistant.components.netatmo.sensor import MODULE_TYPE_WIND +from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY +from homeassistant.helpers import entity_registry as er +from homeassistant.util import dt + +from .common import TEST_TIME +from .conftest import selected_platforms + +from tests.common import async_fire_time_changed + + +async def test_weather_sensor(hass, sensor_entry): + """Test weather sensor setup.""" + prefix = "sensor.netatmo_mystation_" + + assert hass.states.get(f"{prefix}temperature").state == "24.6" + assert hass.states.get(f"{prefix}humidity").state == "36" + assert hass.states.get(f"{prefix}co2").state == "749" + assert hass.states.get(f"{prefix}pressure").state == "1017.3" + + +async def test_public_weather_sensor(hass, sensor_entry): + """Test public weather sensor setup.""" + prefix = "sensor.netatmo_home_max_" + + assert hass.states.get(f"{prefix}temperature").state == "27.4" + assert hass.states.get(f"{prefix}humidity").state == "76" + assert hass.states.get(f"{prefix}pressure").state == "1014.4" + + prefix = "sensor.netatmo_home_avg_" + + assert hass.states.get(f"{prefix}temperature").state == "22.7" + assert hass.states.get(f"{prefix}humidity").state == "63.2" + assert hass.states.get(f"{prefix}pressure").state == "1010.3" + + assert len(hass.states.async_all()) > 0 + entities_before_change = len(hass.states.async_all()) + + valid_option = { + "lat_ne": 32.91336, + "lon_ne": -117.187429, + "lat_sw": 32.83336, + "lon_sw": -117.26743, + "show_on_map": True, + "area_name": "Home avg", + "mode": "max", + } + + result = await hass.config_entries.options.async_init(sensor_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={"new_area": "Home avg"} + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input=valid_option + ) + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={} + ) + await hass.async_block_till_done() + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1), + ) + await hass.async_block_till_done() + + assert hass.states.get(f"{prefix}temperature").state == "27.4" + assert hass.states.get(f"{prefix}humidity").state == "76" + assert hass.states.get(f"{prefix}pressure").state == "1014.4" + + assert len(hass.states.async_all()) == entities_before_change + + +@pytest.mark.parametrize( + "strength, expected", + [(50, "Full"), (60, "High"), (80, "Medium"), (90, "Low")], +) +async def test_process_wifi(strength, expected): + """Test wifi strength translation.""" + assert sensor.process_wifi(strength) == expected + + +@pytest.mark.parametrize( + "strength, expected", + [(50, "Full"), (70, "High"), (80, "Medium"), (90, "Low")], +) +async def test_process_rf(strength, expected): + """Test radio strength translation.""" + assert sensor.process_rf(strength) == expected + + +@pytest.mark.parametrize( + "health, expected", + [(4, "Unhealthy"), (3, "Poor"), (2, "Fair"), (1, "Fine"), (0, "Healthy")], +) +async def test_process_health(health, expected): + """Test health index translation.""" + assert sensor.process_health(health) == expected + + +@pytest.mark.parametrize( + "model, data, expected", + [ + (MODULE_TYPE_WIND, 5591, "Full"), + (MODULE_TYPE_WIND, 5181, "High"), + (MODULE_TYPE_WIND, 4771, "Medium"), + (MODULE_TYPE_WIND, 4361, "Low"), + (MODULE_TYPE_WIND, 4300, "Very Low"), + ], +) +async def test_process_battery(model, data, expected): + """Test battery level translation.""" + assert sensor.process_battery(data, model) == expected + + +@pytest.mark.parametrize( + "angle, expected", + [ + (0, "N"), + (40, "NE"), + (70, "E"), + (130, "SE"), + (160, "S"), + (220, "SW"), + (250, "W"), + (310, "NW"), + (340, "N"), + ], +) +async def test_process_angle(angle, expected): + """Test wind direction translation.""" + assert sensor.process_angle(angle) == expected + + +@pytest.mark.parametrize( + "angle, expected", + [(-1, 359), (-40, 320)], +) +async def test_fix_angle(angle, expected): + """Test wind angle fix.""" + assert sensor.fix_angle(angle) == expected + + +@pytest.mark.parametrize( + "uid, name, expected", + [ + ("12:34:56:37:11:ca-reachable", "netatmo_mystation_reachable", "True"), + ("12:34:56:03:1b:e4-rf_status", "netatmo_mystation_yard_radio", "Full"), + ( + "12:34:56:05:25:6e-rf_status", + "netatmo_valley_road_rain_gauge_radio", + "Medium", + ), + ( + "12:34:56:36:fc:de-rf_status_lvl", + "netatmo_mystation_netatmooutdoor_radio_level", + "65", + ), + ( + "12:34:56:37:11:ca-wifi_status_lvl", + "netatmo_mystation_wifi_level", + "45", + ), + ( + "12:34:56:37:11:ca-wifi_status", + "netatmo_mystation_wifi_status", + "Full", + ), + ( + "12:34:56:37:11:ca-temp_trend", + "netatmo_mystation_temperature_trend", + "stable", + ), + ( + "12:34:56:37:11:ca-pressure_trend", + "netatmo_mystation_pressure_trend", + "down", + ), + ("12:34:56:05:51:20-sum_rain_1", "netatmo_mystation_yard_rain_last_hour", "0"), + ("12:34:56:05:51:20-sum_rain_24", "netatmo_mystation_yard_rain_today", "0"), + ("12:34:56:03:1b:e4-windangle", "netatmo_mystation_garden_direction", "SW"), + ( + "12:34:56:03:1b:e4-windangle_value", + "netatmo_mystation_garden_angle", + "217", + ), + ("12:34:56:03:1b:e4-gustangle", "mystation_garden_gust_direction", "S"), + ( + "12:34:56:03:1b:e4-gustangle", + "netatmo_mystation_garden_gust_direction", + "S", + ), + ( + "12:34:56:03:1b:e4-gustangle_value", + "netatmo_mystation_garden_gust_angle_value", + "206", + ), + ( + "12:34:56:03:1b:e4-guststrength", + "netatmo_mystation_garden_gust_strength", + "9", + ), + ( + "12:34:56:26:68:92-health_idx", + "netatmo_baby_bedroom_health", + "Fine", + ), + ], +) +async def test_weather_sensor_enabling(hass, config_entry, uid, name, expected): + """Test enabling of by default disabled sensors.""" + with patch("time.time", return_value=TEST_TIME), selected_platforms(["sensor"]): + states_before = len(hass.states.async_all()) + assert hass.states.get(f"sensor.{name}") is None + + registry = er.async_get(hass) + registry.async_get_or_create( + "sensor", + "netatmo", + uid, + suggested_object_id=name, + disabled_by=None, + ) + await hass.config_entries.async_setup(config_entry.entry_id) + + await hass.async_block_till_done() + + assert len(hass.states.async_all()) > states_before + assert hass.states.get(f"sensor.{name}").state == expected diff --git a/tests/fixtures/netatmo/gethomecoachsdata.json b/tests/fixtures/netatmo/gethomecoachsdata.json new file mode 100644 index 00000000000..3f9de74bd1a --- /dev/null +++ b/tests/fixtures/netatmo/gethomecoachsdata.json @@ -0,0 +1,202 @@ +{ + "body": { + "devices": [ + { + "_id": "12:34:56:26:69:0c", + "cipher_id": "enc:16:1UqwQlYV5AY2pfyEi5H47dmmFOOL3mCUo+KAkchL4A2CLI5u0e45Xr5jeAswO+XO", + "date_setup": 1544560184, + "last_setup": 1544560184, + "type": "NHC", + "last_status_store": 1558268332, + "firmware": 45, + "last_upgrade": 1544560186, + "wifi_status": 58, + "reachable": false, + "co2_calibrating": false, + "station_name": "Bedroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:25:cf:a8", + "cipher_id": "enc:16:A+Jm0yFWBwUyKinFDutPZK7I2PuHN1fqaE9oB/KF+McbFs3oN9CKpR/dYbqL4om2", + "date_setup": 1544562192, + "last_setup": 1544562192, + "type": "NHC", + "last_status_store": 1559198922, + "firmware": 45, + "last_upgrade": 1544562194, + "wifi_status": 41, + "reachable": true, + "co2_calibrating": false, + "station_name": "Kitchen", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:26:65:14", + "cipher_id": "enc:16:7kK6ZzG4L7NgfZZ6+dMvNxw4l6vXu+88SEJkCUklNdPa4KYIHmsfa1moOilEK61i", + "date_setup": 1544564061, + "last_setup": 1544564061, + "type": "NHC", + "last_status_store": 1559067159, + "firmware": 45, + "last_upgrade": 1544564302, + "wifi_status": 66, + "reachable": true, + "co2_calibrating": false, + "station_name": "Livingroom", + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + } + }, + { + "_id": "12:34:56:3e:c5:46", + "station_name": "Parents Bedroom", + "date_setup": 1570732241, + "last_setup": 1570732241, + "type": "NHC", + "last_status_store": 1572073818, + "module_name": "Indoor", + "firmware": 45, + "wifi_status": 67, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1572073816, + "Temperature": 20.3, + "CO2": 494, + "Humidity": 63, + "Noise": 42, + "Pressure": 1014.5, + "AbsolutePressure": 1004.1, + "health_idx": 1, + "min_temp": 20.3, + "max_temp": 21.6, + "date_max_temp": 1572059333, + "date_min_temp": 1572073816 + } + }, + { + "_id": "12:34:56:26:68:92", + "station_name": "Baby Bedroom", + "date_setup": 1571342643, + "last_setup": 1571342643, + "type": "NHC", + "last_status_store": 1572073995, + "module_name": "Indoor", + "firmware": 45, + "wifi_status": 68, + "reachable": true, + "co2_calibrating": false, + "data_type": [ + "Temperature", + "CO2", + "Humidity", + "Noise", + "Pressure", + "health_idx" + ], + "place": { + "city": "Frankfurt", + "country": "DE", + "timezone": "Europe/Berlin", + "location": [ + 52.516263, + 13.377726 + ] + }, + "dashboard_data": { + "time_utc": 1572073994, + "Temperature": 21.6, + "CO2": 1053, + "Humidity": 66, + "Noise": 45, + "Pressure": 1021.4, + "AbsolutePressure": 1011, + "health_idx": 1, + "min_temp": 20.9, + "max_temp": 21.6, + "date_max_temp": 1572073690, + "date_min_temp": 1572064254 + } + } + ], + "user": { + "mail": "john@doe.com", + "administrative": { + "lang": "de-DE", + "reg_locale": "de-DE", + "country": "DE", + "unit": 0, + "windunit": 0, + "pressureunit": 0, + "feel_like_algo": 0 + } + } + }, + "status": "ok", + "time_exec": 0.095954179763794, + "time_server": 1559463229 +} \ No newline at end of file diff --git a/tests/fixtures/netatmo/getpublicdata.json b/tests/fixtures/netatmo/getpublicdata.json new file mode 100644 index 00000000000..55202713890 --- /dev/null +++ b/tests/fixtures/netatmo/getpublicdata.json @@ -0,0 +1,392 @@ +{ + "status": "ok", + "time_server": 1560248397, + "time_exec": 0, + "body": [ + { + "_id": "70:ee:50:36:94:7c", + "place": { + "location": [ + 8.791382999999996, + 50.2136394 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 132 + }, + "mark": 14, + "measures": { + "02:00:00:36:f2:94": { + "res": { + "1560248022": [ + 21.4, + 62 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:36:94:7c": { + "res": { + "1560248030": [ + 1010.6 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:05:33:84": { + "rain_60min": 0.2, + "rain_24h": 12.322000000000001, + "rain_live": 0.5, + "rain_timeutc": 1560248022 + } + }, + "modules": [ + "05:00:00:05:33:84", + "02:00:00:36:f2:94" + ], + "module_types": { + "05:00:00:05:33:84": "NAModule3", + "02:00:00:36:f2:94": "NAModule1" + } + }, + { + "_id": "70:ee:50:1f:68:9e", + "place": { + "location": [ + 8.795445200000017, + 50.2130169 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 125 + }, + "mark": 14, + "measures": { + "02:00:00:1f:82:28": { + "res": { + "1560248312": [ + 21.1, + 69 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:1f:68:9e": { + "res": { + "1560248344": [ + 1007.3 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:02:bb:6e": { + "rain_60min": 0, + "rain_24h": 9.999, + "rain_live": 0, + "rain_timeutc": 1560248344 + } + }, + "modules": [ + "02:00:00:1f:82:28", + "05:00:00:02:bb:6e" + ], + "module_types": { + "02:00:00:1f:82:28": "NAModule1", + "05:00:00:02:bb:6e": "NAModule3" + } + }, + { + "_id": "70:ee:50:27:25:b0", + "place": { + "location": [ + 8.7807159, + 50.1946167 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 112 + }, + "mark": 14, + "measures": { + "02:00:00:27:19:b2": { + "res": { + "1560247889": [ + 23.2, + 60 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:27:25:b0": { + "res": { + "1560247907": [ + 1012.8 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:03:5d:2e": { + "rain_60min": 0, + "rain_24h": 11.716000000000001, + "rain_live": 0, + "rain_timeutc": 1560247896 + } + }, + "modules": [ + "02:00:00:27:19:b2", + "05:00:00:03:5d:2e" + ], + "module_types": { + "02:00:00:27:19:b2": "NAModule1", + "05:00:00:03:5d:2e": "NAModule3" + } + }, + { + "_id": "70:ee:50:04:ed:7a", + "place": { + "location": [ + 8.785034, + 50.192169 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 112 + }, + "mark": 14, + "measures": { + "02:00:00:04:c2:2e": { + "res": { + "1560248137": [ + 19.8, + 76 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:04:ed:7a": { + "res": { + "1560248152": [ + 1005.4 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:04:c2:2e" + ], + "module_types": { + "02:00:00:04:c2:2e": "NAModule1" + } + }, + { + "_id": "70:ee:50:27:9f:2c", + "place": { + "location": [ + 8.785342, + 50.193573 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 116 + }, + "mark": 1, + "measures": { + "02:00:00:27:aa:70": { + "res": { + "1560247821": [ + 25.5, + 56 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:27:9f:2c": { + "res": { + "1560247853": [ + 1010.6 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:27:aa:70" + ], + "module_types": { + "02:00:00:27:aa:70": "NAModule1" + } + }, + { + "_id": "70:ee:50:01:20:fa", + "place": { + "location": [ + 8.7953, + 50.195241 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 119 + }, + "mark": 1, + "measures": { + "02:00:00:00:f7:ba": { + "res": { + "1560247831": [ + 27.4, + 58 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:01:20:fa": { + "res": { + "1560247876": [ + 1014.4 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:00:f7:ba" + ], + "module_types": { + "02:00:00:00:f7:ba": "NAModule1" + } + }, + { + "_id": "70:ee:50:3c:02:78", + "place": { + "location": [ + 8.795953681700666, + 50.19530139868166 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 119 + }, + "mark": 7, + "measures": { + "02:00:00:3c:21:f2": { + "res": { + "1560248225": [ + 23.3, + 58 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:3c:02:78": { + "res": { + "1560248270": [ + 1011.7 + ] + }, + "type": [ + "pressure" + ] + } + }, + "modules": [ + "02:00:00:3c:21:f2" + ], + "module_types": { + "02:00:00:3c:21:f2": "NAModule1" + } + }, + { + "_id": "70:ee:50:36:a9:fc", + "place": { + "location": [ + 8.801164269110814, + 50.19596181704958 + ], + "timezone": "Europe/Berlin", + "country": "DE", + "altitude": 113 + }, + "mark": 14, + "measures": { + "02:00:00:36:a9:50": { + "res": { + "1560248145": [ + 20.1, + 67 + ] + }, + "type": [ + "temperature", + "humidity" + ] + }, + "70:ee:50:36:a9:fc": { + "res": { + "1560248191": [ + 1010 + ] + }, + "type": [ + "pressure" + ] + }, + "05:00:00:02:92:82": { + "rain_60min": 0, + "rain_24h": 11.009, + "rain_live": 0, + "rain_timeutc": 1560248184 + }, + "06:00:00:03:19:76": { + "wind_strength": 15, + "wind_angle": 17, + "gust_strength": 31, + "gust_angle": 217, + "wind_timeutc": 1560248190 + } + }, + "modules": [ + "05:00:00:02:92:82", + "02:00:00:36:a9:50", + "06:00:00:03:19:76" + ], + "module_types": { + "05:00:00:02:92:82": "NAModule3", + "02:00:00:36:a9:50": "NAModule1", + "06:00:00:03:19:76": "NAModule2" + } + } + ] +} \ No newline at end of file