Allow binary and dimmer switches to be grouped together. Allow a custom host to be sent to the power monitor hubs.

This commit is contained in:
Mark Milligan 2021-01-30 17:11:12 -06:00
parent 1a1acb9cbc
commit 7075c702df
9 changed files with 159 additions and 112 deletions

View File

@ -3,7 +3,7 @@
<groupId>com.lanternsoftware.currentmonitor</groupId> <groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-currentmonitor</artifactId> <artifactId>lantern-currentmonitor</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<version>1.0.0</version> <version>0.9.5</version>
<name>lantern-currentmonitor</name> <name>lantern-currentmonitor</name>
<properties> <properties>

View File

@ -9,10 +9,8 @@ import com.lanternsoftware.datamodel.currentmonitor.HubConfigService;
import com.lanternsoftware.util.CollectionUtils; import com.lanternsoftware.util.CollectionUtils;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class BluetoothConfig implements Runnable { public class BluetoothConfig {
private final AtomicBoolean running = new AtomicBoolean(true);
private final BleApplication app; private final BleApplication app;
public BluetoothConfig(String _hubName, BleCharacteristicListener _listener) { public BluetoothConfig(String _hubName, BleCharacteristicListener _listener) {
@ -25,15 +23,11 @@ public class BluetoothConfig implements Runnable {
app = new BleApplication("Lantern", _hubName, new BleService("HubConfig", service.getServiceUUID(), chars)); app = new BleApplication("Lantern", _hubName, new BleService("HubConfig", service.getServiceUUID(), chars));
} }
@Override public void start() {
public void run() {
app.start(); app.start();
} }
public void stop() { public void stop() {
synchronized (running) {
running.set(false);
}
app.stop(); app.stop();
} }
} }

View File

@ -90,7 +90,14 @@ public class MonitorApp {
HubConfigCharacteristic ch = NullUtils.toEnum(HubConfigCharacteristic.class, _name); HubConfigCharacteristic ch = NullUtils.toEnum(HubConfigCharacteristic.class, _name);
LOG.info("Char Received, Name: {} Value: {}", _name, _value); LOG.info("Char Received, Name: {} Value: {}", _name, _value);
monitor.submit(()->{ monitor.submit(()->{
synchronized (monitor) {
switch (ch) { 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: case HubIndex:
if ((_value.length > 0)) { if ((_value.length > 0)) {
config.setHub(_value[0]); config.setHub(_value[0]);
@ -116,11 +123,9 @@ public class MonitorApp {
if (flasher != null) { if (flasher != null) {
flasher.stop(); flasher.stop();
flasher = null; flasher = null;
} } else
else
LEDFlasher.setLEDOn(false); LEDFlasher.setLEDOn(false);
} } else {
else {
if (flasher == null) { if (flasher == null) {
flasher = new LEDFlasher(); flasher = new LEDFlasher();
monitor.submit(flasher); monitor.submit(flasher);
@ -130,7 +135,7 @@ public class MonitorApp {
case Restart: case Restart:
LOG.info("Restarting Current Monitor..."); LOG.info("Restarting Current Monitor...");
try { try {
Runtime.getRuntime().exec("echo \"sudo systemctl restart currentmonitor\" | at now + 1 minute"); Runtime.getRuntime().exec(new String[]{"systemctl","restart","currentmonitor"});
} catch (IOException _e) { } catch (IOException _e) {
LOG.error("Exception occurred while trying to restart", _e); LOG.error("Exception occurred while trying to restart", _e);
} }
@ -138,12 +143,13 @@ public class MonitorApp {
case Reboot: case Reboot:
LOG.info("Rebooting Pi..."); LOG.info("Rebooting Pi...");
try { try {
Runtime.getRuntime().exec("sudo reboot now"); Runtime.getRuntime().exec(new String[]{"reboot","now"});
} catch (IOException _e) { } catch (IOException _e) {
LOG.error("Exception occurred while trying to reboot", _e); LOG.error("Exception occurred while trying to reboot", _e);
} }
break; break;
} }
}
}); });
} }
@ -159,7 +165,7 @@ public class MonitorApp {
return null; return null;
} }
}); });
monitor.submit(bluetoothConfig); bluetoothConfig.start();
if (NullUtils.isNotEmpty(config.getAuthCode())) if (NullUtils.isNotEmpty(config.getAuthCode()))
authCode = config.getAuthCode(); authCode = config.getAuthCode();
else { else {
@ -190,6 +196,7 @@ public class MonitorApp {
} }
List<Breaker> breakers = breakerConfig.getBreakersForHub(config.getHub()); List<Breaker> breakers = breakerConfig.getBreakersForHub(config.getHub());
LOG.info("Monitoring {} breakers for hub {}", CollectionUtils.size(breakers), hub.getHub()); LOG.info("Monitoring {} breakers for hub {}", CollectionUtils.size(breakers), hub.getHub());
if (CollectionUtils.size(breakers) > 0)
monitor.monitorPower(hub, breakers, 1000, logger); monitor.monitorPower(hub, breakers, 1000, logger);
} }
monitor.submit(new PowerPoster()); monitor.submit(new PowerPoster());
@ -341,7 +348,7 @@ public class MonitorApp {
ResourceLoader.writeFile(WORKING_DIR + "lantern-currentmonitor.jar", jar); ResourceLoader.writeFile(WORKING_DIR + "lantern-currentmonitor.jar", jar);
ConcurrencyUtils.sleep(10000); ConcurrencyUtils.sleep(10000);
try { try {
Runtime.getRuntime().exec("echo \"sudo systemctl restart currentmonitor\" | at now + 1 minute"); Runtime.getRuntime().exec(new String[]{"systemctl","restart","currentmonitor"});
} catch (IOException _e) { } catch (IOException _e) {
LOG.error("Exception occurred while trying to restart", _e); LOG.error("Exception occurred while trying to restart", _e);
} }
@ -375,9 +382,9 @@ public class MonitorApp {
else if (NullUtils.isEqual(command, "extend_filesystem")) { else if (NullUtils.isEqual(command, "extend_filesystem")) {
LOG.info("Extending filesystem and rebooting"); LOG.info("Extending filesystem and rebooting");
try { try {
Runtime.getRuntime().exec("sudo raspi-config --expand-rootfs"); Runtime.getRuntime().exec(new String[]{"sudo","raspi-config","--expand-rootfs"});
ConcurrencyUtils.sleep(5000); ConcurrencyUtils.sleep(5000);
Runtime.getRuntime().exec("sudo reboot now"); Runtime.getRuntime().exec(new String[]{"reboot","now"});
} catch (IOException _e) { } catch (IOException _e) {
LOG.error("Exception occurred while trying to extend filesystem", _e); LOG.error("Exception occurred while trying to extend filesystem", _e);
} }
@ -386,7 +393,7 @@ public class MonitorApp {
else if (NullUtils.isEqual(command, "restart")) { else if (NullUtils.isEqual(command, "restart")) {
LOG.info("Restarting..."); LOG.info("Restarting...");
try { try {
Runtime.getRuntime().exec("echo \"sudo systemctl restart currentmonitor\" | at now + 1 minute"); Runtime.getRuntime().exec(new String[]{"systemctl","restart","currentmonitor"});
} catch (IOException _e) { } catch (IOException _e) {
LOG.error("Exception occurred while trying to restart", _e); LOG.error("Exception occurred while trying to restart", _e);
} }

View File

@ -1,5 +1,7 @@
package com.lanternsoftware.currentmonitor.wifi; package com.lanternsoftware.currentmonitor.wifi;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader; import com.lanternsoftware.util.ResourceLoader;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -11,28 +13,17 @@ public abstract class WifiConfig {
private static final Logger LOG = LoggerFactory.getLogger(WifiConfig.class); 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 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"; private static final String CONF_FORMAT = "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\ncountry=US\n";
public static void setCredentials(String _ssid, String _password) { public static void setCredentials(String _ssid, String _password) {
String[] commands = {"wpa_passphrase", _ssid, _password};
InputStream is = null; InputStream is = null;
try { try {
is = Runtime.getRuntime().exec(commands).getInputStream(); is = Runtime.getRuntime().exec(new String[]{"wpa_passphrase", _ssid, _password}).getInputStream();
String newConf = IOUtils.toString(is); String newConf = CollectionUtils.delimit(CollectionUtils.filter(CollectionUtils.asArrayList(NullUtils.cleanSplit(IOUtils.toString(is), "\\r?\\n")), _s->!_s.trim().startsWith("#")), "\n");
if (newConf == null) if (newConf == null)
return; return;
int idx = newConf.indexOf("psk="); ResourceLoader.writeFile(WIFI_CONFIG_PATH, CONF_FORMAT+newConf);
if (idx > 0) { Runtime.getRuntime().exec(new String[]{"wpa_cli","-i","wlan0","reconfigure"});
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) { catch (Exception _e) {
LOG.error("Failed to write wifi credentials", _e); LOG.error("Failed to write wifi credentials", _e);

View File

@ -14,7 +14,8 @@ public enum HubConfigCharacteristic {
Reboot(6, CharacteristicFlag.WRITE), Reboot(6, CharacteristicFlag.WRITE),
AccountId(7, CharacteristicFlag.READ), AccountId(7, CharacteristicFlag.READ),
NetworkState(8, CharacteristicFlag.READ), NetworkState(8, CharacteristicFlag.READ),
Flash(9, CharacteristicFlag.WRITE); Flash(9, CharacteristicFlag.WRITE),
Host(10, CharacteristicFlag.WRITE);
public final int idx; public final int idx;
public final UUID uuid; public final UUID uuid;

View File

@ -17,6 +17,7 @@ import com.lanternsoftware.zwave.controller.Controller;
import com.lanternsoftware.zwave.dao.MongoZWaveDao; import com.lanternsoftware.zwave.dao.MongoZWaveDao;
import com.lanternsoftware.zwave.message.IMessageSubscriber; import com.lanternsoftware.zwave.message.IMessageSubscriber;
import com.lanternsoftware.zwave.message.MessageEngine; import com.lanternsoftware.zwave.message.MessageEngine;
import com.lanternsoftware.zwave.message.impl.BinarySwitchReportRequest;
import com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest; import com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest;
import com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest; import com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest;
import com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest; import com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest;
@ -45,7 +46,7 @@ public class ZWaveApp {
private ZWaveConfig config; private ZWaveConfig config;
private Controller controller; private Controller controller;
private final Map<Integer, Switch> switches = new HashMap<>(); private final Map<Integer, Switch> switches = new HashMap<>();
private final Map<Integer, List<Integer>> peers = new HashMap<>(); private final Map<Integer, List<Switch>> peers = new HashMap<>();
private Timer timer; private Timer timer;
private HttpPool pool; private HttpPool pool;
private SwitchScheduleTask nextScheduleTask; private SwitchScheduleTask nextScheduleTask;
@ -78,17 +79,17 @@ public class ZWaveApp {
t.printStackTrace(); t.printStackTrace();
} }
config = dao.getConfig(1); config = dao.getConfig(1);
Map<String, List<Integer>> groups = new HashMap<>(); Map<String, List<Switch>> groups = new HashMap<>();
for (Switch sw : CollectionUtils.makeNotNull(config.getSwitches())) { for (Switch sw : CollectionUtils.makeNotNull(config.getSwitches())) {
switches.put(sw.getNodeId(), sw); switches.put(sw.getNodeId(), sw);
CollectionUtils.addToMultiMap(sw.getRoom() + ":" + sw.getName(), sw.getNodeId(), groups); CollectionUtils.addToMultiMap(sw.getRoom() + ":" + sw.getName(), sw, groups);
} }
if (CollectionUtils.filterOne(config.getSwitches(), Switch::isUrlThermostat) != null) { if (CollectionUtils.filterOne(config.getSwitches(), Switch::isUrlThermostat) != null) {
timer.scheduleAtFixedRate(new ThermostatTask(), 0, 30000); timer.scheduleAtFixedRate(new ThermostatTask(), 0, 30000);
} }
for (List<Integer> group : groups.values()) { for (List<Switch> group : groups.values()) {
for (Integer node : group) { for (Switch sw : group) {
peers.put(node, CollectionUtils.filter(group, _i -> !_i.equals(node))); peers.put(sw.getNodeId(), CollectionUtils.filter(group, _sw -> _sw.getNodeId() != sw.getNodeId()));
} }
} }
scheduleNextTransition(); scheduleNextTransition();
@ -136,19 +137,19 @@ public class ZWaveApp {
@Override @Override
public void onMessage(MultilevelSwitchReportRequest _message) { public void onMessage(MultilevelSwitchReportRequest _message) {
synchronized (switches) { onSwitchLevelChange(_message.getNodeId(), _message.getLevel());
Switch sw = switches.get((int) _message.getNodeId());
if (sw != null) {
sw.setLevel(_message.getLevel());
for (Integer node : CollectionUtils.makeNotNull(peers.get((int) _message.getNodeId()))) {
sw = switches.get(node);
sw.setLevel(_message.getLevel());
logger.info("Mirror Event from node {} to node {}", _message.getNodeId(), node);
controller.send(new MultilevelSwitchSetRequest(node.byteValue(), _message.getLevel()));
}
persistConfig();
} }
});
MessageEngine.subscribe(new IMessageSubscriber<BinarySwitchReportRequest>() {
@Override
public Class<BinarySwitchReportRequest> getHandledMessageClass() {
return BinarySwitchReportRequest.class;
} }
@Override
public void onMessage(BinarySwitchReportRequest _message) {
onSwitchLevelChange(_message.getNodeId(), _message.getLevel());
} }
}); });
@ -164,6 +165,28 @@ public class ZWaveApp {
// controller.send(new ThermostatModeGetRequest((byte)11)); // controller.send(new ThermostatModeGetRequest((byte)11));
} }
private void onSwitchLevelChange(int _primaryNodeId, int _primaryLevel) {
synchronized (switches) {
Switch sw = switches.get(_primaryNodeId);
if (sw != null) {
int newLevel = sw.isMultilevel()?_primaryLevel:((_primaryLevel == 0)?0:99);
sw.setLevel(newLevel);
for (Switch peer : CollectionUtils.makeNotNull(peers.get(_primaryNodeId))) {
logger.info("Mirror Event from node {} to node {}", _primaryNodeId, peer.getNodeId());
if (peer.isMultilevel()) {
peer.setLevel(newLevel);
controller.send(new MultilevelSwitchSetRequest((byte)peer.getNodeId(), newLevel));
}
else {
peer.setLevel(newLevel > 0?0xff:0);
controller.send(new BinarySwitchSetRequest((byte)peer.getNodeId(), newLevel > 0));
}
}
persistConfig();
}
}
}
private void scheduleNextTransition() { private void scheduleNextTransition() {
TimeZone tz = TimeZone.getTimeZone("America/Chicago"); TimeZone tz = TimeZone.getTimeZone("America/Chicago");
if (nextScheduleTask != null) if (nextScheduleTask != null)
@ -185,7 +208,7 @@ public class ZWaveApp {
return; return;
sw.setLevel(_level); sw.setLevel(_level);
if (!sw.isThermostat()) { if (!sw.isThermostat()) {
setGroupSwitchLevel(_nodeId, _level, sw.isMultilevel()); setGroupSwitchLevel(sw, _level);
} else if (sw.isZWaveThermostat()) { } else if (sw.isZWaveThermostat()) {
controller.send(new ThermostatSetPointSetRequest((byte) sw.getNodeId(), sw.getThermostatMode() == ThermostatMode.COOL ? ThermostatSetPointIndex.COOLING : ThermostatSetPointIndex.HEATING, _level)); controller.send(new ThermostatSetPointSetRequest((byte) sw.getNodeId(), sw.getThermostatMode() == ThermostatMode.COOL ? ThermostatSetPointIndex.COOLING : ThermostatSetPointIndex.HEATING, _level));
} else { } else {
@ -252,11 +275,13 @@ public class ZWaveApp {
} }
} }
private void setGroupSwitchLevel(int _primary, int _level, boolean _multilevel) { private void setGroupSwitchLevel(Switch _primary, int _level) {
List<Integer> nodes = CollectionUtils.asArrayList(_primary); if (_primary == null)
nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary))); return;
for (int node : nodes) { List<Switch> nodes = CollectionUtils.asArrayList(_primary);
controller.send(_multilevel ? new MultilevelSwitchSetRequest((byte) node, _level) : new BinarySwitchSetRequest((byte) node, _level > 0)); nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary.getNodeId())));
for (Switch node : nodes) {
controller.send(node.isMultilevel() ? new MultilevelSwitchSetRequest((byte) node.getNodeId(), _level) : new BinarySwitchSetRequest((byte) node.getNodeId(), _level > 0));
} }
} }
@ -268,10 +293,10 @@ public class ZWaveApp {
if (sw.isUrlThermostat() && !sw.isThermometer()) { if (sw.isUrlThermostat() && !sw.isThermometer()) {
double tempF = getTemperatureCelsius(sw) * 1.8 + 32; double tempF = getTemperatureCelsius(sw) * 1.8 + 32;
if (tempF > sw.getLevel() + 0.4) { if (tempF > sw.getLevel() + 0.4) {
setGroupSwitchLevel(sw.getNodeId(), 0, false); setGroupSwitchLevel(sw, 0);
logger.info("Turning {} {} off, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel()); logger.info("Turning {} {} off, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel());
} else if (tempF < sw.getLevel() - 0.4) { } else if (tempF < sw.getLevel() - 0.4) {
setGroupSwitchLevel(sw.getNodeId(), (byte) 0xf, false); setGroupSwitchLevel(sw, (byte) 0xf);
logger.info("Turning {} {} on, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel()); logger.info("Turning {} {} on, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel());
} }
} }

View File

@ -53,8 +53,8 @@ public class Controller {
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(_port); CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(_port);
serialPort = portIdentifier.open("zwaveport", 2000); serialPort = portIdentifier.open("zwaveport", 2000);
serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.enableReceiveThreshold(1); serialPort.disableReceiveThreshold();
serialPort.enableReceiveTimeout(1000); serialPort.enableReceiveTimeout(500);
os = serialPort.getOutputStream(); os = serialPort.getOutputStream();
running = true; running = true;
executor.submit(new MessageReceiver()); executor.submit(new MessageReceiver());

View File

@ -0,0 +1,28 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class BinarySwitchReportRequest extends RequestMessage {
private int level;
public BinarySwitchReportRequest() {
super(ControllerMessageType.ApplicationCommandHandler, CommandClass.SWITCH_BINARY, (byte) 0x03);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
level = _payload[9];
}
public int getLevel() {
return level;
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + level;
}
}

View File

@ -1,5 +1,6 @@
com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest
com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest
com.lanternsoftware.zwave.message.impl.BinarySwitchReportRequest
com.lanternsoftware.zwave.message.impl.ByteMessage com.lanternsoftware.zwave.message.impl.ByteMessage
com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest
com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesResponse com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesResponse