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 492d090
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 101 deletions.
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
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;
}
}
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,35 +62,52 @@ 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);
Expand Down
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

0 comments on commit 492d090

Please sign in to comment.