Skip to content

Commit

Permalink
[rest] Add widget state pattern and default unit to ItemUIRegistry (#…
Browse files Browse the repository at this point in the history
…3644)

* expose widget format pattern to sitemap REST
* Add unit fields to sitemap REST

Signed-off-by: Mark Herwege <[email protected]>
  • Loading branch information
mherwege authored Jul 2, 2023
1 parent 505e512 commit 5ceaa64
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 28 deletions.
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

0 comments on commit 5ceaa64

Please sign in to comment.