Add a rules engine so I can be notified when I forget to close my garage door.

This commit is contained in:
MarkBryanMilligan
2021-07-15 23:34:15 -05:00
parent de50645a2c
commit 3d5cd6500f
81 changed files with 2044 additions and 231 deletions

View File

@@ -1,6 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.services</groupId>
<groupId>com.lanternsoftware.zwave</groupId>
<artifactId>lantern-datamodel-zwave</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>

View File

@@ -154,6 +154,14 @@ public class Switch {
return type == SwitchType.RELAY;
}
public boolean isRelayButton() {
return type == SwitchType.RELAY_BUTTON;
}
public boolean isSecurity() {
return type == SwitchType.SECURITY;
}
public boolean isControlledBy(String _controllerUrl) {
return NullUtils.isEqual(_controllerUrl, controllerUrl);
}

View File

@@ -7,5 +7,6 @@ public enum SwitchType {
SPACE_HEATER_THERMOSTAT,
THERMOMETER,
RELAY,
SECURITY
SECURITY,
RELAY_BUTTON
}

View File

@@ -13,6 +13,7 @@ public class ZWaveConfig {
private String commPort;
private String url;
private String masterUrl;
private String rulesUrl;
private List<Switch> switches;
public int getAccountId() {
@@ -47,6 +48,14 @@ public class ZWaveConfig {
masterUrl = _masterUrl;
}
public String getRulesUrl() {
return rulesUrl;
}
public void setRulesUrl(String _rulesUrl) {
rulesUrl = _rulesUrl;
}
public List<Switch> getSwitches() {
return switches;
}

View File

@@ -36,11 +36,11 @@ public class SwitchSerializer extends AbstractDaoSerializer<Switch>
d.put("gpio_pin", _o.getGpioPin());
d.put("primary", _o.isPrimary());
d.put("hold", _o.isHold());
d.put("hidden", _o.isHidden());
d.put("thermometer_url", _o.getThermometerUrl());
d.put("controller_url", _o.getControllerUrl());
d.put("thermostat_mode", DaoSerializer.toEnumName(_o.getThermostatMode()));
d.put("low_level", _o.getLowLevel());
d.put("hidden", _o.isHidden());
d.put("schedule", DaoSerializer.toDaoEntities(_o.getSchedule(), DaoProxyType.MONGO));
return d;
}
@@ -57,11 +57,11 @@ public class SwitchSerializer extends AbstractDaoSerializer<Switch>
o.setGpioPin(DaoSerializer.getInteger(_d, "gpio_pin"));
o.setPrimary(DaoSerializer.getBoolean(_d, "primary"));
o.setHold(DaoSerializer.getBoolean(_d, "hold"));
o.setHidden(DaoSerializer.getBoolean(_d, "hidden"));
o.setThermometerUrl(DaoSerializer.getString(_d, "thermometer_url"));
o.setControllerUrl(DaoSerializer.getString(_d, "controller_url"));
o.setThermostatMode(DaoSerializer.getEnum(_d, "thermostat_mode", ThermostatMode.class));
o.setLowLevel(DaoSerializer.getInteger(_d, "low_level"));
o.setHidden(DaoSerializer.getBoolean(_d, "hidden"));
o.setSchedule(DaoSerializer.getList(_d, "schedule", SwitchSchedule.class));
return o;
}

View File

@@ -31,6 +31,7 @@ public class ZWaveConfigSerializer extends AbstractDaoSerializer<ZWaveConfig>
d.put("comm_port", _o.getCommPort());
d.put("url", _o.getUrl());
d.put("master_url", _o.getMasterUrl());
d.put("rules_url", _o.getRulesUrl());
d.put("switches", DaoSerializer.toDaoEntities(_o.getSwitches(), DaoProxyType.MONGO));
return d;
}
@@ -43,6 +44,7 @@ public class ZWaveConfigSerializer extends AbstractDaoSerializer<ZWaveConfig>
o.setCommPort(DaoSerializer.getString(_d, "comm_port"));
o.setUrl(DaoSerializer.getString(_d, "url"));
o.setMasterUrl(DaoSerializer.getString(_d, "master_url"));
o.setRulesUrl(DaoSerializer.getString(_d, "rules_url"));
o.setSwitches(DaoSerializer.getList(_d, "switches", Switch.class));
return o;
}

View File

@@ -14,7 +14,7 @@
<dependencies>
<dependency>
<groupId>com.lanternsoftware.services</groupId>
<groupId>com.lanternsoftware.zwave</groupId>
<artifactId>lantern-datamodel-zwave</artifactId>
<version>1.0.0</version>
</dependency>
@@ -33,6 +33,11 @@
<artifactId>lantern-zwave</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.rules</groupId>
<artifactId>lantern-datamodel-rules</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao-mongo</artifactId>

View File

@@ -1,6 +1,8 @@
package com.lanternsoftware.zwave.context;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.datamodel.rules.Event;
import com.lanternsoftware.datamodel.rules.EventType;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.SwitchTransition;
@@ -32,6 +34,7 @@ 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 com.lanternsoftware.zwave.security.SecurityController;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
@@ -39,6 +42,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -48,8 +52,8 @@ 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)));
public static final AESTool aes = AESTool.authTool();
public static String authCode;
private static final Logger logger = LoggerFactory.getLogger(ZWaveApp.class);
@@ -57,6 +61,7 @@ public class ZWaveApp {
private ZWaveConfig config;
private Controller controller;
private RelayController relayController;
private SecurityController securityController;
private final Map<Integer, Switch> originalSwitches = new HashMap<>();
private final Map<Integer, Switch> switches = new HashMap<>();
private final Map<Integer, Switch> mySwitches = new HashMap<>();
@@ -79,12 +84,14 @@ public class ZWaveApp {
controller = new Controller();
controller.start(config.getCommPort());
}
authCode = aes.encryptToBase64(DaoSerializer.toZipBson(new AuthCode(config.getAccountId(), null)));
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());
config.setRulesUrl(switchConfig.getRulesUrl());
}
else {
logger.error("Failed to retrieve switch config from master controller");
@@ -125,6 +132,20 @@ public class ZWaveApp {
if (CollectionUtils.anyQualify(mySwitches.values(), Switch::isRelay)) {
relayController = new RelayController();
}
List<Switch> securitySwitches = CollectionUtils.filter(mySwitches.values(), Switch::isSecurity);
if (!securitySwitches.isEmpty()) {
securityController = new SecurityController();
for (Switch s : securitySwitches) {
s.setLevel(securityController.isOpen(s.getGpioPin())?1:0);
logger.info("Monitoring security sensor " + s.getFullDisplay() + " on gpio pin " + s.getGpioPin());
securityController.listen(s, (_nodeId, _open) -> {
s.setLevel(_open?1:0);
logger.info(s.getFullDisplay() + " is " + ((s.getLevel() == 0)?"closed":"open"));
fireSwitchLevelEvent(s);
persistConfig();
});
}
}
for (List<Switch> group : groups.values()) {
for (Switch sw : group) {
peers.put(sw.getNodeId(), CollectionUtils.filter(group, _sw -> _sw.getNodeId() != sw.getNodeId()));
@@ -161,6 +182,7 @@ public class ZWaveApp {
if (sw != null) {
if (NullUtils.isOneOf(_message.getIndex(), ThermostatSetPointIndex.HEATING, ThermostatSetPointIndex.COOLING)) {
sw.setLevel((int) Math.round(_message.getTemperatureCelsius() * 1.8) + 32);
fireSwitchLevelEvent(sw);
persistConfig();
}
}
@@ -222,6 +244,7 @@ public class ZWaveApp {
if ((sw != null) && !sw.isPrimary()) {
int newLevel = sw.isMultilevel()?_primaryLevel:((_primaryLevel == 0)?0:99);
sw.setLevel(newLevel);
fireSwitchLevelEvent(sw);
for (Switch peer : CollectionUtils.makeNotNull(peers.get(_secondaryNodeId))) {
if (peer.isPrimary()) {
logger.info("Mirror Event from node {} to node {}", _secondaryNodeId, peer.getNodeId());
@@ -254,22 +277,52 @@ public class ZWaveApp {
nextScheduleTask = null;
}
public int getAccountId() {
return config == null ? 0 : config.getAccountId();
}
public void setSwitchLevel(int _nodeId, int _level) {
setSwitchLevel(_nodeId, _level, true);
}
public void fireSwitchLevelEvent(Switch _sw) {
if (NullUtils.isEmpty(config.getRulesUrl()))
return;
Event event = new Event();
event.setEventDescription(_sw.getFullDisplay() + " set to " + _sw.getLevel());
event.setType(EventType.SWITCH_LEVEL);
event.setTime(new Date());
event.setValue(_sw.getLevel());
event.setSourceId(String.valueOf(_sw.getNodeId()));
event.setAccountId(config.getAccountId());
logger.info("Sending event to rules server - " + event.getEventDescription());
HttpPost post = new HttpPost(NullUtils.terminateWith(config.getRulesUrl(), "/") + "event");
post.setHeader("auth_code", authCode);
post.setEntity(new ByteArrayEntity(DaoSerializer.toZipBson(event)));
pool.execute(post);
}
public void setSwitchLevel(int _nodeId, int _level, boolean _updatePeers) {
Switch sw = switches.get(_nodeId);
if ((sw == null) || !sw.isPrimary())
return;
sw.setLevel(_level);
if (config.isMySwitch(sw)) {
fireSwitchLevelEvent(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 if (sw.isRelayButton()) {
relayController.setRelay(sw.getGpioPin(), true);
timer.schedule(new TimerTask() {
@Override
public void run() {
relayController.setRelay(sw.getGpioPin(), false);
}
}, 250);
} else {
setGroupSwitchLevel(sw, _level);
}
@@ -331,7 +384,8 @@ public class ZWaveApp {
}
}
if (_updatePeers) {
Set<String> peers = CollectionUtils.transformToSet(modified, Switch::getControllerUrl);
Set<String> peers = CollectionUtils.transformToSet(switches.values(), Switch::getControllerUrl);
peers.add(config.getMasterUrl());
peers.remove(config.getUrl());
for (String peer : peers) {
for (Switch sw : modified) {
@@ -354,11 +408,18 @@ public class ZWaveApp {
}
public void stop() {
controller.stop();
if (controller != null) {
controller.stop();
controller = null;
}
if (relayController != null) {
relayController.shutdown();
relayController = null;
}
if (securityController != null) {
securityController.shutdown();
securityController = null;
}
if (timer != null) {
timer.cancel();
timer = null;

View File

@@ -1,13 +1,11 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.zwave.context.Globals;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/config")
public class ConfigServlet extends SecureServlet {

View File

@@ -1,9 +1,6 @@
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.auth.AuthCode;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.zwave.context.Globals;
import com.lanternsoftware.zwave.context.ZWaveApp;
@@ -16,7 +13,7 @@ public abstract class SecureServlet extends ZWaveServlet {
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = DaoSerializer.fromZipBson(ZWaveApp.aes.decryptFromBase64(_req.getHeader("auth_code")), AuthCode.class);
if ((authCode == null) || (authCode.getAccountId() != 100)) {
if ((authCode == null) || (authCode.getAccountId() != Globals.app.getAccountId())) {
_rep.setStatus(401);
return;
}
@@ -29,7 +26,7 @@ public abstract class SecureServlet extends ZWaveServlet {
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = DaoSerializer.fromZipBson(ZWaveApp.aes.decryptFromBase64(_req.getHeader("auth_code")), AuthCode.class);
if ((authCode == null) || (authCode.getAccountId() != 100)) {
if ((authCode == null) || (authCode.getAccountId() != Globals.app.getAccountId())) {
_rep.setStatus(401);
return;
}

View File

@@ -1,6 +1,6 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.ThermostatMode;

View File

@@ -1,6 +1,6 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.util.dao.auth.AuthCode;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoEntity;

View File

@@ -6,6 +6,6 @@ import com.lanternsoftware.util.dao.generator.DaoSerializerGenerator;
public class GenerateSerializers {
public static void main(String[] args) {
DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH + "zwave", true, null);
DaoSerializerGenerator.generateSerializers(LanternFiles.SOURCE_PATH, true, null);
}
}

View File

@@ -1,6 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lanternsoftware.services</groupId>
<groupId>com.lanternsoftware.zwave</groupId>
<artifactId>lantern-uirt</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>