diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa2bdf3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.iml
+*.project
+*.classpath
+.idea/
+.gradle/
+*.settings/
+*target/
+build/
+release/
\ No newline at end of file
diff --git a/currentmonitor/lantern-currentmonitor/pom.xml b/currentmonitor/lantern-currentmonitor/pom.xml
new file mode 100644
index 0000000..4127a67
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/pom.xml
@@ -0,0 +1,105 @@
+
+ 4.0.0
+ com.lanternsoftware.currentmonitor
+ lantern-currentmonitor
+ jar
+ 1.0.0
+ lantern-currentmonitor
+
+
+ 1.8
+ 1.8
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.29
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+ com.pi4j
+ pi4j-gpio-extension
+ 1.2
+
+
+ com.github.hypfvieh
+ bluez-dbus
+ 0.1.3
+
+
+ com.lanternsoftware.currentmonitor
+ lantern-datamodel-currentmonitor
+ 1.0.0
+
+
+ com.lanternsoftware.util
+ lantern-util-http
+ 1.0.0
+
+
+
+
+
+ src/main/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.2
+
+
+
+ testCompile
+
+ compile
+
+
+
+ true
+ true
+ UTF-8
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.1
+
+ false
+
+
+
+ package
+
+ shade
+
+
+ lantern-currentmonitor
+
+
+
+
+ com.lanternsoftware.currentmonitor.MonitorApp
+ Lantern Power Monitor
+ ${project.version}
+ Lantern Software, Inc.
+
+
+
+
+
+
+
+
+
+
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java
new file mode 100644
index 0000000..c95a8dc
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java
@@ -0,0 +1,39 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.currentmonitor.bluetooth.BleApplication;
+import com.lanternsoftware.currentmonitor.bluetooth.BleCharacteristic;
+import com.lanternsoftware.currentmonitor.bluetooth.BleCharacteristicListener;
+import com.lanternsoftware.currentmonitor.bluetooth.BleHelper;
+import com.lanternsoftware.currentmonitor.bluetooth.BleService;
+import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
+import com.lanternsoftware.util.CollectionUtils;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class BluetoothConfig implements Runnable {
+ private final AtomicBoolean running = new AtomicBoolean(true);
+ private final BleApplication app;
+
+ public BluetoothConfig(String _hubName, BleCharacteristicListener _listener) {
+ BleHelper.getAdapter().setPowered(true);
+ BleHelper.requestBusName("com.lanternsoftware");
+ BleHelper.setBasePath("/com/lanternsoftware");
+ HubConfigService service = new HubConfigService();
+ List chars = CollectionUtils.transform(service.getCharacteristics(), _c->new BleCharacteristic("HubConfig", _c.getUUID(), _c.name(), _c.getFlags()));
+ chars.forEach(_c->_c.setListener(_listener));
+ app = new BleApplication("Lantern", _hubName, new BleService("HubConfig", service.getServiceUUID(), chars));
+ }
+
+ @Override
+ public void run() {
+ app.start();
+ }
+
+ public void stop() {
+ synchronized (running) {
+ running.set(false);
+ }
+ app.stop();
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java
new file mode 100644
index 0000000..8b171e4
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BreakerSamples.java
@@ -0,0 +1,49 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.pi4j.io.gpio.GpioPinAnalogInput;
+
+import java.util.List;
+
+public class BreakerSamples {
+ private final Breaker breaker;
+ private final GpioPinAnalogInput voltagePin;
+ private final GpioPinAnalogInput currentPin;
+ private final List samples;
+ private int sampleCnt;
+
+ public BreakerSamples(Breaker _breaker, GpioPinAnalogInput _voltagePin, GpioPinAnalogInput _currentPin, List _samples) {
+ breaker = _breaker;
+ voltagePin = _voltagePin;
+ currentPin = _currentPin;
+ samples = _samples;
+ }
+
+ public Breaker getBreaker() {
+ return breaker;
+ }
+
+ public GpioPinAnalogInput getVoltagePin() {
+ return voltagePin;
+ }
+
+ public GpioPinAnalogInput getCurrentPin() {
+ return currentPin;
+ }
+
+ public List getSamples() {
+ return samples;
+ }
+
+ public PowerSample getSample(int _sample) {
+ return samples.get(_sample);
+ }
+
+ public int getSampleCnt() {
+ return sampleCnt;
+ }
+
+ public void setSampleCnt(int _sampleCnt) {
+ sampleCnt = _sampleCnt;
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java
new file mode 100644
index 0000000..c820f41
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentListener.java
@@ -0,0 +1,7 @@
+package com.lanternsoftware.currentmonitor;
+
+import java.util.Date;
+
+public interface CurrentListener {
+ void onCurrentEvent(int _chip, int _pin, double _currentAmps, Date _start);
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java
new file mode 100644
index 0000000..6f180ed
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/CurrentMonitor.java
@@ -0,0 +1,241 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
+import com.pi4j.gpio.extension.base.AdcGpioProvider;
+import com.pi4j.gpio.extension.mcp.MCP3008GpioProvider;
+import com.pi4j.gpio.extension.mcp.MCP3008Pin;
+import com.pi4j.io.gpio.GpioController;
+import com.pi4j.io.gpio.GpioFactory;
+import com.pi4j.io.gpio.GpioPinAnalogInput;
+import com.pi4j.io.spi.SpiChannel;
+import com.pi4j.io.spi.SpiDevice;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class CurrentMonitor {
+ private static final Logger LOG = LoggerFactory.getLogger(CurrentMonitor.class);
+ private static final int BATCH_CNT = 4;
+ private GpioController gpio;
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+ private final Map chips = new HashMap<>();
+ private final Map pins = new HashMap<>();
+ private Sampler sampler;
+ private PowerListener listener;
+ private boolean debug = false;
+
+ public void start() {
+ try {
+ gpio = GpioFactory.getInstance();
+ LOG.info("Current Monitor Started");
+ }
+ catch (Throwable t) {
+ LOG.info("Failed to get gpio factory", t);
+ }
+ }
+
+ public void stop() {
+ stopMonitoring();
+ ConcurrencyUtils.sleep(1000);
+ executor.shutdownNow();
+ chips.clear();
+ pins.clear();
+ gpio.shutdown();
+ LOG.info("Current Monitor Stopped");
+ }
+
+ public void setDebug(boolean _debug) {
+ debug = _debug;
+ }
+
+ public void monitorPower(BreakerHub _hub, List _breakers, int _intervalMs, PowerListener _listener) {
+ stopMonitoring();
+ listener = _listener;
+ sampler = new Sampler(_hub, _breakers, _intervalMs, 2);
+ LOG.info("Starting to monitor ports {}", CollectionUtils.transformToCommaSeparated(_breakers, _b->String.valueOf(_b.getPort())));
+ executor.submit(sampler);
+ }
+
+ private GpioPinAnalogInput getPin(int _chip, int _pin) {
+ GpioPinAnalogInput pin;
+ synchronized (pins) {
+ AdcGpioProvider chip = chips.get(_chip);
+ if (chip == null) {
+ SpiChannel channel = SpiChannel.getByNumber(_chip);
+ if (channel == null)
+ return null;
+ try {
+ chip = new MCP3008GpioProvider(channel, 1250000, SpiDevice.DEFAULT_SPI_MODE, false);
+ chips.put(_chip, chip);
+ } catch (IOException _e) {
+ LOG.error("Failed to connect to chip {}", _chip, _e);
+ return null;
+ }
+ }
+ int pinKey = pinKey(_chip, _pin);
+ pin = pins.get(pinKey);
+ if (pin == null) {
+ pin = gpio.provisionAnalogInputPin(chip, MCP3008Pin.ALL[_pin], String.valueOf(pinKey));
+ pins.put(pinKey, pin);
+ }
+ }
+ return pin;
+ }
+
+ private Integer pinKey(int _chip, int _pin) {
+ return (_chip*8)+_pin;
+ }
+
+ public void submit(Runnable _runnable) {
+ executor.submit(_runnable);
+ }
+
+ public void stopMonitoring() {
+ if (sampler != null) {
+ sampler.stop();
+ sampler = null;
+ }
+ }
+
+ private class Sampler implements Runnable {
+ private boolean running = true;
+ private final BreakerHub hub;
+ private final List> breakers;
+ private final int intervalNs;
+ private final int concurrentBreakerCnt;
+
+ public Sampler(BreakerHub _hub, List _breakers, int _intervalMs, int _concurrentBreakerCnt) {
+ hub = _hub;
+ GpioPinAnalogInput voltagePin = getPin(0, 0);
+ breakers = CollectionUtils.transform(_breakers, _b->{
+ LOG.info("Getting Chip {}, Pin {} for port {}", _b.getChip(), _b.getPin(), _b.getPort());
+ GpioPinAnalogInput currentPin = getPin(_b.getChip(), _b.getPin());
+ List batches = new ArrayList<>(BATCH_CNT);
+ for (int i=0; i samples = new ArrayList<>(30000/_breakers.size());
+ for (int j=0; j<30000/_breakers.size(); j++) {
+ samples.add(new PowerSample());
+ }
+ batches.add(new BreakerSamples(_b, voltagePin, currentPin, samples));
+ }
+ return batches;
+ });
+ intervalNs = _intervalMs*1000000;
+ concurrentBreakerCnt = Math.min(_breakers.size(), _concurrentBreakerCnt);
+ }
+
+ @Override
+ public void run() {
+ long start = System.nanoTime();
+ long interval = 0;
+ int cycle;
+ BreakerSamples[] cycleBreakers = new BreakerSamples[concurrentBreakerCnt];
+ try {
+ while (true) {
+ synchronized (this) {
+ if (!running)
+ break;
+ }
+ final Date readTime = new Date();
+ final long intervalStart = (interval * intervalNs) + start;
+ long intervalEnd = intervalStart + intervalNs;
+ cycle = 0;
+ final int batch = (int) (interval % BATCH_CNT);
+ int curBreaker;
+ for (curBreaker = 0; curBreaker < breakers.size(); curBreaker++) {
+ breakers.get(curBreaker).get(batch).setSampleCnt(0);
+ }
+ while (System.nanoTime() < intervalEnd) {
+ for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) {
+ cycleBreakers[curBreaker] = breakers.get(((cycle * concurrentBreakerCnt) + curBreaker) % breakers.size()).get(batch);
+ }
+ cycle++;
+ long cycleEnd = intervalStart + (cycle * (intervalNs / hub.getFrequency()));
+ while (System.nanoTime() < cycleEnd) {
+ for (curBreaker = 0; curBreaker < concurrentBreakerCnt; curBreaker++) {
+ PowerSample sample = cycleBreakers[curBreaker].getSample(cycleBreakers[curBreaker].getSampleCnt());
+ sample.voltage = cycleBreakers[curBreaker].getVoltagePin().getValue();
+ sample.current = cycleBreakers[curBreaker].getCurrentPin().getValue();
+ cycleBreakers[curBreaker].setSampleCnt(cycleBreakers[curBreaker].getSampleCnt()+1);
+ }
+ }
+ }
+ interval++;
+ executor.submit(() -> {
+ for (List breaker : breakers) {
+ double vOffset = 0.0;
+ double iOffset = 0.0;
+ BreakerSamples samples = breaker.get(batch);
+ List validSamples = samples.getSamples().subList(0, samples.getSampleCnt());
+ for (PowerSample sample : validSamples) {
+ vOffset += sample.voltage;
+ iOffset += sample.current;
+ }
+ vOffset /= samples.getSampleCnt();
+ iOffset /= samples.getSampleCnt();
+ int lowSamples = 0;
+ double pSum = 0.0;
+ double vRms = 0.0;
+ double lowPassFilter = samples.getBreaker().getLowPassFilter();
+ for (PowerSample sample : validSamples) {
+ sample.current -= iOffset;
+ if (Math.abs(sample.current) < lowPassFilter)
+ lowSamples++;
+ sample.voltage -= vOffset;
+ pSum += sample.current * sample.voltage;
+ vRms += sample.voltage * sample.voltage;
+ }
+ vRms /= validSamples.size();
+ vRms = hub.getVoltageCalibrationFactor() * Math.sqrt(vRms);
+ int lowSampleRatio = (lowSamples * 100) / samples.getSampleCnt();
+ double realPower = Math.abs((hub.getVoltageCalibrationFactor() * samples.getBreaker().getFinalCalibrationFactor() * pSum) / samples.getSampleCnt());
+ if ((lowSampleRatio > 75) && realPower < 13.0)
+ realPower = 0.0;
+ if (samples.getBreaker().getPolarity() == BreakerPolarity.SOLAR)
+ realPower = -realPower;
+ if (debug) {
+ synchronized (CurrentMonitor.this) {
+ LOG.info("===========================Start Port {}", samples.getBreaker().getPort());
+ LOG.info("Samples: {}", samples.getSampleCnt());
+ LOG.info("vMin: {}, vMax: {}, vOffset: {}", String.format("%.3f", CollectionUtils.getSmallest(validSamples, Comparator.comparing(_v -> _v.voltage)).voltage), String.format("%.3f", CollectionUtils.getLargest(validSamples, Comparator.comparing(_v -> _v.voltage)).voltage), String.format("%.3f", vOffset));
+ LOG.info("iMin: {}, iMax: {}, iOffset: {}", String.format("%.3f", CollectionUtils.getSmallest(validSamples, Comparator.comparing(_v -> _v.current)).current), String.format("%.3f", CollectionUtils.getLargest(validSamples, Comparator.comparing(_v -> _v.current)).current), String.format("%.3f", iOffset));
+ double iRms = samples.getBreaker().getFinalCalibrationFactor() * Math.sqrt(CollectionUtils.mean(CollectionUtils.transform(validSamples, _p -> _p.current * _p.current)));
+ LOG.info("vRms: {}", String.format("%.3f", vRms));
+ LOG.info("iRms: {}", String.format("%.3f", iRms));
+ double apparentPower = vRms * iRms;
+ LOG.info("Apparent Power: {} watts", String.format("%.3f", apparentPower));
+ LOG.info("Real Power: {} watts", String.format("%.3f", realPower));
+ double powerFactor = realPower / apparentPower;
+ LOG.info("Power Factor: {}", String.format("%.3f", powerFactor));
+ LOG.info("===========================End Port {}", samples.getBreaker().getPort());
+ }
+ }
+ listener.onPowerEvent(new BreakerPower(samples.getBreaker().getPanel(), samples.getBreaker().getSpace(), readTime, realPower, vRms));
+ }
+ });
+ }
+ }
+ catch (Throwable t) {
+ LOG.error("Exception while monitoring power", t);
+ }
+ }
+
+ synchronized void stop() {
+ running = false;
+ }
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java
new file mode 100644
index 0000000..c586080
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java
@@ -0,0 +1,409 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.currentmonitor.bluetooth.BleCharacteristicListener;
+import com.lanternsoftware.currentmonitor.led.LEDFlasher;
+import com.lanternsoftware.currentmonitor.util.NetworkMonitor;
+import com.lanternsoftware.currentmonitor.wifi.WifiConfig;
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute;
+import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
+import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
+import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.DateUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.http.HttpPool;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.Console;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+public class MonitorApp {
+ private static final Logger LOG = LoggerFactory.getLogger(MonitorApp.class);
+ private static final String WORKING_DIR = "/opt/currentmonitor/";
+ private static String authCode;
+ private static MonitorConfig config;
+ private static BreakerConfig breakerConfig;
+ private static String host;
+ private static Date lastUpdateCheck = new Date();
+ private static HttpPool pool;
+ private static LEDFlasher flasher = null;
+ private static final AtomicBoolean running = new AtomicBoolean(true);
+ private static final CurrentMonitor monitor = new CurrentMonitor();
+ private static final List readings = new ArrayList<>();
+ private static final String version = getVersionNumber();
+ private static final PowerListener logger = _p -> {
+ if (!config.isDebug()) {
+ _p.setHubVersion(version);
+ if (breakerConfig != null)
+ _p.setAccountId(breakerConfig.getAccountId());
+ synchronized (readings) {
+ readings.add(_p);
+ }
+ } else
+ LOG.info("Panel{} - Space{} Power: {}W", _p.getPanel(), Breaker.toSpaceDisplay(_p.getSpace()), String.format("%.3f", _p.getPower()));
+ };
+
+ public static void main(String[] args) {
+ config = DaoSerializer.parse(ResourceLoader.loadFileAsString(WORKING_DIR + "config.json"), MonitorConfig.class);
+ if (config == null) {
+ LOG.error("Failed to load config file from {}", WORKING_DIR + "config.json");
+ return;
+ }
+ pool = new HttpPool(10, 10, config.getSocketTimeout(), config.getConnectTimeout(), config.getSocketTimeout());
+ host = NullUtils.terminateWith(config.getHost(), "/");
+ monitor.setDebug(config.isDebug());
+ monitor.start();
+ LEDFlasher.setLEDOn(false);
+ final BluetoothConfig bluetoothConfig = new BluetoothConfig("Lantern Hub", new BleCharacteristicListener() {
+ @Override
+ public void write(String _name, byte[] _value) {
+ HubConfigCharacteristic ch = NullUtils.toEnum(HubConfigCharacteristic.class, _name);
+ LOG.info("Char Received, Name: {} Value: {}", _name, _value);
+ monitor.submit(()->{
+ switch (ch) {
+ case HubIndex:
+ if ((_value.length > 0)) {
+ config.setHub(_value[0]);
+ ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
+ }
+ break;
+ case AuthCode:
+ String value = NullUtils.toString(_value);
+ if (NullUtils.isNotEmpty(value)) {
+ authCode = value;
+ config.setAuthCode(value);
+ ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
+ }
+ break;
+ case WifiCredentials:
+ String ssid = HubConfigService.decryptWifiSSID(_value);
+ String pwd = HubConfigService.decryptWifiPassword(_value);
+ if (NullUtils.isNotEmpty(ssid) && NullUtils.isNotEmpty(pwd))
+ WifiConfig.setCredentials(ssid, pwd);
+ break;
+ case Flash:
+ if ((CollectionUtils.length(_value) == 0) || (_value[0] == 0)) {
+ if (flasher != null) {
+ flasher.stop();
+ flasher = null;
+ }
+ else
+ LEDFlasher.setLEDOn(false);
+ }
+ else {
+ if (flasher == null) {
+ flasher = new LEDFlasher();
+ monitor.submit(flasher);
+ }
+ }
+ break;
+ case Restart:
+ LOG.info("Restarting Current Monitor...");
+ try {
+ Runtime.getRuntime().exec("service currentmonitor restart");
+ } catch (IOException _e) {
+ LOG.error("Exception occurred while trying to restart", _e);
+ }
+ break;
+ case Reboot:
+ LOG.info("Rebooting Pi...");
+ try {
+ Runtime.getRuntime().exec("reboot now");
+ } catch (IOException _e) {
+ LOG.error("Exception occurred while trying to reboot", _e);
+ }
+ break;
+ }
+ });
+ }
+
+ @Override
+ public byte[] read(String _name) {
+ HubConfigCharacteristic ch = NullUtils.toEnum(HubConfigCharacteristic.class, _name);
+ if (HubConfigCharacteristic.HubIndex == ch)
+ return new byte[]{(byte)(config == null?0:config.getHub())};
+ if (HubConfigCharacteristic.AccountId == ch)
+ return ByteBuffer.allocate(4).putInt(breakerConfig == null?0:breakerConfig.getAccountId()).array();
+ if (HubConfigCharacteristic.NetworkState == ch)
+ return new byte[]{NetworkMonitor.getNetworkStatus().toMask()};
+ return null;
+ }
+ });
+ monitor.submit(bluetoothConfig);
+ if (NullUtils.isNotEmpty(config.getAuthCode())) {
+ authCode = config.getAuthCode();
+ //TODO: check auth code validity
+ }
+ else {
+ HttpGet auth = new HttpGet(host + "auth");
+ HttpPool.addBasicAuthHeader(auth, config.getUsername(), config.getPassword());
+ authCode = DaoSerializer.getString(DaoSerializer.parse(pool.executeToString(auth)), "auth_code");
+ }
+ while (true) {
+ HttpGet get = new HttpGet(host + "config");
+ get.addHeader("auth_code", authCode);
+ breakerConfig = DaoSerializer.parse(pool.executeToString(get), BreakerConfig.class);
+ if (breakerConfig != null)
+ break;
+ LOG.error("Failed to load breaker config. Retrying in 5 seconds...");
+ ConcurrencyUtils.sleep(5000);
+ }
+ LOG.info("Breaker Config loaded");
+ LOG.debug(DaoSerializer.toJson(breakerConfig));
+ BreakerHub hub = breakerConfig.getHub(config.getHub());
+ if (hub != null) {
+ List breakers = breakerConfig.getBreakersForHub(config.getHub());
+ LOG.info("Monitoring {} breakers for hub {}", CollectionUtils.size(breakers), hub.getHub());
+ monitor.monitorPower(hub, breakers, 1000, logger);
+ }
+ monitor.submit(new PowerPoster());
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ synchronized (running) {
+ running.set(false);
+ }
+ bluetoothConfig.stop();
+ monitor.stop();
+ pool.shutdown();
+ }, "Monitor Shutdown"));
+ Console c = System.console();
+ BufferedReader reader = (c == null)?new BufferedReader(new InputStreamReader(System.in)):null;
+ while (running.get()) {
+ try {
+ String command = c != null ? c.readLine() : reader.readLine();
+ if (NullUtils.isEqual("exit", command))
+ break;
+ }
+ catch (Exception _e) {
+ LOG.error("Exception while reading from console input", _e);
+ break;
+ }
+ }
+ }
+
+ private static final class PowerPoster implements Runnable {
+ private final long firstPost;
+ private long lastPost;
+ private int lastMinute;
+ private final Map breakers = new HashMap<>();
+
+ public PowerPoster() {
+ firstPost = (new Date().getTime()/1000)*1000;
+ lastPost = new Date().getTime();
+ lastMinute = (int)(new Date().getTime()/60000);
+ }
+
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ synchronized (running) {
+ if (!running.get())
+ break;
+ }
+ DaoEntity post = null;
+ DaoEntity minutePost = null;
+ int curMinute = (int) (new Date().getTime() / 60000);
+ synchronized (readings) {
+ if (!readings.isEmpty()) {
+ post = new DaoEntity("readings", DaoSerializer.toDaoEntities(readings));
+ if (curMinute != lastMinute) {
+ HubPowerMinute minute = new HubPowerMinute();
+ minute.setAccountId(breakerConfig.getAccountId());
+ minute.setHub(config.getHub());
+ minute.setMinute(lastMinute);
+ minute.setBreakers(CollectionUtils.transform(breakers.entrySet(), _e -> {
+ BreakerPowerMinute breaker = new BreakerPowerMinute();
+ breaker.setPanel(Breaker.toPanel(_e.getKey()));
+ breaker.setSpace(Breaker.toSpace(_e.getKey()));
+ breaker.setReadings(CollectionUtils.asArrayList(_e.getValue()));
+ return breaker;
+ }));
+ breakers.clear();
+ minutePost = DaoSerializer.toDaoEntity(minute);
+ lastMinute = curMinute;
+ }
+ for (BreakerPower power : readings) {
+ Float[] breakerReadings = breakers.computeIfAbsent(Breaker.toId(power.getPanel(), power.getSpace()), _i -> new Float[60]);
+ breakerReadings[(int) ((power.getReadTime().getTime() / 1000)%60)] = (float) power.getPower();
+ }
+ readings.clear();
+ }
+ }
+ if (minutePost != null) {
+ byte[] payload = DaoSerializer.toZipBson(minutePost);
+ if (!post(payload, "power/hub")) {
+ LOG.info("Failed Posting HubPowerMinute, writing cache");
+ ResourceLoader.writeFile(WORKING_DIR + "cache/" + UUID.randomUUID().toString() + ".min", payload);
+ }
+ }
+ if (post != null) {
+ byte[] payload = DaoSerializer.toZipBson(post);
+ if (post(payload, "power/batch")) {
+ File[] files = new File(WORKING_DIR + "cache").listFiles();
+ if (files != null) {
+ for (File file : files) {
+ payload = ResourceLoader.loadFile(file.getAbsolutePath());
+ if (post(payload, file.getName().endsWith("dat") ? "power/batch" : "power/hub"))
+ file.delete();
+ else
+ break;
+ }
+ }
+ }
+ }
+ if (DateUtils.diffInSeconds(new Date(), lastUpdateCheck) >= config.getUpdateInterval()) {
+ lastUpdateCheck = new Date();
+ monitor.submit(new UpdateChecker());
+ monitor.submit(new CommandChecker());
+ }
+ long now = new Date().getTime();
+ long duration = (now - firstPost)%1000;
+ if (now - lastPost < 1000) {
+ ConcurrencyUtils.sleep(1000 - duration);
+ }
+ lastPost = now;
+ }
+ }
+ catch (Throwable t) {
+ LOG.error("Exception in PowerPoster", t);
+ }
+ }
+ }
+
+ private static void uploadLog() {
+ LOG.info("Commanded to upload log file, preparing...");
+ String log = ResourceLoader.loadFileAsString(WORKING_DIR + "log/log.txt");
+ if (NullUtils.isNotEmpty(log)) {
+ DaoEntity payload = new DaoEntity("command", "log").and("payload", log);
+ post(DaoSerializer.toZipBson(payload), "command");
+ }
+ }
+
+ private static boolean post(byte[] _payload, String _path) {
+ HttpPost post = new HttpPost(host + _path);
+ post.addHeader("auth_code", authCode);
+ post.setEntity(new ByteArrayEntity(_payload, ContentType.APPLICATION_OCTET_STREAM));
+ CloseableHttpResponse resp = pool.execute(post);
+ try {
+ return ((resp != null) && (resp.getStatusLine() != null) && (resp.getStatusLine().getStatusCode() == 200));
+ } finally {
+ IOUtils.closeQuietly(resp);
+ }
+ }
+
+
+ private static final class UpdateChecker implements Runnable {
+ @Override
+ public void run() {
+ DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version")));
+ String newVersion = DaoSerializer.getString(meta, "version");
+ if (NullUtils.isNotEqual(newVersion, version)) {
+ LOG.info("New version found, {}, downloading...", newVersion);
+ byte[] jar = pool.executeToByteArray(new HttpGet(host + "update"));
+ if (CollectionUtils.length(jar) == DaoSerializer.getInteger(meta, "size") && NullUtils.isEqual(DigestUtils.md5Hex(jar), DaoSerializer.getString(meta, "checksum"))) {
+ LOG.info("Update downloaded, writing jar and restarting...");
+ ResourceLoader.writeFile(WORKING_DIR + "lantern-currentmonitor.jar", jar);
+ try {
+ Runtime.getRuntime().exec("service currentmonitor restart");
+ } catch (IOException _e) {
+ LOG.error("Exception occurred while trying to restart", _e);
+ }
+ }
+ }
+ }
+ }
+
+ private static final class CommandChecker implements Runnable {
+ @Override
+ public void run() {
+ HttpGet get = new HttpGet(host + "command");
+ get.addHeader("auth_code", authCode);
+ DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(get));
+ for (String command : DaoSerializer.getList(meta, "commands", String.class)) {
+ if (NullUtils.isEqual(command, "log")) {
+ uploadLog();
+ }
+ else if (NullUtils.makeNotNull(command).startsWith("timeout")) {
+ LOG.info("Updating timeouts...");
+ String[] timeouts = NullUtils.cleanSplit(command, "-");
+ if (CollectionUtils.size(timeouts) != 3)
+ continue;
+ config.setConnectTimeout(DaoSerializer.toInteger(timeouts[1]));
+ config.setSocketTimeout(DaoSerializer.toInteger(timeouts[2]));
+ HttpPool old = pool;
+ pool = new HttpPool(10, 10, config.getSocketTimeout(), config.getConnectTimeout(), config.getSocketTimeout());
+ old.shutdown();
+ ResourceLoader.writeFile(WORKING_DIR+"config.json", DaoSerializer.toJson(config));
+ }
+ else if (NullUtils.isEqual(command, "extend_filesystem")) {
+ LOG.info("Extending filesystem and rebooting");
+ try {
+ Runtime.getRuntime().exec("raspi-config --expand-rootfs");
+ ConcurrencyUtils.sleep(3000);
+ Runtime.getRuntime().exec("reboot");
+ } catch (IOException _e) {
+ LOG.error("Exception occurred while trying to extend filesystem", _e);
+ }
+
+ }
+ else if (NullUtils.isEqual(command, "restart")) {
+ LOG.info("Restarting...");
+ try {
+ Runtime.getRuntime().exec("service currentmonitor restart");
+ } catch (IOException _e) {
+ LOG.error("Exception occurred while trying to restart", _e);
+ }
+ }
+ }
+ }
+ }
+
+ private static String getVersionNumber() {
+ InputStream is = null;
+ try {
+ is = MonitorApp.class.getResourceAsStream("/META-INF/MANIFEST.MF");
+ Manifest manifest = new Manifest(is);
+ Attributes attr = manifest.getMainAttributes();
+ String version = attr.getValue("Specification-Version");
+ LOG.info("Current Version: {}", version);
+ return version;
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to get current version number", _e);
+ return "";
+ }
+ finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java
new file mode 100644
index 0000000..0d0d405
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java
@@ -0,0 +1,97 @@
+package com.lanternsoftware.currentmonitor;
+
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+@DBSerializable
+public class MonitorConfig {
+ private String host;
+ private String authCode;
+ private String username;
+ private String password;
+ private int hub;
+ private boolean debug;
+ private int connectTimeout;
+ private int socketTimeout;
+ private int updateInterval;
+
+ public MonitorConfig() {
+ }
+
+ public MonitorConfig(int _hub, String _host) {
+ hub = _hub;
+ host = _host;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public void setHost(String _host) {
+ host = _host;
+ }
+
+ public String getAuthCode() {
+ return authCode;
+ }
+
+ public void setAuthCode(String _authCode) {
+ authCode = _authCode;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String _username) {
+ username = _username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String _password) {
+ password = _password;
+ }
+
+ public int getHub() {
+ return hub;
+ }
+
+ public void setHub(int _hub) {
+ hub = _hub;
+ }
+
+ public boolean isDebug() {
+ return debug;
+ }
+
+ public void setDebug(boolean _debug) {
+ debug = _debug;
+ }
+
+ public int getConnectTimeout() {
+ return connectTimeout == 0?3000:connectTimeout;
+ }
+
+ public void setConnectTimeout(int _connectTimeout) {
+ connectTimeout = _connectTimeout;
+ }
+
+ public int getSocketTimeout() {
+ return socketTimeout == 0?5000:socketTimeout;
+ }
+
+ public void setSocketTimeout(int _socketTimeout) {
+ socketTimeout = _socketTimeout;
+ }
+
+ public int getUpdateInterval() {
+ return updateInterval == 0?300:updateInterval;
+ }
+
+ public void setUpdateInterval(int _updateInterval) {
+ updateInterval = _updateInterval;
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java
new file mode 100644
index 0000000..a3a0a5a
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerListener.java
@@ -0,0 +1,7 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+
+public interface PowerListener {
+ void onPowerEvent(BreakerPower _power);
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java
new file mode 100644
index 0000000..5efb1f4
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/PowerSample.java
@@ -0,0 +1,6 @@
+package com.lanternsoftware.currentmonitor;
+
+public class PowerSample {
+ public double voltage;
+ public double current;
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java
new file mode 100644
index 0000000..69c4232
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/AbstractProperties.java
@@ -0,0 +1,90 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.interfaces.DBusInterface;
+import org.freedesktop.dbus.interfaces.Properties;
+import org.freedesktop.dbus.types.Variant;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public abstract class AbstractProperties implements Properties {
+ protected final String interfaceName;
+ protected final Map> properties = new HashMap<>();
+
+ public AbstractProperties(Class extends DBusInterface> _bleClass) {
+ interfaceName = _bleClass.getCanonicalName();
+ }
+
+ public String getInterfaceName() {
+ return interfaceName;
+ }
+
+ public abstract DBusPath getPath();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public A Get(String _interface, String _propertyName) {
+ if (NullUtils.isNotEqual(_interface, interfaceName))
+ return null;
+ Variant> var = properties.get(_propertyName);
+ try {
+ return (A) var.getValue();
+ }
+ catch (ClassCastException _e) {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void Set(String _interface, String _propertyName, A _value) {
+ if ((_value == null) || NullUtils.isNotEqual(_interface, interfaceName))
+ return;
+ properties.put(_propertyName, new Variant(_value));
+ }
+
+ @Override
+ public Map> GetAll(String _interfaceName) {
+ if (NullUtils.isNotEqual(_interfaceName, getInterfaceName()))
+ return new HashMap<>();
+ return getProperties();
+ }
+
+ public Map> getProperties() {
+ return properties;
+ }
+
+ public List getAllObjects() {
+ List objects = new ArrayList<>();
+ getAllObjects(objects);
+ return objects;
+ }
+
+ public void getAllObjects(List _objects) {
+ _objects.add(this);
+ for (AbstractProperties o : CollectionUtils.makeNotNull(getChildObjects())) {
+ o.getAllObjects(_objects);
+ }
+ }
+
+ public List extends AbstractProperties> getChildObjects() {
+ return null;
+ }
+
+ public Map>>> getAllManagedObjects() {
+ return getAllManagedObjects(getAllObjects());
+ }
+
+ public static Map>>> getAllManagedObjects(List _objects) {
+ Map>>> objects = new HashMap<>();
+ for (AbstractProperties o : _objects) {
+ objects.put(o.getPath(), CollectionUtils.asHashMap(o.getInterfaceName(), o.getProperties()));
+ }
+ return objects;
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java
new file mode 100644
index 0000000..d36267a
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleAdvertisement.java
@@ -0,0 +1,69 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import com.lanternsoftware.util.CollectionUtils;
+import org.bluez.LEAdvertisement1;
+import org.bluez.LEAdvertisingManager1;
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.interfaces.Properties;
+import org.freedesktop.dbus.types.Variant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+
+public class BleAdvertisement extends AbstractProperties implements LEAdvertisement1, Properties {
+ private static final Logger LOG = LoggerFactory.getLogger(BleAdvertisement.class);
+
+ private final LEAdvertisingManager1 advertiser;
+ private final DBusPath advertisementPath;
+
+ public BleAdvertisement(String _name, BleApplication _app) {
+ super(LEAdvertisement1.class);
+ String[] serviceUUIDs = CollectionUtils.transform(_app.getServices(), _s->_s.getUuid().toString()).toArray(new String[0]);
+ advertisementPath = new DBusPath(BleHelper.advertismentPath(_app.getName()));
+ advertiser = BleHelper.getRemoteObject(BleHelper.getAdapter().getDbusPath(), LEAdvertisingManager1.class);
+ properties.put("Type", new Variant<>("peripheral"));
+ properties.put("ServiceUUIDs", new Variant<>(serviceUUIDs));
+ properties.put("LocalName", new Variant<>(_name));
+ properties.put("Includes", new Variant<>(new String[]{"tx-power"}));
+ BleHelper.unExportObject(this);
+ BleHelper.exportObject(this);
+ }
+
+ public void start() {
+ try {
+ advertiser.RegisterAdvertisement(advertisementPath, new HashMap<>());
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to register advertisement", _e);
+ }
+ }
+
+ public void stop() {
+ try {
+ advertiser.UnregisterAdvertisement(advertisementPath);
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to unregister advertisement", _e);
+ }
+ }
+
+ @Override
+ public boolean isRemote() {
+ return false;
+ }
+
+ @Override
+ public String getObjectPath() {
+ return advertisementPath.getPath();
+ }
+
+ @Override
+ public DBusPath getPath() {
+ return advertisementPath;
+ }
+
+ @Override
+ public void Release() {
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java
new file mode 100644
index 0000000..e4ca956
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleApplication.java
@@ -0,0 +1,90 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import com.lanternsoftware.util.CollectionUtils;
+import org.bluez.GattApplication1;
+import org.bluez.GattManager1;
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.interfaces.ObjectManager;
+import org.freedesktop.dbus.types.Variant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BleApplication implements GattApplication1, ObjectManager {
+ private static final Logger LOG = LoggerFactory.getLogger(BleApplication.class);
+
+ private final String name;
+ private final DBusPath appPath;
+ private final GattManager1 appManager;
+ private final List services;
+ private final BleAdvertisement advertisement;
+
+ public BleApplication(String _name, String _advertisedName, BleService... _services) {
+ this(_name, _advertisedName, CollectionUtils.asArrayList(_services));
+ }
+
+ public BleApplication(String _name, String _advertisedName, List _services) {
+ name = _name;
+ appPath = new DBusPath(BleHelper.applicationPath(_name));
+ appManager = BleHelper.getRemoteObject(BleHelper.getAdapter().getDbusPath(), GattManager1.class);
+ services = _services;
+ advertisement = new BleAdvertisement(_advertisedName, this);
+ List objects = getManagedObjects();
+ BleHelper.unExportObject(this);
+ objects.forEach(BleHelper::unExportObject);
+ BleHelper.exportObject(this);
+ objects.forEach(BleHelper::exportObject);
+ }
+
+ public List getManagedObjects() {
+ return CollectionUtils.aggregate(services, AbstractProperties::getAllObjects);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getServices() {
+ return services;
+ }
+
+ @Override
+ public Map>>> GetManagedObjects() {
+ return AbstractProperties.getAllManagedObjects(getManagedObjects());
+ }
+
+ public void start() {
+ try {
+ appManager.RegisterApplication(appPath, new HashMap<>());
+ advertisement.start();
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to register application", _e);
+ _e.printStackTrace();
+ }
+ }
+
+ public void stop() {
+ try {
+ advertisement.stop();
+ appManager.UnregisterApplication(appPath);
+ BleHelper.connection.disconnect();
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to unregister application", _e);
+ }
+ }
+
+ @Override
+ public boolean isRemote() {
+ return false;
+ }
+
+ @Override
+ public String getObjectPath() {
+ return appPath.getPath();
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java
new file mode 100644
index 0000000..358952e
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristic.java
@@ -0,0 +1,121 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import com.lanternsoftware.datamodel.currentmonitor.CharacteristicFlag;
+import com.lanternsoftware.util.CollectionUtils;
+import org.bluez.GattCharacteristic1;
+import org.bluez.datatypes.TwoTuple;
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.FileDescriptor;
+import org.freedesktop.dbus.types.UInt16;
+import org.freedesktop.dbus.types.Variant;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class BleCharacteristic extends AbstractProperties implements GattCharacteristic1 {
+ private final String charName;
+ private final DBusPath charPath;
+ private final UUID uuid;
+ private final List descriptors;
+ private BleCharacteristicListener listener;
+
+ public BleCharacteristic(String _serviceName, UUID _uuid, String _characteristicName, Collection _flags) {
+ this(_serviceName, _uuid, _characteristicName, _flags, null);
+ }
+
+ public BleCharacteristic(String _serviceName, UUID _uuid, String _characteristicName, Collection _flags, List _descriptors) {
+ super(GattCharacteristic1.class);
+ charName = _characteristicName;
+ charPath = new DBusPath(BleHelper.characteristicPath(_serviceName, _characteristicName));
+ uuid = _uuid;
+ descriptors = _descriptors;
+ properties.put("Service", new Variant<>(new DBusPath(BleHelper.servicePath(_serviceName))));
+ if (uuid != null)
+ properties.put("UUID", new Variant<>(uuid.toString()));
+ if (CollectionUtils.isNotEmpty(_flags))
+ properties.put("Flags", new Variant<>(CharacteristicFlag.toArray(_flags)));
+ if (CollectionUtils.isNotEmpty(descriptors))
+ properties.put("Descriptors", new Variant<>(BleHelper.toPaths(descriptors)));
+ }
+
+ public void setListener(BleCharacteristicListener _listener) {
+ listener = _listener;
+ }
+
+ @Override
+ public List extends AbstractProperties> getChildObjects() {
+ return descriptors;
+ }
+
+ @Override
+ public boolean isRemote() {
+ return false;
+ }
+
+ public DBusPath getPath() {
+ return charPath;
+ }
+
+ @Override
+ public String getObjectPath() {
+ return charPath.getPath();
+ }
+
+ @Override
+ public byte[] ReadValue(Map> _options) {
+ if (listener == null)
+ return null;
+ int offset = 0;
+ Variant> voffset = _options.get("offset");
+ if (voffset != null) {
+ if (voffset.getValue() instanceof UInt16)
+ offset = ((UInt16)voffset.getValue()).intValue();
+ }
+ byte[] ret = listener.read(charName);
+ if (ret == null)
+ return null;
+ return offset > 0?Arrays.copyOfRange(ret, offset, ret.length):ret;
+ }
+
+ @Override
+ public void WriteValue(byte[] _bytes, Map> _map) {
+ if (listener != null)
+ listener.write(charName, _bytes);
+ }
+
+ @Override
+ public TwoTuple AcquireWrite(Map> _map) {
+ return null;
+ }
+
+ @Override
+ public TwoTuple AcquireNotify(Map> _map) {
+ return null;
+ }
+
+ @Override
+ public void StartNotify() {
+
+ }
+
+ @Override
+ public void StopNotify() {
+
+ }
+
+ @Override
+ public void Confirm() {
+
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public List getDescriptors() {
+ return descriptors;
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java
new file mode 100644
index 0000000..be746c9
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleCharacteristicListener.java
@@ -0,0 +1,6 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+public interface BleCharacteristicListener {
+ void write(String _name, byte[] _value);
+ byte[] read(String _name);
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java
new file mode 100644
index 0000000..bf5ad35
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleDescriptor.java
@@ -0,0 +1,32 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import org.bluez.GattDescriptor1;
+import org.freedesktop.dbus.DBusPath;
+
+import java.util.UUID;
+
+public class BleDescriptor extends AbstractProperties {
+ private final DBusPath charPath;
+ private final UUID uuid;
+
+ public BleDescriptor(String _serviceName, String _characteristicName, String _descriptorName, UUID _uuid) {
+ super(GattDescriptor1.class);
+ charPath = new DBusPath(BleHelper.descriptorPath(_serviceName, _characteristicName, _descriptorName));
+ uuid = _uuid;
+ }
+
+ @Override
+ public DBusPath getPath() {
+ return charPath;
+ }
+
+ @Override
+ public boolean isRemote() {
+ return false;
+ }
+
+ @Override
+ public String getObjectPath() {
+ return charPath.getPath();
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java
new file mode 100644
index 0000000..cdad5a2
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleHelper.java
@@ -0,0 +1,115 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import com.github.hypfvieh.bluetooth.DeviceManager;
+import com.github.hypfvieh.bluetooth.wrapper.BluetoothAdapter;
+import com.lanternsoftware.util.CollectionUtils;
+import org.freedesktop.dbus.connections.impl.DBusConnection;
+import org.freedesktop.dbus.exceptions.DBusException;
+import org.freedesktop.dbus.interfaces.DBusInterface;
+import org.freedesktop.dbus.interfaces.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public abstract class BleHelper {
+ private static final Logger LOG = LoggerFactory.getLogger(BleHelper.class);
+
+ private static String basePath;
+ private static final DeviceManager deviceManager;
+ public static final DBusConnection connection;
+ static {
+ DeviceManager m = null;
+ DBusConnection c = null;
+ try {
+ DeviceManager.createInstance(false);
+ m = DeviceManager.getInstance();
+ c = m.getDbusConnection();
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to get dbus connection", _e);
+ }
+ deviceManager = m;
+ connection = c;
+ }
+
+ public static void setBasePath(String _basePath) {
+ basePath = _basePath;
+ }
+
+ public static String getBasePath() {
+ return basePath;
+ }
+
+ public static String advertismentPath(String _advertisementName) {
+ return BleHelper.getBasePath() + "/advertisement/" + _advertisementName;
+ }
+
+ public static String applicationPath(String _appPath) {
+ return BleHelper.getBasePath() + "/application/" + _appPath;
+ }
+
+ public static String servicePath(String _serviceName) {
+ return BleHelper.getBasePath() + "/service/" + _serviceName;
+ }
+
+ public static String characteristicPath(String _serviceName, String _characteristicName) {
+ return servicePath(_serviceName) + "/" + _characteristicName;
+ }
+
+ public static String descriptorPath(String _serviceName, String _characteristicName, String _descriptorPath) {
+ return servicePath(_serviceName) + "/" + _characteristicName + "/" + _descriptorPath;
+ }
+
+ public static String[] toPaths(List extends AbstractProperties> _properties) {
+ return CollectionUtils.transform(_properties, Properties::getObjectPath).toArray(new String[0]);
+ }
+
+ public static BluetoothAdapter getAdapter() {
+ return (deviceManager != null)?deviceManager.getAdapter():null;
+ }
+
+ public static void requestBusName(String _name) {
+ try {
+ if (connection != null)
+ connection.requestBusName(_name);
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to request bus name", _e);
+ }
+ }
+
+ public static T getRemoteObject(String _path, Class _objClass) {
+ try {
+ return connection.getRemoteObject("org.bluez", _path, _objClass);
+ } catch (DBusException _e) {
+ LOG.error("Failed to get remote object", _e);
+ return null;
+ }
+ }
+
+ public static void exportObject(DBusInterface _object) {
+ try {
+ if (connection != null)
+ connection.exportObject(_object.getObjectPath(), _object);
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to export object", _e);
+ }
+ }
+
+ public static void unExportObject(DBusInterface _object) {
+ if (_object != null)
+ unExportObject(_object.getObjectPath());
+ }
+
+ public static void unExportObject(String _objectPath) {
+ try {
+ if (connection != null)
+ connection.unExportObject(_objectPath);
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to unexport object", _e);
+ }
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java
new file mode 100644
index 0000000..bc4c279
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/bluetooth/BleService.java
@@ -0,0 +1,55 @@
+package com.lanternsoftware.currentmonitor.bluetooth;
+
+import com.lanternsoftware.util.CollectionUtils;
+import org.bluez.GattService1;
+import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.types.Variant;
+
+import java.util.List;
+import java.util.UUID;
+
+
+public class BleService extends AbstractProperties implements GattService1 {
+ private final DBusPath servicePath;
+ private final UUID uuid;
+ private final List characteristics;
+
+ public BleService(String _serviceName, UUID _uuid, List _characteristics) {
+ super(GattService1.class);
+ servicePath = new DBusPath(BleHelper.servicePath(_serviceName));
+ uuid = _uuid;
+ characteristics = _characteristics;
+ if (uuid != null)
+ properties.put("UUID", new Variant<>(uuid.toString()));
+ properties.put("Primary", new Variant<>(Boolean.TRUE));
+ if (CollectionUtils.isNotEmpty(characteristics))
+ properties.put("Characteristics", new Variant<>(BleHelper.toPaths(characteristics)));
+ }
+
+ public DBusPath getPath() {
+ return servicePath;
+ }
+
+ @Override
+ public boolean isRemote() {
+ return false;
+ }
+
+ @Override
+ public String getObjectPath() {
+ return getPath().getPath();
+ }
+
+ @Override
+ public List extends AbstractProperties> getChildObjects() {
+ return characteristics;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public List getCharacteristics() {
+ return characteristics;
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java
new file mode 100644
index 0000000..d815a81
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java
@@ -0,0 +1,56 @@
+package com.lanternsoftware.currentmonitor.dao;
+
+import com.lanternsoftware.currentmonitor.MonitorConfig;
+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 MonitorConfigSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return MonitorConfig.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(MonitorConfig _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("host", _o.getHost());
+ d.put("auth_code", _o.getAuthCode());
+ d.put("username", _o.getUsername());
+ d.put("password", _o.getPassword());
+ d.put("hub", _o.getHub());
+ d.put("debug", _o.isDebug());
+ d.put("socket_timeout", _o.getSocketTimeout());
+ d.put("connect_timeout", _o.getConnectTimeout());
+ d.put("update_interval", _o.getUpdateInterval());
+ return d;
+ }
+
+ @Override
+ public MonitorConfig fromDaoEntity(DaoEntity _d)
+ {
+ MonitorConfig o = new MonitorConfig();
+ o.setHost(DaoSerializer.getString(_d, "host"));
+ o.setAuthCode(DaoSerializer.getString(_d, "auth_code"));
+ o.setUsername(DaoSerializer.getString(_d, "username"));
+ o.setPassword(DaoSerializer.getString(_d, "password"));
+ o.setHub(DaoSerializer.getInteger(_d, "hub"));
+ o.setDebug(DaoSerializer.getBoolean(_d, "debug"));
+ o.setSocketTimeout(DaoSerializer.getInteger(_d, "socket_timeout"));
+ o.setConnectTimeout(DaoSerializer.getInteger(_d, "connect_timeout"));
+ o.setUpdateInterval(DaoSerializer.getInteger(_d, "update_interval"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java
new file mode 100644
index 0000000..e7a5475
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/led/LEDFlasher.java
@@ -0,0 +1,40 @@
+package com.lanternsoftware.currentmonitor.led;
+
+import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class LEDFlasher implements Runnable {
+ private static final Logger LOG = LoggerFactory.getLogger(LEDFlasher.class);
+
+ private final AtomicBoolean running = new AtomicBoolean(true);
+ private boolean on = false;
+
+ @Override
+ public void run() {
+ while (running.get()) {
+ on = !on;
+ setLEDOn(on);
+ ConcurrencyUtils.sleep(250);
+ }
+ setLEDOn(false);
+ }
+
+ public void stop() {
+ running.set(false);
+ }
+
+ public static void setLEDOn(boolean _on) {
+ try {
+ if (_on)
+ Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo default-on > /sys/class/leds/led1/trigger"});
+ else
+ Runtime.getRuntime().exec(new String[]{"sh", "-c", "echo none > /sys/class/leds/led1/trigger"});
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to change LED state", _e);
+ }
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java
new file mode 100644
index 0000000..3130ef8
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/util/NetworkMonitor.java
@@ -0,0 +1,53 @@
+package com.lanternsoftware.currentmonitor.util;
+
+import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class NetworkMonitor {
+ private static final Logger LOG = LoggerFactory.getLogger(NetworkMonitor.class);
+
+ private static final Pattern ethernetPattern = Pattern.compile(".*(eth[0-9]):(.*)");
+ private static final Pattern wifiPattern = Pattern.compile(".*(wlan[0-9]):(.*)");
+
+ public static NetworkStatus getNetworkStatus() {
+ NetworkStatus status = new NetworkStatus();
+ String[] commands = {"ifconfig", "-a"};
+ InputStream is = null;
+ try {
+ is = Runtime.getRuntime().exec(commands).getInputStream();
+ String ifconfig = IOUtils.toString(is);
+ status.setEthernetIPs(getIPs(ifconfig, ethernetPattern));
+ status.setWifiIPs(getIPs(ifconfig, wifiPattern));
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to check network state", _e);
+ }
+ finally {
+ IOUtils.closeQuietly(is);
+ }
+ return status;
+ }
+
+ private static List getIPs(String _ifConfig, Pattern _pattern) {
+ List ips = new ArrayList<>();
+ Matcher m = _pattern.matcher(_ifConfig);
+ while (m.find()) {
+ int start = m.start(0);
+ int ipStart = _ifConfig.indexOf("inet ", start) + 5;
+ if (ipStart > 4) {
+ int ipEnd = _ifConfig.indexOf(" ", ipStart);
+ if (ipEnd > -1)
+ ips.add(_ifConfig.substring(ipStart, ipEnd));
+ }
+ }
+ return ips;
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java
new file mode 100644
index 0000000..cd3e9ed
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/wifi/WifiConfig.java
@@ -0,0 +1,44 @@
+package com.lanternsoftware.currentmonitor.wifi;
+
+import com.lanternsoftware.util.ResourceLoader;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+public abstract class WifiConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(WifiConfig.class);
+
+ private static final String WIFI_CONFIG_PATH = "/etc/wpa_supplicant/wpa_supplicant.conf";
+ private static final String CONF_FORMAT = "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\ncountry=US\nnetwork={\n\tssid=\"%s\"\n\t%s\n}\n";
+
+ public static void setCredentials(String _ssid, String _password) {
+ String[] commands = {"wpa_passphrase", _ssid, _password};
+ InputStream is = null;
+ try {
+ is = Runtime.getRuntime().exec(commands).getInputStream();
+ String newConf = IOUtils.toString(is);
+ if (newConf == null)
+ return;
+ int idx = newConf.indexOf("psk=");
+ if (idx > 0) {
+ if (newConf.charAt(idx-1) == '#')
+ idx = newConf.indexOf("psk=", idx+1);
+ if (idx > 0) {
+ int endIdx = newConf.indexOf("\n", idx);
+ if (endIdx > 0) {
+ String finalConf = String.format(CONF_FORMAT, _ssid, newConf.substring(idx, endIdx));
+ ResourceLoader.writeFile(WIFI_CONFIG_PATH, finalConf);
+ }
+ }
+ }
+ }
+ catch (Exception _e) {
+ LOG.error("Failed to write wifi credentials", _e);
+ }
+ finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java b/currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java
new file mode 100644
index 0000000..e56b17c
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/java/org/bluez/GattApplication1.java
@@ -0,0 +1,6 @@
+package org.bluez;
+
+import org.freedesktop.dbus.interfaces.DBusInterface;
+
+public interface GattApplication1 extends DBusInterface {
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer
new file mode 100644
index 0000000..871a7e6
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer
@@ -0,0 +1 @@
+com.lanternsoftware.currentmonitor.dao.MonitorConfigSerializer
diff --git a/currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml b/currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml
new file mode 100644
index 0000000..939eaaa
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/main/resources/logback.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ ${log.pattern}
+
+
+
+
+ /opt/currentmonitor/log/log.txt
+
+ /opt/currentmonitor/log/log.%d{yyyy-MM-dd}.%i.txt
+ 20MB
+ 20
+
+
+ ${log.pattern}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java
new file mode 100644
index 0000000..c882746
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateConfig.java
@@ -0,0 +1,15 @@
+package com.lanternsoftware.currentmonitor;
+
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+public class CreateConfig {
+ public static void main(String[] args) {
+// MonitorConfig c = new MonitorConfig(0, "https://mark.lanternsoftware.com/currentmonitor");
+ MonitorConfig c = new MonitorConfig(1, "https://mark.lanternsoftware.com/currentmonitor");
+ c.setDebug(true);
+ ResourceLoader.writeFile(LanternFiles.OPS_PATH + "hub1.json", DaoSerializer.toJson(c));
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java
new file mode 100644
index 0000000..da52298
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateSSIDKey.java
@@ -0,0 +1,9 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.util.cryptography.AESTool;
+
+public class CreateSSIDKey {
+ public static void main(String[] args) {
+ AESTool.printRandomSecretKey();
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java
new file mode 100644
index 0000000..e7dc8fb
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorAppSerializers.java
@@ -0,0 +1,11 @@
+package com.lanternsoftware.currentmonitor;
+
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator;
+
+public class CurrentMonitorAppSerializers {
+ public static void main(String[] args) {
+ DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "currentmonitor", true, null);
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java
new file mode 100644
index 0000000..b81ebc2
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/NetworkTest.java
@@ -0,0 +1,10 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.currentmonitor.util.NetworkMonitor;
+import com.lanternsoftware.datamodel.currentmonitor.NetworkStatus;
+
+public class NetworkTest {
+ public static void main(String[] args) {
+ NetworkStatus status = NetworkMonitor.getNetworkStatus();
+ }
+}
diff --git a/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java
new file mode 100644
index 0000000..bd3efdb
--- /dev/null
+++ b/currentmonitor/lantern-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/ReleaseCurrentMonitor.java
@@ -0,0 +1,38 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.xml.XmlNode;
+import com.lanternsoftware.util.xml.XmlParser;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+
+public class ReleaseCurrentMonitor {
+ public static void main(String[] args) {
+ XmlNode pom = XmlParser.loadXmlFile(LanternFiles.SOURCE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "pom.xml");
+ if (pom == null)
+ return;
+ XmlNode versionNode = pom.getChild(Collections.singletonList("version"));
+ String version = versionNode.getContent();
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.directory(new File(LanternFiles.SOURCE_PATH));
+ builder.command("cmd.exe", "/c", "mvn clean install");
+ builder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
+ try {
+ Process process = builder.start();
+ int exitCode = process.waitFor();
+ assert exitCode == 0;
+ } catch (Exception _e) {
+ _e.printStackTrace();
+ }
+ byte[] jar = ResourceLoader.loadFile(LanternFiles.SOURCE_PATH + "currentmonitor" + File.separator + "lantern-currentmonitor" + File.separator + "target" + File.separator + "lantern-currentmonitor.jar");
+ DaoEntity meta = new DaoEntity("version", version).and("size", jar.length).and("checksum", DigestUtils.md5Hex(jar));
+ ResourceLoader.writeFile(LanternFiles.OPS_PATH + "release" + File.separator + "lantern-currentmonitor.jar", jar);
+ ResourceLoader.writeFile(LanternFiles.OPS_PATH + "release" + File.separator + "version.json", DaoSerializer.toJson(meta));
+ }
+}
diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/pom.xml b/currentmonitor/lantern-dataaccess-currentmonitor/pom.xml
new file mode 100644
index 0000000..11cc06a
--- /dev/null
+++ b/currentmonitor/lantern-dataaccess-currentmonitor/pom.xml
@@ -0,0 +1,83 @@
+
+ 4.0.0
+ com.lanternsoftware.currentmonitor
+ lantern-dataaccess-currentmonitor
+ jar
+ 1.0.0
+ lantern-dataaccess-currentmonitor
+
+
+ 1.8
+ 1.8
+
+
+
+
+ com.lanternsoftware.currentmonitor
+ lantern-datamodel-currentmonitor
+ 1.0.0
+
+
+ com.lanternsoftware.util
+ lantern-util-dao-mongo
+ 1.0.0
+
+
+ org.mindrot
+ jbcrypt
+ 0.4
+
+
+
+
+
+ src/main/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.2
+
+
+
+ testCompile
+
+ compile
+
+
+
+ true
+ true
+ UTF-8
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.4
+
+
+ package
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.5
+
+
+ true
+
+
+
+
+
+
diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java
new file mode 100644
index 0000000..183f783
--- /dev/null
+++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/CurrentMonitorDao.java
@@ -0,0 +1,45 @@
+package com.lanternsoftware.dataaccess.currentmonitor;
+
+import com.lanternsoftware.datamodel.currentmonitor.Account;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode;
+import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
+import com.lanternsoftware.util.dao.mongo.MongoProxy;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+public interface CurrentMonitorDao {
+ void shutdown();
+
+ void putBreakerPower(BreakerPower _current);
+ List getBreakerPowerForAccount(int _accountId);
+ BreakerPower getLatestBreakerPower(int _accountId, int _hub, int _port);
+ BreakerGroupEnergy getBreakerGroupEnergy(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start);
+ void putBreakerGroupEnergy(BreakerGroupEnergy _energy);
+
+ void putHubPowerMinute(HubPowerMinute _power);
+
+ BreakerConfig getConfig(int _accountId);
+ BreakerConfig getMergedConfig(AuthCode _authCode);
+ void putConfig(BreakerConfig _config);
+
+ void updateSummaries(BreakerGroup _rootGroup, Set _daysToSummarize, TimeZone _tz);
+
+ String authenticateAccount(String _username, String _password);
+ String getAuthCodeForEmail(String _email);
+ Account authCodeToAccount(String _authCode);
+ AuthCode decryptAuthCode(String _authCode);
+
+ Account putAccount(Account _account);
+ Account getAccount(int _accountId);
+ Account getAccountByUsername(String _username);
+
+ MongoProxy getProxy();
+}
diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java
new file mode 100644
index 0000000..5fdac49
--- /dev/null
+++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/DirtyMinute.java
@@ -0,0 +1,57 @@
+package com.lanternsoftware.dataaccess.currentmonitor;
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.Date;
+
+@DBSerializable(autogen = false)
+public class DirtyMinute {
+ private int accountId;
+ private int minute;
+ private Date posted;
+
+ public DirtyMinute() {
+ }
+
+ public DirtyMinute(int _accountId, int _minute, Date _posted) {
+ accountId = _accountId;
+ minute = _minute;
+ posted = _posted;
+ }
+
+ public String getId() {
+ return accountId + "-" + minute;
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public Date getMinuteAsDate() {
+ return new Date(((long)minute)*60000);
+ }
+
+ public int getMinute() {
+ return minute;
+ }
+
+ public void setMinute(int _minute) {
+ minute = _minute;
+ }
+
+ public void setMinute(Date _minute) {
+ minute = (int)(_minute.getTime()/60000);
+ }
+
+ public Date getPosted() {
+ return posted;
+ }
+
+ public void setPosted(Date _posted) {
+ posted = _posted;
+ }
+}
diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java
new file mode 100644
index 0000000..6f62d6c
--- /dev/null
+++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/MongoCurrentMonitorDao.java
@@ -0,0 +1,273 @@
+package com.lanternsoftware.dataaccess.currentmonitor;
+
+import com.lanternsoftware.datamodel.currentmonitor.Account;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode;
+import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
+import com.lanternsoftware.datamodel.currentmonitor.Sequence;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.DateUtils;
+import com.lanternsoftware.util.DebugTimer;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.cryptography.AESTool;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoQuery;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.dao.DaoSort;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+import com.lanternsoftware.util.dao.mongo.MongoProxy;
+import org.mindrot.jbcrypt.BCrypt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class MongoCurrentMonitorDao implements CurrentMonitorDao {
+ private static final Logger logger = LoggerFactory.getLogger(MongoCurrentMonitorDao.class);
+ private static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "authKey.dat"));
+ private static final int BCRYPT_ROUNDS = 11;
+ private final Timer delayTimer = new Timer();
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+
+ private final MongoProxy proxy;
+
+ public MongoCurrentMonitorDao(MongoConfig _config) {
+ proxy = new MongoProxy(_config);
+ proxy.ensureIndex(BreakerPower.class, DaoSort.sort("account_id").then("key"));
+ proxy.ensureIndex(HubPowerMinute.class, DaoSort.sort("account_id").then("minute"));
+ proxy.ensureIndex(BreakerGroupEnergy.class, DaoSort.sort("account_id").then("group_id").then("view_mode"));
+ proxy.ensureIndex(BreakerGroupSummary.class, DaoSort.sort("account_id").then("group_id").then("view_mode"));
+ proxy.ensureIndex(DirtyMinute.class, DaoSort.sort("posted"));
+ for (DirtyMinute minute : proxy.queryAll(DirtyMinute.class)) {
+ updateSummaries(minute);
+ }
+ proxy.delete(DirtyMinute.class, new DaoQuery());
+ }
+
+ public void shutdown() {
+ delayTimer.cancel();
+ executor.shutdownNow();
+ proxy.shutdown();
+ }
+
+ @Override
+ public void putBreakerPower(BreakerPower _power) {
+ proxy.save(_power);
+ }
+
+ @Override
+ public void putHubPowerMinute(HubPowerMinute _power) {
+ if (_power == null)
+ return;
+ proxy.save(_power);
+ DirtyMinute minute = new DirtyMinute(_power.getAccountId(), _power.getMinute(), new Date());
+ proxy.save(minute);
+ delayTimer.schedule(new TimerTask(){
+ @Override
+ public void run() {
+ executor.submit(()->{
+ if (proxy.queryOneAndDelete(DirtyMinute.class, new DaoQuery("_id", minute.getId())) != null)
+ updateSummaries(new DirtyMinute(_power.getAccountId(), _power.getMinute(), new Date()));
+ });
+ }
+ }, 10000);
+ }
+
+ private void updateSummaries(DirtyMinute _minute) {
+ DebugTimer timer = new DebugTimer("Updating summaries", logger);
+ List minutes = proxy.query(HubPowerMinute.class, new DaoQuery("account_id", _minute.getAccountId()).and("minute", _minute.getMinute()));
+ TimeZone tz = TimeZone.getTimeZone("America/Chicago");
+ BreakerConfig config = getConfig(_minute.getAccountId());
+ BreakerGroup group = CollectionUtils.getFirst(config.getBreakerGroups());
+ Date day = DateUtils.getMidnightBefore(_minute.getMinuteAsDate(), tz);
+ BreakerGroupEnergy summary = getBreakerGroupEnergy(_minute.getAccountId(), group.getId(), EnergyBlockViewMode.DAY, day);
+ if (summary == null)
+ summary = new BreakerGroupEnergy(group, minutes, EnergyBlockViewMode.DAY, day, tz);
+ else
+ summary.addEnergy(group, minutes, tz);
+ putBreakerGroupEnergy(summary);
+ updateSummaries(group, CollectionUtils.asHashSet(day), tz);
+ timer.stop();
+ }
+
+ @Override
+ public List getBreakerPowerForAccount(int _accountId) {
+ return proxy.query(BreakerPower.class, new DaoQuery("account_id", _accountId));
+ }
+
+ @Override
+ public BreakerPower getLatestBreakerPower(int _accountId, int _panel, int _space) {
+ return proxy.queryOne(BreakerPower.class, new DaoQuery("account_id", _accountId).and("key", Breaker.key(_panel, _space)), DaoSort.sortDesc("read_time"));
+ }
+
+ @Override
+ public BreakerGroupEnergy getBreakerGroupEnergy(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start) {
+ return proxy.queryOne(BreakerGroupEnergy.class, new DaoQuery("_id", BreakerGroupEnergy.toId(_accountId, _groupId, _viewMode, _start)));
+ }
+
+ @Override
+ public void updateSummaries(BreakerGroup _rootGroup, Set _daysToSummarize, TimeZone _tz) {
+ Set monthsToSummarize = CollectionUtils.transformToSet(_daysToSummarize, _c -> DateUtils.getStartOfMonth(_c, _tz));
+ Set yearsToSummarize = CollectionUtils.transformToSet(monthsToSummarize, _c -> DateUtils.getStartOfYear(_c, _tz));
+ for (Date month : monthsToSummarize) {
+ Calendar calDayStart = DateUtils.toCalendar(month, _tz);
+ Calendar end = DateUtils.getEndOfMonthCal(month, _tz);
+ List groupEnergyIds = new ArrayList<>();
+ while (calDayStart.before(end)) {
+ groupEnergyIds.add(BreakerGroupEnergy.toId(_rootGroup.getAccountId(), _rootGroup.getId(), EnergyBlockViewMode.DAY, calDayStart.getTime()));
+ calDayStart.add(Calendar.DAY_OF_YEAR, 1);
+ }
+ List groupEnergies = CollectionUtils.aggregate(proxy.query(BreakerGroupSummary.class, DaoQuery.in("_id", groupEnergyIds)), BreakerGroupSummary::getAllGroups);
+ Map> energies = CollectionUtils.transformToMultiMap(groupEnergies, BreakerGroupSummary::getGroupId);
+ BreakerGroupEnergy summary = BreakerGroupEnergy.summary(_rootGroup, energies, EnergyBlockViewMode.MONTH, month, _tz);
+ putBreakerGroupEnergy(summary);
+ }
+ for (Date year : yearsToSummarize) {
+ Calendar calMonthStart = DateUtils.toCalendar(year, _tz);
+ Calendar end = DateUtils.getEndOfYearCal(year, _tz);
+ List groupEnergyIds = new ArrayList<>();
+ while (calMonthStart.before(end)) {
+ groupEnergyIds.add(BreakerGroupEnergy.toId(_rootGroup.getAccountId(), _rootGroup.getId(), EnergyBlockViewMode.MONTH, calMonthStart.getTime()));
+ calMonthStart.add(Calendar.DAY_OF_YEAR, 1);
+ }
+ List groupEnergies = CollectionUtils.aggregate(proxy.query(BreakerGroupSummary.class, DaoQuery.in("_id", groupEnergyIds)), BreakerGroupSummary::getAllGroups);
+ Map> energies = CollectionUtils.transformToMultiMap(groupEnergies, BreakerGroupSummary::getGroupId);
+ BreakerGroupEnergy summary = BreakerGroupEnergy.summary(_rootGroup, energies, EnergyBlockViewMode.YEAR, year, _tz);
+ putBreakerGroupEnergy(summary);
+ }
+ List groupEnergies = CollectionUtils.aggregate(proxy.query(BreakerGroupSummary.class, new DaoQuery("group_id", _rootGroup.getId()).and("view_mode", EnergyBlockViewMode.YEAR.name())), BreakerGroupSummary::getAllGroups);
+ Map> energies = CollectionUtils.transformToMultiMap(groupEnergies, BreakerGroupSummary::getGroupId);
+ BreakerGroupEnergy summary = BreakerGroupEnergy.summary(_rootGroup, energies, EnergyBlockViewMode.ALL, new Date(0), _tz);
+ putBreakerGroupEnergy(summary);
+ }
+
+ @Override
+ public void putBreakerGroupEnergy(BreakerGroupEnergy _energy) {
+ proxy.save(_energy);
+ proxy.save(new BreakerGroupSummary(_energy));
+ }
+
+ @Override
+ public BreakerConfig getConfig(int _accountId) {
+ return proxy.queryOne(BreakerConfig.class, new DaoQuery("_id", String.valueOf(_accountId)));
+ }
+
+ @Override
+ public BreakerConfig getMergedConfig(AuthCode _authCode) {
+ if (_authCode == null)
+ return null;
+ List configs = CollectionUtils.transform(_authCode.getAllAccountIds(), this::getConfig, true);
+ BreakerConfig config = new BreakerConfig();
+ config.setAccountId(_authCode.getAccountId());
+ config.setBreakerHubs(CollectionUtils.aggregate(configs, BreakerConfig::getBreakerHubs));
+ config.setBreakerGroups(CollectionUtils.aggregate(configs, BreakerConfig::getBreakerGroups));
+ config.setPanels(CollectionUtils.aggregate(configs, BreakerConfig::getPanels));
+ config.setMeters(CollectionUtils.aggregate(configs, BreakerConfig::getMeters));
+ return config;
+ }
+
+ @Override
+ public void putConfig(BreakerConfig _config) {
+ DaoQuery configQuery = new DaoQuery("_id", String.valueOf(_config.getAccountId()));
+ BreakerConfig oldConfig = proxy.queryOne(BreakerConfig.class, configQuery);
+ if (oldConfig != null)
+ proxy.delete(BreakerGroup.class, DaoQuery.in("_id", oldConfig.getAllBreakerGroupIds()));
+ proxy.save(_config);
+ }
+
+ @Override
+ public String authenticateAccount(String _username, String _password) {
+ Account acct = proxy.queryOne(Account.class, new DaoQuery("username", _username));
+ if ((acct == null) || !BCrypt.checkpw(_password, acct.getPassword()))
+ return null;
+ return aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(acct.getId(), acct.getAuxiliaryAccountIds())));
+ }
+
+ @Override
+ public Account authCodeToAccount(String _authCode) {
+ AuthCode code = decryptAuthCode(_authCode);
+ if (code == null)
+ return null;
+ return proxy.queryOne(Account.class, new DaoQuery("_id", code.getAccountId()));
+ }
+
+ @Override
+ public AuthCode decryptAuthCode(String _authCode) {
+ return DaoSerializer.fromZipBson(aes.decryptFromBase64(_authCode), AuthCode.class);
+ }
+
+ @Override
+ public String getAuthCodeForEmail(String _email) {
+ _email = _email.toLowerCase().trim();
+ Account account = getAccountByUsername(_email);
+ if (account == null) {
+ account = new Account();
+ account.setUsername(_email);
+ putAccount(account);
+ }
+ return aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(account.getId(), account.getAuxiliaryAccountIds())));
+ }
+
+ @Override
+ public Account putAccount(Account _account) {
+ if (_account == null)
+ return null;
+ _account.setUsername(NullUtils.makeNotNull(_account.getUsername()).toLowerCase().trim());
+ Account account = getAccountByUsername(_account.getUsername());
+ if (account != null) {
+ _account.setId(account.getId());
+ if (NullUtils.isEmpty(_account.getPassword()))
+ _account.setPassword(account.getPassword());
+ else
+ _account.setPassword(BCrypt.hashpw(_account.getPassword(), BCrypt.gensalt(BCRYPT_ROUNDS)));
+ }
+ else if (NullUtils.isNotEmpty(_account.getPassword())) {
+ _account.setPassword(BCrypt.hashpw(_account.getPassword(), BCrypt.gensalt(BCRYPT_ROUNDS)));
+ }
+ if (_account.getId() == 0)
+ _account.setId(proxy.updateOne(Sequence.class, null, new DaoEntity("$inc", new DaoEntity("sequence", 1))).getSequence());
+ proxy.save(_account);
+ return clearPassword(_account);
+ }
+
+ @Override
+ public Account getAccount(int _accountId) {
+ return clearPassword(proxy.queryOne(Account.class, new DaoQuery("_id", String.valueOf(_accountId))));
+ }
+
+ @Override
+ public Account getAccountByUsername(String _username) {
+ return clearPassword(proxy.queryOne(Account.class, new DaoQuery("username", NullUtils.makeNotNull(_username).toLowerCase().trim())));
+ }
+
+ private Account clearPassword(Account _account) {
+ if (_account == null)
+ return null;
+ _account.setPassword(null);
+ return _account;
+ }
+
+ @Override
+ public MongoProxy getProxy() {
+ return proxy;
+ }
+}
diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java
new file mode 100644
index 0000000..ffe6c9e
--- /dev/null
+++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/java/com/lanternsoftware/dataaccess/currentmonitor/dao/DirtyMinuteSerializer.java
@@ -0,0 +1,44 @@
+package com.lanternsoftware.dataaccess.currentmonitor.dao;
+
+import com.lanternsoftware.dataaccess.currentmonitor.DirtyMinute;
+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 DirtyMinuteSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return DirtyMinute.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(DirtyMinute _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("_id", _o.getId());
+ d.put("account_id", _o.getAccountId());
+ d.put("minute", _o.getMinute());
+ d.put("posted", DaoSerializer.toLong(_o.getPosted()));
+ return d;
+ }
+
+ @Override
+ public DirtyMinute fromDaoEntity(DaoEntity _d)
+ {
+ DirtyMinute o = new DirtyMinute();
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setMinute(DaoSerializer.getInteger(_d, "minute"));
+ o.setPosted(DaoSerializer.getDate(_d, "posted"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer
new file mode 100644
index 0000000..dfb31c6
--- /dev/null
+++ b/currentmonitor/lantern-dataaccess-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer
@@ -0,0 +1 @@
+com.lanternsoftware.dataaccess.currentmonitor.dao.DirtyMinuteSerializer
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/pom.xml b/currentmonitor/lantern-datamodel-currentmonitor/pom.xml
new file mode 100644
index 0000000..cf72be0
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/pom.xml
@@ -0,0 +1,73 @@
+
+ 4.0.0
+ com.lanternsoftware.currentmonitor
+ lantern-datamodel-currentmonitor
+ jar
+ 1.0.0
+ lantern-datamodel-currentmonitor
+
+
+ 1.8
+ 1.8
+
+
+
+
+ com.lanternsoftware.util
+ lantern-util-dao
+ 1.0.0
+
+
+
+
+
+ src/main/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.2
+
+
+
+ testCompile
+
+ compile
+
+
+
+ true
+ true
+ UTF-8
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 2.4
+
+
+ package
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.5
+
+
+ true
+
+
+
+
+
+
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java
new file mode 100644
index 0000000..a418213
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Account.java
@@ -0,0 +1,46 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+import com.lanternsoftware.util.dao.annotations.PrimaryKey;
+
+import java.util.List;
+
+@DBSerializable(autogen = false)
+public class Account {
+ @PrimaryKey private int id;
+ private String username;
+ private String password;
+ private List auxiliaryAccountIds;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int _id) {
+ id = _id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String _username) {
+ username = _username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String _password) {
+ password = _password;
+ }
+
+ public List getAuxiliaryAccountIds() {
+ return auxiliaryAccountIds;
+ }
+
+ public void setAuxiliaryAccountIds(List _auxiliaryAccountIds) {
+ auxiliaryAccountIds = _auxiliaryAccountIds;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java
new file mode 100644
index 0000000..fd5bf04
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/AuthCode.java
@@ -0,0 +1,49 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@DBSerializable(autogen = false)
+public class AuthCode {
+ private int accountId;
+ private List auxiliaryAccountIds;
+
+ public AuthCode() {
+ }
+
+ public AuthCode(int _accountId, List _auxiliaryAccountIds) {
+ accountId = _accountId;
+ auxiliaryAccountIds = _auxiliaryAccountIds;
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public List getAuxiliaryAccountIds() {
+ return auxiliaryAccountIds;
+ }
+
+ public void setAuxiliaryAccountIds(List _auxiliaryAccountIds) {
+ auxiliaryAccountIds = _auxiliaryAccountIds;
+ }
+
+ public List getAllAccountIds() {
+ List ids = new ArrayList<>();
+ ids.add(accountId);
+ if (auxiliaryAccountIds != null)
+ ids.addAll(auxiliaryAccountIds);
+ return ids;
+ }
+
+ public boolean isAuthorized(int _accountId) {
+ return accountId == _accountId || CollectionUtils.contains(auxiliaryAccountIds, _accountId);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java
new file mode 100644
index 0000000..ba456bd
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Breaker.java
@@ -0,0 +1,232 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+@DBSerializable()
+public class Breaker {
+ private static final int TANDEM_BREAKER_MASK = 3072;
+ private static final int SPACE_MASK = 1023;
+ private static final int TANDEM_BREAKER_A_MASK = 1024;
+ private static final int TANDEM_BREAKER_B_MASK = 2048;
+
+ private int panel;
+ private int space;
+ private int meter;
+ private int hub;
+ private int port;
+ private String name;
+ private String description;
+ private int sizeAmps;
+ private double calibrationFactor;
+ private double lowPassFilter;
+ private BreakerPolarity polarity;
+ private BreakerType type;
+ private transient String key;
+
+ public Breaker() {
+ }
+
+ public Breaker(String _name, int _panel, int _space, int _hub, int _port, int _sizeAmps, double _lowPassFilter) {
+ name = _name;
+ panel = _panel;
+ space = _space;
+ hub = _hub;
+ port = _port;
+ sizeAmps = _sizeAmps;
+ lowPassFilter = _lowPassFilter;
+ }
+
+ public int getPanel() {
+ return panel;
+ }
+
+ public void setPanel(int _panel) {
+ panel = _panel;
+ key = null;
+ }
+
+ public int getSpace() {
+ return space;
+ }
+
+ public void setSpace(int _space) {
+ space = _space;
+ key = null;
+ }
+
+ public int getMeter() {
+ return meter;
+ }
+
+ public void setMeter(int _meter) {
+ meter = _meter;
+ }
+
+ public String getSpaceDisplay() {
+ return toSpaceDisplay(space);
+ }
+
+ public void setSpaceTandemA(int _space) {
+ space = TANDEM_BREAKER_A_MASK | _space;
+ }
+
+ public void setSpaceTandemB(int _space) {
+ space = TANDEM_BREAKER_B_MASK | _space;
+ }
+
+ public boolean isTandemBreaker() {
+ return (TANDEM_BREAKER_MASK & space) != 0;
+ }
+
+ public boolean isTandemBreakerA() {
+ return isTandemBreakerA(space);
+ }
+
+ public boolean isTandemBreakerB() {
+ return isTandemBreakerB(space);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String _name) {
+ name = _name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String _description) {
+ description = _description;
+ }
+
+ public int getHub() {
+ return hub;
+ }
+
+ public void setHub(int _hub) {
+ hub = _hub;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int _port) {
+ port = _port;
+ }
+
+ public int getChip() {
+ return portToChip(port);
+ }
+
+ public int getPin() {
+ return portToPin(port);
+ }
+
+ public int getSizeAmps() {
+ return sizeAmps;
+ }
+
+ public void setSizeAmps(int _sizeAmps) {
+ sizeAmps = _sizeAmps;
+ }
+
+ public double getLowPassFilter() {
+ return lowPassFilter;
+ }
+
+ public void setLowPassFilter(double _lowPassFilter) {
+ lowPassFilter = _lowPassFilter;
+ }
+
+ public BreakerPolarity getPolarity() {
+ return polarity;
+ }
+
+ public void setPolarity(BreakerPolarity _polarity) {
+ polarity = _polarity;
+ }
+
+ public double getCalibrationFactor() {
+ return calibrationFactor == 0.0?1.0:calibrationFactor;
+ }
+
+ public void setCalibrationFactor(double _calibrationFactor) {
+ calibrationFactor = _calibrationFactor;
+ }
+
+ public BreakerType getType() {
+ if (type == null) {
+ if (isTandemBreaker())
+ return BreakerType.SINGLE_POLE_TANDEM;
+ return BreakerType.SINGLE_POLE;
+ }
+ return type;
+ }
+
+ public void setType(BreakerType _type) {
+ type = _type;
+ }
+
+ public double getFinalCalibrationFactor() {
+ return getCalibrationFactor() * getSizeAmps() / 380.0;
+ }
+
+ public String getKey() {
+ if (key == null)
+ key = key(panel, space);
+ return key;
+ }
+
+ public static String key(int _panel, int _space) {
+ return String.format("%d-%d", _panel, _space);
+ }
+
+ public static int portToChip(int _port) {
+ return (_port < 9)?1:0;
+ }
+
+ public static int portToPin(int _port) {
+ return (_port < 9)?_port-1:_port-8;
+ }
+
+ public static int toPort(int _chip, int _pin) {
+ return (_chip == 0)?_pin+8:_pin+1;
+ }
+
+ public static boolean isTandemBreakerA(int _space) {
+ return (TANDEM_BREAKER_A_MASK & _space) != 0;
+ }
+
+ public static boolean isTandemBreakerB(int _space) {
+ return (TANDEM_BREAKER_B_MASK & _space) != 0;
+ }
+
+ public static int toId(int _panel, int _space) {
+ return (_panel << 12) | _space;
+ }
+
+ public static int toPanel(int _id) {
+ return _id >> 12;
+ }
+
+ public static int toSpace(int _id) {
+ return _id & (TANDEM_BREAKER_MASK |SPACE_MASK);
+ }
+
+ public static String toSpaceDisplay(int _space) {
+ if (isTandemBreakerA(_space))
+ return String.format("%dA", _space & SPACE_MASK);
+ if (isTandemBreakerB(_space))
+ return String.format("%dB", _space & SPACE_MASK);
+ return String.valueOf(_space);
+ }
+
+ public int getSpaceIndex() {
+ return space & SPACE_MASK;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java
new file mode 100644
index 0000000..9452743
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerConfig.java
@@ -0,0 +1,162 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+import com.lanternsoftware.util.dao.annotations.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@DBSerializable(autogen = false)
+public class BreakerConfig {
+ @PrimaryKey
+ private int accountId;
+ private List meters;
+ private List panels;
+ private List breakerHubs;
+ private List breakerGroups;
+
+ public BreakerConfig() {
+ }
+
+ public BreakerConfig(List _breakerGroups) {
+ breakerGroups = _breakerGroups;
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public List getMeters() {
+ return meters;
+ }
+
+ public void setMeters(List _meters) {
+ meters = _meters;
+ }
+
+ public List getPanels() {
+ return panels;
+ }
+
+ public void setPanels(List _panels) {
+ panels = _panels;
+ }
+
+ public List getBreakerHubs() {
+ return breakerHubs;
+ }
+
+ public void setBreakerHubs(List _breakerHubs) {
+ breakerHubs = _breakerHubs;
+ }
+
+ public List getBreakerGroups() {
+ return breakerGroups;
+ }
+
+ public void setBreakerGroups(List _breakerGroups) {
+ breakerGroups = _breakerGroups;
+ }
+
+ public List getAllBreakers() {
+ List allBreakers = new ArrayList<>();
+ for (BreakerGroup g : CollectionUtils.makeNotNull(breakerGroups)) {
+ allBreakers.addAll(g.getAllBreakers());
+ }
+ return allBreakers;
+ }
+
+ public List getAllBreakerGroups() {
+ List groups = new ArrayList<>();
+ for (BreakerGroup g : CollectionUtils.makeNotNull(breakerGroups)) {
+ groups.addAll(g.getAllBreakerGroups());
+ }
+ return groups;
+ }
+
+ public List getAllBreakerGroupIds() {
+ List ids = new ArrayList<>();
+ for (BreakerGroup g : CollectionUtils.makeNotNull(breakerGroups)) {
+ ids.addAll(g.getAllBreakerGroupIds());
+ }
+ return ids;
+ }
+
+ public List getBreakersForHub(int _hub) {
+ return CollectionUtils.filter(getAllBreakers(), _b -> _b.getHub() == _hub);
+ }
+
+ public BreakerHub getHub(int _hub) {
+ return CollectionUtils.filterOne(breakerHubs, _h->_h.getHub() == _hub);
+ }
+
+ public boolean isSolarConfigured() {
+ return CollectionUtils.anyQualify(getAllBreakers(), _b->_b.getPolarity() == BreakerPolarity.SOLAR);
+ }
+
+ public String nextGroupId() {
+ List ids = CollectionUtils.transform(getAllBreakerGroupIds(), NullUtils::toInteger);
+ return String.valueOf(CollectionUtils.getLargest(ids) + 1);
+ }
+
+ public void addGroup(BreakerGroup _group) {
+ if (NullUtils.isEmpty(_group.getId())) {
+ _group.setId(nextGroupId());
+ }
+ if (breakerGroups == null)
+ breakerGroups = new ArrayList<>();
+ breakerGroups.add(_group);
+ }
+
+ public void removeInvalidGroups() {
+ if (breakerGroups != null)
+ breakerGroups.removeIf(_g->!_g.removeInvalidGroups());
+ }
+
+ public String getGroupIdForBreaker(Breaker _breaker) {
+ return getGroupIdForBreaker(_breaker.getKey());
+ }
+
+ public String getGroupIdForBreaker(int _panel, int _space) {
+ return getGroupIdForBreaker(Breaker.key(_panel, _space));
+ }
+
+ public String getGroupIdForBreaker(String _breakerKey) {
+ BreakerGroup group = getGroupForBreaker(_breakerKey);
+ return group != null ? group.getId() : null;
+ }
+
+ public BreakerGroup getGroupForBreaker(Breaker _breaker) {
+ return getGroupForBreaker(_breaker.getKey());
+ }
+
+ public BreakerGroup getGroupForBreaker(int _panel, int _space) {
+ return getGroupForBreaker(Breaker.key(_panel, _space));
+ }
+
+ public BreakerGroup getGroupForBreaker(String _breakerKey) {
+ if (_breakerKey == null)
+ return null;
+ for (BreakerGroup subGroup : CollectionUtils.makeNotNull(breakerGroups)) {
+ BreakerGroup group = subGroup.getGroupForBreaker(_breakerKey);
+ if (group != null)
+ return group;
+ }
+ return null;
+ }
+
+ public BreakerGroup findParentGroup(BreakerGroup _group) {
+ for (BreakerGroup group : CollectionUtils.makeNotNull(breakerGroups)) {
+ BreakerGroup parent = group.findParentGroup(_group);
+ if (parent != null)
+ return parent;
+ }
+ return null;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java
new file mode 100644
index 0000000..654411b
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroup.java
@@ -0,0 +1,192 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+import com.lanternsoftware.util.dao.annotations.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+@DBSerializable()
+public class BreakerGroup {
+ @PrimaryKey private String id;
+ private int accountId;
+ private String name;
+ private List subGroups;
+ private List breakers;
+
+ public BreakerGroup() {
+ }
+
+ public BreakerGroup(String _id, String _name, List _breakers) {
+ id = _id;
+ name = _name;
+ breakers = _breakers;
+ }
+
+ public BreakerGroup(String _id, String _name, List _subGroups, List _breakers) {
+ id = _id;
+ name = _name;
+ subGroups = _subGroups;
+ breakers = _breakers;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String _id) {
+ id = _id;
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String _name) {
+ name = _name;
+ }
+
+ public List getSubGroups() {
+ return subGroups;
+ }
+
+ public void setSubGroups(List _subGroups) {
+ subGroups = _subGroups;
+ }
+
+ public List getBreakers() {
+ return breakers;
+ }
+
+ public void setBreakers(List _breakers) {
+ breakers = _breakers;
+ }
+
+ public List getAllBreakers() {
+ List allBreakers = new ArrayList<>();
+ getAllBreakers(allBreakers);
+ return allBreakers;
+ }
+
+ private void getAllBreakers(List _breakers) {
+ if (breakers != null)
+ _breakers.addAll(breakers);
+ for (BreakerGroup group : CollectionUtils.makeNotNull(subGroups)) {
+ group.getAllBreakers(_breakers);
+ }
+ }
+
+ public List getAllBreakerKeys() {
+ return CollectionUtils.transform(getAllBreakers(), Breaker::getKey);
+ }
+
+ public List getAllBreakerGroups() {
+ List allGroups = new ArrayList<>();
+ getAllBreakerGroups(allGroups);
+ return allGroups;
+ }
+
+ private void getAllBreakerGroups(List _groups) {
+ _groups.add(this);
+ for (BreakerGroup group : CollectionUtils.makeNotNull(subGroups)) {
+ group.getAllBreakerGroups(_groups);
+ }
+ }
+
+ public List getAllBreakerGroupIds() {
+ return CollectionUtils.transform(getAllBreakerGroups(), BreakerGroup::getId);
+ }
+
+ public Breaker getBreaker(String _breakerKey) {
+ for (Breaker b : CollectionUtils.makeNotNull(breakers)) {
+ if (NullUtils.isEqual(b.getKey(), _breakerKey))
+ return b;
+ }
+ for (BreakerGroup group : CollectionUtils.makeNotNull(subGroups)) {
+ Breaker b = group.getBreaker(_breakerKey);
+ if (b != null)
+ return b;
+ }
+ return null;
+ }
+
+ public String getGroupIdForBreaker(Breaker _breaker) {
+ return getGroupIdForBreaker(_breaker.getKey());
+ }
+
+ public String getGroupIdForBreaker(int _panel, int _space) {
+ return getGroupIdForBreaker(Breaker.key(_panel, _space));
+ }
+
+ public String getGroupIdForBreaker(String _breakerKey) {
+ BreakerGroup group = getGroupForBreaker(_breakerKey);
+ return group != null ? group.getId() : null;
+ }
+
+ public BreakerGroup getGroupForBreaker(Breaker _breaker) {
+ return getGroupForBreaker(_breaker.getKey());
+ }
+
+ public BreakerGroup getGroupForBreaker(int _panel, int _space) {
+ return getGroupForBreaker(Breaker.key(_panel, _space));
+ }
+
+ public BreakerGroup getGroupForBreaker(String _breakerKey) {
+ if (_breakerKey == null)
+ return null;
+ Breaker b = CollectionUtils.filterOne(breakers, _b->_breakerKey.equals(_b.getKey()));
+ if (b != null)
+ return this;
+ for (BreakerGroup subGroup : CollectionUtils.makeNotNull(subGroups)) {
+ BreakerGroup group = subGroup.getGroupForBreaker(_breakerKey);
+ if (group != null)
+ return group;
+ }
+ return null;
+ }
+
+ public BreakerGroup findParentGroup(BreakerGroup _group) {
+ if (CollectionUtils.contains(subGroups, _group))
+ return this;
+ for (BreakerGroup subGroup : CollectionUtils.makeNotNull(subGroups)) {
+ BreakerGroup parent = subGroup.findParentGroup(_group);
+ if (parent != null)
+ return parent;
+ }
+ return null;
+ }
+
+ public boolean removeInvalidGroups() {
+ if (subGroups != null)
+ subGroups.removeIf(_g->!_g.removeInvalidGroups());
+ if (breakers != null)
+ breakers.removeIf(_b->(_b.getType() == null) || (_b.getType() == BreakerType.EMPTY) || (_b.getPort() < 1));
+ return CollectionUtils.isNotEmpty(subGroups) || CollectionUtils.isNotEmpty(breakers);
+ }
+
+ @Override
+ public boolean equals(Object _o) {
+ if (this == _o) return true;
+ if (_o == null || getClass() != _o.getClass()) return false;
+ BreakerGroup that = (BreakerGroup) _o;
+ return Objects.equals(id, that.id);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java
new file mode 100644
index 0000000..943bc64
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupEnergy.java
@@ -0,0 +1,337 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeMap;
+
+@DBSerializable(autogen = false)
+public class BreakerGroupEnergy {
+ private int accountId;
+ private String groupId;
+ private String groupName;
+ private EnergyBlockViewMode viewMode;
+ private Date start;
+ private List subGroups;
+ private List energyBlocks;
+ private double toGrid;
+ private double fromGrid;
+
+ public BreakerGroupEnergy() {
+ }
+
+ public BreakerGroupEnergy(BreakerGroup _group, Map> _powerReadings, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) {
+ groupId = _group.getId();
+ groupName = _group.getName();
+ viewMode = _viewMode;
+ start = _start;
+ accountId = _group.getAccountId();
+ subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, _powerReadings, _viewMode, _start, _tz));
+ energyBlocks = new ArrayList<>();
+ List breakerKeys = CollectionUtils.transform(_group.getBreakers(), Breaker::getKey);
+ if (!breakerKeys.isEmpty()) {
+ for (BreakerPower power : CollectionUtils.aggregate(breakerKeys, _powerReadings::get)) {
+ addEnergy(groupId, power.getReadTime(), power.getPower(), _tz);
+ }
+ }
+ }
+
+ public BreakerGroupEnergy(BreakerGroup _group, List _power, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) {
+ groupId = _group.getId();
+ groupName = _group.getName();
+ viewMode = _viewMode;
+ start = _start;
+ accountId = _group.getAccountId();
+ subGroups = CollectionUtils.transform(_group.getSubGroups(), _g -> new BreakerGroupEnergy(_g, (List)null, _viewMode, _start, _tz));
+ energyBlocks = new ArrayList<>();
+ addEnergy(_group, _power, _tz);
+ }
+
+ public void addEnergy(BreakerGroup _group, List _hubPower, TimeZone _tz) {
+ Map breakers = CollectionUtils.transformToMap(_group.getAllBreakers(), Breaker::getKey);
+ Map breakerKeyToGroup = new HashMap<>();
+ for (BreakerGroup group : _group.getAllBreakerGroups()) {
+ for (Breaker b : group.getAllBreakers()) {
+ breakerKeyToGroup.put(b.getKey(), group);
+ }
+ }
+ addEnergy(breakers, breakerKeyToGroup, _hubPower, _tz);
+ }
+
+ public void addEnergy(Map _breakers, Map _breakerKeyToGroup, List _hubPower, TimeZone _tz) {
+ if (CollectionUtils.isEmpty(_hubPower) || CollectionUtils.anyQualify(_hubPower, _p->_p.getAccountId() != accountId))
+ return;
+ Date minute = CollectionUtils.getFirst(_hubPower).getMinuteAsDate();
+ resetEnergy(minute, _tz);
+ Map meters = new HashMap<>();
+ for (HubPowerMinute hubPower : _hubPower) {
+ for (BreakerPowerMinute breaker : CollectionUtils.makeNotNull(hubPower.getBreakers())) {
+ Breaker b = _breakers.get(breaker.breakerKey());
+ if (b == null)
+ continue;
+ BreakerGroup group = _breakerKeyToGroup.get(breaker.breakerKey());
+ if (group == null)
+ continue;
+ MeterMinute meter = meters.computeIfAbsent(b.getMeter(), _p->new MeterMinute());
+ int idx = 0;
+ for (Float power : CollectionUtils.makeNotNull(breaker.getReadings())) {
+ if (power > 0)
+ meter.usage[idx] += power;
+ else
+ meter.solar[idx] += -power;
+ if (power != 0.0)
+ addEnergy(group.getId(), minute, power, _tz);
+ idx++;
+ }
+ }
+ }
+
+ for (MeterMinute meter : meters.values()) {
+ for (int i = 0; i < 60; i++) {
+ if (meter.usage[i] > meter.solar[i])
+ fromGrid += meter.usage[i] - meter.solar[i];
+ else
+ toGrid += meter.solar[i] - meter.usage[i];
+ }
+ }
+ }
+
+ public void resetEnergy(Date _readTime, TimeZone _tz) {
+ EnergyBlock block = getBlock(_readTime, _tz, false);
+ if (block != null)
+ block.setJoules(0);
+ for (BreakerGroupEnergy subGroup : CollectionUtils.makeNotNull(subGroups)) {
+ subGroup.resetEnergy(_readTime, _tz);
+ }
+ }
+
+ public void addEnergy(String _groupId, Date _readTime, double _joules, TimeZone _tz) {
+ if (NullUtils.isEqual(groupId, _groupId))
+ getBlock(_readTime, _tz).addJoules(_joules);
+ else {
+ for (BreakerGroupEnergy subGroup : CollectionUtils.makeNotNull(subGroups)) {
+ subGroup.addEnergy(_groupId, _readTime, _joules, _tz);
+ }
+ }
+ }
+
+ public static BreakerGroupEnergy summary(BreakerGroup _group, Map> _energies, EnergyBlockViewMode _viewMode, Date _start, TimeZone _tz) {
+ BreakerGroupEnergy energy = new BreakerGroupEnergy();
+ energy.setGroupId(_group.getId());
+ energy.setGroupName(_group.getName());
+ energy.setAccountId(_group.getAccountId());
+ energy.setViewMode(_viewMode);
+ energy.setStart(_start);
+ energy.setSubGroups(CollectionUtils.transform(_group.getSubGroups(), _g -> BreakerGroupEnergy.summary(_g, _energies, _viewMode, _start, _tz)));
+ for (BreakerGroupSummary curEnergy : CollectionUtils.makeNotNull(_energies.get(_group.getId()))) {
+ EnergyBlock block = energy.getBlock(curEnergy.getStart(), _tz);
+ block.addJoules(curEnergy.getJoules());
+ energy.setToGrid(energy.getToGrid()+curEnergy.getToGrid());
+ energy.setFromGrid(energy.getFromGrid()+curEnergy.getFromGrid());
+ }
+ return energy;
+ }
+
+ private EnergyBlock getBlock(Date _readTime, TimeZone _tz) {
+ return getBlock(_readTime, _tz, true);
+ }
+
+ private EnergyBlock getBlock(Date _readTime, TimeZone _tz, boolean _add) {
+ int size = CollectionUtils.size(energyBlocks);
+ int idx = viewMode.blockIndex(_readTime, _tz);
+ if (_add && (idx >= size)) {
+ if (energyBlocks == null)
+ energyBlocks = new ArrayList<>();
+ LinkedList newBlocks = new LinkedList<>();
+ Date end = viewMode.toBlockEnd(_readTime, _tz);
+ while (idx >= size) {
+ Date start = viewMode.decrementBlock(end, _tz);
+ newBlocks.add(new EnergyBlock(start, end, 0));
+ end = start;
+ size++;
+ }
+ Iterator iter = newBlocks.descendingIterator();
+ while (iter.hasNext()) {
+ energyBlocks.add(iter.next());
+ }
+ }
+ return CollectionUtils.get(energyBlocks, idx);
+ }
+
+ public String getId() {
+ return toId(accountId, groupId, viewMode, start);
+ }
+
+ public static String toId(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start) {
+ return _accountId + "-" + _groupId + "-" + DaoSerializer.toEnumName(_viewMode) + "-" + _start.getTime();
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String _groupId) {
+ groupId = _groupId;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public void setGroupName(String _groupName) {
+ groupName = _groupName;
+ }
+
+ public BreakerGroupEnergy getSubGroup(String _groupId) {
+ return CollectionUtils.filterOne(subGroups, _g->_groupId.equals(_g.getGroupId()));
+ }
+
+ public List getSubGroups() {
+ return subGroups;
+ }
+
+ public EnergyBlockViewMode getViewMode() {
+ return viewMode;
+ }
+
+ public void setViewMode(EnergyBlockViewMode _viewMode) {
+ viewMode = _viewMode;
+ }
+
+ public Date getStart() {
+ return start;
+ }
+
+ public void setStart(Date _start) {
+ start = _start;
+ }
+
+ public void setSubGroups(List _subGroups) {
+ subGroups = _subGroups;
+ }
+
+ public List getEnergyBlocks() {
+ return energyBlocks;
+ }
+
+ public void setEnergyBlocks(List _energyBlocks) {
+ energyBlocks = _energyBlocks;
+ }
+
+ public double getToGrid() {
+ return toGrid;
+ }
+
+ public void setToGrid(double _toGrid) {
+ toGrid = _toGrid;
+ }
+
+ public double getFromGrid() {
+ return fromGrid;
+ }
+
+ public void setFromGrid(double _fromGrid) {
+ fromGrid = _fromGrid;
+ }
+
+ public double wattHours() {
+ return joules() / 3600;
+ }
+
+ public double wattHours(Set _selectedBreakers) {
+ return joules(_selectedBreakers) / 3600;
+ }
+
+ public double joules() {
+ return joules(null);
+ }
+
+ public double joules(Set _selectedBreakers) {
+ return joules(_selectedBreakers, true);
+ }
+
+ public double joules(Set _selectedBreakers, boolean _includeSubgroups) {
+ double joules = 0.0;
+ if (_includeSubgroups) {
+ for (BreakerGroupEnergy group : CollectionUtils.makeNotNull(subGroups)) {
+ joules += group.joules(_selectedBreakers);
+ }
+ }
+ if ((energyBlocks != null) && ((_selectedBreakers == null) || _selectedBreakers.contains(getGroupId()))) {
+ for (EnergyBlock energy : energyBlocks) {
+ joules += energy.getJoules();
+ }
+ }
+ return joules;
+ }
+
+ public List getAllGroups() {
+ Map groups = new TreeMap<>();
+ getAllGroups(groups);
+ return new ArrayList<>(groups.values());
+ }
+
+ public void getAllGroups(Map _groups) {
+ _groups.put(getGroupId(), this);
+ for (BreakerGroupEnergy group : CollectionUtils.makeNotNull(subGroups)) {
+ group.getAllGroups(_groups);
+ }
+ }
+
+ public List getAllEnergyBlocks() {
+ return getAllEnergyBlocks(null);
+ }
+
+ public List getAllEnergyBlocks(Set _selectedGroups) {
+ return getAllEnergyBlocks(_selectedGroups, EnergyBlockType.ANY);
+ }
+
+ public List getAllEnergyBlocks(Set _selectedGroups, EnergyBlockType _type) {
+ Map blocks = new TreeMap<>();
+ getAllEnergyBlocks(_selectedGroups, blocks, _type);
+ return new ArrayList<>(blocks.values());
+ }
+
+ private void getAllEnergyBlocks(Set _selectedGroups, Map _energyBlocks, EnergyBlockType _type) {
+ if ((energyBlocks != null) && ((_selectedGroups == null) || _selectedGroups.contains(getGroupId()))) {
+ for (EnergyBlock block : energyBlocks) {
+ if ((_type == EnergyBlockType.ANY) || ((_type == EnergyBlockType.POSITIVE) && block.getJoules() >= 0.0) || ((_type == EnergyBlockType.NEGATIVE) && block.getJoules() <= 0.0)) {
+ EnergyBlock b = _energyBlocks.get(block.getStart().getTime());
+ if (b == null) {
+ b = new EnergyBlock(block.getStart(), block.getEnd(), block.getJoules());
+ _energyBlocks.put(block.getStart().getTime(), b);
+ } else
+ b.addJoules(block.getJoules());
+ }
+ }
+ }
+ for (BreakerGroupEnergy group : CollectionUtils.makeNotNull(subGroups)) {
+ group.getAllEnergyBlocks(_selectedGroups, _energyBlocks, _type);
+ }
+ }
+
+ private static class MeterMinute {
+ public double[] usage = new double[60];
+ public double[] solar = new double[60];
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java
new file mode 100644
index 0000000..b881da2
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerGroupSummary.java
@@ -0,0 +1,139 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+@DBSerializable(autogen = false)
+public class BreakerGroupSummary {
+ private int accountId;
+ private String groupId;
+ private String groupName;
+ private EnergyBlockViewMode viewMode;
+ private Date start;
+ private List subGroups;
+ private double joules;
+ private double toGrid;
+ private double fromGrid;
+
+ public BreakerGroupSummary() {
+ }
+
+ public BreakerGroupSummary(BreakerGroupEnergy _energy) {
+ accountId = _energy.getAccountId();
+ groupId = _energy.getGroupId();
+ groupName = _energy.getGroupName();
+ viewMode = _energy.getViewMode();
+ start = _energy.getStart();
+ subGroups = CollectionUtils.transform(_energy.getSubGroups(), BreakerGroupSummary::new);
+ joules = _energy.joules(null, false);
+ toGrid = _energy.getToGrid();
+ fromGrid = _energy.getFromGrid();
+ }
+
+ public String getId() {
+ return toId(accountId, groupId, viewMode, start);
+ }
+
+ public static String toId(int _accountId, String _groupId, EnergyBlockViewMode _viewMode, Date _start) {
+ return _accountId + "-" + _groupId + "-" + DaoSerializer.toEnumName(_viewMode) + "-" + _start.getTime();
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String _groupId) {
+ groupId = _groupId;
+ }
+
+ public String getGroupName() {
+ return groupName;
+ }
+
+ public void setGroupName(String _groupName) {
+ groupName = _groupName;
+ }
+
+ public BreakerGroupSummary getSubGroup(String _groupId) {
+ return CollectionUtils.filterOne(subGroups, _g->_groupId.equals(_g.getGroupId()));
+ }
+
+ public List getSubGroups() {
+ return subGroups;
+ }
+
+ public EnergyBlockViewMode getViewMode() {
+ return viewMode;
+ }
+
+ public void setViewMode(EnergyBlockViewMode _viewMode) {
+ viewMode = _viewMode;
+ }
+
+ public Date getStart() {
+ return start;
+ }
+
+ public void setStart(Date _start) {
+ start = _start;
+ }
+
+ public void setSubGroups(List _subGroups) {
+ subGroups = _subGroups;
+ }
+
+ public double getJoules() {
+ return joules;
+ }
+
+ public void setJoules(double _joules) {
+ joules = _joules;
+ }
+
+ public double getToGrid() {
+ return toGrid;
+ }
+
+ public void setToGrid(double _toGrid) {
+ toGrid = _toGrid;
+ }
+
+ public double getFromGrid() {
+ return fromGrid;
+ }
+
+ public void setFromGrid(double _fromGrid) {
+ fromGrid = _fromGrid;
+ }
+
+ public List getAllGroups() {
+ Map groups = new TreeMap<>();
+ getAllGroups(groups);
+ return new ArrayList<>(groups.values());
+ }
+
+ public void getAllGroups(Map _groups) {
+ if (NullUtils.isNotEmpty(getGroupId()))
+ _groups.put(getGroupId(), this);
+ for (BreakerGroupSummary group : CollectionUtils.makeNotNull(subGroups)) {
+ group.getAllGroups(_groups);
+ }
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java
new file mode 100644
index 0000000..7c9a51a
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerHub.java
@@ -0,0 +1,44 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+@DBSerializable
+public class BreakerHub {
+ private int hub;
+ private double voltageCalibrationFactor;
+ private int frequency;
+ private String bluetoothMac;
+
+ public int getHub() {
+ return hub;
+ }
+
+ public void setHub(int _hub) {
+ hub = _hub;
+ }
+
+ public double getVoltageCalibrationFactor() {
+ return voltageCalibrationFactor;
+ }
+
+ public void setVoltageCalibrationFactor(double _voltageCalibrationFactor) {
+ voltageCalibrationFactor = _voltageCalibrationFactor;
+ }
+
+ public int getFrequency() {
+ return frequency;
+ }
+
+ public void setFrequency(int _frequency) {
+ frequency = _frequency;
+ }
+
+ public String getBluetoothMac() {
+ return bluetoothMac;
+ }
+
+ public void setBluetoothMac(String _bluetoothMac) {
+ bluetoothMac = _bluetoothMac;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java
new file mode 100644
index 0000000..a24ac82
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPanel.java
@@ -0,0 +1,52 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+@DBSerializable
+public class BreakerPanel {
+ private int accountId;
+ private String name;
+ private int index;
+ private int spaces;
+ private int meter;
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String _name) {
+ name = _name;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int _index) {
+ index = _index;
+ }
+
+ public int getSpaces() {
+ return spaces;
+ }
+
+ public void setSpaces(int _spaces) {
+ spaces = _spaces;
+ }
+
+ public int getMeter() {
+ return meter;
+ }
+
+ public void setMeter(int _meter) {
+ meter = _meter;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java
new file mode 100644
index 0000000..13fe60b
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPolarity.java
@@ -0,0 +1,6 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+public enum BreakerPolarity {
+ NORMAL,
+ SOLAR;
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java
new file mode 100644
index 0000000..66af06f
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPower.java
@@ -0,0 +1,92 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.Date;
+
+@DBSerializable(autogen = false)
+public class BreakerPower {
+ private int accountId;
+ private int panel;
+ private int space;
+ private Date readTime;
+ private String hubVersion;
+ private double power;
+ private double voltage;
+
+ public BreakerPower() {
+ }
+
+ public BreakerPower(int _panel, int _space, Date _readTime, double _power, double _voltage) {
+ panel = _panel;
+ space = _space;
+ readTime = _readTime;
+ power = _power;
+ voltage = _voltage;
+ }
+
+ public String getId() {
+ return String.format("%d-%d-%d", accountId, panel, space);
+ }
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ 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 Date getReadTime() {
+ return readTime;
+ }
+
+ public void setReadTime(Date _readTime) {
+ readTime = _readTime;
+ }
+
+ public String getHubVersion() {
+ return hubVersion;
+ }
+
+ public void setHubVersion(String _hubVersion) {
+ hubVersion = _hubVersion;
+ }
+
+ public double getPower() {
+ return power;
+ }
+
+ public void setPower(double _power) {
+ power = _power;
+ }
+
+ public double getVoltage() {
+ return voltage;
+ }
+
+ public void setVoltage(double _voltage) {
+ voltage = _voltage;
+ }
+
+ public String getKey() {
+ return Breaker.key(panel, space);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java
new file mode 100644
index 0000000..a995e2b
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerPowerMinute.java
@@ -0,0 +1,40 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.List;
+
+@DBSerializable(autogen = false)
+public class BreakerPowerMinute {
+ private int panel;
+ private int space;
+ private List readings;
+
+ 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 String breakerKey() {
+ return Breaker.key(panel, space);
+ }
+
+ public List getReadings() {
+ return readings;
+ }
+
+ public void setReadings(List _readings) {
+ readings = _readings;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java
new file mode 100644
index 0000000..c07b03a
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/BreakerType.java
@@ -0,0 +1,36 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+
+import java.util.List;
+
+public enum BreakerType {
+ EMPTY("Empty"),
+ SINGLE_POLE("Single Pole"),
+ SINGLE_POLE_TANDEM("Single Pole Tandem (Two Breakers in One)"),
+ DOUBLE_POLE_TOP("Double Pole (240V)"),
+ DOUBLE_POLE_BOTTOM("Double Pole (240V)");
+
+ private final String display;
+
+ BreakerType(String _display) {
+ display = _display;
+ }
+
+ public String getDisplay() {
+ return display;
+ }
+
+ public static List selectable() {
+ return CollectionUtils.asArrayList(EMPTY, SINGLE_POLE, SINGLE_POLE_TANDEM, DOUBLE_POLE_TOP);
+ }
+
+ public static BreakerType fromDisplay(String _display) {
+ for (BreakerType type : values()) {
+ if (NullUtils.isEqual(_display, type.getDisplay()))
+ return type;
+ }
+ return null;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java
new file mode 100644
index 0000000..fb8cad7
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/CharacteristicFlag.java
@@ -0,0 +1,31 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.CollectionUtils;
+
+import java.util.Collection;
+
+public enum CharacteristicFlag {
+ BROADCAST("broadcast"),
+ READ("read"),
+ WRITE_WITHOUT_RESPONSE("write-without-response"),
+ WRITE("write"),
+ NOTIFY("notify"),
+ INDICATE("indicate"),
+ AUTHENTICATED_SIGNED_WRITES("authenticated-signed-writes"),
+ RELIABLE_WRITE("reliable-write"),
+ WRITABLE_AUXILIARIES("writable-auxiliaries"),
+ ENCRYPT_READ("encrypt-read"),
+ ENCRYPT_WRITE("encrypt-write"),
+ ENCRYPT_AUTHENTICATED_READ("encrypt-authenticated-read"),
+ ENCRYPT_AUTHENTICATED_WRITE("encrypt-authenticated-write");
+
+ CharacteristicFlag(String _value) {
+ value = _value;
+ }
+
+ public final String value;
+
+ public static String[] toArray(Collection _flags) {
+ return CollectionUtils.transform(_flags, _c->_c.value).toArray(new String[0]);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java
new file mode 100644
index 0000000..188ee26
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlock.java
@@ -0,0 +1,60 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.Date;
+
+@DBSerializable
+public class EnergyBlock {
+ private Date start;
+ private Date end;
+ private double joules;
+
+ public EnergyBlock() {
+ }
+
+ public EnergyBlock(Date _start, Date _end, double _joules) {
+ start = _start;
+ end = _end;
+ joules = _joules;
+ }
+
+ public Date getStart() {
+ return start;
+ }
+
+ public void setStart(Date _start) {
+ start = _start;
+ }
+
+ public Date getEnd() {
+ return end;
+ }
+
+ public void setEnd(Date _end) {
+ end = _end;
+ }
+
+ public double getJoules() {
+ return joules;
+ }
+
+ public void addJoules(double _joules) {
+ joules += _joules;
+ }
+
+ public void setJoules(double _joules) {
+ joules = _joules;
+ }
+
+ public double wattHours() {
+ return joules / 3600;
+ }
+
+ public double getAveragePower() {
+ if ((end == null) || (start == null))
+ return 0;
+ return 1000*joules/(end.getTime()-start.getTime());
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java
new file mode 100644
index 0000000..24eddad
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockType.java
@@ -0,0 +1,7 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+public enum EnergyBlockType {
+ ANY,
+ POSITIVE,
+ NEGATIVE
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java
new file mode 100644
index 0000000..74f7a38
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/EnergyBlockViewMode.java
@@ -0,0 +1,144 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.DateUtils;
+
+import javax.management.remote.rmi._RMIConnection_Stub;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+public enum EnergyBlockViewMode {
+ DAY,
+ MONTH,
+ YEAR,
+ ALL;
+
+ public Date toStart(Date _dt, TimeZone _tz) {
+ if (this == DAY)
+ return DateUtils.getMidnightBefore(_dt, _tz);
+ if (this == MONTH)
+ return DateUtils.getStartOfMonth(_dt, _tz);
+ if (this == YEAR)
+ return DateUtils.getStartOfYear(_dt, _tz);
+ return new Date(0);
+ }
+
+ public Date toEnd(Date _dt, TimeZone _tz) {
+ if (this == DAY)
+ return DateUtils.getMidnightAfter(_dt, _tz);
+ if (this == MONTH)
+ return DateUtils.getEndOfMonth(_dt, _tz);
+ if (this == YEAR)
+ return DateUtils.getEndOfYear(_dt, _tz);
+ return new Date(0);
+ }
+
+ public Date toBlockStart(Date _dt, TimeZone _tz) {
+ if (this == DAY)
+ return DateUtils.getStartOfMinute(_dt, _tz);
+ if (this == MONTH)
+ return DateUtils.getMidnightBefore(_dt, _tz);
+ if (this == YEAR)
+ return DateUtils.getStartOfMonth(_dt, _tz);
+ return new Date(0);
+ }
+
+ public Date toBlockEnd(Date _dt, TimeZone _tz) {
+ if (this == DAY)
+ return DateUtils.getEndOfMinute(_dt, _tz);
+ if (this == MONTH)
+ return DateUtils.getMidnightAfter(_dt, _tz);
+ if (this == YEAR)
+ return DateUtils.getEndOfMonth(_dt, _tz);
+ return new Date(0);
+ }
+
+ public Date incrementBlock(Date _dt, TimeZone _tz) {
+ Calendar cal = DateUtils.toCalendar(_dt, _tz);
+ if (this == DAY)
+ cal.add(Calendar.MINUTE, 1);
+ else if (this == MONTH)
+ cal.add(Calendar.DAY_OF_YEAR, 1);
+ if (this == YEAR)
+ cal.add(Calendar.MONTH, 1);
+ return cal.getTime();
+ }
+
+ public Date decrementBlock(Date _dt, TimeZone _tz) {
+ Calendar cal = DateUtils.toCalendar(_dt, _tz);
+ if (this == DAY)
+ cal.add(Calendar.MINUTE, -1);
+ else if (this == MONTH)
+ cal.add(Calendar.DAY_OF_YEAR, -1);
+ if (this == YEAR)
+ cal.add(Calendar.MONTH, -1);
+ return cal.getTime();
+ }
+
+ public Date incrementView(Date _dt, TimeZone _tz) {
+ Calendar cal = DateUtils.toCalendar(_dt, _tz);
+ if (this == DAY)
+ cal.add(Calendar.DAY_OF_YEAR, 1);
+ else if (this == MONTH)
+ cal.add(Calendar.MONTH, 1);
+ if (this == YEAR)
+ cal.add(Calendar.YEAR, 1);
+ return cal.getTime();
+ }
+
+ public Date decrementView(Date _dt, TimeZone _tz) {
+ Calendar cal = DateUtils.toCalendar(_dt, _tz);
+ if (this == DAY)
+ cal.add(Calendar.DAY_OF_YEAR, -1);
+ else if (this == MONTH)
+ cal.add(Calendar.MONTH, -1);
+ if (this == YEAR)
+ cal.add(Calendar.YEAR, -1);
+ return cal.getTime();
+ }
+
+ public int blockCount(Date _start, TimeZone _tz) {
+ if (this == ALL)
+ return 1;
+ Date end = toEnd(_start, _tz);
+ int blockCnt = 0;
+ while (_start.before(end)) {
+ blockCnt++;
+ _start = toBlockEnd(_start, _tz);
+ }
+ return blockCnt;
+ }
+
+ public int blockIndex(Date _readTime, TimeZone _tz) {
+ if (this == DAY) {
+ Date start = DateUtils.getMidnightBefore(_readTime, _tz);
+ return (int)((_readTime.getTime() - start.getTime())/60000);
+ }
+ else if (this == MONTH) {
+ Calendar read = DateUtils.toCalendar(_readTime, _tz);
+ return read.get(Calendar.DAY_OF_MONTH) - 1;
+ }
+ if (this == YEAR) {
+ Calendar read = DateUtils.toCalendar(_readTime, _tz);
+ return read.get(Calendar.MONTH);
+ }
+ return 0;
+ }
+
+ public Date toBlockStart(int _index, Date _start, TimeZone _tz) {
+ if (this == DAY)
+ return new Date(_start.getTime() + _index*60000);
+ else if (this == MONTH) {
+ Calendar read = DateUtils.toCalendar(_start, _tz);
+ read.add(Calendar.DAY_OF_MONTH, _index);
+ return read.getTime();
+ }
+ if (this == YEAR) {
+ Calendar read = DateUtils.toCalendar(_start, _tz);
+ read.add(Calendar.MONTH, _index);
+ return read.getTime();
+ }
+ return new Date(0);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java
new file mode 100644
index 0000000..b58eea9
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigCharacteristic.java
@@ -0,0 +1,44 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.NullUtils;
+
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.UUID;
+
+public enum HubConfigCharacteristic {
+ WifiCredentials(2, CharacteristicFlag.WRITE),
+ AuthCode(3, CharacteristicFlag.WRITE),
+ HubIndex(4, CharacteristicFlag.READ, CharacteristicFlag.WRITE),
+ Restart(5, CharacteristicFlag.WRITE),
+ Reboot(6, CharacteristicFlag.WRITE),
+ AccountId(7, CharacteristicFlag.READ),
+ NetworkState(8, CharacteristicFlag.READ),
+ Flash(9, CharacteristicFlag.WRITE);
+
+ public final int idx;
+ public final UUID uuid;
+ public final EnumSet flags;
+
+ HubConfigCharacteristic(int _idx, CharacteristicFlag... _flags) {
+ idx = _idx;
+ uuid = HubConfigService.uuidFormat.format(_idx);
+ flags = EnumSet.copyOf(Arrays.asList(_flags));
+ }
+
+ public int getIdx() {
+ return idx;
+ }
+
+ public UUID getUUID() {
+ return uuid;
+ }
+
+ public EnumSet getFlags() {
+ return flags;
+ }
+
+ public boolean isChar(String _char) {
+ return NullUtils.isEqual(name(), _char);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java
new file mode 100644
index 0000000..635a8d0
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubConfigService.java
@@ -0,0 +1,39 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.cryptography.AESTool;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+public class HubConfigService {
+ public static final UUIDFormatter uuidFormat = new UUIDFormatter("c5650001-d50f-49af-b906-cada0dc17937");
+ private static final AESTool aes = new AESTool(37320708309265127L,-8068168662055796771L,-4867793276337148572L,4425609941731230765L);
+ private static final UUID serviceUUID = uuidFormat.format(1);
+
+ public HubConfigService() {
+ }
+
+ public static UUID getServiceUUID() {
+ return serviceUUID;
+ }
+
+ public List getCharacteristics() {
+ return Arrays.asList(HubConfigCharacteristic.values());
+ }
+
+ public static byte[] encryptWifiCreds(String _ssid, String _password) {
+ DaoEntity creds = new DaoEntity("ssid", _ssid).and("pwd", _password);
+ return aes.encrypt(DaoSerializer.toZipBson(creds));
+ }
+
+ public static String decryptWifiSSID(byte[] _payload) {
+ return DaoSerializer.getString(DaoSerializer.fromZipBson(aes.decrypt(_payload)), "ssid");
+ }
+
+ public static String decryptWifiPassword(byte[] _payload) {
+ return DaoSerializer.getString(DaoSerializer.fromZipBson(aes.decrypt(_payload)), "pwd");
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java
new file mode 100644
index 0000000..2eb3137
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/HubPowerMinute.java
@@ -0,0 +1,58 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+import java.util.Date;
+import java.util.List;
+
+@DBSerializable
+public class HubPowerMinute {
+ private int accountId;
+ private int hub;
+ private int minute;
+ private List breakers;
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public int getHub() {
+ return hub;
+ }
+
+ public void setHub(int _hub) {
+ hub = _hub;
+ }
+
+ public Date getMinuteAsDate() {
+ return new Date(((long)minute)*60000);
+ }
+
+ public int getMinute() {
+ return minute;
+ }
+
+ public void setMinute(int _minute) {
+ minute = _minute;
+ }
+
+ public void setMinute(Date _minute) {
+ minute = (int)(_minute.getTime()/60000);
+ }
+
+ public List getBreakers() {
+ return breakers;
+ }
+
+ public void setBreakers(List _breakers) {
+ breakers = _breakers;
+ }
+
+ public String getId() {
+ return String.format("%d-%d-%d", accountId, hub, minute);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java
new file mode 100644
index 0000000..ebf13f8
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Meter.java
@@ -0,0 +1,34 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+@DBSerializable
+public class Meter {
+ private int accountId;
+ private int index;
+ private String name;
+
+ public int getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(int _accountId) {
+ accountId = _accountId;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int _index) {
+ index = _index;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String _name) {
+ name = _name;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java
new file mode 100644
index 0000000..5e0862e
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkAdapter.java
@@ -0,0 +1,42 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.CollectionUtils;
+
+import java.util.Collection;
+import java.util.EnumSet;
+
+public enum NetworkAdapter {
+ ETHERNET((byte)0x1),
+ WIFI((byte)0x2);
+
+ public final byte bt;
+
+ NetworkAdapter(byte _bt) {
+ bt = _bt;
+ }
+
+ public static NetworkAdapter fromByte(byte _bt) {
+ for (NetworkAdapter a : values()) {
+ if (a.bt == _bt)
+ return a;
+ }
+ return null;
+ }
+
+ public static EnumSet fromMask(byte _bt) {
+ EnumSet values = EnumSet.noneOf(NetworkAdapter.class);
+ for (NetworkAdapter a : values()) {
+ if ((a.bt & _bt) == a.bt)
+ values.add(a);
+ }
+ return values;
+ }
+
+ public static byte toMask(Collection _adapters) {
+ byte mask = 0;
+ for (NetworkAdapter a : CollectionUtils.makeNotNull(_adapters)) {
+ mask |= a.bt;
+ }
+ return mask;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java
new file mode 100644
index 0000000..75ab05a
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/NetworkStatus.java
@@ -0,0 +1,44 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.CollectionUtils;
+
+import java.util.EnumSet;
+import java.util.List;
+
+public class NetworkStatus {
+ private List wifiIPs;
+ private List ethernetIPs;
+
+ public List getWifiIPs() {
+ return wifiIPs;
+ }
+
+ public void setWifiIPs(List _wifiIPs) {
+ wifiIPs = _wifiIPs;
+ }
+
+ public List getEthernetIPs() {
+ return ethernetIPs;
+ }
+
+ public void setEthernetIPs(List _ethernetIPs) {
+ ethernetIPs = _ethernetIPs;
+ }
+
+ public boolean isWifiConnected() {
+ return CollectionUtils.isNotEmpty(wifiIPs);
+ }
+
+ public boolean isEthernetConnected() {
+ return CollectionUtils.isNotEmpty(ethernetIPs);
+ }
+
+ public byte toMask() {
+ EnumSet adapters = EnumSet.noneOf(NetworkAdapter.class);
+ if (isWifiConnected())
+ adapters.add(NetworkAdapter.WIFI);
+ if (isEthernetConnected())
+ adapters.add(NetworkAdapter.ETHERNET);
+ return NetworkAdapter.toMask(adapters);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java
new file mode 100644
index 0000000..308051a
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/Sequence.java
@@ -0,0 +1,28 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+import com.lanternsoftware.util.dao.annotations.PrimaryKey;
+
+@DBSerializable
+public class Sequence {
+ @PrimaryKey
+ private String id;
+ private int sequence;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String _id) {
+ id = _id;
+ }
+
+ public int getSequence() {
+ return sequence;
+ }
+
+ public void setSequence(int _sequence) {
+ sequence = _sequence;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java
new file mode 100644
index 0000000..2e0a0f5
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/SignupResponse.java
@@ -0,0 +1,45 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.annotations.DBSerializable;
+
+@DBSerializable
+public class SignupResponse {
+ private String error;
+ private String authCode;
+
+ public SignupResponse() {
+ }
+
+ public static SignupResponse error(String _error) {
+ SignupResponse response = new SignupResponse();
+ response.setError(_error);
+ return response;
+ }
+
+ public static SignupResponse success(String _authCode) {
+ SignupResponse response = new SignupResponse();
+ response.setAuthCode(_authCode);
+ return response;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String _error) {
+ error = _error;
+ }
+
+ public String getAuthCode() {
+ return authCode;
+ }
+
+ public void setAuthCode(String _authCode) {
+ authCode = _authCode;
+ }
+
+ public boolean isSuccess() {
+ return NullUtils.isEmpty(error) && NullUtils.isNotEmpty(authCode);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java
new file mode 100644
index 0000000..d63e238
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/UUIDFormatter.java
@@ -0,0 +1,17 @@
+package com.lanternsoftware.datamodel.currentmonitor;
+
+import java.util.UUID;
+
+public class UUIDFormatter {
+ private final String uuidPrefix;
+ private final String uuidSuffix;
+
+ public UUIDFormatter(String _uuid) {
+ uuidPrefix = _uuid.substring(0,4);
+ uuidSuffix = _uuid.substring(8);
+ }
+
+ public UUID format(int _idx) {
+ return UUID.fromString(uuidPrefix + String.format("%04X", _idx) + uuidSuffix);
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java
new file mode 100644
index 0000000..c4c9eb9
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AccountSerializer.java
@@ -0,0 +1,50 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.Account;
+import com.lanternsoftware.util.CollectionUtils;
+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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class AccountSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return Account.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(Account _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("_id", String.valueOf(_o.getId()));
+ d.put("username", _o.getUsername());
+ d.put("password", _o.getPassword());
+ if (CollectionUtils.isNotEmpty(_o.getAuxiliaryAccountIds()))
+ d.put("aux_account_ids", CollectionUtils.toByteArray(_o.getAuxiliaryAccountIds()));
+ return d;
+ }
+
+ @Override
+ public Account fromDaoEntity(DaoEntity _d)
+ {
+ Account o = new Account();
+ o.setId(DaoSerializer.getInteger(_d, "_id"));
+ o.setUsername(DaoSerializer.getString(_d, "username"));
+ o.setPassword(DaoSerializer.getString(_d, "password"));
+ o.setAuxiliaryAccountIds(CollectionUtils.fromByteArrayOfIntegers(DaoSerializer.getByteArray(_d, "aux_account_ids")));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java
new file mode 100644
index 0000000..f0afb65
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/AuthCodeSerializer.java
@@ -0,0 +1,46 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.util.CollectionUtils;
+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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class AuthCodeSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return AuthCode.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(AuthCode _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("account_id", _o.getAccountId());
+ if (CollectionUtils.isNotEmpty(_o.getAuxiliaryAccountIds()))
+ d.put("aux_account_ids", CollectionUtils.toByteArray(_o.getAuxiliaryAccountIds()));
+ return d;
+ }
+
+ @Override
+ public AuthCode fromDaoEntity(DaoEntity _d)
+ {
+ AuthCode o = new AuthCode();
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setAuxiliaryAccountIds(CollectionUtils.fromByteArrayOfIntegers(DaoSerializer.getByteArray(_d, "aux_account_ids")));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java
new file mode 100644
index 0000000..598713b
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerConfigSerializer.java
@@ -0,0 +1,52 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel;
+import com.lanternsoftware.datamodel.currentmonitor.Meter;
+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 BreakerConfigSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return BreakerConfig.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerConfig _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("_id", String.valueOf(_o.getAccountId()));
+ d.put("meters", DaoSerializer.toDaoEntities(_o.getMeters(), DaoProxyType.MONGO));
+ d.put("panels", DaoSerializer.toDaoEntities(_o.getPanels(), DaoProxyType.MONGO));
+ d.put("breaker_hubs", DaoSerializer.toDaoEntities(_o.getBreakerHubs(), DaoProxyType.MONGO));
+ d.put("breaker_groups", DaoSerializer.toDaoEntities(_o.getBreakerGroups(), DaoProxyType.MONGO));
+ return d;
+ }
+
+ @Override
+ public BreakerConfig fromDaoEntity(DaoEntity _d)
+ {
+ BreakerConfig o = new BreakerConfig();
+ o.setAccountId(DaoSerializer.getInteger(_d, "_id"));
+ o.setMeters(DaoSerializer.getList(_d, "meters", Meter.class));
+ o.setPanels(DaoSerializer.getList(_d, "panels", BreakerPanel.class));
+ o.setBreakerHubs(DaoSerializer.getList(_d, "breaker_hubs", BreakerHub.class));
+ o.setBreakerGroups(DaoSerializer.getList(_d, "breaker_groups", BreakerGroup.class));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java
new file mode 100644
index 0000000..382e2c7
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupEnergySerializer.java
@@ -0,0 +1,98 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlock;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode;
+import com.lanternsoftware.util.CollectionUtils;
+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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+public class BreakerGroupEnergySerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return BreakerGroupEnergy.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerGroupEnergy _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("_id", _o.getId());
+ d.put("account_id", _o.getAccountId());
+ d.put("group_id", _o.getGroupId());
+ d.put("group_name", _o.getGroupName());
+ d.put("view_mode", DaoSerializer.toEnumName(_o.getViewMode()));
+ d.put("start", DaoSerializer.toLong(_o.getStart()));
+ d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO));
+ if (CollectionUtils.size(_o.getEnergyBlocks()) > 0) {
+ Date start = _o.getStart();
+ Date now = new Date();
+ TimeZone tz = TimeZone.getTimeZone("America/Chicago");
+ ByteBuffer bb = ByteBuffer.allocate(_o.getViewMode().blockCount(start, tz) * 4);
+ for (EnergyBlock b : _o.getEnergyBlocks()) {
+ if (b.getStart().before(start))
+ continue;
+ if (now.before(start))
+ break;
+ while (start.before(b.getStart())) {
+ bb.putFloat(0);
+ start = _o.getViewMode().toBlockEnd(start, tz);
+ }
+ bb.putFloat((float) b.getJoules());
+ start = _o.getViewMode().toBlockEnd(start, tz);
+ }
+ if (bb.position() < bb.limit())
+ d.put("blocks", Arrays.copyOfRange(bb.array(), 0, bb.position()));
+ else
+ d.put("blocks", bb.array());
+ }
+ d.put("to_grid", _o.getToGrid());
+ d.put("from_grid", _o.getFromGrid());
+ return d;
+ }
+
+ @Override
+ public BreakerGroupEnergy fromDaoEntity(DaoEntity _d)
+ {
+ TimeZone tz = TimeZone.getTimeZone("America/Chicago");
+ BreakerGroupEnergy o = new BreakerGroupEnergy();
+ o.setGroupId(DaoSerializer.getString(_d, "group_id"));
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setGroupName(DaoSerializer.getString(_d, "group_name"));
+ o.setViewMode(DaoSerializer.getEnum(_d, "view_mode", EnergyBlockViewMode.class));
+ o.setStart(DaoSerializer.getDate(_d, "start"));
+ o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", BreakerGroupEnergy.class));
+ List blocks = new ArrayList<>();
+ byte[] blockData = DaoSerializer.getByteArray(_d, "blocks");
+ if (CollectionUtils.length(blockData) > 0) {
+ ByteBuffer bb = ByteBuffer.wrap(blockData);
+ Date start = o.getStart();
+ while (bb.hasRemaining()) {
+ EnergyBlock block = new EnergyBlock(start, o.getViewMode().toBlockEnd(start, tz), bb.getFloat());
+ blocks.add(block);
+ start = block.getEnd();
+ }
+ }
+ o.setEnergyBlocks(blocks);
+ o.setToGrid(DaoSerializer.getDouble(_d, "to_grid"));
+ o.setFromGrid(DaoSerializer.getDouble(_d, "from_grid"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java
new file mode 100644
index 0000000..48bcafd
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSerializer.java
@@ -0,0 +1,49 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+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 BreakerGroupSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return BreakerGroup.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerGroup _o)
+ {
+ DaoEntity d = new DaoEntity();
+ if (_o.getId() != null)
+ d.put("_id", _o.getId());
+ d.put("account_id", _o.getAccountId());
+ d.put("name", _o.getName());
+ d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO));
+ d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO));
+ return d;
+ }
+
+ @Override
+ public BreakerGroup fromDaoEntity(DaoEntity _d)
+ {
+ BreakerGroup o = new BreakerGroup();
+ o.setId(DaoSerializer.getString(_d, "_id"));
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setName(DaoSerializer.getString(_d, "name"));
+ o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", BreakerGroup.class));
+ o.setBreakers(DaoSerializer.getList(_d, "breakers", Breaker.class));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java
new file mode 100644
index 0000000..05a6008
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerGroupSummarySerializer.java
@@ -0,0 +1,55 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode;
+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;
+import java.util.TimeZone;
+
+public class BreakerGroupSummarySerializer extends AbstractDaoSerializer {
+ @Override
+ public Class getSupportedClass() {
+ return BreakerGroupSummary.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerGroupSummary _o) {
+ DaoEntity d = new DaoEntity();
+ d.put("_id", _o.getId());
+ d.put("account_id", _o.getAccountId());
+ d.put("group_id", _o.getGroupId());
+ d.put("group_name", _o.getGroupName());
+ d.put("view_mode", DaoSerializer.toEnumName(_o.getViewMode()));
+ d.put("start", DaoSerializer.toLong(_o.getStart()));
+ d.put("sub_groups", DaoSerializer.toDaoEntities(_o.getSubGroups(), DaoProxyType.MONGO));
+ d.put("joules", _o.getJoules());
+ d.put("to_grid", _o.getToGrid());
+ d.put("from_grid", _o.getFromGrid());
+ return d;
+ }
+
+ @Override
+ public BreakerGroupSummary fromDaoEntity(DaoEntity _d) {
+ BreakerGroupSummary o = new BreakerGroupSummary();
+ o.setGroupId(DaoSerializer.getString(_d, "group_id"));
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setGroupName(DaoSerializer.getString(_d, "group_name"));
+ o.setViewMode(DaoSerializer.getEnum(_d, "view_mode", EnergyBlockViewMode.class));
+ o.setStart(DaoSerializer.getDate(_d, "start"));
+ o.setSubGroups(DaoSerializer.getList(_d, "sub_groups", BreakerGroupSummary.class));
+ o.setJoules(DaoSerializer.getDouble(_d, "joules"));
+ o.setToGrid(DaoSerializer.getDouble(_d, "to_grid"));
+ o.setFromGrid(DaoSerializer.getDouble(_d, "from_grid"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java
new file mode 100644
index 0000000..c70e31e
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerHubSerializer.java
@@ -0,0 +1,45 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
+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 BreakerHubSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return BreakerHub.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerHub _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("hub", _o.getHub());
+ d.put("voltage_calibration_factor", _o.getVoltageCalibrationFactor());
+ d.put("frequency", _o.getFrequency());
+ d.put("bluetooth_mac", _o.getBluetoothMac());
+ return d;
+ }
+
+ @Override
+ public BreakerHub fromDaoEntity(DaoEntity _d)
+ {
+ BreakerHub o = new BreakerHub();
+ o.setHub(DaoSerializer.getInteger(_d, "hub"));
+ o.setVoltageCalibrationFactor(DaoSerializer.getDouble(_d, "voltage_calibration_factor"));
+ o.setFrequency(DaoSerializer.getInteger(_d, "frequency"));
+ o.setBluetoothMac(DaoSerializer.getString(_d, "bluetooth_mac"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java
new file mode 100644
index 0000000..9e0f8eb
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPanelSerializer.java
@@ -0,0 +1,47 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel;
+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 BreakerPanelSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return BreakerPanel.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerPanel _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("account_id", _o.getAccountId());
+ d.put("name", _o.getName());
+ d.put("index", _o.getIndex());
+ d.put("spaces", _o.getSpaces());
+ d.put("meter", _o.getMeter());
+ return d;
+ }
+
+ @Override
+ public BreakerPanel fromDaoEntity(DaoEntity _d)
+ {
+ BreakerPanel o = new BreakerPanel();
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setName(DaoSerializer.getString(_d, "name"));
+ o.setIndex(DaoSerializer.getInteger(_d, "index"));
+ o.setSpaces(DaoSerializer.getInteger(_d, "spaces"));
+ o.setMeter(DaoSerializer.getInteger(_d, "meter"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java
new file mode 100644
index 0000000..cf6d912
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerMinuteSerializer.java
@@ -0,0 +1,48 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.dao.AbstractDaoSerializer;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BreakerPowerMinuteSerializer extends AbstractDaoSerializer {
+ @Override
+ public Class getSupportedClass() {
+ return BreakerPowerMinute.class;
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerPowerMinute _o) {
+ DaoEntity d = new DaoEntity();
+ d.put("panel", _o.getPanel());
+ d.put("space", _o.getSpace());
+ ByteBuffer bb = ByteBuffer.allocate(240);
+ for (Float reading : CollectionUtils.makeNotNull(_o.getReadings())) {
+ bb.putFloat(DaoSerializer.toFloat(reading));
+ }
+ d.put("readings", bb.array());
+ return d;
+ }
+
+ @Override
+ public BreakerPowerMinute fromDaoEntity(DaoEntity _d) {
+ BreakerPowerMinute o = new BreakerPowerMinute();
+ o.setPanel(DaoSerializer.getInteger(_d, "panel"));
+ o.setSpace(DaoSerializer.getInteger(_d, "space"));
+ byte[] data = DaoSerializer.getByteArray(_d, "readings");
+ List readings = new ArrayList<>();
+ o.setReadings(readings);
+ if (CollectionUtils.length(data) > 0) {
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ while (bb.hasRemaining()) {
+ readings.add(bb.getFloat());
+ }
+ }
+ return o;
+ }
+}
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java
new file mode 100644
index 0000000..8852bd8
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerPowerSerializer.java
@@ -0,0 +1,54 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+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 BreakerPowerSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return BreakerPower.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(BreakerPower _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("_id", _o.getId());
+ d.put("account_id", _o.getAccountId());
+ d.put("panel", _o.getPanel());
+ d.put("space", _o.getSpace());
+ d.put("key", _o.getKey());
+ d.put("read_time", DaoSerializer.toLong(_o.getReadTime()));
+ d.put("hub_version", _o.getHubVersion());
+ d.put("power", _o.getPower());
+ d.put("voltage", _o.getVoltage());
+ return d;
+ }
+
+ @Override
+ public BreakerPower fromDaoEntity(DaoEntity _d)
+ {
+ BreakerPower o = new BreakerPower();
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setPanel(DaoSerializer.getInteger(_d, "panel"));
+ o.setSpace(DaoSerializer.getInteger(_d, "space"));
+ o.setReadTime(DaoSerializer.getDate(_d, "read_time"));
+ o.setHubVersion(DaoSerializer.getString(_d, "hub_version"));
+ o.setPower(DaoSerializer.getDouble(_d, "power"));
+ o.setVoltage(DaoSerializer.getDouble(_d, "voltage"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java
new file mode 100644
index 0000000..9c3635e
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/BreakerSerializer.java
@@ -0,0 +1,63 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerType;
+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 BreakerSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return Breaker.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(Breaker _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("panel", _o.getPanel());
+ d.put("space", _o.getSpace());
+ d.put("meter", _o.getMeter());
+ d.put("hub", _o.getHub());
+ d.put("port", _o.getPort());
+ d.put("name", _o.getName());
+ d.put("description", _o.getDescription());
+ d.put("size_amps", _o.getSizeAmps());
+ d.put("calibration_factor", _o.getCalibrationFactor());
+ d.put("low_pass_filter", _o.getLowPassFilter());
+ d.put("polarity", DaoSerializer.toEnumName(_o.getPolarity()));
+ d.put("type", DaoSerializer.toEnumName(_o.getType()));
+ return d;
+ }
+
+ @Override
+ public Breaker fromDaoEntity(DaoEntity _d)
+ {
+ Breaker o = new Breaker();
+ o.setPanel(DaoSerializer.getInteger(_d, "panel"));
+ o.setSpace(DaoSerializer.getInteger(_d, "space"));
+ o.setMeter(DaoSerializer.getInteger(_d, "meter"));
+ o.setHub(DaoSerializer.getInteger(_d, "hub"));
+ o.setPort(DaoSerializer.getInteger(_d, "port"));
+ o.setName(DaoSerializer.getString(_d, "name"));
+ o.setDescription(DaoSerializer.getString(_d, "description"));
+ o.setSizeAmps(DaoSerializer.getInteger(_d, "size_amps"));
+ o.setCalibrationFactor(DaoSerializer.getDouble(_d, "calibration_factor"));
+ o.setLowPassFilter(DaoSerializer.getDouble(_d, "low_pass_filter"));
+ o.setPolarity(DaoSerializer.getEnum(_d, "polarity", BreakerPolarity.class));
+ o.setType(DaoSerializer.getEnum(_d, "type", BreakerType.class));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java
new file mode 100644
index 0000000..a59abd7
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/EnergyBlockSerializer.java
@@ -0,0 +1,43 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlock;
+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 EnergyBlockSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return EnergyBlock.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(EnergyBlock _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("start", DaoSerializer.toLong(_o.getStart()));
+ d.put("end", DaoSerializer.toLong(_o.getEnd()));
+ d.put("joules", _o.getJoules());
+ return d;
+ }
+
+ @Override
+ public EnergyBlock fromDaoEntity(DaoEntity _d)
+ {
+ EnergyBlock o = new EnergyBlock();
+ o.setStart(DaoSerializer.getDate(_d, "start"));
+ o.setEnd(DaoSerializer.getDate(_d, "end"));
+ o.setJoules(DaoSerializer.getDouble(_d, "joules"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java
new file mode 100644
index 0000000..3656467
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/HubPowerMinuteSerializer.java
@@ -0,0 +1,46 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute;
+import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
+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 HubPowerMinuteSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return HubPowerMinute.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(HubPowerMinute _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("account_id", _o.getAccountId());
+ d.put("hub", _o.getHub());
+ d.put("minute", _o.getMinute());
+ d.put("breakers", DaoSerializer.toDaoEntities(_o.getBreakers(), DaoProxyType.MONGO));
+ return d;
+ }
+
+ @Override
+ public HubPowerMinute fromDaoEntity(DaoEntity _d)
+ {
+ HubPowerMinute o = new HubPowerMinute();
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setHub(DaoSerializer.getInteger(_d, "hub"));
+ o.setMinute(DaoSerializer.getInteger(_d, "minute"));
+ o.setBreakers(DaoSerializer.getList(_d, "breakers", BreakerPowerMinute.class));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java
new file mode 100644
index 0000000..eaeced1
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/MeterSerializer.java
@@ -0,0 +1,43 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.Meter;
+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 MeterSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return Meter.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(Meter _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("account_id", _o.getAccountId());
+ d.put("index", _o.getIndex());
+ d.put("name", _o.getName());
+ return d;
+ }
+
+ @Override
+ public Meter fromDaoEntity(DaoEntity _d)
+ {
+ Meter o = new Meter();
+ o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
+ o.setIndex(DaoSerializer.getInteger(_d, "index"));
+ o.setName(DaoSerializer.getString(_d, "name"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java
new file mode 100644
index 0000000..a5c0766
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SequenceSerializer.java
@@ -0,0 +1,42 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.Sequence;
+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 SequenceSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return Sequence.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(Sequence _o)
+ {
+ DaoEntity d = new DaoEntity();
+ if (_o.getId() != null)
+ d.put("_id", _o.getId());
+ d.put("sequence", _o.getSequence());
+ return d;
+ }
+
+ @Override
+ public Sequence fromDaoEntity(DaoEntity _d)
+ {
+ Sequence o = new Sequence();
+ o.setId(DaoSerializer.getString(_d, "_id"));
+ o.setSequence(DaoSerializer.getInteger(_d, "sequence"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java
new file mode 100644
index 0000000..099cbf2
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/java/com/lanternsoftware/datamodel/currentmonitor/dao/SignupResponseSerializer.java
@@ -0,0 +1,41 @@
+package com.lanternsoftware.datamodel.currentmonitor.dao;
+
+import com.lanternsoftware.datamodel.currentmonitor.SignupResponse;
+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 SignupResponseSerializer extends AbstractDaoSerializer
+{
+ @Override
+ public Class getSupportedClass()
+ {
+ return SignupResponse.class;
+ }
+
+ @Override
+ public List getSupportedProxies() {
+ return Collections.singletonList(DaoProxyType.MONGO);
+ }
+
+ @Override
+ public DaoEntity toDaoEntity(SignupResponse _o)
+ {
+ DaoEntity d = new DaoEntity();
+ d.put("error", _o.getError());
+ d.put("auth_code", _o.getAuthCode());
+ return d;
+ }
+
+ @Override
+ public SignupResponse fromDaoEntity(DaoEntity _d)
+ {
+ SignupResponse o = new SignupResponse();
+ o.setError(DaoSerializer.getString(_d, "error"));
+ o.setAuthCode(DaoSerializer.getString(_d, "auth_code"));
+ return o;
+ }
+}
\ No newline at end of file
diff --git a/currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer b/currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer
new file mode 100644
index 0000000..ece1bb1
--- /dev/null
+++ b/currentmonitor/lantern-datamodel-currentmonitor/src/main/resources/META-INF/services/com.lanternsoftware.util.dao.IDaoSerializer
@@ -0,0 +1,16 @@
+com.lanternsoftware.datamodel.currentmonitor.dao.AccountSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.AuthCodeSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerConfigSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerGroupEnergySerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerGroupSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerGroupSummarySerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerHubSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPanelSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPowerMinuteSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerPowerSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.BreakerSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.EnergyBlockSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.HubPowerMinuteSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.MeterSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.SequenceSerializer
+com.lanternsoftware.datamodel.currentmonitor.dao.SignupResponseSerializer
diff --git a/currentmonitor/lantern-service-currentmonitor/pom.xml b/currentmonitor/lantern-service-currentmonitor/pom.xml
new file mode 100644
index 0000000..68af784
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/pom.xml
@@ -0,0 +1,89 @@
+
+ 4.0.0
+ com.lanternsoftware.currentmonitor
+ lantern-service-currentmonitor
+ war
+ 1.0.0
+ lantern-service-currentmonitor
+
+
+ 1.8
+ 1.8
+
+
+
+
+ com.lanternsoftware.currentmonitor
+ lantern-dataaccess-currentmonitor
+ 1.0.0
+
+
+ com.lanternsoftware.util
+ lantern-util-servlet
+ 1.0.0
+
+
+ com.google.api-client
+ google-api-client
+ 1.30.4
+
+
+ javax
+ javaee-api
+ 8.0
+ provided
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.29
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+
+
+
+ src/main/resources
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.2
+
+
+
+ testCompile
+
+ compile
+
+
+
+ true
+ true
+ UTF-8
+
+ 1.8
+
+
+
+ maven-war-plugin
+ 2.5
+
+
+
+ true
+ lib/
+
+
+
+
+
+
+
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java
new file mode 100644
index 0000000..3ee445c
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/context/Globals.java
@@ -0,0 +1,23 @@
+package com.lanternsoftware.currentmonitor.context;
+
+import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
+import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+public class Globals implements ServletContextListener {
+ public static CurrentMonitorDao dao;
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ dao.shutdown();
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java
new file mode 100644
index 0000000..021fcff
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/AuthServlet.java
@@ -0,0 +1,57 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
+import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
+import com.google.api.client.http.javanet.NetHttpTransport;
+import com.google.api.client.json.jackson2.JacksonFactory;
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.servlet.BasicAuth;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Collections;
+
+@WebServlet("/auth/*")
+public class AuthServlet extends CMServlet {
+ private static final NetHttpTransport transport = new NetHttpTransport();
+ private static final JacksonFactory jsonFactory = new JacksonFactory();
+ private static final Logger logger = LoggerFactory.getLogger(AuthServlet.class);
+ private static final String googleSsoKey = ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "google_sso_key.txt");
+
+ @Override
+ protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
+ String authCode = _req.getHeader("auth_code");
+ if (NullUtils.isEmpty(authCode)) {
+ BasicAuth auth = new BasicAuth(_req);
+ if (NullUtils.isEqual(auth.getUsername(), "googlesso")) {
+ GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Collections.singletonList(googleSsoKey)).build();
+ try {
+ GoogleIdToken idToken = verifier.verify(auth.getPassword());
+ if (idToken != null) {
+ GoogleIdToken.Payload payload = idToken.getPayload();
+ String email = payload.getEmail();
+ authCode = Globals.dao.getAuthCodeForEmail(email);
+ }
+ }
+ catch (Exception _e) {
+ logger.error("Failed to validate google auth token", _e);
+ }
+ }
+ else
+ authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword());
+ }
+ DaoEntity rep = new DaoEntity("auth_code", authCode);
+ if (isPath(_req, 0, "bin"))
+ zipBsonResponse(_rep, rep);
+ else
+ jsonResponse(_rep, rep);
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java
new file mode 100644
index 0000000..ccc1bfd
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CMServlet.java
@@ -0,0 +1,101 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import org.apache.commons.io.IOUtils;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public abstract class CMServlet extends HttpServlet {
+ public static void setResponseHtml(HttpServletResponse _response, String _sHtml) {
+ setResponseEntity(_response, MediaType.TEXT_HTML, _sHtml);
+ }
+
+ public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) {
+ setResponseEntity(_response, 200, _sContentType, _sEntity);
+ }
+
+ public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) {
+ setResponseEntity(_response, 200, _sContentType, _btData);
+ }
+
+ public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) {
+ setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity));
+ }
+
+ public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) {
+ OutputStream os = null;
+ try {
+ _response.setStatus(_iStatus);
+ _response.setCharacterEncoding("UTF-8");
+ _response.setContentType(_sContentType);
+ if ((_btData != null) && (_btData.length > 0)) {
+ _response.setContentLength(_btData.length);
+ os = _response.getOutputStream();
+ os.write(_btData);
+ } else
+ _response.setContentLength(0);
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(os);
+ }
+ }
+
+ protected void zipBsonResponse(HttpServletResponse _response, Object _object)
+ {
+ setResponseEntity(_response, 200, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(_object));
+ }
+
+ protected void jsonResponse(HttpServletResponse _response, Object _object)
+ {
+ setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, DaoSerializer.toJson(_object));
+ }
+
+ protected void jsonResponse(HttpServletResponse _response, String _json)
+ {
+ setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, _json);
+ }
+
+ protected String getRequestPayloadAsString(HttpServletRequest _req) {
+ return NullUtils.toString(getRequestPayload(_req));
+ }
+
+ protected byte[] getRequestPayload(HttpServletRequest _req) {
+ InputStream is = null;
+ try {
+ is = _req.getInputStream();
+ return IOUtils.toByteArray(is);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ finally {
+ IOUtils.closeQuietly(is);
+ }
+ }
+
+ protected DaoEntity getRequestZipBson(HttpServletRequest _req) {
+ return DaoSerializer.fromZipBson(getRequestPayload(_req));
+ }
+
+ protected T getRequestPayload(HttpServletRequest _req, Class _retClass) {
+ return DaoSerializer.fromZipBson(getRequestPayload(_req), _retClass);
+ }
+
+ protected String[] path(HttpServletRequest _req) {
+ return NullUtils.cleanSplit(NullUtils.makeNotNull(_req.getPathInfo()), "/");
+ }
+
+ protected boolean isPath(HttpServletRequest _req, int _index, String _path) {
+ return NullUtils.isEqual(_path, CollectionUtils.get(path(_req), _index));
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java
new file mode 100644
index 0000000..8003152
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/CommandServlet.java
@@ -0,0 +1,49 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+@WebServlet("/command")
+public class CommandServlet extends SecureServlet {
+
+ @Override
+ protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ File folder = new File(LanternFiles.OPS_PATH + _authCode.getAccountId());
+ List commands = new ArrayList<>();
+ if (folder.exists() && folder.isDirectory()) {
+ for (File command : CollectionUtils.asArrayList(folder.listFiles())) {
+ if (command.isDirectory())
+ continue;
+ String c = command.getName();
+ String extension = NullUtils.after(c, ".");
+ if (NullUtils.isNotEmpty(extension))
+ c = c.replace("." + extension, "");
+ commands.add(c);
+ }
+ }
+ zipBsonResponse(_rep, new DaoEntity("commands", commands));
+ }
+
+ @Override
+ protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ DaoEntity payload = getRequestZipBson(_req);
+ if (payload == null)
+ return;
+ String command = DaoSerializer.getString(payload, "command");
+ String path = LanternFiles.OPS_PATH + _authCode.getAccountId() + File.separator + "payload" + File.separator;
+ new File(path).mkdirs();
+ ResourceLoader.writeFile(path+ command + ".txt", DaoSerializer.getString(payload, "payload"));
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java
new file mode 100644
index 0000000..c7ed38d
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/ConfigServlet.java
@@ -0,0 +1,34 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/config/*")
+public class ConfigServlet extends SecureServlet {
+ @Override
+ protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ if (isPath(_req, 0, "bin"))
+ zipBsonResponse(_rep, Globals.dao.getMergedConfig(_authCode));
+ else
+ jsonResponse(_rep, Globals.dao.getMergedConfig(_authCode));
+ }
+
+ @Override
+ protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ BreakerConfig config = getRequestPayload(_req, BreakerConfig.class);
+ if (config == null) {
+ _rep.setStatus(400);
+ return;
+ }
+ if (config.getAccountId() != _authCode.getAccountId()) {
+ _rep.setStatus(401);
+ return;
+ }
+ Globals.dao.putConfig(config);
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java
new file mode 100644
index 0000000..449729c
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupEnergyServlet.java
@@ -0,0 +1,48 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Date;
+import java.util.List;
+
+@WebServlet("/energy/group/*")
+public class GroupEnergyServlet extends SecureServlet {
+ @Override
+ protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ String[] path = path(_req);
+ if (path.length < 3) {
+ _rep.setStatus(400);
+ return;
+ }
+ EnergyBlockViewMode viewMode = NullUtils.toEnum(EnergyBlockViewMode.class, path[1], EnergyBlockViewMode.DAY);
+ Date start = new Date(NullUtils.toLong(path[2]));
+ List energies = CollectionUtils.transform(_authCode.getAllAccountIds(), _id->Globals.dao.getBreakerGroupEnergy(_id, path[0], viewMode, start), true);
+ if (CollectionUtils.isNotEmpty(energies)) {
+ BreakerGroupEnergy energy;
+ if (energies.size() > 1) {
+ energy = new BreakerGroupEnergy();
+ energy.setAccountId(_authCode.getAccountId());
+ energy.setGroupId("Sites");
+ energy.setGroupName("Sites");
+ energy.setStart(start);
+ energy.setViewMode(viewMode);
+ energy.setSubGroups(CollectionUtils.asArrayList(energies));
+ }
+ else
+ energy = CollectionUtils.getFirst(energies);
+ if (NullUtils.isEqual(CollectionUtils.get(path, 3), "bin"))
+ zipBsonResponse(_rep, energy);
+ else
+ jsonResponse(_rep, energy);
+ } else
+ _rep.setStatus(404);
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java
new file mode 100644
index 0000000..5af0898
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/GroupPowerServlet.java
@@ -0,0 +1,26 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import org.bson.Document;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/power/group/*")
+public class GroupPowerServlet extends SecureServlet {
+ @Override
+ protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ String[] path = path(_req);
+ if (path.length < 1)
+ zipBsonResponse(_rep, new DaoEntity("breakers", DaoSerializer.toDaoEntities(Globals.dao.getBreakerPowerForAccount(_authCode.getAccountId()))));
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java
new file mode 100644
index 0000000..a0bd27c
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/PowerServlet.java
@@ -0,0 +1,49 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
+import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+@WebServlet("/power/*")
+public class PowerServlet extends SecureServlet {
+ @Override
+ protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ String[] path = path(_req);
+ if (path.length < 2) {
+ _rep.setStatus(400);
+ return;
+ }
+ int hub = DaoSerializer.toInteger(CollectionUtils.get(path, 0));
+ int port = DaoSerializer.toInteger(CollectionUtils.get(path, 1));
+ jsonResponse(_rep, Globals.dao.getLatestBreakerPower(_authCode.getAccountId(), hub, port));
+ }
+
+ @Override
+ protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ String[] path = path(_req);
+ if ((path.length > 0) && NullUtils.isEqual(CollectionUtils.get(path, 0), "hub")) {
+ Globals.dao.putHubPowerMinute(getRequestPayload(_req, HubPowerMinute.class));
+ return;
+ }
+ if ((path.length > 0) && NullUtils.isEqual(CollectionUtils.get(path, 0), "batch")) {
+ List powers = DaoSerializer.getList(getRequestZipBson(_req), "readings", BreakerPower.class);
+ if (!powers.isEmpty()) {
+ CollectionUtils.edit(powers, _p->_p.setAccountId(_authCode.getAccountId()));
+ Globals.dao.getProxy().save(powers);
+ }
+ return;
+ }
+ BreakerPower power = getRequestPayload(_req, BreakerPower.class);
+ power.setAccountId(_authCode.getAccountId());
+ Globals.dao.putBreakerPower(power);
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java
new file mode 100644
index 0000000..bf89561
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SecureServlet.java
@@ -0,0 +1,35 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public abstract class SecureServlet extends CMServlet {
+ @Override
+ protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
+ AuthCode authCode = Globals.dao.decryptAuthCode(_req.getHeader("auth_code"));
+ if (authCode == null) {
+ _rep.setStatus(401);
+ return;
+ }
+ get(authCode, _req, _rep);
+ }
+
+ protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ }
+
+ @Override
+ protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
+ AuthCode authCode = Globals.dao.decryptAuthCode(_req.getHeader("auth_code"));
+ if (authCode == null) {
+ _rep.setStatus(401);
+ return;
+ }
+ post(authCode, _req, _rep);
+ }
+
+ protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java
new file mode 100644
index 0000000..8bf2ed2
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/SignupServlet.java
@@ -0,0 +1,45 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.currentmonitor.context.Globals;
+import com.lanternsoftware.datamodel.currentmonitor.Account;
+import com.lanternsoftware.datamodel.currentmonitor.SignupResponse;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.dao.DaoEntity;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.email.EmailValidator;
+import com.lanternsoftware.util.servlet.BasicAuth;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@WebServlet("/signup")
+public class SignupServlet extends CMServlet {
+ @Override
+ protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
+ BasicAuth auth = new BasicAuth(_req);
+ Account acct = Globals.dao.getAccountByUsername(auth.getUsername());
+ if (acct != null) {
+ jsonResponse(_rep, SignupResponse.error("An account for " + auth.getUsername() + " already exists"));
+ return;
+ }
+ if (!EmailValidator.getInstance().isValid(auth.getUsername())) {
+ jsonResponse(_rep, SignupResponse.error(auth.getUsername() + " is not a valid email address"));
+ return;
+ }
+ if (NullUtils.length(auth.getPassword()) < 8) {
+ jsonResponse(_rep, SignupResponse.error("Your password must be at least 8 characters long"));
+ return;
+ }
+ if (NullUtils.isEqual("password", auth.getPassword())) {
+ jsonResponse(_rep, SignupResponse.error("Seriously? \"password\"? Come on."));
+ return;
+ }
+ acct = new Account();
+ acct.setUsername(auth.getUsername());
+ acct.setPassword(auth.getPassword());
+ Globals.dao.putAccount(acct);
+ String authCode = Globals.dao.authenticateAccount(auth.getUsername(), auth.getPassword());
+ jsonResponse(_rep, SignupResponse.success(authCode));
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java
new file mode 100644
index 0000000..b5ab090
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/servlet/UpdateServlet.java
@@ -0,0 +1,22 @@
+package com.lanternsoftware.currentmonitor.servlet;
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.core.MediaType;
+import java.io.File;
+
+@WebServlet("/update/*")
+public class UpdateServlet extends CMServlet {
+ @Override
+ protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
+ if (isPath(_req, 0, "version"))
+ setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "release" + File.separator + "version.json"))));
+ else
+ setResponseEntity(_rep, MediaType.APPLICATION_OCTET_STREAM, ResourceLoader.loadFile(LanternFiles.OPS_PATH + "release" + File.separator + "lantern-currentmonitor.jar"));
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml b/currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml
new file mode 100644
index 0000000..ece41c5
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ ${log.pattern}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml b/currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..8f6b092
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ com.lanternsoftware.currentmonitor.context.Globals
+
+
\ No newline at end of file
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java
new file mode 100644
index 0000000..fb5faa4
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAccount.java
@@ -0,0 +1,25 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
+import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
+import com.lanternsoftware.datamodel.currentmonitor.Account;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+
+import java.util.Arrays;
+
+public class CreateAccount {
+ public static void main(String[] args) {
+ CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
+ Account account = new Account();
+ account.setId(1);
+ account.setPassword("*redacted*");
+
+ account.setId(2);
+ account.setUsername("admin@lanternsoftware.com");
+ account.setPassword("*redacted*");
+
+ dao.putAccount(account);
+ dao.shutdown();
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java
new file mode 100644
index 0000000..93ec941
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthCode.java
@@ -0,0 +1,15 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.cryptography.AESTool;
+import com.lanternsoftware.util.dao.DaoSerializer;
+
+public class CreateAuthCode {
+ private static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "authKey.dat"));
+
+ public static void main(String[] args) {
+ System.out.println(aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(100, null))));
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java
new file mode 100644
index 0000000..e67192b
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateAuthKey.java
@@ -0,0 +1,11 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.cryptography.AESTool;
+
+public class CreateAuthKey {
+ public static void main(String[] args) {
+ ResourceLoader.writeFile(LanternFiles.OPS_PATH + "authKey.dat", AESTool.generateRandomSecretKey().getEncoded());
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java
new file mode 100644
index 0000000..f899866
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateBreakers.java
@@ -0,0 +1,241 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
+import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPanel;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerPolarity;
+import com.lanternsoftware.datamodel.currentmonitor.Meter;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.NullUtils;
+import com.lanternsoftware.util.ResourceLoader;
+import com.lanternsoftware.util.dao.DaoSerializer;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class CreateBreakers {
+ public static void main(String[] args) {
+ CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
+
+/* Breaker bf1 = new Breaker("Solar A", 2, 20, 0, 1, 50, 1.6);
+ bf1.setPolarity(BreakerPolarity.SOLAR);
+ Breaker bf2 = new Breaker("Solar B", 2, 18, 0, 2, 50, 1.6);
+ bf2.setPolarity(BreakerPolarity.SOLAR);
+ Breaker bf3 = new Breaker("Septic Agitator", 2, 11, 0, 3, 20, 1.6);
+ Breaker bf4 = new Breaker("Garage/Guest Baths", 2, 5, 0, 4, 20, 1.6);
+ Breaker bf5 = new Breaker("Office", 2, 2, 0, 5, 20, 1.6);
+ Breaker bf6 = new Breaker("Upstairs Furnace R-A", 1, 2, 0, 6, 50, 1.6);
+ Breaker bf7 = new Breaker("Upstairs Furnace R-B", 1, 4, 0, 7, 50, 1.6);
+ Breaker bf8 = new Breaker("Upstairs Furnace R-C", 1, 6, 0, 8, 30, 1.6);
+ Breaker bf9 = new Breaker("Upstairs Furnace R-D", 1, 8, 0, 9, 30, 1.6);
+ Breaker bf10 = new Breaker("Upstairs Furnace HP-A", 1, 1, 0, 10, 30, 1.6);
+ Breaker bf11 = new Breaker("Upstairs Furnace HP-B", 1, 3, 0, 11, 30, 1.6);
+ Breaker bf12 = new Breaker("Dryer A", 2, 8, 0, 12, 30, 1.6);
+ Breaker bf13 = new Breaker("Dryer B", 2, 10, 0, 13, 30, 1.6);
+ Breaker bf2_1 = new Breaker("Main Furnace R-A", 0, 6, 1, 1, 50, 1.6);
+ Breaker bf2_2 = new Breaker("Main Furnace R-B", 0, 8, 1, 2, 50, 1.6);
+ Breaker bf2_3 = new Breaker("Main Furnace R-C", 0, 2, 1, 3, 50, 1.6);
+ Breaker bf2_4 = new Breaker("Main Furnace R-D", 0, 4, 1, 4, 50, 1.6);
+ Breaker bf2_5 = new Breaker("Main Furnace HP-A", 0, 1, 1, 5, 30, 1.6);
+ Breaker bf2_6 = new Breaker("Main Furnace HP-B", 0, 3, 1, 6, 30, 1.6);
+ Breaker bf2_7 = new Breaker("Hot Water Heater A", 0, 5, 1, 7, 30, 1.6);
+ Breaker bf2_8 = new Breaker("Hot Water Heater B", 0, 7, 1, 8, 30, 1.6);
+ Breaker bf2_9 = new Breaker("Basement HP-A", 1, 13, 1, 9, 30, 1.6);
+ Breaker bf2_10 = new Breaker("Basement HP-B", 1, 15, 1, 10, 30, 1.6);
+ Breaker bf2_11 = new Breaker("Oven A", 2, 12, 1, 11, 50, 1.6);
+ Breaker bf2_12 = new Breaker("Oven B", 2, 14, 1, 12, 50, 1.6);
+ Breaker bf2_13 = new Breaker("Master Bathroom", 3, 9, 2, 5, 20, 1.6);
+ Breaker bf2_14 = new Breaker("Refrigerator", 2, 6, 1, 14, 20, 1.6);
+ bf2_14.setSpaceTandemB(6);
+ Breaker bf2_15 = new Breaker("Master Bedroom", 2, 1, 1, 15, 20, 1.6);
+ BreakerGroup basementHP = new BreakerGroup("6", "Heat Pump", Arrays.asList(bf2_9, bf2_10));
+ BreakerGroup basementCC = new BreakerGroup("3", "Basement", Arrays.asList(basementHP), null);
+ BreakerGroup mainHP = new BreakerGroup("5", "Heat Pump", Arrays.asList(bf2_5, bf2_6));
+ BreakerGroup mainR = new BreakerGroup("4", "Air Handler/Resistive", Arrays.asList(bf2_1, bf2_2, bf2_3, bf2_4));
+ BreakerGroup mainCC = new BreakerGroup("2", "Main Floor", Arrays.asList(mainHP, mainR), null);
+ BreakerGroup upstairsHP = new BreakerGroup("34", "Heat Pump", Arrays.asList(bf10, bf11));
+ BreakerGroup upstairsR = new BreakerGroup("35", "Air Handler/Resistive", Arrays.asList(bf6, bf7, bf8, bf9));
+ BreakerGroup upstairsCC = new BreakerGroup("36", "Upstairs", Arrays.asList(upstairsHP, upstairsR), null);
+ BreakerGroup cc = new BreakerGroup("1", "Climate Control", Arrays.asList(mainCC, upstairsCC, basementCC), null);
+ BreakerGroup hotWater = new BreakerGroup("7", "Hot Water Heater", Arrays.asList(bf2_7, bf2_8));
+ BreakerGroup oven = new BreakerGroup("8", "Oven/Cooktop", Arrays.asList(bf2_11, bf2_12));
+ BreakerGroup masterBR = new BreakerGroup("9", "Master Bedroom", Arrays.asList(bf2_13, bf2_15));
+ BreakerGroup fridge = new BreakerGroup("10", "Refrigerator", Arrays.asList(bf2_14));
+ BreakerGroup solar = new BreakerGroup("11", "Solar", Arrays.asList(bf1, bf2));
+ BreakerGroup septic = new BreakerGroup("13", "Septic Aerator", Arrays.asList(bf3));
+ BreakerGroup garage = new BreakerGroup("14", "Garage/Guest Baths", Arrays.asList(bf4));
+ BreakerGroup office = new BreakerGroup("15", "Office", Arrays.asList(bf5));
+ BreakerGroup dryer = new BreakerGroup("37", "Dryer", Arrays.asList(bf12, bf13));
+ Breaker bf3_1 = new Breaker("Hub 2 Port 1", 3, 1, 2, 1, 20, 1.6);
+ Breaker bf3_2 = new Breaker("Hub 2 Port 2", 3, 3, 2, 2, 20, 1.6);
+ Breaker bf3_3 = new Breaker("Hub 2 Port 3", 3, 5, 2, 3, 20, 1.6);
+ Breaker bf3_4 = new Breaker("Hub 2 Port 4", 3, 7, 2, 4, 20, 1.6);
+ Breaker bf3_6 = new Breaker("Hub 2 Port 6", 3, 11, 2, 6, 20, 1.6);
+ Breaker bf3_7 = new Breaker("Hub 2 Port 7", 3, 13, 2, 7, 20, 1.6);
+ Breaker bf3_8 = new Breaker("Hub 2 Port 8", 3, 15, 2, 8, 20, 1.6);
+ Breaker bf3_9 = new Breaker("Hub 2 Port 9", 3, 2, 2, 9, 20, 1.6);
+ Breaker bf3_10 = new Breaker("Hub 2 Port 10", 3, 4, 2, 10, 20, 1.6);
+ Breaker bf3_12 = new Breaker("Hub 2 Port 12", 3, 6, 2, 12, 20, 1.6);
+ Breaker bf3_13 = new Breaker("Hub 2 Port 13", 3, 8, 2, 13, 20, 1.6);
+ Breaker bf3_14 = new Breaker("Hub 2 Port 14", 3, 10, 2, 14, 20, 1.6);
+ BreakerGroup g1 = new BreakerGroup("17", "Living Room/Printer/Outdoor Lights", Arrays.asList(bf3_1));
+ BreakerGroup g2 = new BreakerGroup("18", "Dishwasher/Disposal/Sink Lights", Arrays.asList(bf3_2));
+ BreakerGroup g3 = new BreakerGroup("19", "Microwave", Arrays.asList(bf3_3));
+ BreakerGroup g4 = new BreakerGroup("20", "Kitchen Outlets", Arrays.asList(bf3_4));
+// BreakerGroup g5 = new BreakerGroup("21", "Hub 2 Port 5", Arrays.asList(bf3_5));
+ BreakerGroup g6 = new BreakerGroup("22", "Mini Fridge", Arrays.asList(bf3_6));
+ BreakerGroup g7 = new BreakerGroup("23", "Basement Lights/Outlets", Arrays.asList(bf3_7));
+ BreakerGroup g8 = new BreakerGroup("24", "Theatre", Arrays.asList(bf3_8));
+ BreakerGroup g9 = new BreakerGroup("25", "Sunroom Fan/Outside Floods", Arrays.asList(bf3_9));
+ BreakerGroup g10 = new BreakerGroup("26", "Radon/Deep Freezer", Arrays.asList(bf3_10));
+// BreakerGroup g11 = new BreakerGroup("27", "Hub 2 Port 11", Arrays.asList(bf3_11));
+ BreakerGroup g12 = new BreakerGroup("28", "Kitchen Lights", Arrays.asList(bf3_12));
+ BreakerGroup g13 = new BreakerGroup("29", "Router/Networking", Arrays.asList(bf3_13));
+ BreakerGroup g14 = new BreakerGroup("30", "Half Bath/Utility/Garage Lights", Arrays.asList(bf3_14));
+// BreakerGroup g15 = new BreakerGroup("31", "Bar Outlets", Arrays.asList(bf3_15));
+ BreakerGroup kitchen = new BreakerGroup("33", "Kitchen", Arrays.asList(oven, fridge, g2, g3, g4, g12), null);
+
+ Breaker b4_1 = new Breaker("Hub 3 Port 1", 4, 1, 3, 1, 20, 1.6);
+ Breaker b4_2 = new Breaker("Hub 3 Port 2", 4, 2, 3, 2, 30, 1.6);
+ Breaker b4_3 = new Breaker("Hub 3 Port 3", 4, 3, 3, 3, 50, 1.6);
+ Breaker b4_4 = new Breaker("Hub 3 Port 4", 4, 4, 3, 4, 20, 1.6);
+ Breaker b4_5 = new Breaker("Hub 3 Port 5", 4, 5, 3, 5, 20, 1.6);
+ Breaker b4_6 = new Breaker("Hub 3 Port 6", 4, 6, 3, 6, 20, 1.6);
+ Breaker b4_7 = new Breaker("Hub 3 Port 7", 4, 7, 3, 7, 20, 1.6);
+ Breaker b4_8 = new Breaker("Hub 3 Port 8", 4, 8, 3, 8, 20, 1.6);
+ Breaker b4_9 = new Breaker("Hub 3 Port 9", 4, 9, 3, 9, 20, 1.6);
+ Breaker b4_10 = new Breaker("Hub 3 Port 10", 4, 10, 3, 10, 20, 1.6);
+ Breaker b4_11 = new Breaker("Hub 3 Port 11", 4, 11, 3, 11, 20, 1.6);
+ Breaker b4_12 = new Breaker("Hub 3 Port 12", 4, 12, 3, 12, 20, 1.6);
+ Breaker b4_13 = new Breaker("Hub 3 Port 13", 4, 13, 3, 13, 20, 1.6);
+ Breaker b4_14 = new Breaker("Hub 3 Port 14", 4, 14, 3, 14, 20, 1.6);
+ Breaker b4_15 = new Breaker("Hub 3 Port 15", 4, 15, 3, 15, 20, 1.6);
+ BreakerGroup g4_1 = new BreakerGroup("41", "Hub 3 Port 1", Arrays.asList(b4_1));
+ BreakerGroup g4_2 = new BreakerGroup("42", "Hub 3 Port 2", Arrays.asList(b4_2));
+ BreakerGroup g4_3 = new BreakerGroup("43", "Hub 3 Port 3", Arrays.asList(b4_3));
+ BreakerGroup g4_4 = new BreakerGroup("44", "Hub 3 Port 4", Arrays.asList(b4_4));
+ BreakerGroup g4_5 = new BreakerGroup("45", "Hub 3 Port 5", Arrays.asList(b4_5));
+ BreakerGroup g4_6 = new BreakerGroup("46", "Hub 3 Port 6", Arrays.asList(b4_6));
+ BreakerGroup g4_7 = new BreakerGroup("47", "Hub 3 Port 7", Arrays.asList(b4_7));
+ BreakerGroup g4_8 = new BreakerGroup("48", "Hub 3 Port 8", Arrays.asList(b4_8));
+ BreakerGroup g4_9 = new BreakerGroup("49", "Hub 3 Port 9", Arrays.asList(b4_9));
+ BreakerGroup g4_10 = new BreakerGroup("50", "Hub 3 Port 10", Arrays.asList(b4_10));
+ BreakerGroup g4_11 = new BreakerGroup("51", "Hub 3 Port 11", Arrays.asList(b4_11));
+ BreakerGroup g4_12 = new BreakerGroup("52", "Hub 3 Port 12", Arrays.asList(b4_12));
+ BreakerGroup g4_13 = new BreakerGroup("53", "Hub 3 Port 13", Arrays.asList(b4_13));
+ BreakerGroup g4_14 = new BreakerGroup("54", "Hub 3 Port 14", Arrays.asList(b4_14));
+ BreakerGroup g4_15 = new BreakerGroup("55", "Hub 3 Port 15", Arrays.asList(b4_15));
+ BreakerGroup debug = new BreakerGroup("40", "Debug Hub 3", Arrays.asList(g4_1, g4_2, g4_3), null);
+// BreakerGroup debug = new BreakerGroup("40", "Debug Hub 3", Arrays.asList(g4_1, g4_2, g4_3, g4_4, g4_5, g4_6, g4_7, g4_8, g4_9, g4_10, g4_11, g4_12, g4_13, g4_14, g4_15), null);
+// BreakerGroup debug = new BreakerGroup("40", "Debug Hub 3", Arrays.asList(b4_1));
+
+
+ BreakerGroup house = new BreakerGroup("0", "*redacted*", Arrays.asList(solar, cc, hotWater, dryer, septic, masterBR, garage, office, kitchen, g1, g6, g7, g8, g9, g10, g13, g14, debug), null);
+ BreakerConfig config = new BreakerConfig(Collections.singletonList(house));
+ CollectionUtils.edit(config.getAllBreakerGroups(), _g->_g.setAccountId(1));
+ CollectionUtils.edit(config.getAllBreakers(), _b->{
+ _b.setAccountId(1);
+ _b.setCalibrationFactor(1.0);
+ });
+ b4_1.setCalibrationFactor(1.215);
+ b4_2.setCalibrationFactor(1.215);
+ b4_3.setCalibrationFactor(1.215);
+ config.setAccountId(1);
+ BreakerHub hub0 = new BreakerHub();
+ hub0.setHub(0);
+ hub0.setVoltageCalibrationFactor(0.4587);
+ hub0.setFrequency(60);
+ BreakerHub hub1 = new BreakerHub();
+ hub1.setHub(1);
+ hub1.setVoltageCalibrationFactor(0.439);
+ hub1.setFrequency(60);
+ BreakerHub hub2 = new BreakerHub();
+ hub2.setHub(2);
+ hub2.setVoltageCalibrationFactor(0.3535);
+ hub2.setFrequency(60);
+ BreakerHub hub3 = new BreakerHub();
+ hub3.setHub(3);
+ hub3.setVoltageCalibrationFactor(0.419);
+ hub3.setFrequency(60);
+ config.setBreakerHubs(Arrays.asList(hub0, hub1, hub2, hub3));
+
+ Meter heatMeter = new Meter();
+ heatMeter.setAccountId(1);
+ heatMeter.setIndex(0);
+ heatMeter.setName("Heat");
+
+ Meter mainMeter = new Meter();
+ mainMeter.setAccountId(1);
+ mainMeter.setIndex(1);
+ mainMeter.setName("Main/Solar");
+
+ config.setMeters(CollectionUtils.asArrayList(heatMeter, mainMeter));
+
+ BreakerPanel heat1 = new BreakerPanel();
+ heat1.setAccountId(1);
+ heat1.setIndex(0);
+ heat1.setMeter(heatMeter.getIndex());
+ heat1.setName("Heat 1");
+ heat1.setSpaces(20);
+
+ BreakerPanel heat2 = new BreakerPanel();
+ heat2.setAccountId(1);
+ heat2.setIndex(1);
+ heat2.setMeter(heatMeter.getIndex());
+ heat2.setName("Heat 2");
+ heat2.setSpaces(20);
+
+ BreakerPanel main = new BreakerPanel();
+ main.setAccountId(1);
+ main.setIndex(2);
+ main.setMeter(mainMeter.getIndex());
+ main.setName("Main");
+ main.setSpaces(20);
+
+ BreakerPanel sub = new BreakerPanel();
+ sub.setAccountId(1);
+ sub.setIndex(3);
+ sub.setMeter(mainMeter.getIndex());
+ sub.setName("Sub-Panel");
+ sub.setSpaces(20);
+
+ config.setPanels(CollectionUtils.asArrayList(heat1, heat2, main, sub));
+
+ Map panelToMeter = CollectionUtils.transformToMap(config.getPanels(), BreakerPanel::getIndex, BreakerPanel::getMeter);
+ CollectionUtils.edit(config.getAllBreakers(), _b->_b.setMeter(DaoSerializer.toInteger(panelToMeter.get(_b.getPanel()))));*/
+
+ BreakerConfig config = dao.getConfig(1);
+// BreakerConfig config = DaoSerializer.parse(ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "breakerconfig_backup_210107.json"), BreakerConfig.class);
+// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "breakerconfig_backup_210107.json", DaoSerializer.toJson(config));
+// CollectionUtils.edit(config.getAllBreakerGroups(), _g->{
+// if (NullUtils.isEmpty(_g.getName())) {
+// Breaker b = CollectionUtils.getFirst(_g.getBreakers());
+// if (b != null)
+// _g.setName(String.format("Panel %d, %s", b.getPanel(), b.getName()));
+// }
+// });
+// for (BreakerGroup group : CollectionUtils.filter(config.getAllBreakerGroups(), _g->NullUtils.isEmpty(_g.getId()))) {
+// List ids = CollectionUtils.transform(config.getAllBreakerGroupIds(), NullUtils::toInteger);
+// group.setId(String.valueOf(CollectionUtils.getLargest(ids) + 1));
+// }
+// BreakerGroup root = CollectionUtils.getFirst(config.getBreakerGroups());
+// root.getSubGroups().removeIf(_g->_g.getName().equals("Debug Hub 3"));
+// config.removeInvalidGroups();
+// for (BreakerGroup group : config.getAllBreakerGroups()) {
+// if (NullUtils.isEmpty(group.getId())) {
+// List ids = CollectionUtils.transform(config.getAllBreakerGroupIds(), NullUtils::toInteger);
+// group.setId(String.valueOf(CollectionUtils.getLargest(ids) + 1));
+// }
+// }
+ dao.putConfig(config);
+ dao.shutdown();
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java
new file mode 100644
index 0000000..64d6628
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CreateMongoConfig.java
@@ -0,0 +1,10 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+
+public class CreateMongoConfig {
+ public static void main(String[] args) {
+ new MongoConfig("localhost", "*redacted*", "*redacted*", "CURRENT_MONITOR").saveToDisk(LanternFiles.OPS_PATH + "mongo.cfg");
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java
new file mode 100644
index 0000000..fdb0ecd
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/CurrentMonitorSerializers.java
@@ -0,0 +1,13 @@
+package com.lanternsoftware.currentmonitor;
+
+
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator;
+import com.lanternsoftware.util.dao.generator.SwiftModelGenerator;
+
+public class CurrentMonitorSerializers {
+ public static void main(String[] args) {
+ DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "currentmonitor", true, null);
+ SwiftModelGenerator.generateModel(LanternFiles.SOURCE_PATH + "currentmonitor", LanternFiles.SOURCE_PATH + "iOS");
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java
new file mode 100644
index 0000000..f5734b7
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/MigrateSummaries.java
@@ -0,0 +1,55 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
+import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+
+public class MigrateSummaries {
+ public static void main(String[] args) {
+ CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
+// TimeZone tz = TimeZone.getTimeZone("America/Chicago");
+// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null);
+// CollectionUtils.edit(summaries, _s->CollectionUtils.edit(_s.getAllGroups(), _t->_t.setAccountId(1)));
+// dao.getProxy().save(summaries);
+
+ dao.getProxy().save(CollectionUtils.transform(dao.getProxy().queryAll(BreakerGroupEnergy.class), BreakerGroupSummary::new));
+
+// List readings = null;
+// while ((readings == null) || !readings.isEmpty()) {
+// readings = dao.getProxy().query(BreakerPower.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 1000000);
+// System.out.println("Adding account id to " + readings.size() + " power readings");
+// CollectionUtils.edit(readings, _s -> _s.setAccountId(1));
+// dao.getProxy().save(readings);
+// }
+//
+// List archives = null;
+// while ((archives == null) || !archives.isEmpty()) {
+// archives = dao.getProxy().query(BreakerPowerArchive.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 50);
+// System.out.println("Adding account id to " + archives.size() + " archives");
+// CollectionUtils.edit(archives, _s -> _s.setAccountId(1));
+// dao.getProxy().save(archives);
+// }
+
+// List readings = CollectionUtils.filter(dao.getBreakerPower(Arrays.asList("0-1", "0-2"), DateUtils.date(6,26,2020, 17, 0, 0, 0, tz), DateUtils.date(6,26,2020, 22, 0, 0, 0, tz)), _p->_p.getPower() > 0.0);
+// CollectionUtils.edit(readings, _p->_p.setPower(-_p.getPower()));
+// dao.getProxy().save(readings);
+
+// Map> dups = CollectionUtils.transformToMultiMap(dao.getBreakerPower(Arrays.asList("2-1","2-2","2-3","2-4","2-5","2-6","2-7","2-8","2-9","2-10","2-11","2-12","2-13","2-14","2-15"), DateUtils.date(6,26,2020, 17, 0, 0, 0, tz), DateUtils.date(6,26,2020, 18, 0, 0, 0, tz)), _p->_p.getKey()+_p.getReadTime().getTime());
+// for (List dup : dups.values()) {
+// if (dup.size() > 1) {
+// CollectionUtils.removeFirst(dup);
+// dao.getProxy().delete(BreakerPower.class, DaoQuery.in("_id", CollectionUtils.transform(dup, BreakerPower::getId)));
+// }
+// }
+
+// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null);
+// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "summaryBackup.json", DaoSerializer.toJson(DaoSerializer.toDaoEntities(summaries)));
+// for (BreakerGroupEnergy summary : summaries) {
+// dao.getProxy().save(summary);
+// }
+ }
+}
diff --git a/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java
new file mode 100644
index 0000000..723c6f9
--- /dev/null
+++ b/currentmonitor/lantern-service-currentmonitor/src/test/java/com/lanternsoftware/currentmonitor/RebuildSummaries.java
@@ -0,0 +1,99 @@
+package com.lanternsoftware.currentmonitor;
+
+import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
+import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
+import com.lanternsoftware.datamodel.currentmonitor.Breaker;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupEnergy;
+import com.lanternsoftware.datamodel.currentmonitor.BreakerGroupSummary;
+import com.lanternsoftware.datamodel.currentmonitor.EnergyBlockViewMode;
+import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
+import com.lanternsoftware.util.CollectionUtils;
+import com.lanternsoftware.util.DateUtils;
+import com.lanternsoftware.util.DebugTimer;
+import com.lanternsoftware.util.LanternFiles;
+import com.lanternsoftware.util.dao.DaoQuery;
+import com.lanternsoftware.util.dao.mongo.MongoConfig;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+public class RebuildSummaries {
+ public static void main(String[] args) {
+ int accountId = 1;
+ CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
+ TimeZone tz = TimeZone.getTimeZone("America/Chicago");
+ Date start = DateUtils.date(1, 7, 2021, tz);
+// Date start = DateUtils.getMidnightBeforeNow(tz);
+ Date end = DateUtils.getMidnightAfterNow(tz);
+ Map> days = CollectionUtils.transformToMultiMap(dao.getProxy().query(HubPowerMinute.class, new DaoQuery("account_id", accountId).andBetweenInclusiveExclusive("minute", (int)(start.getTime()/60000), (int)(end.getTime()/60000))), _m->DateUtils.getMidnightBefore(_m.getMinuteAsDate(), tz));
+ BreakerConfig config = dao.getConfig(accountId);
+ BreakerGroup root = CollectionUtils.getFirst(config.getBreakerGroups());
+ Map breakers = CollectionUtils.transformToMap(root.getAllBreakers(), Breaker::getKey);
+ Map breakerKeyToGroup = new HashMap<>();
+ for (BreakerGroup group : root.getAllBreakerGroups()) {
+ for (Breaker b : group.getAllBreakers()) {
+ breakerKeyToGroup.put(b.getKey(), group);
+ }
+ }
+
+ for (Map.Entry> day : days.entrySet()) {
+ BreakerGroupEnergy energy = null;
+ DebugTimer timer = new DebugTimer("Time to rebuild one day");
+ Map> minutes = CollectionUtils.transformToMultiMap(day.getValue(), HubPowerMinute::getMinute);
+ for (List minute : minutes.values()) {
+ if (energy == null)
+ energy = new BreakerGroupEnergy(root, minute, EnergyBlockViewMode.DAY, day.getKey(), tz);
+ else
+ energy.addEnergy(breakers, breakerKeyToGroup, minute, tz);
+ }
+ timer.stop();
+ if (energy != null)
+ dao.putBreakerGroupEnergy(energy);
+ }
+ dao.updateSummaries(root, days.keySet(), tz);
+
+// List summaries = dao.getProxy().query(BreakerGroupEnergy.class, null);
+// CollectionUtils.edit(summaries, _s->CollectionUtils.edit(_s.getAllGroups(), _t->_t.setAccountId(1)));
+// dao.getProxy().save(summaries);
+
+// List readings = null;
+// while ((readings == null) || !readings.isEmpty()) {
+// readings = dao.getProxy().query(BreakerPower.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 1000000);
+// System.out.println("Adding account id to " + readings.size() + " power readings");
+// CollectionUtils.edit(readings, _s -> _s.setAccountId(1));
+// dao.getProxy().save(readings);
+// }
+//
+// List archives = null;
+// while ((archives == null) || !archives.isEmpty()) {
+// archives = dao.getProxy().query(BreakerPowerArchive.class, new DaoQuery("account_id", new DaoQuery("$ne", 1)), null, null, 0, 50);
+// System.out.println("Adding account id to " + archives.size() + " archives");
+// CollectionUtils.edit(archives, _s -> _s.setAccountId(1));
+// dao.getProxy().save(archives);
+// }
+
+// List