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

[rest] Add widget state pattern and default unit to ItemUIRegistry #3644

Merged
merged 7 commits into from
Jul 2, 2023
Merged
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 @@ -125,6 +125,7 @@
* @author Markus Rathgeb - Migrated to JAX-RS Whiteboard Specification
* @author Wouter Born - Migrated to OpenAPI annotations
* @author Laurent Garnier - Added support for icon color
* @author Mark Herwege - Added pattern and unit fields
*/
@Component(service = RESTResource.class)
@JaxrsResource
Expand Down Expand Up @@ -520,6 +521,8 @@ private PageDTO createPageBean(String sitemapName, @Nullable String title, @Null
bean.valuecolor = convertItemValueColor(itemUIRegistry.getValueColor(widget), itemState);
bean.iconcolor = convertItemValueColor(itemUIRegistry.getIconColor(widget), itemState);
bean.label = itemUIRegistry.getLabel(widget);
bean.pattern = itemUIRegistry.getFormatPattern(widget);
bean.unit = itemUIRegistry.getUnitForWidget(widget);
bean.type = widget.eClass().getName();
bean.visibility = itemUIRegistry.getVisiblity(widget);
if (widget instanceof LinkableWidget linkableWidget) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* @author Kai Kreuzer - Initial contribution
* @author Chris Jackson - Initial contribution
* @author Laurent Garnier - New field iconcolor
* @author Mark herwege - New fields pattern, unit
*/
public class WidgetDTO {

Expand All @@ -38,6 +39,9 @@ public class WidgetDTO {
public String valuecolor;
public String iconcolor;

public String pattern;
public String unit;

// widget-specific attributes
public final List<MappingDTO> mappings = new ArrayList<>();
public Boolean switchSupport;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
* @author Stefan Triller - Method to convert a state into something a sitemap entity can understand
* @author Erdoan Hadzhiyusein - Adapted the class to work with the new DateTimeType
* @author Laurent Garnier - new method getIconColor
* @author Mark Herwege - new method getFormatPattern(widget), clean pattern
*/
@NonNullByDefault
@Component(immediate = true, configurationPid = "org.openhab.sitemap", //
Expand All @@ -121,9 +122,9 @@ public class ItemUIRegistryImpl implements ItemUIRegistry {
protected static final Pattern EXTRACT_TRANSFORM_FUNCTION_PATTERN = Pattern.compile("(.*?)\\((.*)\\):(.*)");

/* RegEx to identify format patterns. See java.util.Formatter#formatSpecifier (without the '%' at the very end). */
protected static final String IDENTIFY_FORMAT_PATTERN_PATTERN = "%((unit%)|((\\d+\\$)?([-#+ 0,(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z])))";
protected static final String IDENTIFY_FORMAT_PATTERN_PATTERN = "%(?:(unit%)|(?:(?:\\d+\\$)?(?:[-#+ 0,(<]*)?(?:\\d+)?(?:\\.\\d+)?(?:[tT])?(?:[a-zA-Z])))";
private static final Pattern FORMAT_PATTERN = Pattern.compile("(?:^|[^%])" + IDENTIFY_FORMAT_PATTERN_PATTERN);

private static final Pattern LABEL_PATTERN = Pattern.compile(".*?\\[.*? (.*?)]");
private static final int MAX_BUTTONS = 4;

private static final String DEFAULT_SORTING = "NONE";
Expand Down Expand Up @@ -331,10 +332,14 @@ private Switch createPlayerButtons() {
String labelMappedOption = null;
State state = null;
StateDescription stateDescription = null;
String formatPattern = getFormatPattern(label);
String formatPattern = getFormatPattern(w);

if (formatPattern != null && label.indexOf("[") < 0) {
label = label + " [" + formatPattern + "]";
}

// now insert the value, if the state is a string or decimal value and there is some formatting pattern defined
// in the label (i.e. it contains at least a %)
// in the label or state description (i.e. it contains at least a %)
try {
final Item item = getItem(itemName);

Expand All @@ -348,13 +353,8 @@ private Switch createPlayerButtons() {
// returned StateDescription. What is expected is the display of a value using the pattern
// provided by the channel state description provider.
stateDescription = item.getStateDescription();
if (formatPattern == null && stateDescription != null && stateDescription.getPattern() != null) {
label = label + " [" + stateDescription.getPattern() + "]";
}

String updatedPattern = getFormatPattern(label);
if (updatedPattern != null) {
formatPattern = updatedPattern;
if (formatPattern != null) {
state = item.getState();

if (formatPattern.contains("%d")) {
Expand All @@ -371,7 +371,7 @@ private Switch createPlayerButtons() {
}
}
} catch (ItemNotFoundException e) {
logger.error("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName());
logger.warn("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName());
}

boolean considerTransform = false;
Expand Down Expand Up @@ -455,22 +455,75 @@ private Switch createPlayerButtons() {
}

label = label.trim();
label = label.substring(0, label.indexOf("[") + 1) + formatPattern + "]";
int index = label.indexOf("[");
if (index >= 0) {
label = label.substring(0, index + 1) + formatPattern + "]";
}
}
}

return transform(label, considerTransform, labelMappedOption);
}

private QuantityType<?> convertStateToWidgetUnit(QuantityType<?> quantityState, Widget w) {
Unit<?> widgetUnit = UnitUtils.parseUnit(getFormatPattern(w.getLabel()));
Unit<?> widgetUnit = UnitUtils.parseUnit(getFormatPattern(w));
if (widgetUnit != null && !widgetUnit.equals(quantityState.getUnit())) {
return Objects.requireNonNullElse(quantityState.toInvertibleUnit(widgetUnit), quantityState);
}

return quantityState;
}

@Override
public @Nullable String getFormatPattern(Widget w) {
String label = getLabelFromWidget(w);
String pattern = getFormatPattern(label);
String itemName = w.getItem();
try {
Item item = null;
if (itemName != null && !itemName.isBlank()) {
item = getItem(itemName);
}
if (item != null && pattern == null) {
StateDescription stateDescription = item.getStateDescription();
if (stateDescription != null) {
pattern = stateDescription.getPattern();
}
}

if (pattern == null) {
return null;
}

// remove last part of pattern, after unit, if it exists, as this is not valid and creates problems with
// updates
if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) {
Matcher m = FORMAT_PATTERN.matcher(pattern);
int matcherEnd = 0;
if (m.find() && m.group(1) == null) {
matcherEnd = m.end();
}
String unit = pattern.substring(matcherEnd).trim();
String postfix = "";
int unitEnd = unit.indexOf(" ");
if (unitEnd > -1) {
postfix = unit.substring(unitEnd + 1).trim();
unit = unit.substring(0, unitEnd);
}
if (!postfix.isBlank()) {
logger.warn(
"Item '{}' with unit, nothing allowed after unit in label pattern '{}', dropping postfix",
itemName, pattern);
}
pattern = pattern.substring(0, matcherEnd) + (!unit.isBlank() ? " " + unit : "");
}
} catch (ItemNotFoundException e) {
logger.warn("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName());
}

return pattern;
}

private @Nullable String getFormatPattern(@Nullable String label) {
if (label == null) {
return null;
Expand Down Expand Up @@ -608,7 +661,7 @@ private String transform(String label, boolean matchTransform, @Nullable String
Item item = getItem(itemName);
return convertState(w, item, item.getState());
} catch (ItemNotFoundException e) {
logger.error("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName());
logger.warn("Cannot retrieve item '{}' for widget {}", itemName, w.eClass().getInstanceTypeName());
}
}
return UnDefType.UNDEF;
Expand All @@ -627,8 +680,8 @@ private String transform(String label, boolean matchTransform, @Nullable String
State returnState = null;

State itemState = i.getState();
if (itemState instanceof QuantityType type) {
itemState = convertStateToWidgetUnit(type, w);
if (itemState instanceof QuantityType<?> quantityTypeState) {
itemState = convertStateToWidgetUnit(quantityTypeState, w);
}

if (w instanceof Switch && i instanceof RollershutterItem) {
Expand All @@ -637,7 +690,7 @@ private String transform(String label, boolean matchTransform, @Nullable String
} else if (w instanceof Slider) {
if (i.getAcceptedDataTypes().contains(PercentType.class)) {
returnState = itemState.as(PercentType.class);
} else {
} else if (!(itemState instanceof QuantityType<?>)) {
returnState = itemState.as(DecimalType.class);
}
} else if (w instanceof Switch sw) {
Expand Down Expand Up @@ -1208,7 +1261,7 @@ public boolean getVisiblity(Widget w) {
try {
item = itemRegistry.getItem(itemName);
} catch (ItemNotFoundException e) {
logger.error("Cannot retrieve visibility item {} for widget {}", rule.getItem(),
logger.warn("Cannot retrieve visibility item {} for widget {}", rule.getItem(),
w.eClass().getInstanceTypeName());

// Default to visible!
Expand Down Expand Up @@ -1325,20 +1378,21 @@ public void removeRegistryHook(RegistryHook<Item> hook) {

// we require the item to define a dimension, otherwise no unit will be reported to the UIs.
if (item instanceof NumberItem numberItem && numberItem.getDimension() != null) {
if (w.getLabel() == null) {
String pattern = getFormatPattern(w);
if (pattern == null || pattern.isBlank()) {
// if no Label was assigned to the Widget we fallback to the items unit
return numberItem.getUnitSymbol();
}

String unit = getUnitFromLabel(w.getLabel());
String unit = getUnitFromPattern(pattern);
if (!UnitUtils.UNIT_PLACEHOLDER.equals(unit)) {
return unit;
}

return numberItem.getUnitSymbol();
}
} catch (ItemNotFoundException e) {
logger.debug("Failed to retrieve item during widget rendering: {}", e.getMessage());
logger.warn("Failed to retrieve item during widget rendering, item does not exist: {}", e.getMessage());
}

return "";
Expand All @@ -1354,14 +1408,13 @@ public void removeRegistryHook(RegistryHook<Item> hook) {
return state;
}

private @Nullable String getUnitFromLabel(@Nullable String label) {
if (label == null || label.isBlank()) {
private @Nullable String getUnitFromPattern(@Nullable String format) {
if (format == null || format.isBlank()) {
return null;
}
Matcher m = LABEL_PATTERN.matcher(label);
if (m.matches()) {
return m.group(1);
}
return null;
int index = format.lastIndexOf(" ");
String unit = index > 0 ? format.substring(index + 1) : null;
unit = UnitUtils.UNIT_PERCENT_FORMAT_STRING.equals(unit) ? "%" : unit;
return unit;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* @author Kai Kreuzer - Initial contribution
* @author Chris Jackson - Initial contribution
* @author Laurent Garnier - new method getIconColor
* @author Mark Herwege - new method getFormatPattern
*/
@NonNullByDefault
public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider {
Expand Down Expand Up @@ -130,6 +131,15 @@ public interface ItemUIRegistry extends ItemRegistry, ItemUIProvider {
@Nullable
EObject getParent(Widget w);

/**
* Gets the format pattern for the widget value, retrieved from widget label, item label or item state description
*
* @param w Widget
* @return String with the format pattern
*/
@Nullable
String getFormatPattern(Widget w);

/**
* Gets the label color for the widget. Checks conditional statements to
* find the color based on the item value
Expand Down