mirror of
https://github.com/zyphlar/LanternPowerMonitor.git
synced 2024-03-08 14:07:47 +00:00
Password reset functionality, ZWave switch schedule improvement, support zwave controller on pi, support relay switches and security sensors.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package com.lanternsoftware.zwave;
|
||||
|
||||
import com.lanternsoftware.datamodel.zwave.Switch;
|
||||
import com.lanternsoftware.datamodel.zwave.SwitchType;
|
||||
import com.lanternsoftware.zwave.security.SecurityController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TestSecurity {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(TestSecurity.class);
|
||||
|
||||
public static void main(String[] args) {
|
||||
SecurityController c = new SecurityController();
|
||||
Switch sw = new Switch("Garage", "Door 1", 1000, true, false, null, 0);
|
||||
sw.setGpioPin(7);
|
||||
sw.setType(SwitchType.SECURITY);
|
||||
c.listen(sw, (nodeId, _open) -> LOG.info("Door is " + (_open ? "OPEN" : "CLOSED")));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,13 @@
|
||||
package com.lanternsoftware.zwave.context;
|
||||
|
||||
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 ZWaveApp app;
|
||||
public static MongoCurrentMonitorDao cmDao;
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
cmDao = new MongoCurrentMonitorDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
|
||||
app = new ZWaveApp();
|
||||
app.start();
|
||||
}
|
||||
@@ -24,7 +18,5 @@ public class Globals implements ServletContextListener {
|
||||
app.stop();
|
||||
app = null;
|
||||
}
|
||||
if (cmDao != null)
|
||||
cmDao.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.lanternsoftware.zwave.context;
|
||||
|
||||
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
|
||||
import com.lanternsoftware.datamodel.zwave.Switch;
|
||||
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
|
||||
import com.lanternsoftware.datamodel.zwave.SwitchTransition;
|
||||
@@ -9,7 +10,9 @@ import com.lanternsoftware.util.CollectionUtils;
|
||||
import com.lanternsoftware.util.DateUtils;
|
||||
import com.lanternsoftware.util.LanternFiles;
|
||||
import com.lanternsoftware.util.NullUtils;
|
||||
import com.lanternsoftware.util.ResourceLoader;
|
||||
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
|
||||
import com.lanternsoftware.util.cryptography.AESTool;
|
||||
import com.lanternsoftware.util.dao.DaoSerializer;
|
||||
import com.lanternsoftware.util.dao.mongo.MongoConfig;
|
||||
import com.lanternsoftware.util.http.HttpPool;
|
||||
@@ -19,6 +22,7 @@ import com.lanternsoftware.zwave.message.IMessageSubscriber;
|
||||
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.CRC16EncapRequest;
|
||||
import com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest;
|
||||
import com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest;
|
||||
import com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest;
|
||||
@@ -27,7 +31,10 @@ import com.lanternsoftware.zwave.message.impl.ThermostatModeSetRequest;
|
||||
import com.lanternsoftware.zwave.message.impl.ThermostatSetPointReportRequest;
|
||||
import com.lanternsoftware.zwave.message.impl.ThermostatSetPointSetRequest;
|
||||
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
|
||||
import com.lanternsoftware.zwave.relay.RelayController;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ByteArrayEntity;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -35,31 +42,57 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class ZWaveApp {
|
||||
public static final AESTool aes = new AESTool(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "authKey.dat"));
|
||||
public static String authCode = aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(100, null)));
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZWaveApp.class);
|
||||
|
||||
private MongoZWaveDao dao;
|
||||
private ZWaveConfig config;
|
||||
private Controller controller;
|
||||
private RelayController relayController;
|
||||
private final Map<Integer, Switch> originalSwitches = new HashMap<>();
|
||||
private final Map<Integer, Switch> switches = new HashMap<>();
|
||||
private final Map<Integer, Switch> mySwitches = new HashMap<>();
|
||||
private final Map<Integer, List<Switch>> peers = new HashMap<>();
|
||||
private Timer timer;
|
||||
private HttpPool pool;
|
||||
private SwitchScheduleTask nextScheduleTask;
|
||||
private final Map<Integer, Double> temperatures = new HashMap<>();
|
||||
private final Map<Integer, Double> sensors = new HashMap<>();
|
||||
private final Object ZWAVE_MUTEX = new Object();
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
|
||||
controller = new Controller();
|
||||
controller.start("COM4");
|
||||
pool = new HttpPool(100, 20, 5000, 5000, 5000);
|
||||
config = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config.json"), ZWaveConfig.class);
|
||||
if (config == null) {
|
||||
dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
|
||||
config = dao.getConfig(1);
|
||||
}
|
||||
if (NullUtils.isNotEmpty(config.getCommPort())) {
|
||||
controller = new Controller();
|
||||
controller.start(config.getCommPort());
|
||||
}
|
||||
if (!config.isMaster()) {
|
||||
HttpGet get = new HttpGet(config.getMasterUrl() + "/config");
|
||||
get.setHeader("auth_code", authCode);
|
||||
ZWaveConfig switchConfig = DaoSerializer.parse(pool.executeToString(get), ZWaveConfig.class);
|
||||
if (switchConfig != null) {
|
||||
config.setSwitches(switchConfig.getSwitches());
|
||||
}
|
||||
else {
|
||||
logger.error("Failed to retrieve switch config from master controller");
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
timer = new Timer("ZWaveApp Timer");
|
||||
pool = new HttpPool(10, 10, 30000, 10000, 10000);
|
||||
|
||||
//// for (int node = 3; node < 7; node++) {
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 7, new byte[]{99}));
|
||||
@@ -78,20 +111,26 @@ public class ZWaveApp {
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
config = dao.getConfig(1);
|
||||
Map<String, List<Switch>> groups = new HashMap<>();
|
||||
for (Switch sw : CollectionUtils.makeNotNull(config.getSwitches())) {
|
||||
for (Switch sw : config.getSwitches()) {
|
||||
switches.put(sw.getNodeId(), sw);
|
||||
originalSwitches.put(sw.getNodeId(), sw.duplicate());
|
||||
if (config.isMySwitch(sw))
|
||||
mySwitches.put(sw.getNodeId(), sw);
|
||||
CollectionUtils.addToMultiMap(sw.getRoom() + ":" + sw.getName(), sw, groups);
|
||||
}
|
||||
if (CollectionUtils.filterOne(config.getSwitches(), Switch::isUrlThermostat) != null) {
|
||||
if (CollectionUtils.anyQualify(mySwitches.values(), Switch::isThermometerUrlValid)) {
|
||||
timer.scheduleAtFixedRate(new ThermostatTask(), 0, 30000);
|
||||
}
|
||||
if (CollectionUtils.anyQualify(mySwitches.values(), Switch::isRelay)) {
|
||||
relayController = new RelayController();
|
||||
}
|
||||
for (List<Switch> group : groups.values()) {
|
||||
for (Switch sw : group) {
|
||||
peers.put(sw.getNodeId(), CollectionUtils.filter(group, _sw -> _sw.getNodeId() != sw.getNodeId()));
|
||||
}
|
||||
}
|
||||
System.out.println("My Switches:\n" + DaoSerializer.toJson(DaoSerializer.toDaoEntities(mySwitches.values())));
|
||||
scheduleNextTransition();
|
||||
|
||||
MessageEngine.subscribe(new IMessageSubscriber<MultilevelSensorReportRequest>() {
|
||||
@@ -102,9 +141,9 @@ public class ZWaveApp {
|
||||
|
||||
@Override
|
||||
public void onMessage(MultilevelSensorReportRequest _message) {
|
||||
synchronized (temperatures) {
|
||||
temperatures.put((int) _message.getNodeId(), _message.getTemperatureCelsius());
|
||||
temperatures.notify();
|
||||
synchronized (sensors) {
|
||||
sensors.put((int) _message.getNodeId(), _message.getTemperatureCelsius());
|
||||
sensors.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -153,6 +192,18 @@ public class ZWaveApp {
|
||||
}
|
||||
});
|
||||
|
||||
MessageEngine.subscribe(new IMessageSubscriber<CRC16EncapRequest>() {
|
||||
@Override
|
||||
public Class<CRC16EncapRequest> getHandledMessageClass() {
|
||||
return CRC16EncapRequest.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(CRC16EncapRequest _message) {
|
||||
onSwitchLevelChange(_message.getNodeId(), _message.isOn()?0xFF:0);
|
||||
}
|
||||
});
|
||||
|
||||
// controller.send(new MultilevelSensorGetRequest((byte)11));
|
||||
// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.HEATING));
|
||||
// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.COOLING));
|
||||
@@ -165,21 +216,22 @@ public class ZWaveApp {
|
||||
// controller.send(new ThermostatModeGetRequest((byte)11));
|
||||
}
|
||||
|
||||
private void onSwitchLevelChange(int _primaryNodeId, int _primaryLevel) {
|
||||
private void onSwitchLevelChange(int _secondaryNodeId, int _primaryLevel) {
|
||||
synchronized (switches) {
|
||||
Switch sw = switches.get(_primaryNodeId);
|
||||
if (sw != null) {
|
||||
Switch sw = switches.get(_secondaryNodeId);
|
||||
if ((sw != null) && !sw.isPrimary()) {
|
||||
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));
|
||||
for (Switch peer : CollectionUtils.makeNotNull(peers.get(_secondaryNodeId))) {
|
||||
if (peer.isPrimary()) {
|
||||
logger.info("Mirror Event from node {} to node {}", _secondaryNodeId, 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();
|
||||
@@ -191,7 +243,7 @@ public class ZWaveApp {
|
||||
TimeZone tz = TimeZone.getTimeZone("America/Chicago");
|
||||
if (nextScheduleTask != null)
|
||||
nextScheduleTask.cancel();
|
||||
List<SwitchTransition> nextTransitions = CollectionUtils.getAllSmallest(CollectionUtils.aggregate(switches.values(), _s->CollectionUtils.transform(_s.getSchedule(), _t->_t.getNextTransition(_s, tz))), Comparator.comparing(SwitchTransition::getTransitionTime));
|
||||
List<SwitchTransition> nextTransitions = CollectionUtils.getAllSmallest(CollectionUtils.aggregate(mySwitches.values(), _s->CollectionUtils.transform(_s.getSchedule(), _t->_t.getNextTransition(_s, tz))), Comparator.comparing(SwitchTransition::getTransitionTime));
|
||||
if (!CollectionUtils.isEmpty(nextTransitions)) {
|
||||
for (SwitchTransition tr : nextTransitions) {
|
||||
logger.info("Next transition scheduled for node {} to level {} at {}", tr.getSwitch().getNodeId(), tr.getLevel(), DateUtils.format("hh:mm:ssa", tz, tr.getTransitionTime()));
|
||||
@@ -203,24 +255,31 @@ public class ZWaveApp {
|
||||
}
|
||||
|
||||
public void setSwitchLevel(int _nodeId, int _level) {
|
||||
setSwitchLevel(_nodeId, _level, true);
|
||||
}
|
||||
|
||||
public void setSwitchLevel(int _nodeId, int _level, boolean _updatePeers) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
if ((sw == null) || !sw.isPrimary())
|
||||
return;
|
||||
sw.setLevel(_level);
|
||||
if (!sw.isThermostat()) {
|
||||
setGroupSwitchLevel(sw, _level);
|
||||
} else if (sw.isZWaveThermostat()) {
|
||||
controller.send(new ThermostatSetPointSetRequest((byte) sw.getNodeId(), sw.getThermostatMode() == ThermostatMode.COOL ? ThermostatSetPointIndex.COOLING : ThermostatSetPointIndex.HEATING, _level));
|
||||
} else {
|
||||
if (timer != null)
|
||||
timer.schedule(new ThermostatTask(), 0);
|
||||
persistConfig();
|
||||
if (config.isMySwitch(sw)) {
|
||||
if (sw.isSpaceHeaterThermostat()) {
|
||||
checkThermostat(sw);
|
||||
} else if (sw.isZWaveThermostat()) {
|
||||
controller.send(new ThermostatSetPointSetRequest((byte) sw.getNodeId(), sw.getThermostatMode() == ThermostatMode.COOL ? ThermostatSetPointIndex.COOLING : ThermostatSetPointIndex.HEATING, _level));
|
||||
} else if (sw.isRelay()) {
|
||||
relayController.setRelay(sw.getGpioPin(), sw.getLevel() > 0);
|
||||
} else {
|
||||
setGroupSwitchLevel(sw, _level);
|
||||
}
|
||||
}
|
||||
persistConfig(_updatePeers);
|
||||
}
|
||||
|
||||
public void setThermostatMode(int _nodeId, ThermostatMode _mode) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
if ((sw == null) || !sw.isPrimary() || !sw.isZWaveThermostat())
|
||||
if ((sw == null) || !sw.isPrimary() || !sw.isZWaveThermostat() || !config.isMySwitch(sw))
|
||||
return;
|
||||
controller.send(new ThermostatModeSetRequest((byte) sw.getNodeId(), com.lanternsoftware.zwave.message.thermostat.ThermostatMode.fromByte(_mode.data)));
|
||||
sw.setThermostatMode(_mode);
|
||||
@@ -236,6 +295,12 @@ public class ZWaveApp {
|
||||
scheduleNextTransition();
|
||||
}
|
||||
|
||||
public void updateSwitch(Switch _sw) {
|
||||
switches.put(_sw.getNodeId(), _sw);
|
||||
mySwitches.put(_sw.getNodeId(), _sw);
|
||||
setSwitchLevel(_sw.getNodeId(), _sw.getLevel(), false);
|
||||
}
|
||||
|
||||
public void setSwitchHold(int _nodeId, boolean _hold) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
if ((sw == null) || !sw.isPrimary())
|
||||
@@ -245,8 +310,37 @@ public class ZWaveApp {
|
||||
}
|
||||
|
||||
private void persistConfig() {
|
||||
persistConfig(true);
|
||||
}
|
||||
|
||||
private void persistConfig(boolean _updatePeers) {
|
||||
List<Switch> modified;
|
||||
synchronized (this) {
|
||||
dao.putConfig(config);
|
||||
modified = CollectionUtils.filter(switches.values(), _s->_s.isModified(originalSwitches.get(_s.getNodeId())));
|
||||
if (!modified.isEmpty()) {
|
||||
originalSwitches.clear();
|
||||
for (Switch s : switches.values()) {
|
||||
originalSwitches.put(s.getNodeId(), s.duplicate());
|
||||
}
|
||||
if (config.isMaster()) {
|
||||
if (dao != null)
|
||||
dao.putConfig(config);
|
||||
else
|
||||
ResourceLoader.writeFile(LanternFiles.OPS_PATH + "config.json", DaoSerializer.toJson(config));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_updatePeers) {
|
||||
Set<String> peers = CollectionUtils.transformToSet(modified, Switch::getControllerUrl);
|
||||
peers.remove(config.getUrl());
|
||||
for (String peer : peers) {
|
||||
for (Switch sw : modified) {
|
||||
HttpPost post = new HttpPost(peer + "/switch/" + sw.getNodeId());
|
||||
post.setHeader("auth_code", authCode);
|
||||
post.setEntity(new ByteArrayEntity(DaoSerializer.toZipBson(sw)));
|
||||
pool.execute(post);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +355,10 @@ public class ZWaveApp {
|
||||
|
||||
public void stop() {
|
||||
controller.stop();
|
||||
if (relayController != null) {
|
||||
relayController.shutdown();
|
||||
relayController = null;
|
||||
}
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
@@ -276,11 +374,12 @@ public class ZWaveApp {
|
||||
}
|
||||
|
||||
private void setGroupSwitchLevel(Switch _primary, int _level) {
|
||||
if (_primary == null)
|
||||
if ((_primary == null) || !config.isMySwitch(_primary))
|
||||
return;
|
||||
List<Switch> nodes = CollectionUtils.asArrayList(_primary);
|
||||
nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary.getNodeId())));
|
||||
nodes.addAll(CollectionUtils.filter(peers.get(_primary.getNodeId()), _p->!_p.isPrimary()));
|
||||
for (Switch node : nodes) {
|
||||
logger.info("Setting {}, Node {} to {}", node.getName(), node.getNodeId(), _level);
|
||||
controller.send(node.isMultilevel() ? new MultilevelSwitchSetRequest((byte) node.getNodeId(), _level) : new BinarySwitchSetRequest((byte) node.getNodeId(), _level > 0));
|
||||
}
|
||||
}
|
||||
@@ -288,24 +387,28 @@ public class ZWaveApp {
|
||||
private class ThermostatTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Switch sw : switches.values()) {
|
||||
try {
|
||||
if (sw.isUrlThermostat() && !sw.isThermometer()) {
|
||||
double tempF = getTemperatureCelsius(sw) * 1.8 + 32;
|
||||
if (tempF > sw.getLevel() + 0.4) {
|
||||
setGroupSwitchLevel(sw, 0);
|
||||
logger.info("Turning {} {} off, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel());
|
||||
} else if (tempF < sw.getLevel() - 0.4) {
|
||||
setGroupSwitchLevel(sw, (byte) 0xf);
|
||||
logger.info("Turning {} {} on, temp is: {} set to: {}", sw.getRoom(), sw.getName(), tempF + " set to: ", sw.getLevel());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("Failed to check temperature for thermostat {}", sw.getName());
|
||||
for (Switch sw : mySwitches.values()) {
|
||||
checkThermostat(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkThermostat(Switch _sw) {
|
||||
try {
|
||||
if (_sw.isSpaceHeaterThermostat()) {
|
||||
double tempF = getTemperatureCelsius(_sw) * 1.8 + 32;
|
||||
if (tempF > _sw.getLevel() + 0.4) {
|
||||
setGroupSwitchLevel(_sw, 0);
|
||||
logger.info("Turning {} {} off, temp is: {} set to: {}", _sw.getRoom(), _sw.getName(), tempF, _sw.getLevel());
|
||||
} else if (tempF < _sw.getLevel() - 0.4) {
|
||||
setGroupSwitchLevel(_sw, 255);
|
||||
logger.info("Turning {} {} on, temp is: {} set to: {}", _sw.getRoom(), _sw.getName(), tempF, _sw.getLevel());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("Failed to check temperature for thermostat {}", _sw.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private class SwitchScheduleTask extends TimerTask {
|
||||
@@ -318,12 +421,13 @@ public class ZWaveApp {
|
||||
@Override
|
||||
public void run() {
|
||||
for (SwitchTransition tr : transitions) {
|
||||
if (!tr.getSwitch().isHold()) {
|
||||
logger.info("Executing scheduled transition of node {} to level {}", tr.getSwitch().getNodeId(), tr.getLevel());
|
||||
Globals.app.setSwitchLevel(tr.getSwitch().getNodeId(), tr.getLevel());
|
||||
Switch sw = switches.get(tr.getSwitch().getNodeId());
|
||||
if (!sw.isHold()) {
|
||||
logger.info("Executing scheduled transition of node {} to level {}", sw.getNodeId(), tr.getLevel());
|
||||
Globals.app.setSwitchLevel(sw.getNodeId(), tr.getLevel());
|
||||
}
|
||||
else
|
||||
logger.info("Skipping scheduled transition of node {} to level {}, switch is on hold", tr.getSwitch().getNodeId(), tr.getLevel());
|
||||
logger.info("Skipping scheduled transition of node {} to level {}, switch is on hold", sw.getNodeId(), tr.getLevel());
|
||||
ConcurrencyUtils.sleep(100);
|
||||
}
|
||||
nextScheduleTask = null;
|
||||
@@ -336,20 +440,20 @@ public class ZWaveApp {
|
||||
}
|
||||
|
||||
private double getTemperatureCelsius(Switch _sw) {
|
||||
if ((pool == null) || (_sw == null) || !(_sw.isThermometer() || _sw.isThermostat()))
|
||||
if ((pool == null) || (_sw == null))
|
||||
return 0.0;
|
||||
if (_sw.isUrlThermostat())
|
||||
return DaoSerializer.getDouble(DaoSerializer.parse(pool.executeToString(new HttpGet(_sw.getThermostatSource()))), "temp");
|
||||
else if (_sw.isZWaveThermostat()) {
|
||||
if (_sw.isThermometerUrlValid())
|
||||
return DaoSerializer.getDouble(DaoSerializer.parse(pool.executeToString(new HttpGet(_sw.getThermometerUrl()))), "temp");
|
||||
else if (_sw.isZWaveThermostat() && config.isMySwitch(_sw)) {
|
||||
synchronized (ZWAVE_MUTEX) {
|
||||
synchronized (temperatures) {
|
||||
synchronized (sensors) {
|
||||
controller.send(new MultilevelSensorGetRequest((byte) _sw.getNodeId()));
|
||||
try {
|
||||
temperatures.wait(5000);
|
||||
sensors.wait(3000);
|
||||
} catch (InterruptedException _e) {
|
||||
_e.printStackTrace();
|
||||
}
|
||||
Double temp = temperatures.get(_sw.getNodeId());
|
||||
Double temp = sensors.get(_sw.getNodeId());
|
||||
return (temp == null) ? 0.0 : temp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
package com.lanternsoftware.zwave.context;
|
||||
|
||||
public class ZWaveSpring {
|
||||
/* private ZWaveConfig config;
|
||||
private static ZWaveSession session;
|
||||
private static Map<Integer, Switch> switches = new HashMap<>();
|
||||
private static Map<Integer, List<Integer>> peers = new HashMap<>();
|
||||
private static Timer timer;
|
||||
private static HttpPool pool;
|
||||
private static SwitchScheduleTask nextScheduleTask;
|
||||
|
||||
public void start() {
|
||||
try {
|
||||
// controller = new Controller();
|
||||
// controller.start("COM4");
|
||||
timer = new Timer("ZWaveApp Timer");
|
||||
pool = new HttpPool(10, 10, 30000, 10000, 10000);
|
||||
session = new LocalZwaveSession();
|
||||
session.connect();
|
||||
while (!session.isNetworkReady()) {
|
||||
System.out.println("Network not ready yet, sleeping");
|
||||
ConcurrencyUtils.sleep(1000);
|
||||
}
|
||||
// session.subscribe(new ZWaveEventListener());
|
||||
|
||||
// for (ZWaveNode node : session.getDeviceManager().getNodes()) {
|
||||
// for (CommandClass cc : node.getCommandClasses()) {
|
||||
// System.out.println(node.getNodeId() + " " + cc.getClassCode() + " " + cc.getLabel());
|
||||
// }
|
||||
// }
|
||||
|
||||
//// for (int node = 3; node < 7; node++) {
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 7, new byte[]{99}));
|
||||
// ConcurrencyUtils.sleep(100);
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 8, new byte[]{0, (byte) 1}));
|
||||
// ConcurrencyUtils.sleep(100);
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 9, new byte[]{99}));
|
||||
// ConcurrencyUtils.sleep(100);
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 10, new byte[]{0, (byte) 1}));
|
||||
// ConcurrencyUtils.sleep(100);
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 11, new byte[]{99}));
|
||||
// ConcurrencyUtils.sleep(100);
|
||||
// session.doAction(new ConfigurationSetAction(node, (byte) 12, new byte[]{0, (byte) 1}));
|
||||
// ConcurrencyUtils.sleep(100);
|
||||
// }
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
config = SerializationEngine.deserialize(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config.dat"), ZWaveConfig.class, SerializationEngine.SerializationType.JSON);
|
||||
Map<String, List<Integer>> groups = new HashMap<>();
|
||||
for (Switch sw : CollectionUtils.makeNotNull(config.getSwitches())) {
|
||||
switches.put(sw.getNodeId(), sw);
|
||||
CollectionUtils.addToMultiMap(sw.getRoom() + ":" + sw.getName(), sw.getNodeId(), groups);
|
||||
}
|
||||
if (CollectionUtils.filterOne(config.getSwitches(), _sw -> NullUtils.isNotEmpty(_sw.getThermostatSource())) != null) {
|
||||
timer.scheduleAtFixedRate(new ThermostatTask(), 0, 30000);
|
||||
}
|
||||
for (List<Integer> group : groups.values()) {
|
||||
for (Integer node : group) {
|
||||
peers.put(node, CollectionUtils.filter(group, _i -> !_i.equals(node)));
|
||||
}
|
||||
}
|
||||
scheduleNextTransition();
|
||||
}
|
||||
|
||||
public void scheduleNextTransition() {
|
||||
TimeZone tz = TimeZone.getTimeZone("America/Chicago");
|
||||
if (nextScheduleTask != null)
|
||||
nextScheduleTask.cancel();
|
||||
Switch next = null;
|
||||
SwitchTransition transition = null;
|
||||
Date transitionDate = null;
|
||||
for (Switch sw : switches.values()) {
|
||||
for (SwitchTransition t : CollectionUtils.makeNotNull(sw.getSchedule())) {
|
||||
Date nextTransition = t.getNextTransition(tz);
|
||||
if ((transitionDate == null) || nextTransition.before(transitionDate)) {
|
||||
transitionDate = nextTransition;
|
||||
transition = t;
|
||||
next = sw;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (transitionDate != null) {
|
||||
System.out.println("Next transition scheduled for node " + next.getNodeId() + " to level " + transition.getLevel() + " at " + DateUtils.format(tz, transitionDate, "hh:mm:ssa"));
|
||||
nextScheduleTask = new SwitchScheduleTask(next, transition);
|
||||
timer.schedule(nextScheduleTask, transitionDate);
|
||||
} else
|
||||
nextScheduleTask = null;
|
||||
}
|
||||
|
||||
public void setSwitchLevel(int _nodeId, int _level) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
if ((sw == null) || !sw.isPrimary())
|
||||
return;
|
||||
sw.setLevel(_level);
|
||||
if (NullUtils.isEmpty(sw.getThermostatSource())) {
|
||||
doGroupSwitchAction(_nodeId, _level, sw.isMultilevel());
|
||||
} else {
|
||||
if (timer != null)
|
||||
timer.schedule(new ThermostatTask(), 0);
|
||||
persistConfig();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setSwitchSchedule(int _nodeId, List<SwitchTransition> _transitions) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
if ((sw == null) || !sw.isPrimary())
|
||||
return;
|
||||
sw.setSchedule(_transitions);
|
||||
persistConfig();
|
||||
scheduleNextTransition();
|
||||
}
|
||||
|
||||
public void setSwitchHold(int _nodeId, boolean _hold) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
if ((sw == null) || !sw.isPrimary())
|
||||
return;
|
||||
sw.setHold(_hold);
|
||||
persistConfig();
|
||||
}
|
||||
|
||||
private void persistConfig() {
|
||||
synchronized (this) {
|
||||
ResourceLoader.writeFile(LanternFiles.OPS_PATH + "config.dat", SerializationEngine.serialize(config, SerializationEngine.SerializationType.JSON));
|
||||
}
|
||||
}
|
||||
|
||||
public int getSwitchLevel(int _nodeId) {
|
||||
Switch sw = switches.get(_nodeId);
|
||||
return (sw != null) ? sw.getLevel() : 0;
|
||||
}
|
||||
|
||||
public ZWaveConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
session.shutdown();
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
timer = null;
|
||||
}
|
||||
if (pool != null) {
|
||||
pool.shutdown();
|
||||
pool = null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public static class ZWaveEventListener implements EventHandler {
|
||||
@EventSubscribe
|
||||
public void receive(ZWaveEvent event) throws Exception {
|
||||
if (event instanceof ApplicationCommandEvent) {
|
||||
ApplicationCommandEvent ace = (ApplicationCommandEvent) event;
|
||||
if (ace.getCommandClass() == CommandClass.SWITCH_MULTILEVEL) {
|
||||
for (Integer node : CollectionUtils.makeNotNull(peers.get(ace.getNodeId()))) {
|
||||
Switch sw = switches.get(node);
|
||||
System.out.println("Mirror Event from node " + ((ApplicationCommandEvent) event).getNodeId() + " to node " + node);
|
||||
// session.doAction(new SwitchAction(node, ace.getPayload()[1], sw == null || sw.isMultilevel()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventSubscribe
|
||||
public void handleSensorEvent(DeviceSensorEvent sensorEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
private void doGroupSwitchAction(int _primary, int _level, boolean _multilevel) {
|
||||
List<Integer> nodes = CollectionUtils.asArrayList(_primary);
|
||||
nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary)));
|
||||
for (int node : nodes) {
|
||||
try {
|
||||
session.doAction(new SwitchAction(node, _level, _multilevel));
|
||||
} catch (HomeAutomationException _e) {
|
||||
_e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ThermostatTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Switch sw : switches.values()) {
|
||||
if (NullUtils.isNotEmpty(sw.getThermostatSource())) {
|
||||
double tempF = getTemperatureCelsius(sw) * 1.8 + 32;
|
||||
if (tempF > sw.getLevel() + 0.4) {
|
||||
doGroupSwitchAction(sw.getNodeId(), 0, false);
|
||||
System.out.println("Turning " + sw.getRoom() + " " + sw.getName() + " off, temp is: " + tempF + " set to: " + sw.getLevel());
|
||||
} else if (tempF < sw.getLevel() - 0.4) {
|
||||
doGroupSwitchAction(sw.getNodeId(), (byte) 0xf, false);
|
||||
System.out.println("Turning " + sw.getRoom() + " " + sw.getName() + " on, temp is: " + tempF + " set to: " + sw.getLevel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SwitchScheduleTask extends TimerTask {
|
||||
private final Switch sw;
|
||||
private final SwitchTransition transition;
|
||||
|
||||
public SwitchScheduleTask(Switch _sw, SwitchTransition _transition) {
|
||||
sw = _sw;
|
||||
transition = _transition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("Executing scheduled transition of node " + sw.getNodeId() + " to level " + transition.getLevel());
|
||||
if (!sw.isHold()) {
|
||||
Globals.app.setSwitchLevel(sw.getNodeId(), transition.getLevel());
|
||||
}
|
||||
nextScheduleTask = null;
|
||||
Globals.app.scheduleNextTransition();
|
||||
}
|
||||
}
|
||||
|
||||
public double getTemperatureCelsius(int _nodeId) {
|
||||
return getTemperatureCelsius(switches.get(_nodeId));
|
||||
}
|
||||
|
||||
private static double getTemperatureCelsius(Switch _sw) {
|
||||
if ((pool == null) || (_sw == null) || NullUtils.isEmpty(_sw.getThermostatSource()))
|
||||
return 0.0;
|
||||
return BsonUtils.getDouble(BsonUtils.parse(pool.executeToString(new HttpGet(_sw.getThermostatSource()))), "temp");
|
||||
}*/
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class MongoZWaveDao implements ZWaveDao {
|
||||
proxy = new MongoProxy(_config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
proxy.shutdown();
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
|
||||
public interface ZWaveDao {
|
||||
void putConfig(ZWaveConfig _config);
|
||||
ZWaveConfig getConfig(int _accountId);
|
||||
void shutdown();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.lanternsoftware.zwave.relay;
|
||||
|
||||
import com.pi4j.io.gpio.GpioFactory;
|
||||
import com.pi4j.io.gpio.GpioPinDigitalOutput;
|
||||
import com.pi4j.io.gpio.PinState;
|
||||
import com.pi4j.io.gpio.RaspiPin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RelayController {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(RelayController.class);
|
||||
|
||||
private final Map<Integer, GpioPinDigitalOutput> pins = new HashMap<>();
|
||||
|
||||
public void setRelay(int _pin, boolean _on) {
|
||||
GpioPinDigitalOutput pin = pins.get(_pin);
|
||||
if (pin == null) {
|
||||
pin = GpioFactory.getInstance().provisionDigitalOutputPin(RaspiPin.getPinByAddress(_pin), "Relay", PinState.LOW);
|
||||
if (pin != null)
|
||||
pins.put(_pin, pin);
|
||||
else {
|
||||
LOG.error("Failed to get pin {}", _pin);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_on)
|
||||
pin.high();
|
||||
else
|
||||
pin.low();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
GpioFactory.getInstance().shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.lanternsoftware.zwave.security;
|
||||
|
||||
import com.lanternsoftware.datamodel.zwave.Switch;
|
||||
import com.pi4j.io.gpio.GpioFactory;
|
||||
import com.pi4j.io.gpio.GpioPinDigitalInput;
|
||||
import com.pi4j.io.gpio.PinPullResistance;
|
||||
import com.pi4j.io.gpio.RaspiPin;
|
||||
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SecurityController {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(SecurityController.class);
|
||||
|
||||
private final Map<Integer, GpioPinDigitalInput> pins = new HashMap<>();
|
||||
|
||||
public boolean isOpen(int _pin) {
|
||||
GpioPinDigitalInput pin = getPin(_pin);
|
||||
return (pin == null) || pin.getState().isHigh();
|
||||
}
|
||||
|
||||
public void listen(Switch _sw, SecurityListener _listener) {
|
||||
GpioPinDigitalInput pin = getPin(_sw.getGpioPin());
|
||||
if (pin != null)
|
||||
pin.addListener((GpioPinListenerDigital) _event -> _listener.onStateChanged(_sw.getNodeId(), _event.getState().isHigh()));
|
||||
}
|
||||
|
||||
private GpioPinDigitalInput getPin(int _pin) {
|
||||
GpioPinDigitalInput pin = pins.get(_pin);
|
||||
if (pin == null) {
|
||||
pin = GpioFactory.getInstance().provisionDigitalInputPin(RaspiPin.getPinByAddress(_pin), "SecuritySensor", PinPullResistance.PULL_UP);
|
||||
if (pin != null)
|
||||
pins.put(_pin, pin);
|
||||
else {
|
||||
LOG.error("Failed to get pin {}", _pin);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return pin;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
for (GpioPinDigitalInput pin : pins.values()) {
|
||||
pin.removeAllListeners();
|
||||
}
|
||||
pins.clear();
|
||||
GpioFactory.getInstance().shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.lanternsoftware.zwave.security;
|
||||
|
||||
public interface SecurityListener {
|
||||
void onStateChanged(int nodeId, boolean _open);
|
||||
}
|
||||
@@ -1,16 +1,22 @@
|
||||
package com.lanternsoftware.zwave.servlet;
|
||||
|
||||
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;
|
||||
import com.lanternsoftware.zwave.context.Globals;
|
||||
import com.lanternsoftware.zwave.context.ZWaveApp;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public abstract class SecureServlet extends ZWaveServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
|
||||
AuthCode authCode = Globals.cmDao.decryptAuthCode(_req.getHeader("auth_code"));
|
||||
if ((authCode == null) || (authCode.getAccountId() != 1)) {
|
||||
AuthCode authCode = DaoSerializer.fromZipBson(ZWaveApp.aes.decryptFromBase64(_req.getHeader("auth_code")), AuthCode.class);
|
||||
if ((authCode == null) || (authCode.getAccountId() != 100)) {
|
||||
_rep.setStatus(401);
|
||||
return;
|
||||
}
|
||||
@@ -22,8 +28,8 @@ public abstract class SecureServlet extends ZWaveServlet {
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
|
||||
AuthCode authCode = Globals.cmDao.decryptAuthCode(_req.getHeader("auth_code"));
|
||||
if ((authCode == null) || (authCode.getAccountId() != 1)) {
|
||||
AuthCode authCode = DaoSerializer.fromZipBson(ZWaveApp.aes.decryptFromBase64(_req.getHeader("auth_code")), AuthCode.class);
|
||||
if ((authCode == null) || (authCode.getAccountId() != 100)) {
|
||||
_rep.setStatus(401);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.lanternsoftware.zwave.servlet;
|
||||
|
||||
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
|
||||
import com.lanternsoftware.datamodel.zwave.Switch;
|
||||
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
|
||||
import com.lanternsoftware.datamodel.zwave.ThermostatMode;
|
||||
import com.lanternsoftware.util.CollectionUtils;
|
||||
@@ -49,5 +50,8 @@ public class SwitchServlet extends SecureServlet {
|
||||
Globals.app.setSwitchSchedule(nodeId, transitions);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Globals.app.updateSwitch(getRequestPayload(_req, Switch.class));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ public abstract class ZWaveServlet extends HttpServlet {
|
||||
IOUtils.closeQuietly(is);
|
||||
}
|
||||
}
|
||||
protected <T> T getRequestPayload(HttpServletRequest _req, Class<T> _retClass) {
|
||||
return DaoSerializer.fromZipBson(getRequestPayload(_req), _retClass);
|
||||
}
|
||||
|
||||
protected String[] path(HttpServletRequest _req) {
|
||||
return NullUtils.cleanSplit(NullUtils.makeNotNull(_req.getPathInfo()), "/");
|
||||
|
||||
Reference in New Issue
Block a user