mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[airgradient] Support firmware v3.1.1 and later (#16851)
* Support calibrated measurements from firmware v3.1.1 Signed-off-by: Jørgen Austvik <jaustvik@acm.org> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
e32dd1718a
commit
3ba3152077
@ -67,18 +67,40 @@ To add a location, you need to know the location ID. To get the location ID, you
|
||||
|
||||
For more information about the data in the channels, please refer to the models in https://api.airgradient.com/public/docs/api/v1/
|
||||
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|-------------|----------------------|------------|----------------------------------------------------------------------------------|
|
||||
| pm01 | Number:Density | Read | Particulate Matter 1 (0.001mm) |
|
||||
| pm02 | Number:Density | Read | Particulate Matter 2 (0.002mm) |
|
||||
| pm10 | Number:Density | Read | Particulate Matter 10 (0.01mm) |
|
||||
| pm003-count | Switch | Read | The number of particles with a diameter beyond 0.3 microns in 1 deciliter of air |
|
||||
| rco2 | Number:Density | Read | Carbon dioxide PPM |
|
||||
| tvoc | Number:Density | Read | Total Volatile Organic Compounds |
|
||||
| atmp | Number:Temperature | Read | Ambient Temperature |
|
||||
| rhum | Number:Dimensionless | Read | Relative Humidity Percentage |
|
||||
| wifi | Number | Read | Received signal strength indicator |
|
||||
| boot | Number:Dimensionless | Read | Number of measure uploads since last reboot (boot) |
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|--------------------|----------------------|------------|----------------------------------------------------------------------------------|
|
||||
| pm01 | Number:Density | Read | Particulate Matter 1 (0.001mm) |
|
||||
| pm02 | Number:Density | Read | Particulate Matter 2 (0.002mm) |
|
||||
| pm10 | Number:Density | Read | Particulate Matter 10 (0.01mm) |
|
||||
| pm003-count | Number:Dimensionless | Read | The number of particles with a diameter beyond 0.3 microns in 1 deciliter of air |
|
||||
| rco2 | Number:Density | Read | Carbon dioxide PPM |
|
||||
| tvoc | Number:Density | Read | Total Volatile Organic Compounds |
|
||||
| atmp | Number:Temperature | Read | Ambient Temperature |
|
||||
| rhum | Number:Dimensionless | Read | Relative Humidity Percentage |
|
||||
| wifi | Number | Read | Received signal strength indicator |
|
||||
| uploads-since-boot | Number:Dimensionless | Read | Number of measure uploads since last reboot (boot) |
|
||||
| leds | String | Read/Write | Sets the leds mode (off/co2/pm) |
|
||||
| calibration | String | Write | Triggers co2 calibration on the device |
|
||||
|
||||
Some configuration channels are only available for local devices (for cloud devices use the AirGradient dashboard to configure these instead).
|
||||
These configuration settings needs AirGradient firmware on the sensor of version 3.1.1 or later.
|
||||
|
||||
| Channel | Type | Read/Write | Description |
|
||||
|-----------------------|----------------------|------------|----------------------------------------------------------------------------------|
|
||||
| country-code | String | Read/Write | The ALPHA-2 Country code used for the device |
|
||||
| pm-standard | String | Read/Write | Standard used for Parts per Million measurements (us-aqi or ugm3) |
|
||||
| abc-days | Number:Days | Read/Write | Co2 calibration automatic baseline calibration days |
|
||||
| tvoc-learning-offset | Number:Dimensionless | Read/Write | Time constant of long-term estimator for offset. |
|
||||
| nox-learning-offset | Number:Dimensionless | Read/Write | Time constant of long-term estimator for offset. |
|
||||
| mqtt-broker-url | String | Read/Write | MQTT Broker URL |
|
||||
| temperature-unit | String | Read/Write | Temperature unit used on the display |
|
||||
| configuration-control | String | Read/Write | Where the unit is configured from (local/cloud/both) |
|
||||
| post-to-cloud | Switch | Read/Write | Send data to the AirGradient cloud |
|
||||
| led-bar-brightness | Number:Dimensionless | Read/Write | Brightness of the LED bar |
|
||||
| display-brightness | Number:Dimensionless | Read/Write | Brightness of the display |
|
||||
| model | String | Read/Write | The model of the device (can be changed e.g. if you change sensors) |
|
||||
| led-bar-test | String | Write | Trigger test of LED bar |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
|
@ -26,7 +26,7 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
@NonNullByDefault
|
||||
public class AirGradientBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "airgradient";
|
||||
public static final String BINDING_ID = "airgradient";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_API = new ThingTypeUID(BINDING_ID, "airgradient-api");
|
||||
@ -46,6 +46,19 @@ public class AirGradientBindingConstants {
|
||||
public static final String CHANNEL_LEDS_MODE = "leds";
|
||||
public static final String CHANNEL_CALIBRATION = "calibration";
|
||||
public static final String CHANNEL_UPLOADS_SINCE_BOOT = "uploads-since-boot";
|
||||
public static final String CHANNEL_COUNTRY_CODE = "country-code";
|
||||
public static final String CHANNEL_PM_STANDARD = "pm-standard";
|
||||
public static final String CHANNEL_ABC_DAYS = "abc-days";
|
||||
public static final String CHANNEL_TVOC_LEARNING_OFFSET = "tvoc-learning-offset";
|
||||
public static final String CHANNEL_NOX_LEARNING_OFFSET = "nox-learning-offset";
|
||||
public static final String CHANNEL_MQTT_BROKER_URL = "mqtt-broker-url";
|
||||
public static final String CHANNEL_TEMPERATURE_UNIT = "temperature-unit";
|
||||
public static final String CHANNEL_CONFIGURATION_CONTROL = "configuration-control";
|
||||
public static final String CHANNEL_POST_TO_CLOUD = "post-to-cloud";
|
||||
public static final String CHANNEL_LED_BAR_BRIGHTNESS = "led-bar-brightness";
|
||||
public static final String CHANNEL_DISPLAY_BRIGHTNESS = "display-brightness";
|
||||
public static final String CHANNEL_MODEL = "model";
|
||||
public static final String CHANNEL_LED_BAR_TEST = "led-bar-test";
|
||||
|
||||
// List of all properties
|
||||
public static final String PROPERTY_NAME = "name";
|
||||
@ -59,6 +72,7 @@ public class AirGradientBindingConstants {
|
||||
// URLs for API
|
||||
public static final String CURRENT_MEASURES_PATH = "/public/api/v1/locations/measures/current?token=%s";
|
||||
public static final String CURRENT_MEASURES_LOCAL_PATH = "/measures/current";
|
||||
public static final String LOCAL_CONFIG_PATH = "/config";
|
||||
public static final String LEDS_MODE_PATH = "/public/api/v1/sensors/%s/config/leds/mode?token=%s";
|
||||
public static final String CALIBRATE_CO2_PATH = "/public/api/v1/sensors/%s/co2/calibration?token=%s";
|
||||
|
||||
|
@ -15,8 +15,10 @@ package org.openhab.binding.airgradient.internal.communication;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CALIBRATE_CO2_PATH;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CURRENT_MEASURES_PATH;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.LEDS_MODE_PATH;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.LOCAL_CONFIG_PATH;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.REQUEST_TIMEOUT;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -42,11 +44,17 @@ public class RESTHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable String generateConfigUrl(AirGradientAPIConfiguration apiConfig) {
|
||||
URI uri = URI.create(apiConfig.hostname);
|
||||
URI configUri = uri.resolve(LOCAL_CONFIG_PATH);
|
||||
return configUri.toString();
|
||||
}
|
||||
|
||||
public static @Nullable String generateCalibrationCo2Url(AirGradientAPIConfiguration apiConfig, String serialNo) {
|
||||
if (apiConfig.hasCloudUrl()) {
|
||||
return apiConfig.hostname + String.format(CALIBRATE_CO2_PATH, serialNo, apiConfig.token);
|
||||
} else {
|
||||
return apiConfig.hostname;
|
||||
return generateConfigUrl(apiConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,7 +62,7 @@ public class RESTHelper {
|
||||
if (apiConfig.hasCloudUrl()) {
|
||||
return apiConfig.hostname + String.format(LEDS_MODE_PATH, serialNo, apiConfig.token);
|
||||
} else {
|
||||
return apiConfig.hostname;
|
||||
return generateConfigUrl(apiConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import static org.openhab.binding.airgradient.internal.AirGradientBindingConstan
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CONTENTTYPE_TEXT;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.REQUEST_TIMEOUT;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -35,11 +36,13 @@ import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.airgradient.internal.config.AirGradientAPIConfiguration;
|
||||
import org.openhab.binding.airgradient.internal.model.LedMode;
|
||||
import org.openhab.binding.airgradient.internal.model.LocalConfiguration;
|
||||
import org.openhab.binding.airgradient.internal.model.Measure;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* Helper for doing rest calls to the AirGradient API.
|
||||
@ -72,7 +75,7 @@ public class RemoteAPIController {
|
||||
RESTHelper.generateRequest(httpClient, RESTHelper.generateMeasuresUrl(apiConfig)));
|
||||
if (response != null) {
|
||||
String contentType = response.getMediaType();
|
||||
logger.debug("Got measurements with status {}: {} ({})", response.getStatus(),
|
||||
logger.trace("Got measurements with status {}: {} ({})", response.getStatus(),
|
||||
response.getContentAsString(), contentType);
|
||||
|
||||
if (HttpStatus.isSuccess(response.getStatus())) {
|
||||
@ -96,6 +99,31 @@ public class RemoteAPIController {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public @Nullable LocalConfiguration getConfig() throws AirGradientCommunicationException {
|
||||
ContentResponse response = sendRequest(
|
||||
RESTHelper.generateRequest(httpClient, RESTHelper.generateConfigUrl(apiConfig)));
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.trace("Got configuration with status {}: {}", response.getStatus(), response.getContentAsString());
|
||||
|
||||
Type configType = new TypeToken<LocalConfiguration>() {
|
||||
}.getType();
|
||||
return gson.fromJson(response.getContentAsString(), configType);
|
||||
}
|
||||
|
||||
public void setConfig(LocalConfiguration config) throws AirGradientCommunicationException {
|
||||
Request request = httpClient.newRequest(RESTHelper.generateConfigUrl(apiConfig));
|
||||
request.timeout(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
|
||||
request.method(HttpMethod.PUT);
|
||||
request.header(HttpHeader.CONTENT_TYPE, CONTENTTYPE_JSON);
|
||||
String configJson = gson.toJson(config);
|
||||
logger.debug("Setting configuration: {}", configJson);
|
||||
request.content(new StringContentProvider(CONTENTTYPE_JSON, configJson, StandardCharsets.UTF_8));
|
||||
sendRequest(request);
|
||||
}
|
||||
|
||||
public void setLedMode(String serialNo, String mode) throws AirGradientCommunicationException {
|
||||
Request request = httpClient.newRequest(RESTHelper.generateGetLedsModeUrl(apiConfig, serialNo));
|
||||
request.timeout(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
|
||||
@ -126,7 +154,8 @@ public class RemoteAPIController {
|
||||
try {
|
||||
response = request.send();
|
||||
if (response != null) {
|
||||
logger.debug("Response from {}: {}", request.getURI(), response.getStatus());
|
||||
logger.trace("Response from {} ({}): {}", request.getURI(), response.getStatus(),
|
||||
response.getContentAsString());
|
||||
if (!HttpStatus.isSuccess(response.getStatus())) {
|
||||
throw new AirGradientCommunicationException("Returned status code: " + response.getStatus());
|
||||
}
|
||||
|
@ -12,13 +12,13 @@
|
||||
*/
|
||||
package org.openhab.binding.airgradient.internal.handler;
|
||||
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CHANNEL_CALIBRATION;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CHANNEL_LEDS_MODE;
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -26,13 +26,17 @@ import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.airgradient.internal.communication.AirGradientCommunicationException;
|
||||
import org.openhab.binding.airgradient.internal.communication.RemoteAPIController;
|
||||
import org.openhab.binding.airgradient.internal.config.AirGradientAPIConfiguration;
|
||||
import org.openhab.binding.airgradient.internal.model.LocalConfiguration;
|
||||
import org.openhab.binding.airgradient.internal.model.Measure;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
@ -72,7 +76,7 @@ public class AirGradientLocalHandler extends BaseThingHandler {
|
||||
pollingCode();
|
||||
} else if (CHANNEL_LEDS_MODE.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
setLedModeOnDevice(stringCommand.toFullString());
|
||||
updateConfiguration((var c) -> c.ledBarMode = stringCommand.toFullString());
|
||||
} else {
|
||||
logger.warn("Received command {} for channel {}, but it needs a string command", command.toString(),
|
||||
channelUID.getId());
|
||||
@ -80,17 +84,63 @@ public class AirGradientLocalHandler extends BaseThingHandler {
|
||||
} else if (CHANNEL_CALIBRATION.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
if ("co2".equals(stringCommand.toFullString())) {
|
||||
calibrateCo2OnDevice();
|
||||
updateConfiguration((var c) -> c.co2CalibrationRequested = true);
|
||||
} else {
|
||||
logger.warn(
|
||||
"Received unknown command {} for calibration on channel {}, which we don't know how to handle",
|
||||
command.toString(), channelUID.getId());
|
||||
}
|
||||
}
|
||||
} else if (CHANNEL_TEMPERATURE_UNIT.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
updateConfiguration((var c) -> c.temperatureUnit = stringCommand.toFullString());
|
||||
}
|
||||
} else if (CHANNEL_PM_STANDARD.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
updateConfiguration((var c) -> c.pmStandard = stringCommand.toFullString());
|
||||
}
|
||||
} else if (CHANNEL_ABC_DAYS.equals(channelUID.getId())) {
|
||||
if (command instanceof QuantityType quantityCommand) {
|
||||
updateConfiguration((var c) -> c.abcDays = quantityCommand.longValue());
|
||||
}
|
||||
} else if (CHANNEL_TVOC_LEARNING_OFFSET.equals(channelUID.getId())) {
|
||||
if (command instanceof QuantityType quantityCommand) {
|
||||
updateConfiguration((var c) -> c.tvocLearningOffset = quantityCommand.longValue());
|
||||
}
|
||||
} else if (CHANNEL_NOX_LEARNING_OFFSET.equals(channelUID.getId())) {
|
||||
if (command instanceof QuantityType quantityCommand) {
|
||||
updateConfiguration((var c) -> c.noxLearningOffset = quantityCommand.longValue());
|
||||
}
|
||||
} else if (CHANNEL_MQTT_BROKER_URL.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
updateConfiguration((var c) -> c.mqttBrokerUrl = stringCommand.toFullString());
|
||||
}
|
||||
} else if (CHANNEL_CONFIGURATION_CONTROL.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
updateConfiguration((var c) -> c.configurationControl = stringCommand.toFullString());
|
||||
}
|
||||
} else if (CHANNEL_LED_BAR_BRIGHTNESS.equals(channelUID.getId())) {
|
||||
if (command instanceof QuantityType quantityCommand) {
|
||||
updateConfiguration((var c) -> c.ledBarBrightness = quantityCommand.longValue());
|
||||
}
|
||||
} else if (CHANNEL_DISPLAY_BRIGHTNESS.equals(channelUID.getId())) {
|
||||
if (command instanceof QuantityType quantityCommand) {
|
||||
updateConfiguration((var c) -> c.displayBrightness = quantityCommand.longValue());
|
||||
}
|
||||
} else if (CHANNEL_POST_TO_CLOUD.equals(channelUID.getId())) {
|
||||
if (command instanceof OnOffType onOffCommand) {
|
||||
updateConfiguration((var c) -> c.postDataToAirGradient = onOffCommand.equals(OnOffType.ON));
|
||||
}
|
||||
} else if (CHANNEL_MODEL.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType stringCommand) {
|
||||
updateConfiguration((var c) -> c.model = stringCommand.toFullString());
|
||||
}
|
||||
} else if (CHANNEL_LED_BAR_TEST.equals(channelUID.getId())) {
|
||||
updateConfiguration((var c) -> c.ledBarTestRequested = true);
|
||||
} else {
|
||||
// This is read only
|
||||
logger.warn("Received command {} for channel {}, which we don't know how to handle", command.toString(),
|
||||
channelUID.getId());
|
||||
logger.warn("Received command {} for channel {}, which we don't know how to handle (type: {})",
|
||||
command.toString(), channelUID.getId(), command.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,50 +174,46 @@ public class AirGradientLocalHandler extends BaseThingHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
updateProperties(MeasureHelper.createProperties(measures.get(0)));
|
||||
Map<String, State> states = MeasureHelper.createStates(measures.get(0));
|
||||
Measure measure = measures.get(0);
|
||||
updateProperties(MeasureHelper.createProperties(measure));
|
||||
Map<String, State> states = MeasureHelper.createStates(measure);
|
||||
for (Map.Entry<String, State> entry : states.entrySet()) {
|
||||
if (isLinked(entry.getKey())) {
|
||||
updateState(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
LocalConfiguration localConfig = apiController.getConfig();
|
||||
if (localConfig != null) {
|
||||
// If we are able to read config, we add config channels
|
||||
ThingBuilder builder = DynamicChannelHelper.updateThingWithConfigurationChannels(thing, editThing());
|
||||
updateThing(builder.build());
|
||||
|
||||
updateProperties(ConfigurationHelper.createProperties(localConfig));
|
||||
Map<String, State> configStates = ConfigurationHelper.createStates(localConfig);
|
||||
for (Map.Entry<String, State> entry : configStates.entrySet()) {
|
||||
if (isLinked(entry.getKey())) {
|
||||
updateState(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (AirGradientCommunicationException agce) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, agce.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void setLedModeOnDevice(String mode) {
|
||||
private void updateConfiguration(Consumer<LocalConfiguration> action) {
|
||||
try {
|
||||
apiController.setLedMode(getSerialNo(), mode);
|
||||
LocalConfiguration config = new LocalConfiguration();
|
||||
action.accept(config);
|
||||
apiController.setConfig(config);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (AirGradientCommunicationException agce) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, agce.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void calibrateCo2OnDevice() {
|
||||
try {
|
||||
apiController.calibrateCo2(getSerialNo());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} catch (AirGradientCommunicationException agce) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, agce.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serial number of this sensor.
|
||||
*
|
||||
* @return serial number of this sensor.
|
||||
*/
|
||||
public String getSerialNo() {
|
||||
String serialNo = thing.getProperties().get(Thing.PROPERTY_SERIAL_NUMBER);
|
||||
if (serialNo == null) {
|
||||
serialNo = "";
|
||||
}
|
||||
|
||||
return serialNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> pollingJob = this.pollingJob;
|
||||
|
@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.airgradient.internal.handler;
|
||||
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.airgradient.internal.model.LocalConfiguration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* Helper class to reduce code duplication across things.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigurationHelper {
|
||||
|
||||
public static Map<String, String> createProperties(LocalConfiguration configuration) {
|
||||
Map<String, String> properties = new HashMap<>(4);
|
||||
|
||||
String model = configuration.model;
|
||||
if (model != null) {
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, model);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static final String CHANNEL_POST_TO_CLOUD = "post-to-cloud";
|
||||
|
||||
public static Map<String, State> createStates(LocalConfiguration configuration) {
|
||||
Map<String, State> states = new HashMap<>(11);
|
||||
|
||||
states.put(CHANNEL_COUNTRY_CODE, toStringType(configuration.country));
|
||||
states.put(CHANNEL_PM_STANDARD, toStringType(configuration.pmStandard));
|
||||
states.put(CHANNEL_ABC_DAYS, toQuantityType(configuration.abcDays, Units.DAY));
|
||||
states.put(CHANNEL_TVOC_LEARNING_OFFSET, toQuantityType(configuration.tvocLearningOffset, Units.ONE));
|
||||
states.put(CHANNEL_NOX_LEARNING_OFFSET, toQuantityType(configuration.noxLearningOffset, Units.ONE));
|
||||
states.put(CHANNEL_MQTT_BROKER_URL, toStringType(configuration.mqttBrokerUrl));
|
||||
states.put(CHANNEL_TEMPERATURE_UNIT, toStringType(configuration.temperatureUnit));
|
||||
states.put(CHANNEL_CONFIGURATION_CONTROL, toStringType(configuration.configurationControl));
|
||||
states.put(CHANNEL_LED_BAR_BRIGHTNESS, toQuantityType(configuration.ledBarBrightness, Units.ONE));
|
||||
states.put(CHANNEL_DISPLAY_BRIGHTNESS, toQuantityType(configuration.displayBrightness, Units.ONE));
|
||||
states.put(CHANNEL_POST_TO_CLOUD, toOnOffType(configuration.postDataToAirGradient));
|
||||
states.put(CHANNEL_MODEL, toStringType(configuration.model));
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
private static State toQuantityType(@Nullable Number value, Unit<?> unit) {
|
||||
return value == null ? UnDefType.NULL : new QuantityType<>(value, unit);
|
||||
}
|
||||
|
||||
private static State toStringType(@Nullable String value) {
|
||||
return value == null ? UnDefType.NULL : StringType.valueOf(value);
|
||||
}
|
||||
|
||||
private static State toOnOffType(@Nullable Boolean value) {
|
||||
return value == null ? UnDefType.NULL : OnOffType.from(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.airgradient.internal.handler;
|
||||
|
||||
import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link DynamicChannelHelper} is responsible for creating dynamic configuration channels.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DynamicChannelHelper {
|
||||
|
||||
private record ConfigurationChannel(String id, String typeId, String itemType) {
|
||||
}
|
||||
|
||||
private final static List<ConfigurationChannel> channels = new ArrayList<ConfigurationChannel>() {
|
||||
{
|
||||
add(new ConfigurationChannel(CHANNEL_COUNTRY_CODE, CHANNEL_COUNTRY_CODE, "String"));
|
||||
add(new ConfigurationChannel(CHANNEL_PM_STANDARD, CHANNEL_PM_STANDARD, "String"));
|
||||
add(new ConfigurationChannel(CHANNEL_ABC_DAYS, CHANNEL_ABC_DAYS, "Number"));
|
||||
add(new ConfigurationChannel(CHANNEL_TVOC_LEARNING_OFFSET, CHANNEL_TVOC_LEARNING_OFFSET, "Number"));
|
||||
add(new ConfigurationChannel(CHANNEL_NOX_LEARNING_OFFSET, CHANNEL_NOX_LEARNING_OFFSET, "Number"));
|
||||
add(new ConfigurationChannel(CHANNEL_MQTT_BROKER_URL, CHANNEL_MQTT_BROKER_URL, "String"));
|
||||
add(new ConfigurationChannel(CHANNEL_TEMPERATURE_UNIT, CHANNEL_TEMPERATURE_UNIT, "String"));
|
||||
add(new ConfigurationChannel(CHANNEL_CONFIGURATION_CONTROL, CHANNEL_CONFIGURATION_CONTROL, "String"));
|
||||
add(new ConfigurationChannel(CHANNEL_POST_TO_CLOUD, CHANNEL_POST_TO_CLOUD, "Switch"));
|
||||
add(new ConfigurationChannel(CHANNEL_LED_BAR_BRIGHTNESS, CHANNEL_LED_BAR_BRIGHTNESS,
|
||||
"Number:Dimensionless"));
|
||||
add(new ConfigurationChannel(CHANNEL_DISPLAY_BRIGHTNESS, CHANNEL_DISPLAY_BRIGHTNESS,
|
||||
"Number:Dimensionless"));
|
||||
add(new ConfigurationChannel(CHANNEL_MODEL, CHANNEL_MODEL, "String"));
|
||||
add(new ConfigurationChannel(CHANNEL_LED_BAR_TEST, CHANNEL_LED_BAR_TEST, "String"));
|
||||
}
|
||||
};
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(DynamicChannelHelper.class);
|
||||
|
||||
public static ThingBuilder updateThingWithConfigurationChannels(Thing thing, ThingBuilder builder) {
|
||||
for (ConfigurationChannel channel : channels) {
|
||||
addLocalConfigurationChannel(thing, builder, channel);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static void addLocalConfigurationChannel(Thing originalThing, ThingBuilder builder,
|
||||
ConfigurationChannel toAdd) {
|
||||
ChannelUID channelId = new ChannelUID(originalThing.getUID(), toAdd.id);
|
||||
if (originalThing.getChannel(channelId) == null) {
|
||||
logger.debug("Adding dynamic channel {} to {}", toAdd.id, originalThing.getUID());
|
||||
ChannelTypeUID typeId = new ChannelTypeUID(BINDING_ID, toAdd.typeId);
|
||||
Channel channel = ChannelBuilder.create(channelId, toAdd.itemType).withType(typeId).build();
|
||||
builder.withChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
@ -66,13 +66,13 @@ public class MeasureHelper {
|
||||
public static Map<String, State> createStates(Measure measure) {
|
||||
Map<String, State> states = new HashMap<>(11);
|
||||
|
||||
states.put(CHANNEL_ATMP, toQuantityType(measure.atmp, SIUnits.CELSIUS));
|
||||
states.put(CHANNEL_ATMP, toQuantityType(measure.getTemperature(), SIUnits.CELSIUS));
|
||||
states.put(CHANNEL_PM_003_COUNT, toQuantityType(measure.pm003Count, Units.ONE));
|
||||
states.put(CHANNEL_PM_01, toQuantityType(measure.pm01, Units.MICROGRAM_PER_CUBICMETRE));
|
||||
states.put(CHANNEL_PM_02, toQuantityType(measure.pm02, Units.MICROGRAM_PER_CUBICMETRE));
|
||||
states.put(CHANNEL_PM_10, toQuantityType(measure.pm10, Units.MICROGRAM_PER_CUBICMETRE));
|
||||
states.put(CHANNEL_RHUM, toQuantityType(measure.rhum, Units.PERCENT));
|
||||
states.put(CHANNEL_UPLOADS_SINCE_BOOT, toQuantityType(measure.boot, Units.ONE));
|
||||
states.put(CHANNEL_RHUM, toQuantityType(measure.getHumidity(), Units.PERCENT));
|
||||
states.put(CHANNEL_UPLOADS_SINCE_BOOT, toQuantityType(measure.getBootCount(), Units.ONE));
|
||||
|
||||
Double rco2 = measure.rco2;
|
||||
if (rco2 != null) {
|
||||
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.airgradient.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Data model class for configuration from a local sensor.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LocalConfiguration {
|
||||
|
||||
@Nullable
|
||||
public String country; // ALPHA-2 Country code
|
||||
|
||||
@Nullable
|
||||
public String pmStandard; // usaqi/ugm3
|
||||
|
||||
@Nullable
|
||||
public String ledBarMode; // off, pm, co2
|
||||
|
||||
@Nullable
|
||||
public Long abcDays; // Co2 calibration automatic baseline calibration days ( 0-200)
|
||||
|
||||
@Nullable
|
||||
public Long tvocLearningOffset; // Time constant of long-term estimator for offset. Past events will be forgotten
|
||||
// after about twice the learning time. Range 1..1000 [hours]
|
||||
|
||||
@Nullable
|
||||
public Long noxLearningOffset; // Time constant of long-term estimator for offset. Past events will be forgotten
|
||||
// after about twice the learning time. Range 1..1000 [hours]
|
||||
|
||||
@Nullable
|
||||
public String mqttBrokerUrl;
|
||||
|
||||
@Nullable
|
||||
public String temperatureUnit; // c/f
|
||||
|
||||
@Nullable
|
||||
public String configurationControl; // local, cloud, both
|
||||
|
||||
@Nullable
|
||||
public Boolean postDataToAirGradient;
|
||||
|
||||
@Nullable
|
||||
public Long ledBarBrightness; // 0 - 100
|
||||
|
||||
@Nullable
|
||||
public Long displayBrightness; // 0 - 100
|
||||
|
||||
@Nullable
|
||||
public Boolean offlineMode; // Don't connect to wifi
|
||||
|
||||
@Nullable
|
||||
public String model;
|
||||
|
||||
@Nullable
|
||||
public Boolean co2CalibrationRequested; // TRIGGER: Calibration of Co2 sensor
|
||||
|
||||
@Nullable
|
||||
public Boolean ledBarTestRequested; // TRIGGER: LEDs will run test sequence
|
||||
}
|
@ -91,6 +91,30 @@ public class Measure {
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable Long getBootCount() {
|
||||
if (bootCount == null) {
|
||||
return boot;
|
||||
}
|
||||
|
||||
return bootCount;
|
||||
}
|
||||
|
||||
public @Nullable Double getTemperature() {
|
||||
if (atmpCompensated == null) {
|
||||
return atmp;
|
||||
}
|
||||
|
||||
return atmpCompensated;
|
||||
}
|
||||
|
||||
public @Nullable Double getHumidity() {
|
||||
if (rhumCompensated == null) {
|
||||
return rhum;
|
||||
}
|
||||
|
||||
return rhumCompensated;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String locationId;
|
||||
|
||||
@ -115,9 +139,15 @@ public class Measure {
|
||||
@Nullable
|
||||
public Double atmp; // The ambient temperature in celsius
|
||||
|
||||
@Nullable
|
||||
public Double atmpCompensated; // The ambient temperature, compensated for sensor inaccuracies
|
||||
|
||||
@Nullable
|
||||
public Double rhum; // The relative humidity in percent
|
||||
|
||||
@Nullable
|
||||
public Double rhumCompensated; // The relative humidity in percent, compensated for sensor inaccuracies
|
||||
|
||||
@Nullable
|
||||
public Double rco2; // The CO2 value in ppm
|
||||
|
||||
@ -127,9 +157,15 @@ public class Measure {
|
||||
@Nullable
|
||||
public Double tvocIndex; // The value of the TVOC index, sensor model dependent
|
||||
|
||||
@Nullable
|
||||
public Double tvocRaw; // Raw data from TVOC senosor
|
||||
|
||||
@Nullable
|
||||
public Double noxIndex; // The value of the NOx index, sensor model dependent
|
||||
|
||||
@Nullable
|
||||
public Double noxRaw; // Raw data from NOx sensor
|
||||
|
||||
@Nullable
|
||||
public Double wifi; // The wifi signal strength in dBm
|
||||
|
||||
@ -157,6 +193,9 @@ public class Measure {
|
||||
@Nullable
|
||||
public Long boot; // Number of times sensor has uploaded data since last reboot
|
||||
|
||||
@Nullable
|
||||
public Long bootCount; // Same as boot, in firmwares > v3
|
||||
|
||||
@Nullable
|
||||
public String fwMode; // Model of sensor from local API
|
||||
|
||||
|
@ -50,6 +50,21 @@
|
||||
<channel id="leds" typeId="leds-mode"/>
|
||||
<channel id="calibration" typeId="calibration"/>
|
||||
<channel id="uploads-since-boot" typeId="uploads-since-boot"/>
|
||||
<!-- These are added dynamically if the device supports them
|
||||
<channel id="country-code" typeId="country-code"/>
|
||||
<channel id="pm-standard" typeId="pm-standard"/>
|
||||
<channel id="abc-days" typeId="abc-days"/>
|
||||
<channel id="tvoc-learning-offset" typeId="tvoc-learning-offset"/>
|
||||
<channel id="nox-learning-offset" typeId="nox-learning-offset"/>
|
||||
<channel id="mqtt-broker-url" typeId="mqtt-broker-url"/>
|
||||
<channel id="temperature-unit" typeId="temperature-unit"/>
|
||||
<channel id="configuration-control" typeId="configuration-control"/>
|
||||
<channel id="post-to-cloud" typeId="post-to-cloud"/>
|
||||
<channel id="led-bar-brightness" typeId="led-bar-brightness"/>
|
||||
<channel id="display-brightness" typeId="display-brightness"/>
|
||||
<channel id="model" typeId="model"/>
|
||||
<channel id="led-bar-test" typeId="led-bar-test"/>
|
||||
-->
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
@ -199,4 +214,113 @@
|
||||
</command>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="country-code">
|
||||
<item-type>String</item-type>
|
||||
<label>Country code</label>
|
||||
<description>2 digit country code (ALPHA-2)</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pm-standard">
|
||||
<item-type>String</item-type>
|
||||
<label>Parts per Million Standard</label>
|
||||
<description>Standard used for Parts per Million measurements</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="us-aqi">USAqi</option>
|
||||
<option value="ugm3">ugm3</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="abc-days">
|
||||
<item-type>Number</item-type>
|
||||
<label>Automatic Baseline Calibration (Days)</label>
|
||||
<description>Co2 calibration automatic baseline calibration days</description>
|
||||
<state min="0" max="200" step="1" readOnly="false" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="tvoc-learning-offset">
|
||||
<item-type>Number</item-type>
|
||||
<label>TVOC learnings offset (hours)</label>
|
||||
<description>Time constant of long-term estimator for offset. Past events will be forgotten after about twice the
|
||||
learning time.</description>
|
||||
<state min="0" max="1000" step="1" readOnly="false" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="nox-learning-offset">
|
||||
<item-type>Number</item-type>
|
||||
<label>NOX learnings offset (hours)</label>
|
||||
<description>Time constant of long-term estimator for offset. Past events will be forgotten after about twice the
|
||||
learning time.</description>
|
||||
<state min="0" max="1000" step="1" readOnly="false" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mqtt-broker-url">
|
||||
<item-type>String</item-type>
|
||||
<label>MQTT Broker URL</label>
|
||||
<description>MQTT Broker URL</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature-unit">
|
||||
<item-type>String</item-type>
|
||||
<label>Temperature Unit</label>
|
||||
<description>Temperature unit used on the display</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="c">Celsius</option>
|
||||
<option value="f">Fahrenheit</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="configuration-control">
|
||||
<item-type>String</item-type>
|
||||
<label>Configuration Control</label>
|
||||
<description>Where the unit is configured from</description>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="both">Both</option>
|
||||
<option value="local">Local</option>
|
||||
<option value="cloud">Cloud</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="post-to-cloud">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Send to cloud</label>
|
||||
<description>Send data to the AirGradient cloud</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="led-bar-brightness">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Led bar brightness</label>
|
||||
<description>Brightness of the LED bar.</description>
|
||||
<state min="0" max="100" step="1" readOnly="false" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="display-brightness">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Display brightness</label>
|
||||
<description>Brightness of the display.</description>
|
||||
<state min="0" max="100" step="1" readOnly="false" pattern="%d"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="model">
|
||||
<item-type>String</item-type>
|
||||
<label>Model</label>
|
||||
<description>Model of the device</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="led-bar-test">
|
||||
<item-type>String</item-type>
|
||||
<label>LED Bar test</label>
|
||||
<description>Test LED bar</description>
|
||||
<state readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
@ -58,6 +58,19 @@ public class AirGradientLocationHandlerTest {
|
||||
}
|
||||
};
|
||||
|
||||
private static final Measure TEST_MEASURE_V3_1_1 = new Measure() {
|
||||
{
|
||||
locationId = "12345";
|
||||
locationName = "Location name";
|
||||
timestamp = "2024-01-07T11:28:56.000Z";
|
||||
serialno = "ecda3b1a2a50";
|
||||
firmwareVersion = "3.1.1";
|
||||
atmpCompensated = 24.2;
|
||||
rhumCompensated = 36d;
|
||||
bootCount = 16l;
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private AirGradientLocationHandler sut;
|
||||
|
||||
@ -102,4 +115,18 @@ public class AirGradientLocationHandlerTest {
|
||||
verify(callbackMock).stateUpdated(new ChannelUID(sut.getThing().getUID(), CHANNEL_TVOC),
|
||||
new QuantityType<>("51 ppb"));
|
||||
}
|
||||
|
||||
// Firmware Version 3.1.1 has slight changes in the Json
|
||||
@Test
|
||||
public void testSetMeasureVersion3_1_1() {
|
||||
sut.setCallback(callbackMock);
|
||||
sut.setMeasurment(TEST_MEASURE_V3_1_1);
|
||||
|
||||
verify(callbackMock).stateUpdated(new ChannelUID(sut.getThing().getUID(), CHANNEL_ATMP),
|
||||
new QuantityType<>("24.2 °C"));
|
||||
verify(callbackMock).stateUpdated(new ChannelUID(sut.getThing().getUID(), CHANNEL_RHUM),
|
||||
new QuantityType<>("36 %"));
|
||||
verify(callbackMock).stateUpdated(new ChannelUID(sut.getThing().getUID(), CHANNEL_UPLOADS_SINCE_BOOT),
|
||||
new QuantityType<>("16"));
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,10 @@ public class RemoteApiControllerTest {
|
||||
}
|
||||
};
|
||||
|
||||
private static final String SINGLE_CONFIG = """
|
||||
{"country":"NO","pmStandard":"ugm3","ledBarMode":"off","abcDays":8,"tvocLearningOffset":12,"noxLearningOffset":12,"mqttBrokerUrl":"https://192.168.1.1/mqtt","temperatureUnit":"c","configurationControl":"both","postDataToAirGradient":true,"ledBarBrightness":100,"displayBrightness":100,"offlineMode":false,"model":"I-9PSL"}
|
||||
""";
|
||||
|
||||
private static final String SINGLE_CONTENT = """
|
||||
{"locationId":4321,"locationName":"Some other name","pm01":1,"pm02":2,"pm10":2,"pm003Count":536,"atmp":20.45,"rhum":16.61,"rco2":456,"tvoc":51.644928,"wifi":-54,"timestamp":"2024-01-07T13:00:20.000Z","ledMode":"co2","ledCo2Threshold1":1000,"ledCo2Threshold2":2000,"ledCo2ThresholdEnd":4000,"serialno":"serial","firmwareVersion":null,"tvocIndex":null,"noxIndex":null}
|
||||
""";
|
||||
@ -259,4 +263,29 @@ public class RemoteApiControllerTest {
|
||||
assertThat(res.get(0).noxIndex, closeTo(1, 0.1));
|
||||
assertThat(res.get(0).serialno, is("4XXXXXXXXXXc"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetConfig() throws Exception {
|
||||
ContentResponse response = Mockito.mock(ContentResponse.class);
|
||||
Mockito.when(httpClientMock.newRequest(anyString())).thenReturn(requestMock);
|
||||
Mockito.when(requestMock.send()).thenReturn(response);
|
||||
Mockito.when(response.getStatus()).thenReturn(200);
|
||||
Mockito.when(response.getMediaType()).thenReturn("application/json");
|
||||
Mockito.when(response.getContentAsString()).thenReturn(SINGLE_CONFIG);
|
||||
|
||||
var res = sut.getConfig();
|
||||
assertThat(res.abcDays, is(8L));
|
||||
assertThat(res.configurationControl, is("both"));
|
||||
assertThat(res.country, is("NO"));
|
||||
assertThat(res.displayBrightness, is(100L));
|
||||
assertThat(res.ledBarBrightness, is(100L));
|
||||
assertThat(res.ledBarMode, is("off"));
|
||||
assertThat(res.model, is("I-9PSL"));
|
||||
assertThat(res.mqttBrokerUrl, is("https://192.168.1.1/mqtt"));
|
||||
assertThat(res.noxLearningOffset, is(12L));
|
||||
assertThat(res.pmStandard, is("ugm3"));
|
||||
assertThat(res.postDataToAirGradient, is(true));
|
||||
assertThat(res.temperatureUnit, is("c"));
|
||||
assertThat(res.tvocLearningOffset, is(12L));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user