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

[Senseenergy] Initial contribution of SenseEnergy Binding #18244

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
/bundles/org.openhab.binding.sensebox/ @hakan42
/bundles/org.openhab.binding.sensibo/ @seime
/bundles/org.openhab.binding.sensorcommunity/ @weymann
/bundles/org.openhab.binding.senseenergy/ @jsjames
/bundles/org.openhab.binding.serial/ @MikeJMajor
/bundles/org.openhab.binding.serialbutton/ @kaikreuzer
/bundles/org.openhab.binding.shelly/ @markus7017
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,11 @@
<artifactId>org.openhab.binding.sensebox</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.senseenergy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.sensibo</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.senseenergy/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.

* Project home: https://www.openhab.org

== Declared Project Licenses

This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.

== Source Code

https://github.com/openhab/openhab-addons
243 changes: 243 additions & 0 deletions bundles/org.openhab.binding.senseenergy/README.md

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions bundles/org.openhab.binding.senseenergy/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>5.0.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.binding.senseenergy</artifactId>

<name>openHAB Add-ons :: Bundles :: SenseEnergy Binding</name>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.senseenergy-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>

<feature name="openhab-binding-senseenergy" description="SenseEnergy Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.senseenergy/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2010-2025 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.openhab.binding.senseenergy.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelGroupTypeUID;

/**
* The {@link SenseEnergyBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
public class SenseEnergyBindingConstants {
private static final String BINDING_ID = "senseenergy";

// List of all Thing Type UIDs
public static final ThingTypeUID APIBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "cloud-connector");
public static final ThingTypeUID MONITOR_THING_TYPE = new ThingTypeUID(BINDING_ID, "monitor");
public static final ThingTypeUID PROXY_DEVICE_THING_TYPE = new ThingTypeUID(BINDING_ID, "proxy-device");

public static final String PARAM_MONITOR_ID = "id";

public static final int HEARTBEAT_MINUTES = 5;

/** Monitor Bridge/Thing ***/
// Channel group type UIDs
public static final ChannelGroupTypeUID CHANNEL_GROUP_TYPE_DEVICE_TEMPLATE = new ChannelGroupTypeUID(BINDING_ID,
"device-template");

// Channel Groups
public static final String CHANNEL_GROUP_GENERAL = "general";
public static final String CHANNEL_GROUP_DISCOVERED_DEVICES = "discovered-devices";
public static final String CHANNEL_GROUP_SELF_REPORTING_DEVICES = "self-reporting-devices";
public static final String CHANNEL_GROUP_PROXY_DEVICES = "proxy-devices";

// Monitor Channel IDs
public static final String CHANNEL_FREQUENCY = "frequency";
public static final String CHANNEL_GRID_POWER = "grid-power";
public static final String CHANNEL_POTENTIAL_1 = "potential-1";
public static final String CHANNEL_POTENTIAL_2 = "potential-2";
public static final String CHANNEL_LEG_1_POWER = "leg-1-power";
public static final String CHANNEL_LEG_2_POWER = "leg-2-power";
public static final String CHANNEL_MAIN_POWER = "main-power";
public static final String CHANNEL_SOLAR_POWER = "solar-power";
public static final String CHANNEL_DEVICES_UPDATED_TRIGGER = "devices-updated-trigger";

// Discovered Device Channel IDs
public static final String CHANNEL_DEVICE_POWER = "device-power";
public static final String CHANNEL_DEVICE_TRIGGER = "device-trigger";

// Properties
public static final String PROPERTY_MONITOR_SOLAR_CONFIGURED = "solarConfigured";
public static final String PROPERTY_MONITOR_IP_ADDRESS = "ipAddress";
public static final String PROPERTY_MONITOR_VERSION = "version";
public static final String PROPERTY_MONITOR_SERIAL = "serial";
public static final String PROPERTY_MONITOR_SSID = "ssid";
public static final String PROPERTY_MONITOR_MAC = "mac";

/** PROXY DEVICE THING ***/
// Channel IDs
public static final String CHANNEL_PROXY_DEVICE_POWER = "proxy-device-power";
public static final String CHANNEL_PROXY_DEVICE_SWITCH = "proxy-device-switch";
public static final String CHANNEL_PROXY_DEVICE_DIMMER = "proxy-device-dimmer";
public static final String CHANNEL_PROXY_DEVICE_STATE = "proxy-device-state";

public static final String CONFIG_PARAMETER_MAC = "mac";
public static final String CONFIG_PARAMETER_POWER_LEVELS = "powerLevels";
public static final String CONFIG_PARAMETER_SENSE_NAME = "senseName";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2010-2025 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.openhab.binding.senseenergy.internal;

import static org.openhab.binding.senseenergy.internal.SenseEnergyBindingConstants.*;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.senseenergy.internal.handler.SenseEnergyBridgeHandler;
import org.openhab.binding.senseenergy.internal.handler.SenseEnergyMonitorHandler;
import org.openhab.binding.senseenergy.internal.handler.SenseEnergyProxyDeviceHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.openhab.core.thing.type.ChannelGroupTypeRegistry;
import org.openhab.core.thing.type.ChannelTypeRegistry;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* The {@link SenseEnergyHandlerFactory}
*
* @author Jeff James - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.senseenergy", service = ThingHandlerFactory.class)
public class SenseEnergyHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(APIBRIDGE_THING_TYPE, MONITOR_THING_TYPE,
PROXY_DEVICE_THING_TYPE);

private final HttpClientFactory httpClientFactory;
private final WebSocketFactory webSocketFactory;
private final ChannelGroupTypeRegistry channelGroupTypeRegistry;
private final ChannelTypeRegistry channelTypeRegistry;

@Activate
public SenseEnergyHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
final @Reference WebSocketFactory webSocketFactory,
final @Reference ChannelGroupTypeRegistry channelGroupTypeRegistry,
final @Reference ChannelTypeRegistry channelTypeRegistry) {
this.httpClientFactory = httpClientFactory;
this.webSocketFactory = webSocketFactory;
this.channelGroupTypeRegistry = channelGroupTypeRegistry;
this.channelTypeRegistry = channelTypeRegistry;
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}

@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (APIBRIDGE_THING_TYPE.equals(thingTypeUID)) {
return new SenseEnergyBridgeHandler((Bridge) thing, this.httpClientFactory.getCommonHttpClient());
} else if (MONITOR_THING_TYPE.equals(thingTypeUID)) {
return new SenseEnergyMonitorHandler((Bridge) thing, webSocketFactory.getCommonWebSocketClient(),
channelGroupTypeRegistry, channelTypeRegistry);
} else if (PROXY_DEVICE_THING_TYPE.equals(thingTypeUID)) {
return new SenseEnergyProxyDeviceHandler(thing);
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2010-2025 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.openhab.binding.senseenergy.internal.actions;

import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Energy;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.senseenergy.internal.api.SenseEnergyApi;
import org.openhab.binding.senseenergy.internal.api.SenseEnergyApiException;
import org.openhab.binding.senseenergy.internal.api.dto.SenseEnergyApiGetTrends;
import org.openhab.binding.senseenergy.internal.handler.SenseEnergyMonitorHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The { @link SenseEnergyMonitorActions } class implements the action(s) methods for the binding.
*
* @author Jeff James - Initial contribution
*/
@Component(scope = ServiceScope.PROTOTYPE, service = SenseEnergyMonitorActions.class)
@ThingActionsScope(name = "senseenergy")
@NonNullByDefault
public class SenseEnergyMonitorActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(SenseEnergyMonitorActions.class);

private @Nullable SenseEnergyMonitorHandler deviceHandler;

@Override
public void setThingHandler(@Nullable ThingHandler handler) {
if (handler instanceof SenseEnergyMonitorHandler deviceHandler) {
this.deviceHandler = deviceHandler;
}
}

@Override
public @Nullable ThingHandler getThingHandler() {
return deviceHandler;
}

/*
* Query water usage
*/
@RuleAction(label = "Query Energy Trend", description = "Queries energy trend over a period of time.")
public @ActionOutput(name = "consumption", type = "QuantityType<Energy>", description = "the total energy (KWh) used over the scale period.") //
@ActionOutput(name = "production", type = "QuantityType<Energy>", description = "the total energy (KWh) produced over the scale period.") //
@ActionOutput(name = "fromGrid", type = "QuantityType<Energy>", description = "the total energy (KWh) from the grid over the scale period.") //
@ActionOutput(name = "toGrid", type = "QuantityType<Energy>", description = "the total energy (KWh) to the grid over the scale period.") //
@ActionOutput(name = "netProduction", type = "QuantityType<Energy>", description = "the difference in energy (KWh) between what was produced and consumed during the scale period.") //
@ActionOutput(name = "solarPowered", type = "QuantityType<Dimensionless>", description = "the percent of solar energy production that was directly consumed (not sent to grid) during the scale period.") //
Map<String, Object> queryEnergyTrend( //
@ActionInput(name = "scale", label = "Scale", required = true, description = "Scale to be returned (DAY, WEEK, MONTH, YEAR)") @Nullable String scale, //
@ActionInput(name = "datetime", label = "Date/Time", required = true, description = "Restrict the query range to data samples since this datetime.") @Nullable Instant datetime) {
logger.info("queryEnergyTrend called");

SenseEnergyMonitorHandler localDeviceHandler = deviceHandler;
if (localDeviceHandler == null) {
logger.warn("querying device usage, but device is undefined.");
return Collections.emptyMap();
}

Instant localDateTime = (datetime == null) ? Instant.now() : datetime;

if (scale == null) {
logger.warn("queryEnergyTrends called with null inputs");
return Collections.emptyMap();
}

if (!SenseEnergyApi.TrendScale.contains(scale)) {
logger.warn("Invalid scale type in call to queryEnergyTrend");
return Collections.emptyMap();
}
SenseEnergyApi.TrendScale trendScale = SenseEnergyApi.TrendScale.valueOf(scale);

SenseEnergyApiGetTrends trends;
try {
trends = localDeviceHandler.getApi().getTrendData(localDeviceHandler.getId(), trendScale, localDateTime);
} catch (InterruptedException | TimeoutException | ExecutionException | SenseEnergyApiException e) {
logger.warn("queryEnergyTrends function failed - {}", e.getMessage());
return Collections.emptyMap();
}

if (trends == null) {
return Collections.emptyMap();
}

Map<String, Object> valuesMap = new HashMap<String, Object>();

valuesMap.put("consumption", new QuantityType<Energy>(trends.consumption.totalPower, Units.KILOWATT_HOUR));
valuesMap.put("production", new QuantityType<Energy>(trends.production.totalPower, Units.KILOWATT_HOUR));
valuesMap.put("toGrid", new QuantityType<Energy>(trends.toGridEnergy, Units.KILOWATT_HOUR));
valuesMap.put("fromGrid", new QuantityType<Energy>(trends.fromGridEnergy, Units.KILOWATT_HOUR));
valuesMap.put("netProduction", new QuantityType<Energy>(trends.netProduction, Units.KILOWATT_HOUR));
valuesMap.put("solarPowered", new QuantityType<Dimensionless>(trends.solarPowered, Units.PERCENT));

return valuesMap;
}

// Static method for Rules DSL backward compatibility
public static @Nullable Map<String, Object> queryEnergyTrend(ThingActions actions, @Nullable String scale,
@Nullable Instant datetime) {
if (actions instanceof SenseEnergyMonitorActions localActions) {
return localActions.queryEnergyTrend(scale, datetime);
} else {
throw new IllegalArgumentException("Instance is not a SenseEnergyMonitorActions class.");
}
}
}
Loading