Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: system.round profile for rounding numbers before reaching an item #1204

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,15 @@ public class SystemProfileFactory implements ProfileFactory, ProfileAdvisor, Pro

private static final Set<ProfileType> SUPPORTED_PROFILE_TYPES = Collections
.unmodifiableSet(Stream
.of(DEFAULT_TYPE, FOLLOW_TYPE, OFFSET_TYPE, RAWBUTTON_ON_OFF_SWITCH_TYPE,
.of(DEFAULT_TYPE, FOLLOW_TYPE, OFFSET_TYPE, ROUND_TYPE, RAWBUTTON_ON_OFF_SWITCH_TYPE,
RAWBUTTON_TOGGLE_PLAYER_TYPE, RAWBUTTON_TOGGLE_PLAYER_TYPE, RAWBUTTON_TOGGLE_SWITCH_TYPE,
RAWROCKER_DIMMER_TYPE, RAWROCKER_NEXT_PREVIOUS_TYPE, RAWROCKER_ON_OFF_TYPE,
RAWROCKER_PLAY_PAUSE_TYPE, RAWROCKER_REWIND_FASTFORWARD_TYPE, RAWROCKER_STOP_MOVE_TYPE,
RAWROCKER_UP_DOWN_TYPE, TIMESTAMP_CHANGE_TYPE, TIMESTAMP_UPDATE_TYPE)
.collect(Collectors.toSet()));

private static final Set<ProfileTypeUID> SUPPORTED_PROFILE_TYPE_UIDS = Collections
.unmodifiableSet(Stream.of(DEFAULT, FOLLOW, OFFSET, RAWBUTTON_ON_OFF_SWITCH, RAWBUTTON_TOGGLE_PLAYER,
.unmodifiableSet(Stream.of(DEFAULT, FOLLOW, OFFSET, ROUND, RAWBUTTON_ON_OFF_SWITCH, RAWBUTTON_TOGGLE_PLAYER,
RAWBUTTON_TOGGLE_PLAYER, RAWBUTTON_TOGGLE_SWITCH, RAWROCKER_DIMMER, RAWROCKER_NEXT_PREVIOUS,
RAWROCKER_ON_OFF, RAWROCKER_PLAY_PAUSE, RAWROCKER_REWIND_FASTFORWARD, RAWROCKER_STOP_MOVE,
RAWROCKER_UP_DOWN, TIMESTAMP_CHANGE, TIMESTAMP_UPDATE).collect(Collectors.toSet()));
Expand Down Expand Up @@ -102,6 +102,8 @@ public SystemProfileFactory(final @Reference ChannelTypeRegistry channelTypeRegi
return new SystemFollowProfile(callback);
} else if (OFFSET.equals(profileTypeUID)) {
return new SystemOffsetProfile(callback, context);
} else if (ROUND.equals(profileTypeUID)) {
return new SystemRoundProfile(callback, context);
} else if (RAWBUTTON_ON_OFF_SWITCH.equals(profileTypeUID)) {
return new RawButtonOnOffSwitchProfile(callback);
} else if (RAWBUTTON_TOGGLE_SWITCH.equals(profileTypeUID)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.core.thing.internal.profiles;

import java.math.BigDecimal;
import java.text.DecimalFormat;

import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.library.types.QuantityType;
import org.eclipse.smarthome.core.thing.profiles.ProfileCallback;
import org.eclipse.smarthome.core.thing.profiles.ProfileContext;
import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID;
import org.eclipse.smarthome.core.thing.profiles.StateProfile;
import org.eclipse.smarthome.core.thing.profiles.SystemProfiles;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.Type;
import org.eclipse.smarthome.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Applies the given parameter "pattern" to a QuantityType or DecimalType state
*
* @author Arne Seime - Initial contribution
*/
@NonNullByDefault
public class SystemRoundProfile implements StateProfile {

private final Logger logger = LoggerFactory.getLogger(SystemRoundProfile.class);
private static final String ROUND_PARAM = "decimals";

private final ProfileCallback callback;
private final ProfileContext context;

private @Nullable DecimalFormat format;
private int numDecimals = 0;

public SystemRoundProfile(ProfileCallback callback, ProfileContext context) {
this.callback = callback;
this.context = context;

Object paramValue = this.context.getConfiguration().get(ROUND_PARAM);
logger.debug("Configuring profile with {} parameter '{}'", ROUND_PARAM, paramValue);
try {
numDecimals = Integer.parseInt((String) paramValue);
if (Math.abs(numDecimals) <= 10) {
if (numDecimals <= 0) {
format = new DecimalFormat("#");
} else if (numDecimals > 0) {
format = new DecimalFormat("#." + StringUtils.rightPad("#", numDecimals));
}
} else {
logger.error("Parameter '{}' must be between -10 and 10, no rounding will occur", ROUND_PARAM);
}
} catch (NullPointerException | NumberFormatException e) {
logger.error("Parameter '{}' is not a valid integer, no rounding will occur", ROUND_PARAM);
}
}

@Override
public ProfileTypeUID getProfileTypeUID() {
return SystemProfiles.ROUND;
}

@Override
public void onStateUpdateFromItem(State state) {
callback.handleUpdate((State) applyRounding(state));
}

@Override
public void onCommandFromItem(Command command) {
callback.handleCommand((Command) applyRounding(command));
}

@Override
public void onCommandFromHandler(Command command) {
callback.sendCommand((Command) applyRounding(command));
}

@Override
public void onStateUpdateFromHandler(State state) {
callback.sendUpdate((State) applyRounding(state));
}

@SuppressWarnings({ "rawtypes", "unchecked" })
public Type applyRounding(Type state) {
if (state instanceof UnDefType) {
return state;
}
Type result = state;
if (format != null) {
if (state instanceof QuantityType) {
QuantityType qtState = (QuantityType) state;
BigDecimal originalValue = qtState.toBigDecimal();
String roundedValue = roundAsString(originalValue);
result = new QuantityType<>(new BigDecimal(roundedValue), qtState.getUnit());
} else if (state instanceof DecimalType) {
BigDecimal originalValue = ((DecimalType) state).toBigDecimal();
String roundedValue = roundAsString(originalValue);
result = new DecimalType(roundedValue);
}
}
return result;
}

@SuppressWarnings("null")
private String roundAsString(BigDecimal originalValue) {
String formattedValue;
if (numDecimals < 0) {
double pow = Math.pow(10, Math.abs(numDecimals));
formattedValue = format.format((Math.round(originalValue.doubleValue() / pow) * pow));
} else {
formattedValue = format.format(originalValue.doubleValue());
}
return formattedValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public interface SystemProfiles {
ProfileTypeUID DEFAULT = new ProfileTypeUID(SYSTEM_SCOPE, "default");
ProfileTypeUID FOLLOW = new ProfileTypeUID(SYSTEM_SCOPE, "follow");
ProfileTypeUID OFFSET = new ProfileTypeUID(SYSTEM_SCOPE, "offset");
ProfileTypeUID ROUND = new ProfileTypeUID(SYSTEM_SCOPE, "round");
ProfileTypeUID RAWBUTTON_ON_OFF_SWITCH = new ProfileTypeUID(SYSTEM_SCOPE, "rawbutton-on-off-switch");
ProfileTypeUID RAWBUTTON_TOGGLE_PLAYER = new ProfileTypeUID(SYSTEM_SCOPE, "rawbutton-toggle-player");
ProfileTypeUID RAWBUTTON_TOGGLE_ROLLERSHUTTER = new ProfileTypeUID(SYSTEM_SCOPE, "rawbutton-toggle-rollershutter");
Expand All @@ -51,6 +52,10 @@ public interface SystemProfiles {
.withSupportedItemTypes(CoreItemFactory.NUMBER).withSupportedItemTypesOfChannel(CoreItemFactory.NUMBER)
.build();

StateProfileType ROUND_TYPE = ProfileTypeBuilder.newState(ROUND, "Round")
.withSupportedItemTypes(CoreItemFactory.NUMBER).withSupportedItemTypesOfChannel(CoreItemFactory.NUMBER)
.build();

TriggerProfileType RAWBUTTON_ON_OFF_SWITCH_TYPE = ProfileTypeBuilder
.newTrigger(RAWBUTTON_ON_OFF_SWITCH, "Raw Button To On Off")
.withSupportedItemTypes(CoreItemFactory.SWITCH, CoreItemFactory.DIMMER, CoreItemFactory.COLOR)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2019 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.eclipse.smarthome.core.thing.internal.profiles;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

import javax.measure.quantity.Temperature;

import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.library.types.QuantityType;
import org.eclipse.smarthome.core.library.unit.SIUnits;
import org.eclipse.smarthome.core.thing.profiles.ProfileCallback;
import org.eclipse.smarthome.core.thing.profiles.ProfileContext;
import org.eclipse.smarthome.core.types.Type;
import org.junit.Test;

/**
* Tests for the system:round profile
*
* @author Arne Seime - Initial contribution
*/
public class SystemRoundProfileTest {

@Test
public void testDecimalType() {
ProfileCallback callback = mock(ProfileCallback.class);
SystemRoundProfile offsetProfile = createProfile(callback, "1");

Type cmd = new DecimalType(1.23);
Type roundedValue = offsetProfile.applyRounding(cmd);

DecimalType decResult = (DecimalType) roundedValue;
assertEquals(1.2, decResult.doubleValue(), 0);
}

@Test
public void testDecimalToInt() {
ProfileCallback callback = mock(ProfileCallback.class);
SystemRoundProfile offsetProfile = createProfile(callback, "0");

Type cmd = new DecimalType(123.4);
Type roundedValue = offsetProfile.applyRounding(cmd);

DecimalType decResult = (DecimalType) roundedValue;
assertEquals(123, decResult.doubleValue(), 0);
}

@Test
public void testDecimalToTen() {
ProfileCallback callback = mock(ProfileCallback.class);
SystemRoundProfile offsetProfile = createProfile(callback, "-1");

Type cmd = new DecimalType(123.4);
Type roundedValue = offsetProfile.applyRounding(cmd);

DecimalType decResult = (DecimalType) roundedValue;
assertEquals(120, decResult.doubleValue(), 0);
}

@Test
public void testDecimalToHundred() {
ProfileCallback callback = mock(ProfileCallback.class);
SystemRoundProfile offsetProfile = createProfile(callback, "-2");

Type cmd = new DecimalType(951);
Type roundedValue = offsetProfile.applyRounding(cmd);

DecimalType decResult = (DecimalType) roundedValue;
assertEquals(1000, decResult.doubleValue(), 0);
}

@Test
public void testDecimalToHundredDown() {
ProfileCallback callback = mock(ProfileCallback.class);
SystemRoundProfile offsetProfile = createProfile(callback, "-2");

Type cmd = new DecimalType(949);
Type roundedValue = offsetProfile.applyRounding(cmd);

DecimalType decResult = (DecimalType) roundedValue;
assertEquals(900, decResult.doubleValue(), 0);
}

@SuppressWarnings("unchecked")
@Test
public void testQuantityType() {
ProfileCallback callback = mock(ProfileCallback.class);
SystemRoundProfile offsetProfile = createProfile(callback, "1");

Type cmd = new QuantityType<>("1.55°C");
Type roundedValue = offsetProfile.applyRounding(cmd);

QuantityType<Temperature> decResult = (QuantityType<Temperature>) roundedValue;
assertEquals(1.6, decResult.doubleValue(), 0);
assertEquals(SIUnits.CELSIUS, decResult.getUnit());

}

private SystemRoundProfile createProfile(ProfileCallback callback, String numDecimals) {
ProfileContext context = mock(ProfileContext.class);
Configuration config = new Configuration();
config.put("decimals", numDecimals);
when(context.getConfiguration()).thenReturn(config);

return new SystemRoundProfile(callback, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void setUp() {
@Test
public void systemProfileTypesAndUidsShouldBeAvailable() {
Collection<ProfileTypeUID> systemProfileTypeUIDs = profileFactory.getSupportedProfileTypeUIDs();
assertEquals(15, systemProfileTypeUIDs.size());
assertEquals(16, systemProfileTypeUIDs.size());

Collection<ProfileType> systemProfileTypes = profileFactory.getProfileTypes(null);
assertEquals(systemProfileTypeUIDs.size(), systemProfileTypes.size());
Expand Down