mirror of
https://github.com/zyphlar/LanternPowerMonitor.git
synced 2024-03-08 14:07:47 +00:00
Improve 3A+ case, making it easier to take the pi out. Improve the fit of the Z2 case.
Make it possible for a hub to reload a config automatically when it changes without being restarted. Prevent the auto-calibration on first install from being stomped by the app. Allow updating the hub software via the app.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<groupId>com.lanternsoftware.currentmonitor</groupId>
|
||||
<artifactId>lantern-currentmonitor</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0.4</version>
|
||||
<version>1.0.6</version>
|
||||
<name>lantern-currentmonitor</name>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -97,12 +97,17 @@ public class CurrentMonitor {
|
||||
}
|
||||
|
||||
public void monitorPower(BreakerHub _hub, List<Breaker> _breakers, int _intervalMs, PowerListener _listener) {
|
||||
stopMonitoring();
|
||||
listener = _listener;
|
||||
List<Breaker> validBreakers = CollectionUtils.filter(_breakers, _b->_b.getPort() > 0 && _b.getPort() < 16);
|
||||
sampler = new Sampler(_hub, validBreakers, _intervalMs, 2);
|
||||
LOG.info("Starting to monitor ports {}", CollectionUtils.transformToCommaSeparated(validBreakers, _b->String.valueOf(_b.getPort())));
|
||||
executor.submit(sampler);
|
||||
try {
|
||||
stopMonitoring();
|
||||
listener = _listener;
|
||||
List<Breaker> validBreakers = CollectionUtils.filter(_breakers, _b -> _b.getPort() > 0 && _b.getPort() < 16);
|
||||
sampler = new Sampler(_hub, validBreakers, _intervalMs, 2);
|
||||
LOG.info("Starting to monitor ports {}", CollectionUtils.transformToCommaSeparated(validBreakers, _b -> String.valueOf(_b.getPort())));
|
||||
executor.submit(sampler);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
LOG.error("throwable", t);
|
||||
}
|
||||
}
|
||||
|
||||
private GpioPinAnalogInput getPin(int _chip, int _pin) {
|
||||
@@ -182,8 +187,10 @@ public class CurrentMonitor {
|
||||
try {
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
if (!running)
|
||||
if (!running) {
|
||||
LOG.error("Power Monitoring Stopped");
|
||||
break;
|
||||
}
|
||||
}
|
||||
final Date readTime = new Date();
|
||||
final long intervalStart = (interval * intervalNs) + start;
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.lanternsoftware.datamodel.currentmonitor.BreakerGroup;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.BreakerHub;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.BreakerPowerMinute;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommands;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
|
||||
@@ -25,6 +27,7 @@ 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.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
@@ -39,10 +42,12 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -64,7 +69,7 @@ public class MonitorApp {
|
||||
private static final AtomicBoolean running = new AtomicBoolean(true);
|
||||
private static final CurrentMonitor monitor = new CurrentMonitor();
|
||||
private static final List<BreakerPower> readings = new ArrayList<>();
|
||||
private static final String version = getVersionNumber();
|
||||
private static String version;
|
||||
private static final PowerListener logger = _p -> {
|
||||
if (!config.isDebug()) {
|
||||
_p.setHubVersion(version);
|
||||
@@ -76,9 +81,138 @@ public class MonitorApp {
|
||||
} else
|
||||
LOG.info("Panel{} - Space{} Power: {}W", _p.getPanel(), Breaker.toSpaceDisplay(_p.getSpace()), String.format("%.3f", _p.getPower()));
|
||||
};
|
||||
private static final BleCharacteristicListener bluetoothListener = 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(()->{
|
||||
synchronized (monitor) {
|
||||
switch (ch) {
|
||||
case Host:
|
||||
if ((_value.length > 0)) {
|
||||
config.setHost(NullUtils.terminateWith(NullUtils.toString(_value), "/") + "currentmonitor/");
|
||||
ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
|
||||
}
|
||||
break;
|
||||
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(new String[]{"systemctl","restart","currentmonitor"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to restart", _e);
|
||||
}
|
||||
break;
|
||||
case Reboot:
|
||||
LOG.info("Rebooting Pi...");
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"reboot","now"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to reboot", _e);
|
||||
}
|
||||
break;
|
||||
case Shutdown:
|
||||
LOG.info("Shutting down Pi...");
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"shutdown","now"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to shutdown", _e);
|
||||
}
|
||||
break;
|
||||
case Update:
|
||||
monitor.submit(new UpdateChecker(true));
|
||||
break;
|
||||
case ReloadConfig:
|
||||
HttpGet get = new HttpGet(host + "config");
|
||||
get.addHeader("auth_code", authCode);
|
||||
BreakerConfig newConfig = DaoSerializer.parse(pool.executeToString(get), BreakerConfig.class);
|
||||
if (newConfig != null) {
|
||||
breakerConfig = newConfig;
|
||||
List<Breaker> breakers = breakerConfig.getBreakersForHub(config.getHub());
|
||||
BreakerHub hub = breakerConfig.getHub(config.getHub());
|
||||
if (hub != null) {
|
||||
LOG.info("Monitoring {} breakers for hub {}", CollectionUtils.size(breakers), hub.getHub());
|
||||
if (CollectionUtils.size(breakers) > 0)
|
||||
monitor.monitorPower(hub, breakers, 1000, logger);
|
||||
}
|
||||
}
|
||||
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()};
|
||||
if (HubConfigCharacteristic.NetworkDetails == ch) {
|
||||
NetworkStatus status = NetworkMonitor.getNetworkStatus();
|
||||
DaoEntity meta = (host == null)?null:DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version")));
|
||||
status.setPingSuccessful(CollectionUtils.isNotEmpty(meta));
|
||||
return DaoSerializer.toZipBson(status);
|
||||
}
|
||||
if (HubConfigCharacteristic.Log == ch) {
|
||||
String[] log = NullUtils.cleanSplit(ResourceLoader.loadFileAsString(WORKING_DIR + "log/log.txt"), "\n");
|
||||
if (log.length > 15)
|
||||
log = Arrays.copyOfRange(log, log.length-15, log.length);
|
||||
return ZipUtils.zip(NullUtils.toByteArray(CollectionUtils.delimit(Arrays.asList(log), "\n")));
|
||||
}
|
||||
if (HubConfigCharacteristic.Version == ch)
|
||||
return NullUtils.toByteArray(version);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
private static BluetoothConfig bluetoothConfig;
|
||||
private static MqttPoster mqttPoster;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"systemctl","restart","dbus"});
|
||||
ConcurrencyUtils.sleep(500);
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to restart", _e);
|
||||
}
|
||||
version = getVersionNumber();
|
||||
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");
|
||||
@@ -90,107 +224,7 @@ public class MonitorApp {
|
||||
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(()->{
|
||||
synchronized (monitor) {
|
||||
switch (ch) {
|
||||
case Host:
|
||||
if ((_value.length > 0)) {
|
||||
config.setHost(NullUtils.terminateWith(NullUtils.toString(_value), "/") + "currentmonitor/");
|
||||
ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config));
|
||||
}
|
||||
break;
|
||||
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(new String[]{"systemctl","restart","currentmonitor"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to restart", _e);
|
||||
}
|
||||
break;
|
||||
case Reboot:
|
||||
LOG.info("Rebooting Pi...");
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"reboot","now"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to reboot", _e);
|
||||
}
|
||||
break;
|
||||
case Shutdown:
|
||||
LOG.info("Shutting down Pi...");
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"shutdown","now"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to shutdown", _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()};
|
||||
if (HubConfigCharacteristic.NetworkDetails == ch) {
|
||||
NetworkStatus status = NetworkMonitor.getNetworkStatus();
|
||||
DaoEntity meta = (host == null)?null:DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version")));
|
||||
status.setPingSuccessful(CollectionUtils.isNotEmpty(meta));
|
||||
return DaoSerializer.toZipBson(status);
|
||||
}
|
||||
if (HubConfigCharacteristic.Log == ch) {
|
||||
String[] log = NullUtils.cleanSplit(ResourceLoader.loadFileAsString(WORKING_DIR + "log/log.txt"), "\n");
|
||||
if (log.length > 15)
|
||||
log = Arrays.copyOfRange(log, log.length-15, log.length);
|
||||
return ZipUtils.zip(NullUtils.toByteArray(CollectionUtils.delimit(Arrays.asList(log), "\n")));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
bluetoothConfig = new BluetoothConfig("Lantern Hub", bluetoothListener);
|
||||
bluetoothConfig.start();
|
||||
if (NullUtils.isNotEmpty(config.getAuthCode()))
|
||||
authCode = config.getAuthCode();
|
||||
@@ -303,6 +337,7 @@ public class MonitorApp {
|
||||
if (!readings.isEmpty()) {
|
||||
mqttReadings.addAll(readings);
|
||||
post = new DaoEntity("readings", DaoSerializer.toDaoEntities(readings));
|
||||
post.put("hub", config.getHub());
|
||||
if (curMinute != lastMinute) {
|
||||
HubPowerMinute minute = new HubPowerMinute();
|
||||
minute.setAccountId(breakerConfig.getAccountId());
|
||||
@@ -336,7 +371,8 @@ public class MonitorApp {
|
||||
}
|
||||
if (post != null) {
|
||||
byte[] payload = DaoSerializer.toZipBson(post);
|
||||
if (post(payload, "power/batch")) {
|
||||
PostResponse<HubCommands> resp = post(payload, "power/batch", HubCommands.class);
|
||||
if (resp.success) {
|
||||
File[] files = new File(WORKING_DIR + "cache").listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
@@ -347,6 +383,11 @@ public class MonitorApp {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resp.t != null) {
|
||||
for (HubCommand command : resp.t.getCommands()) {
|
||||
bluetoothListener.write(command.getCharacteristic().name(), command.getData());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,7 +396,6 @@ public class MonitorApp {
|
||||
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;
|
||||
@@ -371,34 +411,65 @@ public class MonitorApp {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return post(_payload, _path, Boolean.class).success;
|
||||
}
|
||||
|
||||
private static boolean post(byte[] _payload, String _path) {
|
||||
private static <T> PostResponse<T> post(byte[] _payload, String _path, Class<T> _class) {
|
||||
if (NullUtils.isEmpty(host))
|
||||
return false;
|
||||
return new PostResponse<>(false, null);
|
||||
HttpPost post = new HttpPost(host + _path);
|
||||
post.addHeader("auth_code", authCode);
|
||||
post.setEntity(new ByteArrayEntity(_payload, ContentType.APPLICATION_OCTET_STREAM));
|
||||
InputStream is = null;
|
||||
CloseableHttpResponse resp = pool.execute(post);
|
||||
try {
|
||||
return ((resp != null) && (resp.getStatusLine() != null) && (resp.getStatusLine().getStatusCode() == 200));
|
||||
} finally {
|
||||
if ((resp != null) && (resp.getStatusLine() != null) && (resp.getStatusLine().getStatusCode() == 200)) {
|
||||
T t = null;
|
||||
HttpEntity entity = resp.getEntity();
|
||||
if (entity != null) {
|
||||
is = entity.getContent();
|
||||
byte[] payload = IOUtils.toByteArray(is);
|
||||
if (CollectionUtils.length(payload) > 0)
|
||||
t = DaoSerializer.fromZipBson(payload, _class);
|
||||
}
|
||||
return new PostResponse<>(true, t);
|
||||
}
|
||||
}
|
||||
catch (Exception _e) {
|
||||
LOG.error("Failed to make http request to " + post.getURI().toString(), _e);
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(resp);
|
||||
}
|
||||
return new PostResponse<>(false, null);
|
||||
}
|
||||
|
||||
private static class PostResponse<T> {
|
||||
public final boolean success;
|
||||
public final T t;
|
||||
|
||||
public PostResponse(boolean _success, T _t) {
|
||||
success = _success;
|
||||
t = _t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class UpdateChecker implements Runnable {
|
||||
private final boolean force;
|
||||
|
||||
public UpdateChecker() {
|
||||
force = false;
|
||||
}
|
||||
|
||||
public UpdateChecker(boolean _force) {
|
||||
force = _force;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (NullUtils.isNotEmpty(host) && config.isAutoUpdate()) {
|
||||
if (NullUtils.isNotEmpty(host) && (force || config.isAutoUpdate())) {
|
||||
DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version")));
|
||||
String newVersion = DaoSerializer.getString(meta, "version");
|
||||
if (NullUtils.isNotEqual(newVersion, version)) {
|
||||
@@ -407,9 +478,14 @@ public class MonitorApp {
|
||||
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);
|
||||
ConcurrencyUtils.sleep(10000);
|
||||
synchronized (running) {
|
||||
running.set(false);
|
||||
}
|
||||
monitor.stopMonitoring();
|
||||
bluetoothConfig.stop();
|
||||
pool.shutdown();
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"systemctl", "restart", "currentmonitor"});
|
||||
Runtime.getRuntime().exec(new String[]{"systemctl","restart","currentmonitor"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to restart", _e);
|
||||
}
|
||||
@@ -419,65 +495,29 @@ public class MonitorApp {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CommandChecker implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
if (NullUtils.isNotEmpty(host)) {
|
||||
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(new String[]{"sudo", "raspi-config", "--expand-rootfs"});
|
||||
ConcurrencyUtils.sleep(5000);
|
||||
Runtime.getRuntime().exec(new String[]{"reboot", "now"});
|
||||
} 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(new String[]{"systemctl", "restart", "currentmonitor"});
|
||||
} catch (IOException _e) {
|
||||
LOG.error("Exception occurred while trying to restart", _e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getVersionNumber() {
|
||||
InputStream is = null;
|
||||
public static String getVersionNumber() {
|
||||
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;
|
||||
Enumeration<URL> resources = MonitorApp.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = resources.nextElement().openStream();
|
||||
Manifest manifest = new Manifest(is);
|
||||
Attributes attr = manifest.getMainAttributes();
|
||||
if (NullUtils.isEqual(attr.getValue("Specification-Title"), "Lantern Power Monitor")) {
|
||||
String version = attr.getValue("Specification-Version");
|
||||
LOG.info("Current Version: {}", version);
|
||||
return version;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception _e) {
|
||||
LOG.error("Failed to get current version number", _e);
|
||||
return "";
|
||||
}
|
||||
finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.lanternsoftware.dataaccess.currentmonitor;
|
||||
|
||||
import com.lanternsoftware.datamodel.currentmonitor.Account;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.ChargeSummary;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.ChargeTotal;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergyTotal;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.Sequence;
|
||||
@@ -10,14 +12,15 @@ import com.lanternsoftware.datamodel.rules.FcmDevice;
|
||||
import com.lanternsoftware.datamodel.rules.Rule;
|
||||
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.List;
|
||||
|
||||
public class Backup {
|
||||
public static void main(String[] args) {
|
||||
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
|
||||
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_PATH + "mongo.cfg"));
|
||||
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_SOURCE + "mongo.cfg"));
|
||||
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST + "mongo.cfg"));
|
||||
|
||||
DebugTimer t1 = new DebugTimer("Query Accounts");
|
||||
List<Account> accounts = dao.getProxy().queryAll(Account.class);
|
||||
@@ -34,17 +37,39 @@ public class Backup {
|
||||
t4.stop();
|
||||
|
||||
DebugTimer t5 = new DebugTimer("Query Energy");
|
||||
List<EnergySummary> energy = dao.getProxy().queryAll(EnergySummary.class);
|
||||
for (Account a : accounts) {
|
||||
List<EnergySummary> energy = dao.getProxy().query(EnergySummary.class, new DaoQuery("account_id", a.getId()));
|
||||
DebugTimer t = new DebugTimer("Save Energy");
|
||||
backupDao.getProxy().save(energy);
|
||||
t.stop();
|
||||
}
|
||||
t5.stop();
|
||||
DebugTimer t6 = new DebugTimer("Save Energy");
|
||||
backupDao.getProxy().save(energy);
|
||||
|
||||
DebugTimer t6 = new DebugTimer("Query Energy Totals");
|
||||
for (Account a : accounts) {
|
||||
List<EnergyTotal> total = dao.getProxy().query(EnergyTotal.class, new DaoQuery("account_id", a.getId()));
|
||||
DebugTimer t = new DebugTimer("Save Summary");
|
||||
backupDao.getProxy().save(total);
|
||||
t.stop();
|
||||
}
|
||||
t6.stop();
|
||||
|
||||
DebugTimer t7 = new DebugTimer("Query Summaries");
|
||||
List<EnergyTotal> summary = dao.getProxy().queryAll(EnergyTotal.class);
|
||||
DebugTimer t7 = new DebugTimer("Query Charges");
|
||||
for (Account a : accounts) {
|
||||
List<ChargeSummary> charges = dao.getProxy().query(ChargeSummary.class, new DaoQuery("account_id", a.getId()));
|
||||
DebugTimer t = new DebugTimer("Save Charges");
|
||||
backupDao.getProxy().save(charges);
|
||||
t.stop();
|
||||
}
|
||||
t7.stop();
|
||||
DebugTimer t8 = new DebugTimer("Save Summaries");
|
||||
backupDao.getProxy().save(summary);
|
||||
|
||||
DebugTimer t8 = new DebugTimer("Query Charge Totals");
|
||||
for (Account a : accounts) {
|
||||
List<ChargeTotal> charges = dao.getProxy().query(ChargeTotal.class, new DaoQuery("account_id", a.getId()));
|
||||
DebugTimer t = new DebugTimer("Save Charge Totals");
|
||||
backupDao.getProxy().save(charges);
|
||||
t.stop();
|
||||
}
|
||||
t8.stop();
|
||||
|
||||
DebugTimer t9 = new DebugTimer("Query Events");
|
||||
|
||||
@@ -16,8 +16,8 @@ import java.util.TimeZone;
|
||||
|
||||
public class BackupMinutes {
|
||||
public static void main(String[] args) {
|
||||
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
|
||||
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_PATH + "mongo.cfg"));
|
||||
CurrentMonitorDao dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_SOURCE + "mongo.cfg"));
|
||||
CurrentMonitorDao backupDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.BACKUP_DEST + "mongo.cfg"));
|
||||
Date now = new Date();
|
||||
for (Account a : dao.getProxy().queryAll(Account.class)) {
|
||||
if (a.getId() == 0)
|
||||
@@ -30,12 +30,11 @@ public class BackupMinutes {
|
||||
HubPowerMinute minute = dao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sort("minute"));
|
||||
if (minute == null)
|
||||
continue;
|
||||
Date minStart = DateUtils.addDays(DateUtils.getMidnightBeforeNow(tz), -60, tz);
|
||||
Date start = DateUtils.getMidnightBefore(minute.getMinuteAsDate(), tz);
|
||||
if (minStart.after(start))
|
||||
start = minStart;
|
||||
HubPowerMinute lastBackup = backupDao.getProxy().queryOne(HubPowerMinute.class, new DaoQuery("account_id", a.getId()), DaoSort.sortDesc("minute"));
|
||||
Date start = lastBackup == null ? DateUtils.getMidnightBefore(minute.getMinuteAsDate(), tz) : lastBackup.getMinuteAsDate();
|
||||
// Date start = DateUtils.date(10,16,2021,tz);
|
||||
Date end = DateUtils.addDays(start, 1, tz);
|
||||
while (end.before(now)) {
|
||||
while (start.before(now)) {
|
||||
DebugTimer t2 = new DebugTimer("Account Id: " + a.getId() + " Query Day " + DateUtils.format("MM/dd/yyyy", tz, start));
|
||||
List<HubPowerMinute> minutes = dao.getProxy().query(HubPowerMinute.class, new DaoQuery("account_id", a.getId()).andBetweenInclusiveExclusive("minute", (int) (start.getTime() / 60000), (int) (end.getTime() / 60000)));
|
||||
t2.stop();
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.BreakerPower;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergyViewMode;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
|
||||
import com.lanternsoftware.util.dao.auth.AuthCode;
|
||||
import com.lanternsoftware.util.dao.mongo.MongoProxy;
|
||||
@@ -48,5 +49,9 @@ public interface CurrentMonitorDao {
|
||||
TimeZone getTimeZoneForAccount(int _accountId);
|
||||
String getTimeZoneForAccount(String _authCode);
|
||||
|
||||
void putHubCommand(HubCommand _command);
|
||||
List<HubCommand> getAllHubCommands();
|
||||
void deleteHubCommand(String _id);
|
||||
|
||||
MongoProxy getProxy();
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import com.lanternsoftware.datamodel.currentmonitor.BillingRate;
|
||||
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.BreakerPower;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.ChargeSummary;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.ChargeTotal;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergySummary;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergyTotal;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.EnergyViewMode;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubPowerMinute;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.Sequence;
|
||||
import com.lanternsoftware.util.CollectionUtils;
|
||||
@@ -404,6 +406,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
|
||||
if (config == null) {
|
||||
config = new BreakerConfig();
|
||||
config.setAccountId(_authCode.getAccountId());
|
||||
config.setVersion(config.getVersion());
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -416,6 +419,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
|
||||
config.setMeters(CollectionUtils.aggregate(configs, BreakerConfig::getMeters));
|
||||
config.setBillingPlans(CollectionUtils.aggregate(configs, BreakerConfig::getBillingPlans));
|
||||
config.setBillingRates(CollectionUtils.aggregate(configs, BreakerConfig::getBillingRates));
|
||||
config.setVersion(CollectionUtils.getLargest(CollectionUtils.transform(configs, BreakerConfig::getVersion)));
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -424,6 +428,16 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
|
||||
DaoQuery configQuery = new DaoQuery("_id", String.valueOf(_config.getAccountId()));
|
||||
BreakerConfig oldConfig = proxy.queryOne(BreakerConfig.class, configQuery);
|
||||
if (oldConfig != null) {
|
||||
logger.info("old version: {}, new version: {}", oldConfig.getVersion(), _config.getVersion());
|
||||
if (oldConfig.getVersion() > _config.getVersion()) {
|
||||
for (BreakerHub hub : CollectionUtils.makeNotNull(_config.getBreakerHubs())) {
|
||||
BreakerHub oldHub = oldConfig.getHub(hub.getHub());
|
||||
if (oldHub != null) {
|
||||
logger.info("Prevent overwrite of voltage calibration");
|
||||
hub.setVoltageCalibrationFactor(oldHub.getRawVoltageCalibrationFactor());
|
||||
}
|
||||
}
|
||||
}
|
||||
_config.setVersion(oldConfig.getVersion() + 1);
|
||||
if (NullUtils.isNotIdentical(_config, oldConfig)) {
|
||||
DaoEntity oldEntity = DaoSerializer.toDaoEntity(oldConfig);
|
||||
@@ -441,6 +455,9 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
|
||||
});
|
||||
}
|
||||
}
|
||||
for (BreakerHub hub : CollectionUtils.makeNotNull(_config.getBreakerHubs())) {
|
||||
logger.info("voltage calibration hub {}: {}", hub.getHub(), hub.getVoltageCalibrationFactor());
|
||||
}
|
||||
proxy.save(_config);
|
||||
}
|
||||
|
||||
@@ -459,7 +476,7 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
|
||||
AuthCode code = decryptAuthCode(_authCode);
|
||||
if (code == null)
|
||||
return null;
|
||||
return proxy.queryOne(Account.class, new DaoQuery("_id", code.getAccountId()));
|
||||
return proxy.queryOne(Account.class, new DaoQuery("_id", String.valueOf(code.getAccountId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -572,6 +589,23 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putHubCommand(HubCommand _command) {
|
||||
BreakerConfig config = getConfig(_command.getAccountId());
|
||||
if (config != null)
|
||||
proxy.save(_command.forAllHubs(config));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HubCommand> getAllHubCommands() {
|
||||
return proxy.queryAll(HubCommand.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteHubCommand(String _id) {
|
||||
proxy.delete(HubCommand.class, new DaoQuery("_id", _id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MongoProxy getProxy() {
|
||||
return proxy;
|
||||
|
||||
@@ -209,7 +209,7 @@ public class BreakerConfig implements IIdentical<BreakerConfig> {
|
||||
@Override
|
||||
public boolean isIdentical(BreakerConfig _o) {
|
||||
if (this == _o) return true;
|
||||
return accountId == _o.accountId && CollectionUtils.isIdentical(meters, _o.meters) && CollectionUtils.isIdentical(panels, _o.panels) && CollectionUtils.isIdentical(breakerHubs, _o.breakerHubs) && CollectionUtils.isIdentical(breakerGroups, _o.breakerGroups) && CollectionUtils.isEqual(billingPlans, _o.billingPlans);
|
||||
return accountId == _o.accountId && CollectionUtils.isIdentical(meters, _o.meters) && CollectionUtils.isIdentical(panels, _o.panels) && CollectionUtils.isIdentical(breakerHubs, _o.breakerHubs) && CollectionUtils.isIdentical(breakerGroups, _o.breakerGroups) && CollectionUtils.isIdentical(billingPlans, _o.billingPlans);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.lanternsoftware.util.dao.annotations.DBSerializable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@DBSerializable
|
||||
@DBSerializable(autogen = false)
|
||||
public class BreakerHub implements IIdentical<BreakerHub> {
|
||||
private int hub;
|
||||
private double voltageCalibrationFactor;
|
||||
@@ -22,16 +22,24 @@ public class BreakerHub implements IIdentical<BreakerHub> {
|
||||
hub = _hub;
|
||||
}
|
||||
|
||||
public double getRawVoltageCalibrationFactor() {
|
||||
return voltageCalibrationFactor;
|
||||
}
|
||||
|
||||
public double getVoltageCalibrationFactor() {
|
||||
return voltageCalibrationFactor == 0.0?1.0:voltageCalibrationFactor;
|
||||
return voltageCalibrationFactor == 0.0?0.3445:voltageCalibrationFactor;
|
||||
}
|
||||
|
||||
public void setVoltageCalibrationFactor(double _voltageCalibrationFactor) {
|
||||
voltageCalibrationFactor = _voltageCalibrationFactor;
|
||||
}
|
||||
|
||||
public double getRawPortCalibrationFactor() {
|
||||
return portCalibrationFactor;
|
||||
}
|
||||
|
||||
public double getPortCalibrationFactor() {
|
||||
return portCalibrationFactor == 0.0?1.0:portCalibrationFactor;
|
||||
return portCalibrationFactor == 0.0?1.25:portCalibrationFactor;
|
||||
}
|
||||
|
||||
public void setPortCalibrationFactor(double _portCalibrationFactor) {
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.lanternsoftware.datamodel.currentmonitor;
|
||||
|
||||
import com.lanternsoftware.util.CollectionUtils;
|
||||
import com.lanternsoftware.util.dao.annotations.DBSerializable;
|
||||
import com.lanternsoftware.util.dao.annotations.PrimaryKey;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@DBSerializable
|
||||
public class HubCommand {
|
||||
@PrimaryKey private String id;
|
||||
private int accountId;
|
||||
private int hub;
|
||||
private Date created;
|
||||
private HubConfigCharacteristic characteristic;
|
||||
private byte[] data;
|
||||
|
||||
public HubCommand() {
|
||||
}
|
||||
|
||||
public HubCommand(int _accountId, HubConfigCharacteristic _characteristic, byte[] _data) {
|
||||
accountId = _accountId;
|
||||
created = new Date();
|
||||
characteristic = _characteristic;
|
||||
data = _data;
|
||||
}
|
||||
|
||||
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 int getHub() {
|
||||
return hub;
|
||||
}
|
||||
|
||||
public void setHub(int _hub) {
|
||||
hub = _hub;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date _created) {
|
||||
created = _created;
|
||||
}
|
||||
|
||||
public HubConfigCharacteristic getCharacteristic() {
|
||||
return characteristic;
|
||||
}
|
||||
|
||||
public void setCharacteristic(HubConfigCharacteristic _characteristic) {
|
||||
characteristic = _characteristic;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(byte[] _data) {
|
||||
data = _data;
|
||||
}
|
||||
|
||||
public List<HubCommand> forAllHubs(BreakerConfig _config) {
|
||||
return CollectionUtils.transform(_config.getBreakerHubs(), _h->forHub(_h.getHub()));
|
||||
}
|
||||
|
||||
public HubCommand forHub(int _hub) {
|
||||
HubCommand c = new HubCommand();
|
||||
c.setAccountId(accountId);
|
||||
c.setHub(_hub);
|
||||
c.setCreated(created);
|
||||
c.setCharacteristic(characteristic);
|
||||
c.setData(data);
|
||||
return c;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object _o) {
|
||||
if (this == _o) return true;
|
||||
if (_o == null || getClass() != _o.getClass()) return false;
|
||||
HubCommand that = (HubCommand) _o;
|
||||
return Objects.equals(id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.lanternsoftware.datamodel.currentmonitor;
|
||||
|
||||
import com.lanternsoftware.util.dao.annotations.DBSerializable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@DBSerializable
|
||||
public class HubCommands {
|
||||
private List<HubCommand> commands;
|
||||
|
||||
public HubCommands() {
|
||||
}
|
||||
|
||||
public HubCommands(List<HubCommand> _commands) {
|
||||
commands = _commands;
|
||||
}
|
||||
|
||||
public List<HubCommand> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void setCommands(List<HubCommand> _commands) {
|
||||
commands = _commands;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,10 @@ public enum HubConfigCharacteristic {
|
||||
Host(10, CharacteristicFlag.WRITE),
|
||||
Log(11, CharacteristicFlag.READ),
|
||||
NetworkDetails(12, CharacteristicFlag.READ),
|
||||
Shutdown(13, CharacteristicFlag.WRITE);
|
||||
Shutdown(13, CharacteristicFlag.WRITE),
|
||||
Version(14, CharacteristicFlag.READ),
|
||||
Update(15, CharacteristicFlag.WRITE),
|
||||
ReloadConfig(15, CharacteristicFlag.WRITE);
|
||||
|
||||
public final int idx;
|
||||
public final UUID uuid;
|
||||
|
||||
@@ -7,7 +7,6 @@ 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;
|
||||
|
||||
@@ -31,7 +30,6 @@ public class BillingRateSerializer extends AbstractDaoSerializer<BillingRate>
|
||||
d.put("meter", _o.getMeter());
|
||||
d.put("flow", DaoSerializer.toEnumName(_o.getFlow()));
|
||||
d.put("rate", _o.getRate());
|
||||
d.put("unit", DaoSerializer.toEnumName(_o.getCurrency())); //TODO: Remove post migration
|
||||
d.put("currency", DaoSerializer.toEnumName(_o.getCurrency()));
|
||||
d.put("time_of_day_start", _o.getTimeOfDayStart());
|
||||
d.put("time_of_day_end", _o.getTimeOfDayEnd());
|
||||
@@ -51,8 +49,6 @@ public class BillingRateSerializer extends AbstractDaoSerializer<BillingRate>
|
||||
o.setFlow(DaoSerializer.getEnum(_d, "flow", GridFlow.class));
|
||||
o.setRate(DaoSerializer.getDouble(_d, "rate"));
|
||||
o.setCurrency(DaoSerializer.getEnum(_d, "currency", BillingCurrency.class));
|
||||
if (o.getCurrency() == null)
|
||||
o.setCurrency(DaoSerializer.getEnum(_d, "unit", BillingCurrency.class));
|
||||
o.setTimeOfDayStart(DaoSerializer.getInteger(_d, "time_of_day_start"));
|
||||
o.setTimeOfDayEnd(DaoSerializer.getInteger(_d, "time_of_day_end"));
|
||||
o.setMonthKWhStart(DaoSerializer.getDouble(_d, "month_kwh_start"));
|
||||
|
||||
@@ -26,8 +26,8 @@ public class BreakerHubSerializer extends AbstractDaoSerializer<BreakerHub>
|
||||
{
|
||||
DaoEntity d = new DaoEntity();
|
||||
d.put("hub", _o.getHub());
|
||||
d.put("voltage_calibration_factor", _o.getVoltageCalibrationFactor());
|
||||
d.put("port_calibration_factor", _o.getPortCalibrationFactor());
|
||||
d.put("voltage_calibration_factor", _o.getRawVoltageCalibrationFactor());
|
||||
d.put("port_calibration_factor", _o.getRawPortCalibrationFactor());
|
||||
d.put("frequency", _o.getFrequency());
|
||||
d.put("bluetooth_mac", _o.getBluetoothMac());
|
||||
return d;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.lanternsoftware.datamodel.currentmonitor.dao;
|
||||
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
|
||||
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 HubCommandSerializer extends AbstractDaoSerializer<HubCommand>
|
||||
{
|
||||
@Override
|
||||
public Class<HubCommand> getSupportedClass()
|
||||
{
|
||||
return HubCommand.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DaoProxyType> getSupportedProxies() {
|
||||
return Collections.singletonList(DaoProxyType.MONGO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoEntity toDaoEntity(HubCommand _o)
|
||||
{
|
||||
DaoEntity d = new DaoEntity();
|
||||
if (_o.getId() != null)
|
||||
d.put("_id", _o.getId());
|
||||
d.put("account_id", _o.getAccountId());
|
||||
d.put("hub", _o.getHub());
|
||||
d.put("created", DaoSerializer.toLong(_o.getCreated()));
|
||||
d.put("characteristic", DaoSerializer.toEnumName(_o.getCharacteristic()));
|
||||
d.put("data", _o.getData());
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HubCommand fromDaoEntity(DaoEntity _d)
|
||||
{
|
||||
HubCommand o = new HubCommand();
|
||||
o.setId(DaoSerializer.getString(_d, "_id"));
|
||||
o.setAccountId(DaoSerializer.getInteger(_d, "account_id"));
|
||||
o.setHub(DaoSerializer.getInteger(_d, "hub"));
|
||||
o.setCreated(DaoSerializer.getDate(_d, "created"));
|
||||
o.setCharacteristic(DaoSerializer.getEnum(_d, "characteristic", HubConfigCharacteristic.class));
|
||||
o.setData(DaoSerializer.getByteArray(_d, "data"));
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.lanternsoftware.datamodel.currentmonitor.dao;
|
||||
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommands;
|
||||
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 HubCommandsSerializer extends AbstractDaoSerializer<HubCommands>
|
||||
{
|
||||
@Override
|
||||
public Class<HubCommands> getSupportedClass()
|
||||
{
|
||||
return HubCommands.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DaoProxyType> getSupportedProxies() {
|
||||
return Collections.singletonList(DaoProxyType.MONGO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoEntity toDaoEntity(HubCommands _o)
|
||||
{
|
||||
DaoEntity d = new DaoEntity();
|
||||
d.put("commands", DaoSerializer.toDaoEntities(_o.getCommands(), DaoProxyType.MONGO));
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HubCommands fromDaoEntity(DaoEntity _d)
|
||||
{
|
||||
HubCommands o = new HubCommands();
|
||||
o.setCommands(DaoSerializer.getList(_d, "commands", HubCommand.class));
|
||||
return o;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ com.lanternsoftware.datamodel.currentmonitor.dao.ChargeTotalSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.EnergyBlockSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.EnergySummarySerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.EnergyTotalSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.HubCommandSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.HubCommandsSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.HubPowerMinuteSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.MeterSerializer
|
||||
com.lanternsoftware.datamodel.currentmonitor.dao.NetworkStatusSerializer
|
||||
|
||||
@@ -2,20 +2,31 @@ package com.lanternsoftware.currentmonitor.context;
|
||||
|
||||
import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
|
||||
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommands;
|
||||
import com.lanternsoftware.rules.RulesEngine;
|
||||
import com.lanternsoftware.util.DateUtils;
|
||||
import com.lanternsoftware.util.LanternFiles;
|
||||
import com.lanternsoftware.util.dao.mongo.MongoConfig;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class Globals implements ServletContextListener {
|
||||
public static CurrentMonitorDao dao;
|
||||
private static final Map<Integer, Map<Integer, List<HubCommand>>> commands = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
dao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
|
||||
RulesEngine.instance().start();
|
||||
RulesEngine.instance().schedule(new CommandTask(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,4 +34,38 @@ public class Globals implements ServletContextListener {
|
||||
dao.shutdown();
|
||||
RulesEngine.shutdown();
|
||||
}
|
||||
|
||||
public static HubCommands getCommandsForHub(int _accountId, int _hub) {
|
||||
List<HubCommand> c = null;
|
||||
synchronized (commands) {
|
||||
Map<Integer, List<HubCommand>> hubCommands = commands.get(_accountId);
|
||||
if (hubCommands != null)
|
||||
c = hubCommands.remove(_hub);
|
||||
}
|
||||
if (c != null) {
|
||||
for (HubCommand command : c) {
|
||||
dao.deleteHubCommand(command.getId());
|
||||
}
|
||||
return new HubCommands(c);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class CommandTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
List<HubCommand> c = Globals.dao.getAllHubCommands();
|
||||
Date stale = DateUtils.addMinutes(new Date(), -5);
|
||||
synchronized (commands) {
|
||||
commands.clear();
|
||||
for (HubCommand command : c) {
|
||||
if (DateUtils.isBefore(command.getCreated(), stale))
|
||||
dao.deleteHubCommand(command.getId());
|
||||
else
|
||||
commands.computeIfAbsent(command.getAccountId(), _t -> new HashMap<>()).computeIfAbsent(command.getHub(), _h->new ArrayList<>()).add(command);
|
||||
}
|
||||
}
|
||||
RulesEngine.instance().schedule(new CommandTask(), 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,8 @@ public class AuthServlet extends LanternServlet {
|
||||
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(transport, new GsonFactory(), "https://oauth2.googleapis.com/token", googleClientId, googleClientSecret, auth.getPassword(), "").execute();
|
||||
if (tokenResponse != null) {
|
||||
GoogleIdToken idToken = tokenResponse.parseIdToken();
|
||||
if (idToken != null) {
|
||||
logger.info("Successfully received google id token");
|
||||
if (idToken != null)
|
||||
authCode = Globals.dao.getAuthCodeForEmail(idToken.getPayload().getEmail(), DateUtils.fromTimeZoneId(_req.getHeader("timezone")));
|
||||
logger.info("Auth code for google user is valid: " + (authCode != null));
|
||||
}
|
||||
}
|
||||
} catch (Exception _e) {
|
||||
logger.error("Failed to validate google auth code", _e);
|
||||
|
||||
@@ -1,49 +1,19 @@
|
||||
package com.lanternsoftware.currentmonitor.servlet;
|
||||
|
||||
import com.lanternsoftware.util.dao.auth.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<String> 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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.lanternsoftware.currentmonitor.servlet;
|
||||
|
||||
import com.lanternsoftware.currentmonitor.context.Globals;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommand;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubConfigCharacteristic;
|
||||
import com.lanternsoftware.util.dao.auth.AuthCode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -34,6 +36,10 @@ public class ConfigServlet extends SecureServlet {
|
||||
return;
|
||||
}
|
||||
logger.info("Received config for account {}", config.getAccountId());
|
||||
BreakerConfig oldConfig = Globals.dao.getConfig(config.getAccountId());
|
||||
if ((oldConfig == null) || !oldConfig.isIdentical(config))
|
||||
Globals.dao.putHubCommand(new HubCommand(config.getAccountId(), HubConfigCharacteristic.ReloadConfig, null));
|
||||
Globals.dao.putConfig(config);
|
||||
zipBsonResponse(_rep, Globals.dao.getMergedConfig(_authCode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.lanternsoftware.currentmonitor.servlet;
|
||||
|
||||
import com.lanternsoftware.currentmonitor.context.Globals;
|
||||
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.HubCommands;
|
||||
import com.lanternsoftware.util.dao.DaoEntity;
|
||||
import com.lanternsoftware.util.dao.auth.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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -15,6 +20,8 @@ import java.util.List;
|
||||
|
||||
@WebServlet("/power/*")
|
||||
public class PowerServlet extends SecureServlet {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MongoCurrentMonitorDao.class);
|
||||
|
||||
@Override
|
||||
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
|
||||
String[] path = path(_req);
|
||||
@@ -32,19 +39,29 @@ public class PowerServlet extends SecureServlet {
|
||||
String[] path = path(_req);
|
||||
if ((path.length > 0) && NullUtils.isEqual(CollectionUtils.get(path, 0), "hub")) {
|
||||
HubPowerMinute m = getRequestPayload(_req, HubPowerMinute.class);
|
||||
if (m == null)
|
||||
return;
|
||||
logger.info("Hub Power from ip {}, account {}, hub {}", _req.getRemoteAddr(), m.getAccountId(), m.getHub());
|
||||
m.setAccountId(_authCode.getAccountId());
|
||||
Globals.dao.putHubPowerMinute(m);
|
||||
return;
|
||||
}
|
||||
if ((path.length > 0) && NullUtils.isEqual(CollectionUtils.get(path, 0), "batch")) {
|
||||
List<BreakerPower> powers = DaoSerializer.getList(getRequestZipBson(_req), "readings", BreakerPower.class);
|
||||
DaoEntity payload = getRequestZipBson(_req);
|
||||
List<BreakerPower> powers = DaoSerializer.getList(payload, "readings", BreakerPower.class);
|
||||
if (!powers.isEmpty()) {
|
||||
CollectionUtils.edit(powers, _p->_p.setAccountId(_authCode.getAccountId()));
|
||||
Globals.dao.getProxy().save(powers);
|
||||
int hub = DaoSerializer.getInteger(payload, "hub");
|
||||
HubCommands commands = Globals.getCommandsForHub(_authCode.getAccountId(), hub);
|
||||
if (commands != null)
|
||||
zipBsonResponse(_rep, commands);
|
||||
}
|
||||
return;
|
||||
}
|
||||
BreakerPower power = getRequestPayload(_req, BreakerPower.class);
|
||||
if (power == null)
|
||||
return;
|
||||
power.setAccountId(_authCode.getAccountId());
|
||||
Globals.dao.putBreakerPower(power);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.lanternsoftware.currentmonitor.servlet;
|
||||
|
||||
import com.lanternsoftware.currentmonitor.context.Globals;
|
||||
import com.lanternsoftware.datamodel.currentmonitor.Account;
|
||||
import com.lanternsoftware.util.CollectionUtils;
|
||||
import com.lanternsoftware.util.NullUtils;
|
||||
import com.lanternsoftware.util.dao.DaoSerializer;
|
||||
import com.lanternsoftware.util.dao.auth.AuthCode;
|
||||
|
||||
@@ -9,15 +11,21 @@ import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@WebServlet("/rebuildSummaries")
|
||||
@WebServlet("/rebuildSummaries/*")
|
||||
public class RebuildSummariesServlet extends SecureServlet {
|
||||
@Override
|
||||
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
|
||||
if (_authCode.getAccountId() == 100) {
|
||||
for (String sId : Globals.dao.getProxy().queryForField(Account.class, null, "_id")) {
|
||||
int id = DaoSerializer.toInteger(sId);
|
||||
if (id != 0)
|
||||
Globals.dao.rebuildSummariesAsync(id);
|
||||
String[] path = path(_req);
|
||||
if (path.length > 0) {
|
||||
Globals.dao.rebuildSummariesAsync(DaoSerializer.toInteger(CollectionUtils.get(path, 0)));
|
||||
}
|
||||
else {
|
||||
for (String sId : Globals.dao.getProxy().queryForField(Account.class, null, "_id")) {
|
||||
int id = DaoSerializer.toInteger(sId);
|
||||
if (id != 0)
|
||||
Globals.dao.rebuildSummariesAsync(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -2,16 +2,21 @@
|
||||
<configuration>
|
||||
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/opt/tomcat/logs/log.txt</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>/opt/tomcat/log/log.%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
|
||||
<maxFileSize>20MB</maxFileSize>
|
||||
<maxHistory>20</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
<logger name="com.lanternsoftware" level="DEBUG"/>
|
||||
|
||||
<root level="OFF">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -2,16 +2,21 @@
|
||||
<configuration>
|
||||
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>/opt/tomcat/logs/log.txt</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>/opt/tomcat/log/log.%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
|
||||
<maxFileSize>20MB</maxFileSize>
|
||||
<maxHistory>20</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${log.pattern}</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
<logger name="com.lanternsoftware" level="DEBUG"/>
|
||||
|
||||
<root level="OFF">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
<appender-ref ref="FILE"/>
|
||||
</root>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user