Skip to content

Commit

Permalink
Fix handling of broadcast frames for SMA meter openhab#11497.
Browse files Browse the repository at this point in the history
Added support for multiple meters in single multicast group openhab#3429.
Ignore frames which are using protocol identifier different than e-meter.

Signed-off-by: Łukasz Dywicki <luke@code-house.org>
  • Loading branch information
splatch committed Nov 6, 2022
1 parent 162e146 commit d882891
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
import static org.openhab.binding.smaenergymeter.internal.SMAEnergyMeterBindingConstants.*;

import org.openhab.binding.smaenergymeter.internal.handler.SMAEnergyMeterHandler;
import org.openhab.binding.smaenergymeter.internal.packet.PacketListenerRegistry;
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.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* The {@link SMAEnergyMeterHandlerFactory} is responsible for creating things and thing
Expand All @@ -31,6 +34,13 @@
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.smaenergymeter")
public class SMAEnergyMeterHandlerFactory extends BaseThingHandlerFactory {

private final PacketListenerRegistry packetListenerRegistry;

@Activate
public SMAEnergyMeterHandlerFactory(@Reference PacketListenerRegistry packetListenerRegistry) {
this.packetListenerRegistry = packetListenerRegistry;
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
Expand All @@ -41,7 +51,7 @@ protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (thingTypeUID.equals(THING_TYPE_ENERGY_METER)) {
return new SMAEnergyMeterHandler(thing);
return new SMAEnergyMeterHandler(thing, packetListenerRegistry);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class EnergyMeterConfig {
private String mcastGroup;
private Integer port;
private Integer pollingPeriod;
private Long serialNumber;

public String getMcastGroup() {
return mcastGroup;
Expand All @@ -46,4 +47,12 @@ public Integer getPollingPeriod() {
public void setPollingPeriod(Integer pollingPeriod) {
this.pollingPeriod = pollingPeriod;
}

public Long getSerialNumber() {
return serialNumber;
}

public void setSerialNumber(Long serialNumber) {
this.serialNumber = serialNumber;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,21 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.openhab.binding.smaenergymeter.internal.handler.EnergyMeter;
import org.openhab.binding.smaenergymeter.internal.packet.PacketListener;
import org.openhab.binding.smaenergymeter.internal.packet.PacketListenerRegistry;
import org.openhab.binding.smaenergymeter.internal.packet.PayloadHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -39,12 +43,16 @@
* @author Osman Basha - Initial contribution
*/
@Component(service = DiscoveryService.class, configurationPid = "discovery.smaenergymeter")
public class SMAEnergyMeterDiscoveryService extends AbstractDiscoveryService {
public class SMAEnergyMeterDiscoveryService extends AbstractDiscoveryService implements PayloadHandler {

private final Logger logger = LoggerFactory.getLogger(SMAEnergyMeterDiscoveryService.class);
private final PacketListenerRegistry listenerRegistry;
private PacketListener packetListener;

public SMAEnergyMeterDiscoveryService() {
@Activate
public SMAEnergyMeterDiscoveryService(@Reference PacketListenerRegistry listenerRegistry) {
super(SUPPORTED_THING_TYPES_UIDS, 15, true);
this.listenerRegistry = listenerRegistry;
}

@Override
Expand All @@ -54,37 +62,54 @@ public Set<ThingTypeUID> getSupportedThingTypes() {

@Override
protected void startBackgroundDiscovery() {
if (packetListener != null) {
return;
}

logger.debug("Start SMAEnergyMeter background discovery");
scheduler.schedule(this::discover, 0, TimeUnit.SECONDS);
try {
packetListener = listenerRegistry.getListener(PacketListener.DEFAULT_MCAST_GRP, PacketListener.DEFAULT_MCAST_PORT);
packetListener.open();
} catch (IOException e) {
logger.error("Could not start background discovery", e);
return;
}

packetListener.addPayloadHandler(this);
}

@Override
public void startScan() {
logger.debug("Start SMAEnergyMeter scan");
discover();
protected void stopBackgroundDiscovery() {
packetListener.removePayloadHandler(this);
}

private synchronized void discover() {
logger.debug("Try to discover a SMA Energy Meter device");

EnergyMeter energyMeter = new EnergyMeter(EnergyMeter.DEFAULT_MCAST_GRP, EnergyMeter.DEFAULT_MCAST_PORT);
@Override
public void startScan() {
logger.debug("Start SMAEnergyMeter scan");
startBackgroundDiscovery();
try {
energyMeter.update();
} catch (IOException e) {
logger.debug("No SMA Energy Meter found.");
logger.debug("Diagnostic: ", e);
return;
Thread.sleep(60_000);
} catch (InterruptedException e) {
logger.debug("Discovery task terminated", e);
} finally {
stopBackgroundDiscovery();
}
}

logger.debug("Adding a new SMA Engergy Meter with S/N '{}' to inbox", energyMeter.getSerialNumber());
@Override
public void handle(EnergyMeter energyMeter) throws IOException {
String identifier = energyMeter.getSerialNumber();
logger.debug("Adding a new SMA Energy Meter with S/N '{}' to inbox", identifier);
Map<String, Object> properties = new HashMap<>();
properties.put(Thing.PROPERTY_VENDOR, "SMA");
properties.put(Thing.PROPERTY_SERIAL_NUMBER, energyMeter.getSerialNumber());
ThingUID uid = new ThingUID(THING_TYPE_ENERGY_METER, energyMeter.getSerialNumber());
properties.put(Thing.PROPERTY_SERIAL_NUMBER, identifier);
ThingUID uid = new ThingUID(THING_TYPE_ENERGY_METER, identifier);
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
.withLabel("SMA Energy Meter").build();
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withLabel("SMA Energy Meter #" + identifier)
.build();
thingDiscovered(result);

logger.debug("Thing discovered '{}'", result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,27 @@
package org.openhab.binding.smaenergymeter.internal.handler;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Date;

import org.openhab.core.library.types.DecimalType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link EnergyMeter} class is responsible for communication with the SMA device
* and extracting the data fields out of the received telegrams.
*
* @author Osman Basha - Initial contribution
* @author Łukasz Dywicki - Extracted multicast group handling to
* {@link org.openhab.binding.smaenergymeter.internal.packet.PacketListener}.
*/
public class EnergyMeter {

private static final byte[] E_METER_PROTOCOL_ID = new byte[] {0x60, 0x69};

private final Logger logger = LoggerFactory.getLogger(EnergyMeter.class);
private String multicastGroup;
private int port;

Expand All @@ -53,13 +57,7 @@ public class EnergyMeter {
private final FieldDTO powerOutL3;
private final FieldDTO energyOutL3;

public static final String DEFAULT_MCAST_GRP = "239.12.255.254";
public static final int DEFAULT_MCAST_PORT = 9522;

public EnergyMeter(String multicastGroup, int port) {
this.multicastGroup = multicastGroup;
this.port = port;

public EnergyMeter() {
powerIn = new FieldDTO(0x20, 4, 10);
energyIn = new FieldDTO(0x28, 8, 3600000);
powerOut = new FieldDTO(0x34, 4, 10);
Expand All @@ -81,23 +79,21 @@ public EnergyMeter(String multicastGroup, int port) {
energyOutL3 = new FieldDTO(0x1E4, 8, 3600000); // +8
}

public void update() throws IOException {
byte[] bytes = new byte[608];
try (MulticastSocket socket = new MulticastSocket(port)) {
socket.setSoTimeout(5000);
InetAddress address = InetAddress.getByName(multicastGroup);
socket.joinGroup(address);

DatagramPacket msgPacket = new DatagramPacket(bytes, bytes.length);
socket.receive(msgPacket);

String sma = new String(Arrays.copyOfRange(bytes, 0x00, 0x03));
public void parse(byte[] bytes) throws IOException {
try {
String sma = new String(Arrays.copyOfRange(bytes, 0, 3));
if (!sma.equals("SMA")) {
throw new IOException("Not a SMA telegram." + sma);
}
byte[] protocolId = Arrays.copyOfRange(bytes, 16, 18);
if (!Arrays.equals(protocolId, E_METER_PROTOCOL_ID)) {
throw new IllegalArgumentException(
"Received frame with wrong protocol ID " + Arrays.toString(protocolId));
}

byte[] idf = Arrays.copyOfRange(bytes, 0x14, 0x18);
ByteBuffer buffer = ByteBuffer.wrap(Arrays.copyOfRange(bytes, 0x14, 0x18));
serialNumber = String.valueOf(buffer.getInt());
serialNumber = Long.toString(buffer.getInt() & 0xFFFFFFFFL);

powerIn.updateValue(bytes);
energyIn.updateValue(bytes);
Expand All @@ -118,8 +114,6 @@ public void update() throws IOException {
energyInL3.updateValue(bytes);
powerOutL3.updateValue(bytes);
energyOutL3.updateValue(bytes);

lastUpdate = new Date(System.currentTimeMillis());
} catch (Exception e) {
throw new IOException(e);
}
Expand All @@ -129,10 +123,6 @@ public String getSerialNumber() {
return serialNumber;
}

public Date getLastUpdate() {
return lastUpdate;
}

public DecimalType getPowerIn() {
return new DecimalType(powerIn.getValue());
}
Expand Down
Loading

0 comments on commit d882891

Please sign in to comment.