Declare new units in an addon

Hello,

In the context of my OpenMeteo addon, I have channels that express values in units that are not provided “out of the box” by openHAB.
As I’d like to avoid declaring them as simple numbers and using a simple DecimalType state instead of a more advanced QuantityType, I have created a class very similar to the one declaring the included units, like this:

package com.obones.binding.openmeteo.internal;

import javax.measure.Unit;
import javax.measure.quantity.Dimensionless;
import javax.measure.spi.SystemOfUnits;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.unit.Units;

import tech.units.indriya.AbstractSystemOfUnits;
import tech.units.indriya.format.SimpleUnitFormat;
import tech.units.indriya.unit.ProductUnit;

@NonNullByDefault
public class OpenMeteoBindingUnits extends AbstractSystemOfUnits {
    private static final OpenMeteoBindingUnits INSTANCE = new OpenMeteoBindingUnits();

    public static final Unit<Dimensionless> GRAINS;
    public static Unit<?> GRAINS_PER_CUBICMETRE;
    public static Unit<?> JOULES_PER_KILOGRAM;

    static {
        GRAINS = addUnit(new ProductUnit<Dimensionless>());
        GRAINS_PER_CUBICMETRE = addUnit(ProductUnit.ofQuotient(GRAINS, tech.units.indriya.unit.Units.CUBIC_METRE));
        JOULES_PER_KILOGRAM = addUnit(ProductUnit.ofQuotient(Units.JOULE, tech.units.indriya.unit.Units.KILOGRAM));

        SimpleUnitFormat.getInstance().label(GRAINS, "grains");
        SimpleUnitFormat.getInstance().label(GRAINS_PER_CUBICMETRE, "grains/m³");
        SimpleUnitFormat.getInstance().label(JOULES_PER_KILOGRAM, "J/kg");
    }

    public String getName() {
        return this.getClass().getSimpleName();
    }

    private OpenMeteoBindingUnits() {
    }

    public static SystemOfUnits getInstance() {
        return INSTANCE;
    }

    private static <U extends Unit<?>> U addUnit(U unit) {
        INSTANCE.units.add(unit);
        return unit;
    }
}

And then, I use it like this:

pollenState = new QuantityType<>(pollenValue, OpenMeteoBindingUnits.GRAINS_PER_CUBICMETRE);

capeState = new QuantityType<>(capeValue, OpenMeteoBindingUnits.JOULES_PER_KILOGRAM);

This compiles just fine, but it does not seem to be what openHAB expects.
On items that have a direct value (not time series), the state changes from NULL to UNDEF upon creation and stays at that, despite pollenValue or capeValue evolving over time.

If I place the same state into a time series, I get these error messages:

15:07:39.913 [WARN ] [openhab.core.library.items.NumberItem] - Unit 'J/kg' could not be parsed to a known unit. Keeping old unit 'kWh' for item 'OpenMeteo_forecast_CAPE_Hourly'.
15:07:39.931 [WARN ] [openhab.core.library.items.NumberItem] - Failed to update item 'OpenMeteo_forecast_CAPE_Hourly' because '0 J/kg' could not be converted to the item unit 'kWh'
15:07:39.931 [INFO ] [openhab.event.ItemTimeSeriesEvent    ] - Item 'OpenMeteo_forecast_CAPE_Hourly' shall process timeseries [Entry[timestamp=2024-07-02T13:00:00Z, state=0 J/kg]]
15:07:39.932 [ERROR] [org.openhab.core.items.GenericItem   ] - Tried to set invalid state in time series org.openhab.core.types.TimeSeries@5c049b55 on item OpenMeteo_forecast_CAPE_Hourly of type NumberItem, ignoring it

How do I then make my units known to openHAB?
I tried looking at other addons but could not find any that declared its own units.

You’ll have to submit a PR to openhab-core to add these units to the list of supported ones. Add-ons cannot create their own units, it has to be implemented in core.

Well that’s quite sad, especially considering that all the system it relies on is provided by Java itself, openHab did not reinvent the wheel here.

As those units are very specific, I don’t think the PR would be accepted anyway…

The most recent new unit I saw that was approved was an obscure radiation unit. I doubt these are more obscure than that.

But I think most of the work in adding it to OH is setting up the relationships between the type of the Item and the units supported by that Item. The actual unit conversion and representation stuff is indeed implemented by the upstream library.

1 Like