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

Introduce collector and energy counter profile #86

Merged
merged 1 commit into from
May 10, 2024
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 @@ -35,7 +35,7 @@ public abstract class BaseCounterProfile implements StateProfile {
protected final ProfileCallback callback;
protected final UninitializedBehavior uninitializedBehavior;
protected final ProfileContext context;
protected Type last;
private Type last;

protected BaseCounterProfile(ProfileCallback callback, ProfileContext context, LinkedItemStateRetriever linkedItemStateRetriever) {
this(false, callback, context, linkedItemStateRetriever);
Expand Down Expand Up @@ -113,6 +113,10 @@ private void handleReading(Type val, boolean incoming) {

protected abstract void handleReading(Type current, Type previous, boolean incoming);

Type last() {
return last;
}

enum UninitializedBehavior {
RESTORE_FROM_ITEM, RESTORE_FROM_PERSISTENCE, RESTORE_FROM_HANDLER;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.connectorio.addons.profile.counter.internal;

import java.util.function.BiFunction;
import java.util.function.Consumer;
import org.connectorio.addons.profile.counter.internal.state.LinkedItemStateRetriever;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.types.Type;

/**
* Profile which will keep collecting received values and adding them to initial item state.
*/
class CollectorProfile extends BaseCounterProfile {

CollectorProfile(ProfileCallback callback, ProfileContext context, LinkedItemStateRetriever linkedItemStateRetriever) {
super(true, callback, context, linkedItemStateRetriever);
}

@Override
public ProfileTypeUID getProfileTypeUID() {
return CounterProfiles.COLLECTOR; // TODO
}

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
protected void handleReading(Type current, Type previous, boolean incoming) {
if (incoming) { // incoming, handler to item
if (current instanceof DecimalType) {
this.update((DecimalType) current, (DecimalType) previous, update(callback::sendUpdate),
(left, right) -> new DecimalType(left.toBigDecimal().add(right.toBigDecimal()))
);
} else if (current instanceof QuantityType) {
this.update((QuantityType) current, (QuantityType) previous, update(callback::sendUpdate),
QuantityType::add
);
}
}
// outgoing communication is not supported
}

private <T extends Comparable<T>> void update(T current, T previous, Consumer<T> consumer, BiFunction<T, T, T> sum) {
if (previous == null) {
// initialization
consumer.accept(current);
return;
}
T collected = sum.apply(previous, current);
consumer.accept(collected);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public CounterProfileFactory(@Reference LinkedItemStateRetriever linkedItemState

@Override
public Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, ProfileContext profileContext) {
if (CounterProfiles.COLLECTOR.equals(profileTypeUID)) {
return new CollectorProfile(callback, profileContext, linkedItemStateRetriever);
}
if (CounterProfiles.ENERGY_COUNTER.equals(profileTypeUID)) {
return new EnergyCounterProfile(callback, profileContext);
}
if (CounterProfiles.LIMIT_COUNTER_TOP.equals(profileTypeUID)) {
return new LimitCounterTopProfile(callback, profileContext, linkedItemStateRetriever);
}
Expand All @@ -61,14 +67,18 @@ public Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback call

@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
return Arrays.asList(CounterProfiles.LIMIT_COUNTER_TOP, CounterProfiles.LIMIT_COUNTER_BOTTOM,
CounterProfiles.PULSE_COUNTER, CounterProfiles.SUSTAINED_COUNTER);
return Arrays.asList(CounterProfiles.COLLECTOR, CounterProfiles.ENERGY_COUNTER,
CounterProfiles.LIMIT_COUNTER_TOP, CounterProfiles.LIMIT_COUNTER_BOTTOM,
CounterProfiles.PULSE_COUNTER, CounterProfiles.SUSTAINED_COUNTER
);
}

@Override
public Collection<ProfileType> getProfileTypes(Locale locale) {
return Arrays.asList(CounterProfiles.LIMIT_COUNTER_TOP_PROFILE_TYPE, CounterProfiles.LIMIT_COUNTER_BOTTOM_PROFILE_TYPE,
CounterProfiles.PULSE_PROFILE_TYPE, CounterProfiles.SUSTAINED_COUNTER_PROFILE_TYPE);
return Arrays.asList(CounterProfiles.COLLECTOR_PROFILE_TYPE, CounterProfiles.ENERGY_COUNTER_PROFILE_TYPE,
CounterProfiles.LIMIT_COUNTER_TOP_PROFILE_TYPE, CounterProfiles.LIMIT_COUNTER_BOTTOM_PROFILE_TYPE,
CounterProfiles.PULSE_PROFILE_TYPE, CounterProfiles.SUSTAINED_COUNTER_PROFILE_TYPE
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@

public interface CounterProfiles {

ProfileTypeUID COLLECTOR = new ProfileTypeUID("connectorio", "collector");
StateProfileType COLLECTOR_PROFILE_TYPE = ProfileTypeBuilder.newState(COLLECTOR, "Collector")
.withSupportedItemTypes("Number")
.withSupportedItemTypesOfChannel("Number")
.build();

ProfileTypeUID ENERGY_COUNTER = new ProfileTypeUID("connectorio", "energy-counter");
StateProfileType ENERGY_COUNTER_PROFILE_TYPE = ProfileTypeBuilder.newState(COLLECTOR, "Energy Counter (transform W to Wh)")
.withSupportedItemTypes("Number")
.withSupportedItemTypesOfChannel("Number")
.build();

ProfileTypeUID LIMIT_COUNTER_TOP = new ProfileTypeUID("connectorio", "limit-counter-top");
StateProfileType LIMIT_COUNTER_TOP_PROFILE_TYPE = ProfileTypeBuilder.newState(LIMIT_COUNTER_TOP, "Filter counter upper values")
.withSupportedItemTypes("Number")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2024-2024 ConnectorIO Sp. z o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.connectorio.addons.profile.counter.internal;

import java.math.BigDecimal;
import java.time.Clock;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import javax.measure.Unit;
import javax.measure.quantity.Energy;
import org.connectorio.addons.profile.counter.internal.state.DummyStateRetriever;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.types.Type;
import tec.uom.se.unit.MetricPrefix;
import tec.uom.se.unit.ProductUnit;

/**
* Little utility profile which, based on power reading will calculate energy consumption.
*/
class EnergyCounterProfile extends BaseCounterProfile {

// helper unit used internally by profile
public static Unit<Energy> MILLISECOND_WATT = new ProductUnit<>(Units.WATT.multiply(MetricPrefix.MILLI(Units.SECOND)));
private final Clock clock;

static class Measurement {
final BigDecimal watts;
final long timestamp;

Measurement(BigDecimal watts, long timestamp) {
this.watts = watts;
this.timestamp = timestamp;
}
}

private AtomicReference<Measurement> previousValue = new AtomicReference<>();

EnergyCounterProfile(ProfileCallback callback, ProfileContext context) {
this(callback, context, Clock.systemUTC());
}

EnergyCounterProfile(ProfileCallback callback, ProfileContext context, Clock clock) {
super(true, callback, context, new DummyStateRetriever());
this.clock = clock;
}

@Override
public ProfileTypeUID getProfileTypeUID() {
return CounterProfiles.ENERGY_COUNTER;
}

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
protected void handleReading(Type current, Type previous, boolean incoming) {
long timestamp = clock.millis();
if (incoming) { // incoming, handler to item
if (current instanceof DecimalType) {
// assume that number we received express power in Watts
Measurement measurement = new Measurement(((DecimalType) current).toBigDecimal(), timestamp);
this.update(measurement, update(callback::sendUpdate));
} else if (current instanceof QuantityType) {
QuantityType value = ((QuantityType) current).toUnit(Units.WATT);
if (value != null) {;
this.update(new Measurement(value.toBigDecimal(), timestamp), update(callback::sendUpdate));
}
}
}
// outgoing communication is not supported
}

private void update(Measurement power, Consumer<QuantityType<Energy>> consumer) {
if (previousValue.compareAndSet(null, power)) {
// initialization
return;
}

previousValue.getAndAccumulate(power, new BinaryOperator<Measurement>() {
@Override
public Measurement apply(Measurement previous, Measurement current) {
long millis = current.timestamp - previous.timestamp;

QuantityType<Energy> consumption = new QuantityType<>(previous.watts.multiply(BigDecimal.valueOf(millis)), MILLISECOND_WATT);
consumer.accept(consumption.toUnit(Units.WATT_HOUR));

return current;
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,18 @@ public ProfileTypeUID getProfileTypeUID() {
@Override
protected void handleReading(Type current, Type previous, boolean incoming) {
if (incoming) { // incoming, handler to item
if (current instanceof DecimalType && last instanceof DecimalType) {
compare((DecimalType) current, (DecimalType) last, update(callback::sendUpdate));
if (current instanceof DecimalType && previous instanceof DecimalType) {
compare((DecimalType) current, (DecimalType) previous, update(callback::sendUpdate));
}
if (current instanceof QuantityType && last instanceof QuantityType<?>) {
compare((QuantityType<?>) current, (QuantityType<?>) last, update(callback::sendUpdate));
if (current instanceof QuantityType && previous instanceof QuantityType<?>) {
compare((QuantityType<?>) current, (QuantityType<?>) previous, update(callback::sendUpdate));
}
} else { // outgoing, item to handler
if (current instanceof DecimalType && last instanceof DecimalType) {
compare((DecimalType) current, (DecimalType) last, update(callback::handleCommand));
if (current instanceof DecimalType && previous instanceof DecimalType) {
compare((DecimalType) current, (DecimalType) previous, update(callback::handleCommand));
}
if (current instanceof QuantityType && last instanceof QuantityType<?>) {
compare((QuantityType<?>) current, (QuantityType<?>) last, update(callback::handleCommand));
if (current instanceof QuantityType && previous instanceof QuantityType<?>) {
compare((QuantityType<?>) current, (QuantityType<?>) previous, update(callback::handleCommand));
}
}
}
Expand All @@ -81,7 +81,7 @@ private void compare(QuantityType current, QuantityType previous, Consumer<Quant
}

public String toString() {
return "LimitCounterBottom [" + last + "]";
return "LimitCounterBottom [" + last() + "]";
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ private BigDecimal convert(QuantityType<?> quantifiedReading, Unit<?> unit) {
}

public String toString() {
return "LimitCounterTop [" + last + " " + anomaly + "]";
return "LimitCounterTop [" + last() + " " + anomaly + "]";
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,20 @@ class SustainedCounterProfile extends BaseCounterProfile {

@Override
public ProfileTypeUID getProfileTypeUID() {
return CounterProfiles.PULSE_COUNTER;
return CounterProfiles.SUSTAINED_COUNTER;
}

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
protected void handleReading(Type current, Type previous, boolean incoming) {
if (incoming) { // incoming, handler to item
if (current instanceof DecimalType) {
this.update((AtomicReference) previousValue, (DecimalType) current, (DecimalType) last, update(callback::sendUpdate),
this.update((AtomicReference) previousValue, (DecimalType) current, (DecimalType) previous, update(callback::sendUpdate),
(left, right) -> new DecimalType(left.toBigDecimal().subtract(right.toBigDecimal())),
(left, right) -> new DecimalType(left.toBigDecimal().add(right.toBigDecimal()))
);
} else if (current instanceof QuantityType) {
this.update((AtomicReference) previousValue, (QuantityType) current, (QuantityType) last, update(callback::sendUpdate),
this.update((AtomicReference) previousValue, (QuantityType) current, (QuantityType) previous, update(callback::sendUpdate),
QuantityType::subtract,
QuantityType::add
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2019-2021 ConnectorIO Sp. z o.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.connectorio.addons.profile.counter.internal.state;

import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.types.State;

public class DummyStateRetriever implements LinkedItemStateRetriever {

@Override
public String getItemName(ProfileCallback callback) {
return "";
}

@Override
public State retrieveState(String itemName) {
return null;
}

}
Loading
Loading