Implement 3-phase voltage logic.

This commit is contained in:
Mark Milligan 2022-08-20 21:20:50 -05:00
parent 1369529e8d
commit e0d4dafd3a
34 changed files with 591 additions and 117 deletions

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>lantern-currentmonitor</artifactId> <artifactId>lantern-currentmonitor</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<version>1.1.0</version> <version>1.1.1</version>
<name>lantern-currentmonitor</name> <name>lantern-currentmonitor</name>
<parent> <parent>

View File

@ -2,6 +2,7 @@ package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.currentmonitor.adc.MCP3008Pin; import com.lanternsoftware.currentmonitor.adc.MCP3008Pin;
import com.lanternsoftware.datamodel.currentmonitor.Breaker; import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import java.util.List; import java.util.List;

View File

@ -6,6 +6,9 @@ import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.datamodel.currentmonitor.BreakerHub; import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity; import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
import com.lanternsoftware.datamodel.currentmonitor.hub.BreakerSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import com.lanternsoftware.pigpio.PiGpioFactory; import com.lanternsoftware.pigpio.PiGpioFactory;
import com.lanternsoftware.util.CollectionUtils; import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils; import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
@ -18,6 +21,8 @@ import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -29,6 +34,23 @@ public class CurrentMonitor {
private Sampler sampler; private Sampler sampler;
private PowerListener listener; private PowerListener listener;
private boolean debug = false; private boolean debug = false;
private boolean postSamples = false;
public boolean isDebug() {
return debug;
}
public void setDebug(boolean _debug) {
debug = _debug;
}
public boolean isPostSamples() {
return postSamples;
}
public void setPostSamples(boolean _postSamples) {
postSamples = _postSamples;
}
public void stop() { public void stop() {
stopMonitoring(); stopMonitoring();
@ -40,10 +62,6 @@ public class CurrentMonitor {
LOG.info("Power Monitor Service Stopped"); LOG.info("Power Monitor Service Stopped");
} }
public void setDebug(boolean _debug) {
debug = _debug;
}
public CalibrationResult calibrateVoltage(double _curCalibration) { public CalibrationResult calibrateVoltage(double _curCalibration) {
LOG.info("Calibrating Voltage"); LOG.info("Calibrating Voltage");
MCP3008Pin voltagePin = new MCP3008Pin(getChip(0), 0); MCP3008Pin voltagePin = new MCP3008Pin(getChip(0), 0);
@ -148,10 +166,10 @@ public class CurrentMonitor {
private boolean running = true; private boolean running = true;
private final BreakerHub hub; private final BreakerHub hub;
private final List<List<BreakerSamples>> breakers; private final List<List<BreakerSamples>> breakers;
private final int intervalNs; private final long intervalNs;
private final int concurrentBreakerCnt; private final int concurrentBreakerCnt;
public Sampler(BreakerHub _hub, List<Breaker> _breakers, int _intervalMs, int _concurrentBreakerCnt) { public Sampler(BreakerHub _hub, List<Breaker> _breakers, long _intervalMs, int _concurrentBreakerCnt) {
hub = _hub; hub = _hub;
MCP3008Pin voltagePin = new MCP3008Pin(getChip(0), 0); MCP3008Pin voltagePin = new MCP3008Pin(getChip(0), 0);
breakers = CollectionUtils.transform(_breakers, _b->{ breakers = CollectionUtils.transform(_breakers, _b->{
@ -177,6 +195,10 @@ public class CurrentMonitor {
long interval = 0; long interval = 0;
int cycle; int cycle;
int curBreaker; int curBreaker;
long intervalStart;
long intervalEnd;
long cycleEnd;
long curTime;
BreakerSamples[] cycleBreakers = new BreakerSamples[concurrentBreakerCnt]; BreakerSamples[] cycleBreakers = new BreakerSamples[concurrentBreakerCnt];
try { try {
while (true) { while (true) {
@ -187,8 +209,8 @@ public class CurrentMonitor {
} }
} }
final Date readTime = new Date(); final Date readTime = new Date();
final long intervalStart = (interval * intervalNs) + start; intervalStart = (interval * intervalNs) + start;
long intervalEnd = intervalStart + intervalNs; intervalEnd = intervalStart + intervalNs;
cycle = 0; cycle = 0;
final int batch = (int) (interval % BATCH_CNT); final int batch = (int) (interval % BATCH_CNT);
while (System.nanoTime() < intervalEnd) { while (System.nanoTime() < intervalEnd) {
@ -197,22 +219,73 @@ public class CurrentMonitor {
cycleBreakers[curBreaker].incrementCycleCnt(); cycleBreakers[curBreaker].incrementCycleCnt();
} }
cycle++; cycle++;
long cycleEnd = intervalStart + (cycle * (intervalNs / hub.getFrequency())); cycleEnd = intervalStart + (cycle * (intervalNs / hub.getFrequency()));
while (System.nanoTime() < cycleEnd) { curTime = System.nanoTime();
while (curTime < cycleEnd) {
for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) { for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) {
PowerSample sample = cycleBreakers[curBreaker].incrementSample(); PowerSample sample = cycleBreakers[curBreaker].incrementSample();
sample.nanoTime = curTime;
sample.cycle = cycle;
sample.voltage = cycleBreakers[curBreaker].getVoltagePin().read(); sample.voltage = cycleBreakers[curBreaker].getVoltagePin().read();
sample.current = cycleBreakers[curBreaker].getCurrentPin().read(); sample.current = cycleBreakers[curBreaker].getCurrentPin().read();
} }
curTime = System.nanoTime();
} }
} }
interval++; interval++;
final HubSample hubSample = (postSamples && (interval == 10)) ? new HubSample() : null;
executor.submit(() -> { executor.submit(() -> {
long cycleLength = 1000000000/hub.getFrequency();
if (hubSample != null) {
hubSample.setSampleDate(new Date());
hubSample.setBreakers(new ArrayList<>());
}
for (List<BreakerSamples> breaker : breakers) { for (List<BreakerSamples> breaker : breakers) {
double vOffset = 0.0;
double iOffset = 0.0;
BreakerSamples samples = breaker.get(batch); BreakerSamples samples = breaker.get(batch);
List<PowerSample> validSamples = samples.getSamples().subList(0, samples.getSampleCnt()); List<PowerSample> validSamples = samples.getSamples().subList(0, samples.getSampleCnt());
if (hubSample != null) {
BreakerSample breakerSample = new BreakerSample();
breakerSample.setSamples(validSamples);
breakerSample.setPanel(samples.getBreaker().getPanel());
breakerSample.setSpace(samples.getBreaker().getSpace());
hubSample.getBreakers().add(breakerSample);
}
int phaseOffsetNs = samples.getBreaker().getPhaseOffsetNs()-hub.getPhaseOffsetNs();
if (phaseOffsetNs != 0) {
Map<Integer, List<PowerSample>> cycles = CollectionUtils.transformToMultiMap(validSamples, _p->_p.cycle);
for (List<PowerSample> cycleSamples : cycles.values()) {
long minNano;
long maxNano = minNano = cycleSamples.get(0).nanoTime;
for (PowerSample sample : cycleSamples) {
if (sample.nanoTime < minNano)
minNano = sample.nanoTime;
if (sample.nanoTime > maxNano)
maxNano = sample.nanoTime;
}
TreeMap<Long, Double> offsetSamples = new TreeMap<>();
for (PowerSample sample : cycleSamples) {
if (sample.nanoTime + phaseOffsetNs < minNano)
offsetSamples.put(sample.nanoTime + phaseOffsetNs + cycleLength, sample.voltage);
else if (sample.nanoTime + phaseOffsetNs > maxNano)
offsetSamples.put(sample.nanoTime + phaseOffsetNs - cycleLength, sample.voltage);
else
offsetSamples.put(sample.nanoTime + phaseOffsetNs, sample.voltage);
}
for (PowerSample sample : cycleSamples) {
List<Double> voltages = new ArrayList<>();
Entry<Long, Double> floorEntry = offsetSamples.floorEntry(sample.nanoTime);
if (floorEntry != null)
voltages.add(floorEntry.getValue());
Entry<Long, Double> ceilingEntry = offsetSamples.ceilingEntry(sample.nanoTime);
if (ceilingEntry != null)
voltages.add(ceilingEntry.getValue());
sample.voltage = CollectionUtils.mean(voltages);
}
}
}
double vOffset = 0.0;
double iOffset = 0.0;
for (PowerSample sample : validSamples) { for (PowerSample sample : validSamples) {
vOffset += sample.voltage; vOffset += sample.voltage;
iOffset += sample.current; iOffset += sample.current;
@ -223,6 +296,7 @@ public class CurrentMonitor {
double pSum = 0.0; double pSum = 0.0;
double vRms = 0.0; double vRms = 0.0;
double lowPassFilter = samples.getBreaker().getLowPassFilter(); double lowPassFilter = samples.getBreaker().getLowPassFilter();
for (PowerSample sample : validSamples) { for (PowerSample sample : validSamples) {
sample.current -= iOffset; sample.current -= iOffset;
if (Math.abs(sample.current) < lowPassFilter) if (Math.abs(sample.current) < lowPassFilter)
@ -233,11 +307,15 @@ public class CurrentMonitor {
} }
vRms /= validSamples.size(); vRms /= validSamples.size();
vRms = hub.getVoltageCalibrationFactor() * Math.sqrt(vRms); vRms = hub.getVoltageCalibrationFactor() * Math.sqrt(vRms);
int lowSampleRatio = (lowSamples * 100) / samples.getSampleCnt(); int lowSampleRatio = (lowSamples * 100) / validSamples.size();
double realPower = Math.abs((hub.getVoltageCalibrationFactor() * hub.getPortCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * pSum) / samples.getSampleCnt()); double realPower = (hub.getVoltageCalibrationFactor() * hub.getPortCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * pSum) / validSamples.size();
if ((lowSampleRatio > 75) && realPower < 13.0) if ((lowSampleRatio > 75) && Math.abs(realPower) < 13.0)
realPower = 0.0; realPower = 0.0;
if (samples.getBreaker().getPolarity() == BreakerPolarity.SOLAR) if (samples.getBreaker().getPolarity() == BreakerPolarity.NORMAL)
realPower = Math.abs(realPower);
else if (samples.getBreaker().getPolarity() == BreakerPolarity.SOLAR)
realPower = -Math.abs(realPower);
else if (samples.getBreaker().getPolarity() == BreakerPolarity.BI_DIRECTIONAL_INVERTED)
realPower = -realPower; realPower = -realPower;
if (samples.getBreaker().isDoublePower()) if (samples.getBreaker().isDoublePower())
realPower *= 2.0; realPower *= 2.0;
@ -263,6 +341,8 @@ public class CurrentMonitor {
samples.setCycleCnt(0); samples.setCycleCnt(0);
listener.onPowerEvent(new BreakerPower(samples.getBreaker().getPanel(), samples.getBreaker().getSpace(), readTime, realPower, vRms)); listener.onPowerEvent(new BreakerPower(samples.getBreaker().getPanel(), samples.getBreaker().getSpace(), readTime, realPower, vRms));
} }
if (hubSample != null)
listener.onSampleEvent(hubSample);
}); });
} }
} }

View File

@ -16,6 +16,7 @@ import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
import com.lanternsoftware.datamodel.currentmonitor.HubConfigService; import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus; import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.CollectionUtils; import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils; import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader; import com.lanternsoftware.util.ResourceLoader;
@ -65,16 +66,24 @@ public class MonitorApp {
private static final CurrentMonitor monitor = new CurrentMonitor(); private static final CurrentMonitor monitor = new CurrentMonitor();
private static final List<BreakerPower> readings = new ArrayList<>(); private static final List<BreakerPower> readings = new ArrayList<>();
private static String version; private static String version;
private static final PowerListener logger = _p -> { private static final PowerListener logger = new PowerListener() {
if (!config.isDebug()) { @Override
_p.setHubVersion(version); public void onPowerEvent(BreakerPower _power) {
if (breakerConfig != null) if (!config.isDebug()) {
_p.setAccountId(breakerConfig.getAccountId()); _power.setHubVersion(version);
synchronized (readings) { if (breakerConfig != null)
readings.add(_p); _power.setAccountId(breakerConfig.getAccountId());
} synchronized (readings) {
} else readings.add(_power);
LOG.info("Panel{} - Space{} Power: {}W", _p.getPanel(), Breaker.toSpaceDisplay(_p.getSpace()), String.format("%.3f", _p.getPower())); }
} else
LOG.info("Panel{} - Space{} Power: {}W", _power.getPanel(), Breaker.toSpaceDisplay(_power.getSpace()), String.format("%.3f", _power.getPower()));
}
@Override
public void onSampleEvent(HubSample _sample) {
post(DaoSerializer.toZipBson(_sample), "sample");
}
}; };
private static final BleCharacteristicListener bluetoothListener = new BleCharacteristicListener() { private static final BleCharacteristicListener bluetoothListener = new BleCharacteristicListener() {
@Override @Override
@ -214,6 +223,7 @@ public class MonitorApp {
if (NullUtils.isNotEmpty(config.getHost())) if (NullUtils.isNotEmpty(config.getHost()))
host = NullUtils.terminateWith(config.getHost(), "/"); host = NullUtils.terminateWith(config.getHost(), "/");
monitor.setDebug(config.isDebug()); monitor.setDebug(config.isDebug());
monitor.setPostSamples(config.isPostSamples());
LEDFlasher.setLEDOn(false); LEDFlasher.setLEDOn(false);
if (NullUtils.isNotEmpty(config.getAuthCode())) if (NullUtils.isNotEmpty(config.getAuthCode()))
authCode = config.getAuthCode(); authCode = config.getAuthCode();

View File

@ -17,6 +17,7 @@ public class MonitorConfig {
private boolean debug; private boolean debug;
private int connectTimeout; private int connectTimeout;
private int socketTimeout; private int socketTimeout;
private boolean postSamples = false;
private boolean needsCalibration = true; private boolean needsCalibration = true;
private String mqttBrokerUrl; private String mqttBrokerUrl;
private String mqttUserName; private String mqttUserName;
@ -98,6 +99,14 @@ public class MonitorConfig {
socketTimeout = _socketTimeout; socketTimeout = _socketTimeout;
} }
public boolean isPostSamples() {
return postSamples;
}
public void setPostSamples(boolean _postSamples) {
postSamples = _postSamples;
}
public boolean isNeedsCalibration() { public boolean isNeedsCalibration() {
return needsCalibration; return needsCalibration;
} }

View File

@ -1,7 +1,9 @@
package com.lanternsoftware.currentmonitor; package com.lanternsoftware.currentmonitor;
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower; import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
public interface PowerListener { public interface PowerListener {
void onPowerEvent(BreakerPower _power); void onPowerEvent(BreakerPower _power);
void onSampleEvent(HubSample _sample);
} }

View File

@ -1,6 +0,0 @@
package com.lanternsoftware.currentmonitor;
public class PowerSample {
public double voltage;
public double current;
}

View File

@ -34,6 +34,7 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer<MonitorConfig
d.put("debug", _o.isDebug()); d.put("debug", _o.isDebug());
d.put("connect_timeout", _o.getConnectTimeout()); d.put("connect_timeout", _o.getConnectTimeout());
d.put("socket_timeout", _o.getSocketTimeout()); d.put("socket_timeout", _o.getSocketTimeout());
d.put("post_samples", _o.isPostSamples());
d.put("needs_calibration", _o.isNeedsCalibration()); d.put("needs_calibration", _o.isNeedsCalibration());
d.put("mqtt_broker_url", _o.getMqttBrokerUrl()); d.put("mqtt_broker_url", _o.getMqttBrokerUrl());
d.put("mqtt_user_name", _o.getMqttUserName()); d.put("mqtt_user_name", _o.getMqttUserName());
@ -57,6 +58,7 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer<MonitorConfig
o.setDebug(DaoSerializer.getBoolean(_d, "debug")); o.setDebug(DaoSerializer.getBoolean(_d, "debug"));
o.setConnectTimeout(DaoSerializer.getInteger(_d, "connect_timeout")); o.setConnectTimeout(DaoSerializer.getInteger(_d, "connect_timeout"));
o.setSocketTimeout(DaoSerializer.getInteger(_d, "socket_timeout")); o.setSocketTimeout(DaoSerializer.getInteger(_d, "socket_timeout"));
o.setPostSamples(DaoSerializer.getBoolean(_d, "post_samples"));
o.setNeedsCalibration(DaoSerializer.getBoolean(_d, "needs_calibration")); o.setNeedsCalibration(DaoSerializer.getBoolean(_d, "needs_calibration"));
o.setMqttBrokerUrl(DaoSerializer.getString(_d, "mqtt_broker_url")); o.setMqttBrokerUrl(DaoSerializer.getString(_d, "mqtt_broker_url"));
o.setMqttUserName(DaoSerializer.getString(_d, "mqtt_user_name")); o.setMqttUserName(DaoSerializer.getString(_d, "mqtt_user_name"));

View File

@ -8,6 +8,7 @@ import com.lanternsoftware.datamodel.currentmonitor.EnergyViewMode;
import com.lanternsoftware.datamodel.currentmonitor.HubCommand; import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute; import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus; import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.DateRange; import com.lanternsoftware.util.DateRange;
import com.lanternsoftware.util.dao.auth.AuthCode; import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.dao.mongo.MongoProxy; import com.lanternsoftware.util.dao.mongo.MongoProxy;
@ -44,7 +45,6 @@ public interface CurrentMonitorDao {
void putConfig(BreakerConfig _config); void putConfig(BreakerConfig _config);
void rebuildSummaries(int _accountId); void rebuildSummaries(int _accountId);
void rebuildSummariesAsync(int _accountId);
void rebuildSummaries(int _accountId, Date _start, Date _end); void rebuildSummaries(int _accountId, Date _start, Date _end);
String addPasswordResetKey(String _email); String addPasswordResetKey(String _email);
@ -66,5 +66,8 @@ public interface CurrentMonitorDao {
List<HubCommand> getAllHubCommands(); List<HubCommand> getAllHubCommands();
void deleteHubCommand(String _id); void deleteHubCommand(String _id);
void putHubSample(HubSample _sample);
List<HubSample> getSamplesForAccount(int _accountId);
MongoProxy getProxy(); MongoProxy getProxy();
} }

View File

@ -21,6 +21,7 @@ import com.lanternsoftware.datamodel.currentmonitor.archive.ArchiveStatus;
import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive; import com.lanternsoftware.datamodel.currentmonitor.archive.BreakerEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive; import com.lanternsoftware.datamodel.currentmonitor.archive.DailyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.archive.MonthlyEnergyArchive; import com.lanternsoftware.datamodel.currentmonitor.archive.MonthlyEnergyArchive;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.CollectionUtils; import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateRange; import com.lanternsoftware.util.DateRange;
import com.lanternsoftware.util.DateUtils; import com.lanternsoftware.util.DateUtils;
@ -487,17 +488,13 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
} }
} }
@Override
public void rebuildSummariesAsync(int _accountId) {
executor.submit(() -> rebuildSummaries(_accountId));
}
@Override @Override
public void rebuildSummaries(int _accountId) { public void rebuildSummaries(int _accountId) {
HubPowerMinute firstMinute = proxy.queryOne(HubPowerMinute.class, new DaoQuery("account_id", _accountId), DaoSort.sort("minute")); HubPowerMinute firstMinute = proxy.queryOne(HubPowerMinute.class, new DaoQuery("account_id", _accountId), DaoSort.sort("minute"));
if (firstMinute == null) if (firstMinute == null)
return; return;
rebuildSummaries(_accountId, firstMinute.getMinuteAsDate(), new Date()); HubPowerMinute lastMinute = proxy.queryOne(HubPowerMinute.class, new DaoQuery("account_id", _accountId), DaoSort.sortDesc("minute"));
rebuildSummaries(_accountId, firstMinute.getMinuteAsDate(), lastMinute.getMinuteAsDate());
} }
@Override @Override
@ -812,6 +809,16 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
proxy.delete(HubCommand.class, new DaoQuery("_id", _id)); proxy.delete(HubCommand.class, new DaoQuery("_id", _id));
} }
@Override
public void putHubSample(HubSample _sample) {
proxy.save(_sample);
}
@Override
public List<HubSample> getSamplesForAccount(int _accountId) {
return proxy.query(HubSample.class, new DaoQuery("account_id", _accountId));
}
@Override @Override
public MongoProxy getProxy() { public MongoProxy getProxy() {
return proxy; return proxy;

View File

@ -21,6 +21,7 @@ public class Breaker implements IIdentical<Breaker> {
private String name; private String name;
private String description; private String description;
private int sizeAmps; private int sizeAmps;
private int phaseOffsetNs;
private double calibrationFactor; private double calibrationFactor;
private double lowPassFilter; private double lowPassFilter;
private BreakerPolarity polarity; private BreakerPolarity polarity;
@ -140,6 +141,14 @@ public class Breaker implements IIdentical<Breaker> {
sizeAmps = _sizeAmps; sizeAmps = _sizeAmps;
} }
public int getPhaseOffsetNs() {
return phaseOffsetNs;
}
public void setPhaseOffsetNs(int _phaseOffsetNs) {
phaseOffsetNs = _phaseOffsetNs;
}
public double getLowPassFilter() { public double getLowPassFilter() {
return Math.abs(lowPassFilter) < 0.05 ? 1.6 : lowPassFilter; return Math.abs(lowPassFilter) < 0.05 ? 1.6 : lowPassFilter;
} }
@ -278,7 +287,7 @@ public class Breaker implements IIdentical<Breaker> {
@Override @Override
public boolean isIdentical(Breaker _o) { public boolean isIdentical(Breaker _o) {
if (this == _o) return true; if (this == _o) return true;
return panel == _o.panel && space == _o.space && meter == _o.meter && hub == _o.hub && port == _o.port && sizeAmps == _o.sizeAmps && Double.compare(_o.calibrationFactor, calibrationFactor) == 0 && Double.compare(_o.lowPassFilter, lowPassFilter) == 0 && doublePower == _o.doublePower && Objects.equals(name, _o.name) && Objects.equals(description, _o.description) && polarity == _o.polarity && type == _o.type; return panel == _o.panel && space == _o.space && meter == _o.meter && hub == _o.hub && port == _o.port && sizeAmps == _o.sizeAmps && phaseOffsetNs == _o.phaseOffsetNs && Double.compare(_o.calibrationFactor, calibrationFactor) == 0 && Double.compare(_o.lowPassFilter, lowPassFilter) == 0 && doublePower == _o.doublePower && Objects.equals(name, _o.name) && Objects.equals(description, _o.description) && polarity == _o.polarity && type == _o.type;
} }
@Override @Override

View File

@ -11,6 +11,8 @@ public class BreakerHub implements IIdentical<BreakerHub> {
private int hub; private int hub;
private double voltageCalibrationFactor; private double voltageCalibrationFactor;
private double portCalibrationFactor; private double portCalibrationFactor;
private int phaseCnt;
private int phaseOffsetNs;
private int frequency; private int frequency;
private String bluetoothMac; private String bluetoothMac;
@ -46,8 +48,24 @@ public class BreakerHub implements IIdentical<BreakerHub> {
portCalibrationFactor = _portCalibrationFactor; portCalibrationFactor = _portCalibrationFactor;
} }
public int getPhaseCnt() {
return phaseCnt == 0 ? 2 : phaseCnt;
}
public void setPhaseCnt(int _phaseCnt) {
phaseCnt = _phaseCnt;
}
public int getPhaseOffsetNs() {
return phaseOffsetNs;
}
public void setPhaseOffsetNs(int _phaseOffsetNs) {
phaseOffsetNs = _phaseOffsetNs;
}
public int getFrequency() { public int getFrequency() {
return frequency; return frequency == 0 ? 60 : frequency;
} }
public void setFrequency(int _frequency) { public void setFrequency(int _frequency) {
@ -73,7 +91,7 @@ public class BreakerHub implements IIdentical<BreakerHub> {
@Override @Override
public boolean isIdentical(BreakerHub _o) { public boolean isIdentical(BreakerHub _o) {
if (this == _o) return true; if (this == _o) return true;
return hub == _o.hub && Double.compare(_o.voltageCalibrationFactor, voltageCalibrationFactor) == 0 && Double.compare(_o.portCalibrationFactor, portCalibrationFactor) == 0 && frequency == _o.frequency && Objects.equals(bluetoothMac, _o.bluetoothMac); return hub == _o.hub && Double.compare(_o.voltageCalibrationFactor, voltageCalibrationFactor) == 0 && Double.compare(_o.portCalibrationFactor, portCalibrationFactor) == 0 && getPhaseCnt() == _o.getPhaseCnt() && getPhaseOffsetNs() == _o.getPhaseOffsetNs() && getFrequency() == _o.getFrequency() && Objects.equals(bluetoothMac, _o.bluetoothMac);
} }
@Override @Override

View File

@ -2,5 +2,7 @@ package com.lanternsoftware.datamodel.currentmonitor;
public enum BreakerPolarity { public enum BreakerPolarity {
NORMAL, NORMAL,
SOLAR; SOLAR,
BI_DIRECTIONAL,
BI_DIRECTIONAL_INVERTED
} }

View File

@ -140,9 +140,9 @@ public class EnergySummary {
idx = 0; idx = 0;
double flow = 0.0; double flow = 0.0;
for (Float power : CollectionUtils.makeNotNull(breaker.getReadings())) { for (Float power : CollectionUtils.makeNotNull(breaker.getReadings())) {
if ((b.getPolarity() == BreakerPolarity.SOLAR) && (meter.flow[idx] < 0.0)) if (power < 0 && (meter.flow[idx] < 0.0))
flow -= meter.flow[idx] * (power / meter.solar[idx]); flow -= meter.flow[idx] * (power / meter.solar[idx]);
else if ((b.getPolarity() != BreakerPolarity.SOLAR) && (meter.flow[idx] > 0.0)) else if (power > 0 && (meter.flow[idx] > 0.0))
flow += meter.flow[idx] * (power / meter.usage[idx]); flow += meter.flow[idx] * (power / meter.usage[idx]);
idx++; idx++;
} }
@ -152,11 +152,11 @@ public class EnergySummary {
} }
public void resetEnergy(Date _readTime) { public void resetEnergy(Date _readTime) {
if (energy == null) if (energy != null) {
return; int idx = viewMode.blockIndex(start, _readTime, timezone);
int idx = viewMode.blockIndex(start, _readTime, timezone); if (idx < energy.length)
if (idx < energy.length) energy[idx] = 0f;
energy[idx] = 0f; }
for (EnergySummary subGroup : CollectionUtils.makeNotNull(subGroups)) { for (EnergySummary subGroup : CollectionUtils.makeNotNull(subGroups)) {
subGroup.resetEnergy(_readTime); subGroup.resetEnergy(_readTime);
} }

View File

@ -28,6 +28,8 @@ public class BreakerHubSerializer extends AbstractDaoSerializer<BreakerHub>
d.put("hub", _o.getHub()); d.put("hub", _o.getHub());
d.put("voltage_calibration_factor", _o.getRawVoltageCalibrationFactor()); d.put("voltage_calibration_factor", _o.getRawVoltageCalibrationFactor());
d.put("port_calibration_factor", _o.getRawPortCalibrationFactor()); d.put("port_calibration_factor", _o.getRawPortCalibrationFactor());
d.put("phase_cnt", _o.getPhaseCnt());
d.put("phase_offset_ns", _o.getPhaseOffsetNs());
d.put("frequency", _o.getFrequency()); d.put("frequency", _o.getFrequency());
d.put("bluetooth_mac", _o.getBluetoothMac()); d.put("bluetooth_mac", _o.getBluetoothMac());
return d; return d;
@ -40,6 +42,8 @@ public class BreakerHubSerializer extends AbstractDaoSerializer<BreakerHub>
o.setHub(DaoSerializer.getInteger(_d, "hub")); o.setHub(DaoSerializer.getInteger(_d, "hub"));
o.setVoltageCalibrationFactor(DaoSerializer.getDouble(_d, "voltage_calibration_factor")); o.setVoltageCalibrationFactor(DaoSerializer.getDouble(_d, "voltage_calibration_factor"));
o.setPortCalibrationFactor(DaoSerializer.getDouble(_d, "port_calibration_factor")); o.setPortCalibrationFactor(DaoSerializer.getDouble(_d, "port_calibration_factor"));
o.setPhaseCnt(DaoSerializer.getInteger(_d, "phase_cnt"));
o.setPhaseOffsetNs(DaoSerializer.getInteger(_d, "phase_offset_ns"));
o.setFrequency(DaoSerializer.getInteger(_d, "frequency")); o.setFrequency(DaoSerializer.getInteger(_d, "frequency"));
o.setBluetoothMac(DaoSerializer.getString(_d, "bluetooth_mac")); o.setBluetoothMac(DaoSerializer.getString(_d, "bluetooth_mac"));
return o; return o;

View File

@ -35,6 +35,7 @@ public class BreakerSerializer extends AbstractDaoSerializer<Breaker>
d.put("name", _o.getName()); d.put("name", _o.getName());
d.put("description", _o.getDescription()); d.put("description", _o.getDescription());
d.put("size_amps", _o.getSizeAmps()); d.put("size_amps", _o.getSizeAmps());
d.put("phase_offset_ns", _o.getPhaseOffsetNs());
d.put("calibration_factor", _o.getCalibrationFactor()); d.put("calibration_factor", _o.getCalibrationFactor());
d.put("low_pass_filter", _o.getLowPassFilter()); d.put("low_pass_filter", _o.getLowPassFilter());
d.put("polarity", DaoSerializer.toEnumName(_o.getPolarity())); d.put("polarity", DaoSerializer.toEnumName(_o.getPolarity()));
@ -56,6 +57,7 @@ public class BreakerSerializer extends AbstractDaoSerializer<Breaker>
o.setName(DaoSerializer.getString(_d, "name")); o.setName(DaoSerializer.getString(_d, "name"));
o.setDescription(DaoSerializer.getString(_d, "description")); o.setDescription(DaoSerializer.getString(_d, "description"));
o.setSizeAmps(DaoSerializer.getInteger(_d, "size_amps")); o.setSizeAmps(DaoSerializer.getInteger(_d, "size_amps"));
o.setPhaseOffsetNs(DaoSerializer.getInteger(_d, "phase_offset_ns"));
o.setCalibrationFactor(DaoSerializer.getDouble(_d, "calibration_factor")); o.setCalibrationFactor(DaoSerializer.getDouble(_d, "calibration_factor"));
o.setLowPassFilter(DaoSerializer.getDouble(_d, "low_pass_filter")); o.setLowPassFilter(DaoSerializer.getDouble(_d, "low_pass_filter"));
o.setPolarity(DaoSerializer.getEnum(_d, "polarity", BreakerPolarity.class)); o.setPolarity(DaoSerializer.getEnum(_d, "polarity", BreakerPolarity.class));

View File

@ -0,0 +1,41 @@
package com.lanternsoftware.datamodel.currentmonitor.hub;
import com.lanternsoftware.datamodel.currentmonitor.Breaker;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.List;
@DBSerializable
public class BreakerSample {
private int panel;
private int space;
private List<PowerSample> samples;
public int key() {
return Breaker.intKey(panel, space);
}
public int getPanel() {
return panel;
}
public void setPanel(int _panel) {
panel = _panel;
}
public int getSpace() {
return space;
}
public void setSpace(int _space) {
space = _space;
}
public List<PowerSample> getSamples() {
return samples;
}
public void setSamples(List<PowerSample> _samples) {
samples = _samples;
}
}

View File

@ -0,0 +1,42 @@
package com.lanternsoftware.datamodel.currentmonitor.hub;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Date;
import java.util.List;
@DBSerializable(autogen = false)
public class HubSample {
private int accountId;
private Date sampleDate;
private List<BreakerSample> breakers;
public String getId() {
return String.format("%d-%d", accountId, DateUtils.toLong(sampleDate));
}
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public Date getSampleDate() {
return sampleDate;
}
public void setSampleDate(Date _sampleDate) {
sampleDate = _sampleDate;
}
public List<BreakerSample> getBreakers() {
return breakers;
}
public void setBreakers(List<BreakerSample> _breakers) {
breakers = _breakers;
}
}

View File

@ -0,0 +1,43 @@
package com.lanternsoftware.datamodel.currentmonitor.hub;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
@DBSerializable
public class PowerSample {
public long nanoTime;
public int cycle;
public double voltage;
public double current;
public long getNanoTime() {
return nanoTime;
}
public void setNanoTime(long _nanoTime) {
nanoTime = _nanoTime;
}
public int getCycle() {
return cycle;
}
public void setCycle(int _cycle) {
cycle = _cycle;
}
public double getVoltage() {
return voltage;
}
public void setVoltage(double _voltage) {
voltage = _voltage;
}
public double getCurrent() {
return current;
}
public void setCurrent(double _current) {
current = _current;
}
}

View File

@ -0,0 +1,44 @@
package com.lanternsoftware.datamodel.currentmonitor.hub.dao;
import com.lanternsoftware.datamodel.currentmonitor.hub.BreakerSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class BreakerSampleSerializer extends AbstractDaoSerializer<BreakerSample>
{
@Override
public Class<BreakerSample> getSupportedClass()
{
return BreakerSample.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(BreakerSample _o)
{
DaoEntity d = new DaoEntity();
d.put("panel", _o.getPanel());
d.put("space", _o.getSpace());
d.put("samples", DaoSerializer.toDaoEntities(_o.getSamples(), DaoProxyType.MONGO));
return d;
}
@Override
public BreakerSample fromDaoEntity(DaoEntity _d)
{
BreakerSample o = new BreakerSample();
o.setPanel(DaoSerializer.getInteger(_d, "panel"));
o.setSpace(DaoSerializer.getInteger(_d, "space"));
o.setSamples(DaoSerializer.getList(_d, "samples", PowerSample.class));
return o;
}
}

View File

@ -0,0 +1,45 @@
package com.lanternsoftware.datamodel.currentmonitor.hub.dao;
import com.lanternsoftware.datamodel.currentmonitor.hub.BreakerSample;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class HubSampleSerializer extends AbstractDaoSerializer<HubSample>
{
@Override
public Class<HubSample> getSupportedClass()
{
return HubSample.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(HubSample _o)
{
DaoEntity d = new DaoEntity();
d.put("_id", _o.getId());
d.put("account_id", _o.getAccountId());
d.put("sample_date", DaoSerializer.toLong(_o.getSampleDate()));
d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO));
return d;
}
@Override
public HubSample fromDaoEntity(DaoEntity _d)
{
HubSample o = new HubSample();
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
o.setSampleDate(DaoSerializer.getDate(_d, "sample_date"));
o.setBreakers(DaoSerializer.getList(_d, "breakers", BreakerSample.class));
return o;
}
}

View File

@ -0,0 +1,45 @@
package com.lanternsoftware.datamodel.currentmonitor.hub.dao;
import com.lanternsoftware.datamodel.currentmonitor.hub.PowerSample;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class PowerSampleSerializer extends AbstractDaoSerializer<PowerSample>
{
@Override
public Class<PowerSample> getSupportedClass()
{
return PowerSample.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(PowerSample _o)
{
DaoEntity d = new DaoEntity();
d.put("nano_time", _o.getNanoTime());
d.put("cycle", _o.getCycle());
d.put("voltage", _o.getVoltage());
d.put("current", _o.getCurrent());
return d;
}
@Override
public PowerSample fromDaoEntity(DaoEntity _d)
{
PowerSample o = new PowerSample();
o.setNanoTime(DaoSerializer.getLong(_d, "nano_time"));
o.setCycle(DaoSerializer.getInteger(_d, "cycle"));
o.setVoltage(DaoSerializer.getDouble(_d, "voltage"));
o.setCurrent(DaoSerializer.getDouble(_d, "current"));
return o;
}
}

View File

@ -26,3 +26,6 @@ com.lanternsoftware.datamodel.currentmonitor.dao.MeterSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.NetworkStatusSerializer com.lanternsoftware.datamodel.currentmonitor.dao.NetworkStatusSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.SequenceSerializer com.lanternsoftware.datamodel.currentmonitor.dao.SequenceSerializer
com.lanternsoftware.datamodel.currentmonitor.dao.SignupResponseSerializer com.lanternsoftware.datamodel.currentmonitor.dao.SignupResponseSerializer
com.lanternsoftware.datamodel.currentmonitor.hub.dao.BreakerSampleSerializer
com.lanternsoftware.datamodel.currentmonitor.hub.dao.HubSampleSerializer
com.lanternsoftware.datamodel.currentmonitor.hub.dao.PowerSampleSerializer

View File

@ -18,9 +18,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Globals implements ServletContextListener { public class Globals implements ServletContextListener {
public static CurrentMonitorDao dao; public static CurrentMonitorDao dao;
public static ExecutorService opsExecutor;
private static final Map<Integer, Map<Integer, List<HubCommand>>> commands = new HashMap<>(); private static final Map<Integer, Map<Integer, List<HubCommand>>> commands = new HashMap<>();
@Override @Override
@ -28,10 +31,12 @@ public class Globals implements ServletContextListener {
dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg")); dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
RulesEngine.instance().start(); RulesEngine.instance().start();
RulesEngine.instance().schedule(new CommandTask(), 0); RulesEngine.instance().schedule(new CommandTask(), 0);
opsExecutor = Executors.newFixedThreadPool(7);
} }
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
opsExecutor.shutdown();
dao.shutdown(); dao.shutdown();
HttpFactory.shutdown(); HttpFactory.shutdown();
RulesEngine.shutdown(); RulesEngine.shutdown();

View File

@ -4,6 +4,7 @@ import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig; import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import com.lanternsoftware.datamodel.currentmonitor.HubCommand; import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic; import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
import com.lanternsoftware.rules.RulesEngine;
import com.lanternsoftware.util.dao.auth.AuthCode; import com.lanternsoftware.util.dao.auth.AuthCode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -40,6 +41,8 @@ public class ConfigServlet extends SecureServiceServlet {
if ((oldConfig == null) || !oldConfig.isIdentical(config)) if ((oldConfig == null) || !oldConfig.isIdentical(config))
Globals.dao.putHubCommand(new HubCommand(config.getAccountId(), HubConfigCharacteristic.ReloadConfig, null)); Globals.dao.putHubCommand(new HubCommand(config.getAccountId(), HubConfigCharacteristic.ReloadConfig, null));
Globals.dao.putConfig(config); Globals.dao.putConfig(config);
zipBsonResponse(_rep, Globals.dao.getMergedConfig(_authCode)); config = Globals.dao.getMergedConfig(_authCode);
RulesEngine.instance().sendFcmMessage(config.getAccountId(), config);
zipBsonResponse(_rep, config);
} }
} }

View File

@ -17,13 +17,14 @@ public class RebuildSummariesServlet extends SecureServiceServlet {
if (_authCode.getAccountId() == 100) { if (_authCode.getAccountId() == 100) {
String[] path = path(_req); String[] path = path(_req);
if (path.length > 0) { if (path.length > 0) {
Globals.dao.rebuildSummariesAsync(DaoSerializer.toInteger(CollectionUtils.get(path, 0))); Globals.opsExecutor.submit(() -> Globals.dao.rebuildSummaries(DaoSerializer.toInteger(CollectionUtils.get(path, 0))));
} }
else { else {
for (String sId : Globals.dao.getProxy().queryForField(Account.class, null, "_id")) { for (String sId : Globals.dao.getProxy().queryForField(Account.class, null, "_id")) {
int id = DaoSerializer.toInteger(sId); int id = DaoSerializer.toInteger(sId);
if (id != 0) if (id != 0) {
Globals.dao.rebuildSummariesAsync(id); Globals.opsExecutor.submit(() -> Globals.dao.rebuildSummaries(id));
}
} }
} }
} }

View File

@ -0,0 +1,21 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.datamodel.currentmonitor.hub.HubSample;
import com.lanternsoftware.util.dao.auth.AuthCode;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/sample")
public class SampleServlet extends SecureServiceServlet {
@Override
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
HubSample sample = getRequestPayload(_req, HubSample.class);
if (sample == null)
return;
sample.setAccountId(_authCode.getAccountId());
Globals.dao.putHubSample(sample);
}
}

View File

@ -4,9 +4,6 @@ import com.lanternsoftware.util.ResourceLoader;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
public class PIGPIO { public class PIGPIO {
protected static final Logger LOG = LoggerFactory.getLogger(PIGPIO.class); protected static final Logger LOG = LoggerFactory.getLogger(PIGPIO.class);
@ -20,10 +17,11 @@ public class PIGPIO {
osArch = "armhf"; osArch = "armhf";
String path = "/lib/" + osArch + "/lantern-pigpio.so"; String path = "/lib/" + osArch + "/lantern-pigpio.so";
byte[] file = ResourceLoader.getByteArrayResource(PIGPIO.class, path); byte[] file = ResourceLoader.getByteArrayResource(PIGPIO.class, path);
String target = Files.createTempFile("lantern-pigpio", "so").toAbsolutePath().toString(); LOG.info("library size: {}", file.length);
ResourceLoader.writeFile(target, file); String libPath = "/opt/currentmonitor/lantern-pigpio.so";
System.load(target); ResourceLoader.writeFile(libPath, file);
} catch (IOException _e) { System.load(libPath);
} catch (Exception _e) {
LOG.error("Failed to load lantern-pigpio.so from resource", _e); LOG.error("Failed to load lantern-pigpio.so from resource", _e);
} }
} }

View File

@ -11,13 +11,16 @@ import com.lanternsoftware.datamodel.rules.Criteria;
import com.lanternsoftware.datamodel.rules.Event; import com.lanternsoftware.datamodel.rules.Event;
import com.lanternsoftware.datamodel.rules.EventId; import com.lanternsoftware.datamodel.rules.EventId;
import com.lanternsoftware.datamodel.rules.EventType; import com.lanternsoftware.datamodel.rules.EventType;
import com.lanternsoftware.datamodel.rules.FcmDevice;
import com.lanternsoftware.datamodel.rules.Rule; import com.lanternsoftware.datamodel.rules.Rule;
import com.lanternsoftware.rules.actions.ActionImpl; import com.lanternsoftware.rules.actions.ActionImpl;
import com.lanternsoftware.util.CollectionUtils; import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateUtils; import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.external.LanternFiles; import com.lanternsoftware.util.cloudservices.google.FirebaseHelper;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer; import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.mongo.MongoConfig; import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.external.LanternFiles;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -36,6 +39,7 @@ import java.util.concurrent.Executors;
public class RulesEngine { public class RulesEngine {
protected static final Logger LOG = LoggerFactory.getLogger(RulesEngine.class); protected static final Logger LOG = LoggerFactory.getLogger(RulesEngine.class);
protected static final FirebaseHelper firebaseHelper = new FirebaseHelper(LanternFiles.CONFIG_PATH + "google_account_key.json");
private static RulesEngine INSTANCE; private static RulesEngine INSTANCE;
private final ExecutorService executor = Executors.newCachedThreadPool(); private final ExecutorService executor = Executors.newCachedThreadPool();
@ -69,6 +73,15 @@ public class RulesEngine {
return dao; return dao;
} }
public void sendFcmMessage(int _accountId, Object _payload) {
List<FcmDevice> devices = RulesEngine.instance().dao().getFcmDevicesForAccount(_accountId);
if (devices.isEmpty())
return;
for (FcmDevice device : devices) {
firebaseHelper.sendMessage(device.getToken(), new DaoEntity("payload", DaoSerializer.toBase64ZipBson(_payload)).and("payloadClass", _payload.getClass().getCanonicalName()));
}
}
public void fireEvent(Event _event) { public void fireEvent(Event _event) {
if (_event.getType() != EventType.TIME) if (_event.getType() != EventType.TIME)
dao.putEvent(_event); dao.putEvent(_event);
@ -108,6 +121,8 @@ public class RulesEngine {
return; return;
Collection<Date> dates = CollectionUtils.aggregate(rules, _r->CollectionUtils.transform(_r.getAllCriteria(), _c->_c.getNextTriggerDate(tz))); Collection<Date> dates = CollectionUtils.aggregate(rules, _r->CollectionUtils.transform(_r.getAllCriteria(), _c->_c.getNextTriggerDate(tz)));
Date nextDate = CollectionUtils.getSmallest(dates); Date nextDate = CollectionUtils.getSmallest(dates);
if (nextDate == null)
return;
LOG.info("Scheduling next time event for account {} at {}", _accountId, DateUtils.format("MM/dd/yyyy HH:mm:ss", nextDate)); LOG.info("Scheduling next time event for account {} at {}", _accountId, DateUtils.format("MM/dd/yyyy HH:mm:ss", nextDate));
nextTask = new EventTimeTask(_accountId, nextDate); nextTask = new EventTimeTask(_accountId, nextDate);
timer.schedule(nextTask, nextDate); timer.schedule(nextTask, nextDate);

View File

@ -1,28 +1,11 @@
package com.lanternsoftware.rules.actions; package com.lanternsoftware.rules.actions;
import com.lanternsoftware.datamodel.rules.Alert; import com.lanternsoftware.datamodel.rules.Alert;
import com.lanternsoftware.datamodel.rules.FcmDevice;
import com.lanternsoftware.datamodel.rules.Rule; import com.lanternsoftware.datamodel.rules.Rule;
import com.lanternsoftware.rules.RulesEngine; import com.lanternsoftware.rules.RulesEngine;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.cloudservices.google.FirebaseHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public abstract class AbstractAlertAction implements ActionImpl { public abstract class AbstractAlertAction implements ActionImpl {
protected static final Logger logger = LoggerFactory.getLogger(AbstractAlertAction.class);
protected static final FirebaseHelper firebaseHelper = new FirebaseHelper(LanternFiles.CONFIG_PATH + "google_account_key.json");
protected void sendAlert(Rule _rule, Alert _alert) { protected void sendAlert(Rule _rule, Alert _alert) {
List<FcmDevice> devices = RulesEngine.instance().dao().getFcmDevicesForAccount(_rule.getAccountId()); RulesEngine.instance().sendFcmMessage(_rule.getAccountId(), _alert);
if (devices.isEmpty())
return;
for (FcmDevice device : devices) {
firebaseHelper.sendMessage(device.getToken(), new DaoEntity("payload", DaoSerializer.toBase64ZipBson(_alert)).and("payloadClass", Alert.class.getCanonicalName()));
}
} }
} }

View File

@ -2,7 +2,6 @@ package com.lanternsoftware.zwave.context;
import com.lanternsoftware.datamodel.rules.Event; import com.lanternsoftware.datamodel.rules.Event;
import com.lanternsoftware.datamodel.rules.EventType; import com.lanternsoftware.datamodel.rules.EventType;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.datamodel.zwave.Switch; import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule; import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.SwitchTransition; import com.lanternsoftware.datamodel.zwave.SwitchTransition;
@ -10,13 +9,14 @@ import com.lanternsoftware.datamodel.zwave.ThermostatMode;
import com.lanternsoftware.datamodel.zwave.ZWaveConfig; import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
import com.lanternsoftware.util.CollectionUtils; import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateUtils; import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.NullUtils; import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader; import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils; import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import com.lanternsoftware.util.cryptography.AESTool; import com.lanternsoftware.util.cryptography.AESTool;
import com.lanternsoftware.util.dao.DaoSerializer; import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.dao.mongo.MongoConfig; import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.external.LanternFiles;
import com.lanternsoftware.util.http.HttpPool; import com.lanternsoftware.util.http.HttpPool;
import com.lanternsoftware.zwave.controller.Controller; import com.lanternsoftware.zwave.controller.Controller;
import com.lanternsoftware.zwave.dao.MongoZWaveDao; import com.lanternsoftware.zwave.dao.MongoZWaveDao;
@ -54,6 +54,8 @@ import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ZWaveApp { public class ZWaveApp {
public static final AESTool aes = AESTool.authTool(); public static final AESTool aes = AESTool.authTool();
@ -75,11 +77,13 @@ public class ZWaveApp {
private SwitchScheduleTask nextScheduleTask; private SwitchScheduleTask nextScheduleTask;
private final Map<Integer, Double> sensors = new HashMap<>(); private final Map<Integer, Double> sensors = new HashMap<>();
private final Object ZWAVE_MUTEX = new Object(); private final Object ZWAVE_MUTEX = new Object();
private ExecutorService executor = null;
public void start() { public void start() {
try { try {
pool = new HttpPool(100, 20, 5000, 5000, 5000); pool = new HttpPool(100, 20, 5000, 5000, 5000);
config = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.CONFIG_PATH + "config.json"), ZWaveConfig.class); config = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.CONFIG_PATH + "config.json"), ZWaveConfig.class);
executor = Executors.newFixedThreadPool(5);
if (config == null) { if (config == null) {
dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg")); dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.CONFIG_PATH + "mongo.cfg"));
config = dao.getConfig(1); config = dao.getConfig(1);
@ -202,6 +206,7 @@ public class ZWaveApp {
@Override @Override
public void onMessage(MultilevelSwitchReportRequest _message) { public void onMessage(MultilevelSwitchReportRequest _message) {
logger.info("Received MultilevelSwitchReportRequest");
onSwitchLevelChange(_message.getNodeId(), _message.getLevel()); onSwitchLevelChange(_message.getNodeId(), _message.getLevel());
} }
}); });
@ -214,6 +219,7 @@ public class ZWaveApp {
@Override @Override
public void onMessage(BinarySwitchReportRequest _message) { public void onMessage(BinarySwitchReportRequest _message) {
logger.info("Received BinarySwitchReportRequest");
onSwitchLevelChange(_message.getNodeId(), _message.getLevel()); onSwitchLevelChange(_message.getNodeId(), _message.getLevel());
} }
}); });
@ -226,10 +232,18 @@ public class ZWaveApp {
@Override @Override
public void onMessage(CRC16EncapRequest _message) { public void onMessage(CRC16EncapRequest _message) {
onSwitchLevelChange(_message.getNodeId(), _message.isOn()?0xFF:0); // logger.info("Received CRC16EncapRequest");
// onSwitchLevelChange(_message.getNodeId(), _message.isOn()?0xFF:0);
} }
}); });
// for (Switch sw : config.getSwitches()) {
// if (sw.getNodeId() < 255) {
// controller.send(new NodeNeighborUpdateRequest(sw.getNodeId()));
// ConcurrencyUtils.sleep(5000);
// }
// }
// controller.send(new MultilevelSwitchSetRequest((byte)2, 0xFF)); // controller.send(new MultilevelSwitchSetRequest((byte)2, 0xFF));
// controller.send(new MultilevelSensorGetRequest((byte)11)); // controller.send(new MultilevelSensorGetRequest((byte)11));
@ -244,27 +258,31 @@ public class ZWaveApp {
// controller.send(new ThermostatModeGetRequest((byte)11)); // controller.send(new ThermostatModeGetRequest((byte)11));
} }
private void onSwitchLevelChange(int _secondaryNodeId, int _primaryLevel) { private void onSwitchLevelChange(int _nodeId, int _level) {
synchronized (switches) { synchronized (switches) {
Switch sw = switches.get(_secondaryNodeId); Switch sw = switches.get(_nodeId);
if ((sw != null) && !sw.isPrimary()) { if (sw != null) {
int newLevel = sw.isMultilevel()?_primaryLevel:((_primaryLevel == 0)?0:99); logger.info("Received level change for node {} to level {} via z-wave", _nodeId, _level);
if (_level == -1)
_level = 255;
int newLevel = sw.isMultilevel()?NullUtils.bound(_level, 0, 99):((_level == 0)?0:99);
sw.setLevel(newLevel); sw.setLevel(newLevel);
fireSwitchLevelEvent(sw); fireSwitchLevelEvent(sw);
for (Switch peer : CollectionUtils.makeNotNull(peers.get(_secondaryNodeId))) { for (Switch peer : CollectionUtils.makeNotNull(peers.get(_nodeId))) {
if (peer.isPrimary()) { logger.info("Mirror Event from {} node {} to {} node {} level {}", sw.isPrimary() ? "primary" : "secondary", _nodeId, peer.isPrimary() ? "primary" : "secondary", peer.getNodeId(), newLevel);
logger.info("Mirror Event from node {} to node {}", _secondaryNodeId, peer.getNodeId()); if (peer.isMultilevel()) {
if (peer.isMultilevel()) { peer.setLevel(newLevel);
peer.setLevel(newLevel); controller.send(new MultilevelSwitchSetRequest((byte) peer.getNodeId(), newLevel));
controller.send(new MultilevelSwitchSetRequest((byte) peer.getNodeId(), newLevel)); } else {
} else { peer.setLevel(newLevel != 0 ? 0xff : 0);
peer.setLevel(newLevel > 0 ? 0xff : 0); controller.send(new BinarySwitchSetRequest((byte) peer.getNodeId(), newLevel != 0));
controller.send(new BinarySwitchSetRequest((byte) peer.getNodeId(), newLevel > 0));
}
} }
} }
persistConfig(); persistConfig();
} }
else {
logger.info("Received level change for unknown node {}", _nodeId);
}
} }
} }
@ -294,18 +312,20 @@ public class ZWaveApp {
public void fireSwitchLevelEvent(Switch _sw) { public void fireSwitchLevelEvent(Switch _sw) {
if (NullUtils.isEmpty(config.getRulesUrl()) || _sw.isSuppressEvents()) if (NullUtils.isEmpty(config.getRulesUrl()) || _sw.isSuppressEvents())
return; return;
Event event = new Event(); executor.submit(()->{
event.setEventDescription(_sw.getFullDisplay() + " set to " + _sw.getLevel()); Event event = new Event();
event.setType(EventType.SWITCH_LEVEL); event.setEventDescription(_sw.getFullDisplay() + " set to " + _sw.getLevel());
event.setTime(new Date()); event.setType(EventType.SWITCH_LEVEL);
event.setValue(_sw.getLevel()); event.setTime(new Date());
event.setSourceId(String.valueOf(_sw.getNodeId())); event.setValue(_sw.getLevel());
event.setAccountId(config.getAccountId()); event.setSourceId(String.valueOf(_sw.getNodeId()));
logger.info("Sending event to rules server - " + event.getEventDescription()); event.setAccountId(config.getAccountId());
HttpPost post = new HttpPost(NullUtils.terminateWith(config.getRulesUrl(), "/") + "event"); logger.info("Sending event to rules server - " + event.getEventDescription());
post.setHeader("auth_code", authCode); HttpPost post = new HttpPost(NullUtils.terminateWith(config.getRulesUrl(), "/") + "event");
post.setEntity(new ByteArrayEntity(DaoSerializer.toZipBson(event))); post.setHeader("auth_code", authCode);
pool.execute(post); post.setEntity(new ByteArrayEntity(DaoSerializer.toZipBson(event)));
pool.execute(post);
});
} }
public void setSwitchLevel(int _nodeId, int _level, boolean _updatePeers) { public void setSwitchLevel(int _nodeId, int _level, boolean _updatePeers) {
@ -401,11 +421,13 @@ public class ZWaveApp {
peers.remove(config.getUrl()); peers.remove(config.getUrl());
for (String peer : peers) { for (String peer : peers) {
for (Switch sw : modified) { for (Switch sw : modified) {
logger.info("Sending update for switch {} {} level {} to {}", sw.getNodeId(), sw.getFullDisplay(), sw.getLevel(), peer); executor.submit(()->{
HttpPost post = new HttpPost(peer + "/switch/" + sw.getNodeId()); logger.info("Sending update for switch {} {} level {} to {}", sw.getNodeId(), sw.getFullDisplay(), sw.getLevel(), peer);
post.setHeader("auth_code", authCode); HttpPost post = new HttpPost(peer + "/switch/" + sw.getNodeId());
post.setEntity(new ByteArrayEntity(DaoSerializer.toZipBson(sw))); post.setHeader("auth_code", authCode);
pool.execute(post); post.setEntity(new ByteArrayEntity(DaoSerializer.toZipBson(sw)));
pool.execute(post);
});
} }
} }
} }
@ -441,6 +463,10 @@ public class ZWaveApp {
pool.shutdown(); pool.shutdown();
pool = null; pool = null;
} }
if (executor != null) {
executor.shutdown();
executor = null;
}
if (dao != null) { if (dao != null) {
dao.shutdown(); dao.shutdown();
dao = null; dao = null;
@ -455,7 +481,7 @@ public class ZWaveApp {
for (Switch node : nodes) { for (Switch node : nodes) {
logger.info("Setting {}, Node {} to {}", node.getName(), node.getNodeId(), _level); logger.info("Setting {}, Node {} to {}", node.getName(), node.getNodeId(), _level);
byte nid = (byte) (node.getNodeId()%1000); byte nid = (byte) (node.getNodeId()%1000);
controller.send(node.isMultilevel() ? new MultilevelSwitchSetRequest(nid, _level) : new BinarySwitchSetRequest(nid, _level > 0)); controller.send(node.isMultilevel() ? new MultilevelSwitchSetRequest(nid, _level) : new BinarySwitchSetRequest(nid, _level != 0));
} }
} }

View File

@ -14,7 +14,7 @@ public class BinarySwitchReportRequest extends RequestMessage {
@Override @Override
public void fromPayload(byte[] _payload) { public void fromPayload(byte[] _payload) {
nodeId = _payload[5]; nodeId = _payload[5];
level = _payload[9]; level = 0xFF & _payload[9];
} }
public int getLevel() { public int getLevel() {

View File

@ -0,0 +1,15 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class NodeNeighborUpdateRequest extends RequestMessage {
public NodeNeighborUpdateRequest() {
super(ControllerMessageType.RequestNodeNeighborUpdate, CommandClass.NO_OPERATION, (byte) 0);
}
public NodeNeighborUpdateRequest(int _nodeId) {
super((byte) _nodeId, ControllerMessageType.RequestNodeNeighborUpdate, CommandClass.NO_OPERATION, (byte) 0);
}
}

View File

@ -21,6 +21,7 @@ com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest
com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest
com.lanternsoftware.zwave.message.impl.NodeInfoRequest com.lanternsoftware.zwave.message.impl.NodeInfoRequest
com.lanternsoftware.zwave.message.impl.NodeInfoResponse com.lanternsoftware.zwave.message.impl.NodeInfoResponse
com.lanternsoftware.zwave.message.impl.NodeNeighborUpdateRequest
com.lanternsoftware.zwave.message.impl.RemoveNodeFromNetworkStartRequest com.lanternsoftware.zwave.message.impl.RemoveNodeFromNetworkStartRequest
com.lanternsoftware.zwave.message.impl.RemoveNodeFromNetworkStopRequest com.lanternsoftware.zwave.message.impl.RemoveNodeFromNetworkStopRequest
com.lanternsoftware.zwave.message.impl.SendDataRequest com.lanternsoftware.zwave.message.impl.SendDataRequest