Password reset functionality, ZWave switch schedule improvement, support zwave controller on pi, support relay switches and security sensors.

This commit is contained in:
MarkBryanMilligan 2021-07-02 12:06:37 -05:00
parent 6c2b567536
commit de50645a2c
65 changed files with 27104 additions and 438 deletions

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,7 @@
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-currentmonitor</artifactId>
<packaging>jar</packaging>
<version>1.0.2</version>
<version>1.0.4</version>
<name>lantern-currentmonitor</name>
<properties>
@ -25,7 +25,7 @@
<dependency>
<groupId>com.pi4j</groupId>
<artifactId>pi4j-gpio-extension</artifactId>
<version>1.2</version>
<version>1.3</version>
</dependency>
<dependency>
<groupId>com.github.hypfvieh</groupId>

View File

@ -178,14 +178,14 @@ public class MonitorApp {
return new byte[]{NetworkMonitor.getNetworkStatus().toMask()};
if (HubConfigCharacteristic.NetworkDetails == ch) {
NetworkStatus status = NetworkMonitor.getNetworkStatus();
DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version")));
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 > 10)
log = Arrays.copyOfRange(log, log.length-10, log.length);
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;
@ -341,7 +341,7 @@ public class MonitorApp {
if (files != null) {
for (File file : files) {
payload = ResourceLoader.loadFile(file.getAbsolutePath());
if (post(payload, file.getName().endsWith("dat") ? "power/batch" : "power/hub"))
if (post(payload, "power/hub"))
file.delete();
else
break;
@ -401,7 +401,7 @@ public class MonitorApp {
private static final class UpdateChecker implements Runnable {
@Override
public void run() {
if (NullUtils.isNotEmpty(host)) {
if (NullUtils.isNotEmpty(host) && config.isAutoUpdate()) {
DaoEntity meta = DaoSerializer.fromZipBson(pool.executeToByteArray(new HttpGet(host + "update/version")));
String newVersion = DaoSerializer.getString(meta, "version");
if (NullUtils.isNotEqual(newVersion, version)) {

View File

@ -17,6 +17,7 @@ public class MonitorConfig {
private int connectTimeout;
private int socketTimeout;
private int updateInterval;
private boolean autoUpdate;
private float autoCalibrationVoltage;
private boolean needsCalibration;
private String mqttBrokerUrl;
@ -107,6 +108,14 @@ public class MonitorConfig {
updateInterval = _updateInterval;
}
public boolean isAutoUpdate() {
return autoUpdate;
}
public void setAutoUpdate(boolean _autoUpdate) {
autoUpdate = _autoUpdate;
}
public float getAutoCalibrationVoltage() {
return autoCalibrationVoltage;
}

View File

@ -35,6 +35,7 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer<MonitorConfig
d.put("connect_timeout", _o.getConnectTimeout());
d.put("socket_timeout", _o.getSocketTimeout());
d.put("update_interval", _o.getUpdateInterval());
d.put("auto_update", _o.isAutoUpdate());
d.put("auto_calibration_voltage", _o.getAutoCalibrationVoltage());
d.put("needs_calibration", _o.isNeedsCalibration());
d.put("mqtt_broker_url", _o.getMqttBrokerUrl());
@ -60,6 +61,7 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer<MonitorConfig
o.setConnectTimeout(DaoSerializer.getInteger(_d, "connect_timeout"));
o.setSocketTimeout(DaoSerializer.getInteger(_d, "socket_timeout"));
o.setUpdateInterval(DaoSerializer.getInteger(_d, "update_interval"));
o.setAutoUpdate(DaoSerializer.getBoolean(_d, "auto_update"));
o.setAutoCalibrationVoltage(DaoSerializer.getFloat(_d, "auto_calibration_voltage"));
o.setNeedsCalibration(DaoSerializer.getBoolean(_d, "needs_calibration"));
o.setMqttBrokerUrl(DaoSerializer.getString(_d, "mqtt_broker_url"));

View File

@ -32,6 +32,9 @@ public interface CurrentMonitorDao {
void updateSummaries(BreakerGroup _rootGroup, Set<Date> _daysToSummarize, TimeZone _tz);
String addPasswordResetKey(String _email);
String getEmailForResetKey(String _key);
boolean resetPassword(String _key, String _password);
String authenticateAccount(String _username, String _password);
String getAuthCodeForEmail(String _email, TimeZone _tz);
Account authCodeToAccount(String _authCode);

View File

@ -296,6 +296,32 @@ public class MongoCurrentMonitorDao implements CurrentMonitorDao {
return _account;
}
@Override
public String addPasswordResetKey(String _email) {
String key = aes.encryptToBase64(_email);
proxy.saveEntity("password_reset", new DaoEntity("_id", key));
return key;
}
@Override
public String getEmailForResetKey(String _key) {
DaoEntity entity = proxy.queryForEntity("password_reset", new DaoQuery("_id", _key));
if (entity == null)
return null;
return aes.decryptFromBase64ToString(_key);
}
@Override
public boolean resetPassword(String _key, String _password) {
DaoEntity entity = proxy.queryForEntity("password_reset", new DaoQuery("_id", _key));
if (entity == null)
return false;
Account acct = getAccountByUsername(aes.decryptFromBase64ToString(_key));
acct.setPassword(_password);
putAccount(acct);
return true;
}
@Override
public MongoProxy getProxy() {
return proxy;

View File

@ -44,6 +44,11 @@
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.7.2</version>
</dependency>
</dependencies>
<build>
<resources>
@ -76,11 +81,25 @@
<artifactId>maven-war-plugin</artifactId>
<version>2.5</version>
<configuration>
<webResources>
<resource>
<filtering>true</filtering>
<directory>src/main/webapp</directory>
<includes>
<include>versioninfo</include>
</includes>
</resource>
</webResources>
<archive>
<manifest>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<manifestEntries>
<Build-Time>${maven.build.timestamp}</Build-Time>
<Build-OS>${os.name}</Build-OS>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin>

View File

@ -1,8 +1,11 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.datamodel.currentmonitor.BreakerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
@ -10,8 +13,13 @@ import javax.servlet.http.HttpServletResponse;
@WebServlet("/config/*")
public class ConfigServlet extends SecureServlet {
private static final Logger logger = LoggerFactory.getLogger(ConfigServlet.class);
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
if (_authCode.getAccountId() == 100) {
logger.error("my ip: " + _req.getRemoteAddr());
}
if (isPath(_req, 0, "bin"))
zipBsonResponse(_rep, Globals.dao.getMergedConfig(_authCode));
else

View File

@ -0,0 +1,87 @@
package com.lanternsoftware.currentmonitor.servlet;
import com.lanternsoftware.currentmonitor.context.Globals;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.email.EmailValidator;
import com.lanternsoftware.util.servlet.FreemarkerConfigUtil;
import com.lanternsoftware.util.servlet.FreemarkerServlet;
import com.sendgrid.Method;
import com.sendgrid.Request;
import com.sendgrid.Response;
import com.sendgrid.SendGrid;
import com.sendgrid.helpers.mail.Mail;
import com.sendgrid.helpers.mail.objects.Content;
import com.sendgrid.helpers.mail.objects.Email;
import freemarker.template.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
@WebServlet("/resetPassword/*")
public class ResetPasswordServlet extends FreemarkerServlet {
protected static final Logger LOG = LoggerFactory.getLogger(ResetPasswordServlet.class);
protected static final Configuration CONFIG = FreemarkerConfigUtil.createConfig(ResetPasswordServlet.class, "/templates", 100);
protected static final String api_key = ResourceLoader.loadFileAsString(LanternFiles.OPS_PATH + "sendgrid.txt");
@Override
protected Configuration getFreemarkerConfig() {
return CONFIG;
}
@Override
protected void doGet(HttpServletRequest _req, HttpServletResponse _resp) {
String[] path = getPath(_req);
String email = Globals.dao.getEmailForResetKey(path[1]);
if (EmailValidator.getInstance().isValid(email)) {
render(_resp, "passwordReset.ftl", model(_req, "key", path[1]));
} else {
render(_resp, "passwordResetMsg.ftl", model(_req, "msg", "This password reset code is no longer valid. Please try sending a new code from the Lantern Power Monitor application."));
}
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _resp) {
if (NullUtils.isEqual(_req.getContentType(), MediaType.APPLICATION_FORM_URLENCODED)) {
String key = _req.getParameter("reset_key");
String password = _req.getParameter("password");
if (NullUtils.length(password) < 8) {
render(_resp, "passwordReset.ftl", model(_req, "key", key).and("error", "Your password must be at least 8 characters."));
return;
}
Globals.dao.resetPassword(key, password);
render(_resp, "passwordResetMsg.ftl", model(_req, "msg", "Your password has been changed."));
} else {
DaoEntity payload = getRequestZipBson(_req);
String email = DaoSerializer.getString(payload, "email");
if (NullUtils.isNotEmpty(email)) {
String key = Globals.dao.addPasswordResetKey(email);
Email from = new Email("info@lanternsoftware.com");
String subject = "Password Reset - Lantern Power Monitor";
Email to = new Email(email);
Content content = new Content("text/plain", "Reset your password using this link:\nhttps://lanternsoftware.com/currentmonitor/resetPassword/" + key);
Mail mail = new Mail(from, subject, to, content);
SendGrid sg = new SendGrid(api_key);
Request request = new Request();
try {
request.setMethod(Method.POST);
request.setEndpoint("mail/send");
request.setBody(mail.build());
Response response = sg.api(request);
zipBsonResponse(_resp, new DaoEntity("success", response.getStatusCode() == 200));
} catch (IOException ex) {
LOG.error("Failed to send password reset email", ex);
zipBsonResponse(_resp, new DaoEntity("success", false));
}
}
}
}
}

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lantern | Reset Password</title>
<link href="${context}/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<section id="reset" class="container features" style="margin-top:40px;margin-bottom:40px;">
<div class="row">
<div class="col-lg-12">
<div class="navy-line"></div>
<h4><img class="mr-1" src="${context}/img/logo_40.png">Reset Password</h4>
<form class="ml-2" method="POST">
<input type="hidden" name="reset_key" value="${key}"/>
<div>New Password:</div>
<input type="password" name="password"/>
<input type="submit" class="btn-primary" value="Submit"/>
</form>
</div>
</div>
</section>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lantern | Reset Password</title>
<link href="${context}/bootstrap/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<section id="reset" class="container features" style="margin-top:40px;margin-bottom:40px;">
<div class="row">
<div class="col-lg-12">
<div class="navy-line"></div>
<h4><img class="mr-1" src="${context}/img/logo_40.png">Reset Password</h4>
<div class="ml-2">${msg}</div>
</div>
</div>
</section>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,325 @@
/*!
* Bootstrap Reboot v4.5.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-family: sans-serif;
line-height: 1.15;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
display: block;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
text-align: left;
background-color: #fff;
}
[tabindex="-1"]:focus:not(:focus-visible) {
outline: 0 !important;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 0.5rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-original-title] {
text-decoration: underline;
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
border-bottom: 0;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: .5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 80%;
}
sub,
sup {
position: relative;
font-size: 75%;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
a {
color: #007bff;
text-decoration: none;
background-color: transparent;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:not([href]) {
color: inherit;
text-decoration: none;
}
a:not([href]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
}
pre {
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
-ms-overflow-style: scrollbar;
}
figure {
margin: 0 0 1rem;
}
img {
vertical-align: middle;
border-style: none;
}
svg {
overflow: hidden;
vertical-align: middle;
}
table {
border-collapse: collapse;
}
caption {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
color: #6c757d;
text-align: left;
caption-side: bottom;
}
th {
text-align: inherit;
}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
button {
border-radius: 0;
}
button:focus {
outline: 1px dotted;
outline: 5px auto -webkit-focus-ring-color;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[role="button"] {
cursor: pointer;
}
select {
word-wrap: normal;
}
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
button:not(:disabled),
[type="button"]:not(:disabled),
[type="reset"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
input[type="radio"],
input[type="checkbox"] {
box-sizing: border-box;
padding: 0;
}
textarea {
overflow: auto;
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
max-width: 100%;
padding: 0;
margin-bottom: .5rem;
font-size: 1.5rem;
line-height: inherit;
color: inherit;
white-space: normal;
}
progress {
vertical-align: baseline;
}
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
[type="search"] {
outline-offset: -2px;
-webkit-appearance: none;
}
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
summary {
display: list-item;
cursor: pointer;
}
template {
display: none;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v4.5.0 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors
* Copyright 2011-2020 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -80,6 +80,8 @@ public class HttpPool {
CloseableHttpResponse resp = null;
try {
resp = execute(_request);
if (resp == null)
return null;
if ((resp.getStatusLine().getStatusCode() < 200) || (resp.getStatusLine().getStatusCode() >= 300)) {
LOG.error("Failed to make http request to " + _request.getURI().toString() + ". Status code: " + resp.getStatusLine().getStatusCode());
return null;

View File

@ -11,7 +11,7 @@
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>lantern-util-common</artifactId>
<artifactId>lantern-util-dao</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>

View File

@ -1,27 +1,24 @@
package com.lanternsoftware.util.servlet;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import freemarker.template.Configuration;
import org.apache.commons.io.IOUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import freemarker.template.Configuration;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
public abstract class FreemarkerServlet extends HttpServlet {
protected static final Logger LOG = LoggerFactory.getLogger(FreemarkerServlet.class);
protected abstract Configuration getFreemarkerConfig();
public static String[] getPath(HttpServletRequest _request) {
@ -29,7 +26,7 @@ public abstract class FreemarkerServlet extends HttpServlet {
if (sPath.startsWith("/"))
sPath = sPath.substring(1);
String[] path = sPath.split("/");
if ((path == null) || (path.length == 0) || (path[0].length() == 0))
if ((path.length == 0) || (path[0].length() == 0))
return new String[] { "index" };
int iExtPos = CollectionUtils.last(path).lastIndexOf(".");
if (iExtPos > -1) {
@ -42,45 +39,6 @@ public abstract class FreemarkerServlet extends HttpServlet {
_response.sendRedirect(_response.encodeRedirectURL(_sURL));
}
public static void setResponseHtml(HttpServletResponse _response, String _sHtml) {
setResponseEntity(_response, "text/html", _sHtml);
}
public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) {
setResponseEntity(_response, 200, _sContentType, _sEntity);
}
public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) {
setResponseEntity(_response, 200, _sContentType, _btData);
}
public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) {
setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity));
}
public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) {
OutputStream os = null;
try {
_response.setStatus(_iStatus);
_response.setCharacterEncoding("UTF-8");
_response.setContentType(_sContentType);
if ((_btData != null) && (_btData.length > 0)) {
_response.setContentLength(_btData.length);
os = _response.getOutputStream();
os.write(_btData);
}
else
_response.setContentLength(0);
}
catch (Exception e) {
if (!e.getClass().getSimpleName().equals("ClientAbortException"))
LOG.error("Failed to set response entity", e);
}
finally {
IOUtils.closeQuietly(os);
}
}
public void render(HttpServletResponse _rep, String _sHtmlResourceKey, Map<String, Object> _mapModel) {
String html = FreemarkerUtil.render(getFreemarkerConfig(), _sHtmlResourceKey, _mapModel);
if (html == null)
@ -89,10 +47,16 @@ public abstract class FreemarkerServlet extends HttpServlet {
setResponseHtml(_rep, html);
}
protected Map<String, Object> simpleModel(String _name, Object _value) {
Map<String, Object> mapModel = new HashMap<String, Object>();
mapModel.put(_name, _value);
return mapModel;
public static DaoEntity model(HttpServletRequest _req, String _name, Object _value) {
DaoEntity model = model(_req);
model.put(_name, _value);
return model;
}
protected static DaoEntity model(HttpServletRequest _req) {
DaoEntity model = new DaoEntity("context", _req.getContextPath());
model.put("css_version", "1.0.0");
return model;
}
public static <T> T getSessionVar(HttpServletRequest _req, String _name) {
@ -122,4 +86,89 @@ public abstract class FreemarkerServlet extends HttpServlet {
return null;
}
public static void setResponseHtml(HttpServletResponse _response, String _sHtml) {
setResponseEntity(_response, MediaType.TEXT_HTML, _sHtml);
}
public static void setResponseEntity(HttpServletResponse _response, String _sContentType, String _sEntity) {
setResponseEntity(_response, 200, _sContentType, _sEntity);
}
public static void setResponseEntity(HttpServletResponse _response, String _sContentType, byte[] _btData) {
setResponseEntity(_response, 200, _sContentType, _btData);
}
public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, String _sEntity) {
setResponseEntity(_response, _iStatus, _sContentType, NullUtils.toByteArray(_sEntity));
}
public static void setResponseEntity(HttpServletResponse _response, int _iStatus, String _sContentType, byte[] _btData) {
OutputStream os = null;
try {
_response.setStatus(_iStatus);
_response.setCharacterEncoding("UTF-8");
_response.setContentType(_sContentType);
if ((_btData != null) && (_btData.length > 0)) {
_response.setContentLength(_btData.length);
os = _response.getOutputStream();
os.write(_btData);
} else
_response.setContentLength(0);
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(os);
}
}
protected void zipBsonResponse(HttpServletResponse _response, Object _object)
{
setResponseEntity(_response, 200, MediaType.APPLICATION_OCTET_STREAM, DaoSerializer.toZipBson(_object));
}
protected void jsonResponse(HttpServletResponse _response, Object _object)
{
setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, DaoSerializer.toJson(_object));
}
protected void jsonResponse(HttpServletResponse _response, String _json)
{
setResponseEntity(_response, 200, MediaType.APPLICATION_JSON, _json);
}
protected String getRequestPayloadAsString(HttpServletRequest _req) {
return NullUtils.toString(getRequestPayload(_req));
}
protected byte[] getRequestPayload(HttpServletRequest _req) {
InputStream is = null;
try {
is = _req.getInputStream();
return IOUtils.toByteArray(is);
}
catch (Exception e) {
e.printStackTrace();
return null;
}
finally {
IOUtils.closeQuietly(is);
}
}
protected DaoEntity getRequestZipBson(HttpServletRequest _req) {
return DaoSerializer.fromZipBson(getRequestPayload(_req));
}
protected <T> 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()), "/");
}
protected boolean isPath(HttpServletRequest _req, int _index, String _path) {
return NullUtils.isEqual(_path, CollectionUtils.get(path(_req), _index));
}
}

View File

@ -1,21 +1,26 @@
package com.lanternsoftware.datamodel.zwave;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.List;
import java.util.Objects;
@DBSerializable
public class Switch {
private SwitchType type;
private String room;
private String name;
private int nodeId;
private int level;
private int gpioPin;
private boolean primary;
private boolean multilevel;
private boolean hold;
private String thermostatSource;
private boolean hidden;
private String thermometerUrl;
private String controllerUrl;
private ThermostatMode thermostatMode;
private int lowLevel;
private List<SwitchSchedule> schedule;
@ -23,23 +28,30 @@ public class Switch {
public Switch() {
}
public Switch(String _room, String _name, int _nodeId, boolean _primary, boolean _multilevel, String _thermostatSource, int _lowLevel) {
this(_room, _name, _nodeId, 0, _primary, _multilevel, false, _thermostatSource, _lowLevel, null);
public Switch(String _room, String _name, int _nodeId, boolean _primary, boolean _multilevel, String _thermometerUrl, int _lowLevel) {
this(_room, _name, _nodeId, 0, _primary, false, _thermometerUrl, _lowLevel, null);
}
public Switch(String _room, String _name, int _nodeId, int _level, boolean _primary, boolean _multilevel, boolean _hold, String _thermostatSource, int _lowLevel, List<SwitchSchedule> _schedule) {
public Switch(String _room, String _name, int _nodeId, int _level, boolean _primary, boolean _hold, String _thermometerUrl, int _lowLevel, List<SwitchSchedule> _schedule) {
room = _room;
name = _name;
nodeId = _nodeId;
level = _level;
primary = _primary;
multilevel = _multilevel;
hold = _hold;
thermostatSource = _thermostatSource;
thermometerUrl = _thermometerUrl;
lowLevel = _lowLevel;
schedule = _schedule;
}
public SwitchType getType() {
return type;
}
public void setType(SwitchType _type) {
type = _type;
}
public String getRoom() {
return room;
}
@ -56,6 +68,12 @@ public class Switch {
name = _name;
}
public String getFullDisplay() {
if (NullUtils.isNotEmpty(room))
return room + " - " + name;
return name;
}
public int getNodeId() {
return nodeId;
}
@ -72,6 +90,14 @@ public class Switch {
level = _level;
}
public int getGpioPin() {
return gpioPin;
}
public void setGpioPin(int _gpioPin) {
gpioPin = _gpioPin;
}
public boolean isPrimary() {
return primary;
}
@ -81,11 +107,7 @@ public class Switch {
}
public boolean isMultilevel() {
return multilevel;
}
public void setMultilevel(boolean _multilevel) {
multilevel = _multilevel;
return type == SwitchType.DIMMER;
}
public boolean isHold() {
@ -96,28 +118,44 @@ public class Switch {
hold = _hold;
}
public String getThermostatSource() {
return thermostatSource;
public String getThermometerUrl() {
return thermometerUrl;
}
public void setThermostatSource(String _thermostatSource) {
thermostatSource = _thermostatSource;
public void setThermometerUrl(String _thermometerUrl) {
thermometerUrl = _thermometerUrl;
}
public String getControllerUrl() {
return controllerUrl;
}
public void setControllerUrl(String _controllerUrl) {
controllerUrl = _controllerUrl;
}
public boolean isThermostat() {
return NullUtils.isNotEmpty(thermostatSource) && (nodeId < 100);
return isSpaceHeaterThermostat() || isZWaveThermostat();
}
public boolean isThermometer() {
return isUrlThermostat() && (nodeId > 99);
public boolean isSpaceHeaterThermostat() {
return type == SwitchType.SPACE_HEATER_THERMOSTAT;
}
public boolean isUrlThermostat() {
return NullUtils.makeNotNull(thermostatSource).startsWith("http");
public boolean isThermometerUrlValid() {
return NullUtils.makeNotNull(thermometerUrl).startsWith("http");
}
public boolean isZWaveThermostat() {
return NullUtils.isEqual(thermostatSource, "ZWAVE");
return type == SwitchType.THERMOSTAT;
}
public boolean isRelay() {
return type == SwitchType.RELAY;
}
public boolean isControlledBy(String _controllerUrl) {
return NullUtils.isEqual(_controllerUrl, controllerUrl);
}
public ThermostatMode getThermostatMode() {
@ -136,6 +174,14 @@ public class Switch {
lowLevel = _lowLevel;
}
public boolean isHidden() {
return hidden;
}
public void setHidden(boolean _hidden) {
hidden = _hidden;
}
public List<SwitchSchedule> getSchedule() {
return schedule;
}
@ -143,4 +189,40 @@ public class Switch {
public void setSchedule(List<SwitchSchedule> _schedule) {
schedule = _schedule;
}
@Override
public boolean equals(Object _o) {
if (this == _o) return true;
if (_o == null || getClass() != _o.getClass()) return false;
Switch aSwitch = (Switch) _o;
return nodeId == aSwitch.nodeId;
}
@Override
public int hashCode() {
return Objects.hash(nodeId);
}
public boolean isModified(Switch _switch) {
return (_switch == null) || (level != _switch.getLevel()) || (hold != _switch.isHold()) || (thermostatMode != _switch.getThermostatMode());
}
public Switch duplicate() {
Switch s = new Switch();
s.setType(getType());
s.setRoom(getRoom());
s.setName(getName());
s.setNodeId(getNodeId());
s.setLevel(getLevel());
s.setGpioPin(getGpioPin());
s.setPrimary(isPrimary());
s.setHold(isHold());
s.setHidden(isHidden());
s.setThermometerUrl(getThermometerUrl());
s.setControllerUrl(getControllerUrl());
s.setThermostatMode(getThermostatMode());
s.setLowLevel(getLowLevel());
s.setSchedule(CollectionUtils.transform(getSchedule(), SwitchSchedule::duplicate));
return s;
}
}

View File

@ -7,20 +7,24 @@ import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Objects;
import java.util.TimeZone;
@DBSerializable
public class SwitchSchedule {
private int dayOfWeek;
private int timeOfDay;
private int minutesPerHour;
private int timeOfDayEnd;
private int onDuration;
private int offDuration;
private int level;
public SwitchSchedule() {
}
public SwitchSchedule(int _minutesPerHour) {
minutesPerHour = _minutesPerHour;
public SwitchSchedule(int _onDuration, int _offDuration) {
onDuration = _onDuration;
offDuration = _offDuration;
}
public SwitchSchedule(int _dayOfWeek, int _timeOfDay, int _level) {
@ -64,12 +68,34 @@ public class SwitchSchedule {
timeOfDay = (_hour * 3600) + (_minute * 60) + _second;
}
public int getMinutesPerHour() {
return minutesPerHour;
public int getTimeOfDayEnd() {
return timeOfDayEnd;
}
public void setMinutesPerHour(int _minutesPerHour) {
minutesPerHour = _minutesPerHour;
public void setTimeOfDayEnd(int _timeOfDayEnd) {
timeOfDayEnd = _timeOfDayEnd;
}
public void setTimeOfDayEnd(int _hour, int _minute) {
timeOfDayEnd = (_hour * 3600) + (_minute * 60);
}
public void setTimeOfDayEnd(int _hour, int _minute, int _second) {
timeOfDayEnd = (_hour * 3600) + (_minute * 60) + _second;
}
public int getOnDuration() {
return onDuration;
}
public void setOnDuration(int _onDuration) {
onDuration = _onDuration;
}
public int getOffDuration() {
return offDuration;
}
public void setOffDuration(int _offDuration) {
offDuration = _offDuration;
}
public int getLevel() {
@ -81,38 +107,88 @@ public class SwitchSchedule {
}
public int hour() {
return timeOfDay/3600;
return hour(timeOfDay);
}
public int minute() {
return (timeOfDay/60)%60;
return minute(timeOfDay);
}
public int second() {
return timeOfDay%60;
return second(timeOfDay);
}
private boolean isOn() {
return GregorianCalendar.getInstance().get(Calendar.MINUTE) < minutesPerHour;
public int hour(int _timeOfDay) {
return _timeOfDay/3600;
}
public int minute(int _timeOfDay) {
return (_timeOfDay/60)%60;
}
public int second(int _timeOfDay) {
return _timeOfDay%60;
}
public Date startToday(TimeZone _tz) {
return today(timeOfDay, _tz);
}
public Date endToday(TimeZone _tz) {
return today(timeOfDayEnd, _tz);
}
public Date today(int _timeOfDay, TimeZone _tz) {
Date now = new Date();
Calendar cal = DateUtils.toCalendar(now, _tz);
if (dayOfWeek > 0)
cal.set(Calendar.DAY_OF_WEEK, dayOfWeek);
cal.set(Calendar.HOUR_OF_DAY, hour(_timeOfDay));
cal.set(Calendar.MINUTE, minute(_timeOfDay));
cal.set(Calendar.SECOND, second(_timeOfDay));
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
public SwitchTransition getNextTransition(Switch _switch, TimeZone _tz) {
if (minutesPerHour > 0) {
Date dt = DateUtils.getStartOfHour(_tz);
Date transition = DateUtils.addMinutes(dt, minutesPerHour);
if (new Date().before(transition))
return new SwitchTransition(_switch, transition, 0);
return new SwitchTransition(_switch, DateUtils.getEndOfHour(_tz), level == 0?255:level);
}
Date startToday = startToday(_tz);
Date now = new Date();
Calendar cal = DateUtils.toCalendar(now, _tz);
cal.set(Calendar.DAY_OF_WEEK, dayOfWeek);
cal.set(Calendar.HOUR_OF_DAY, hour());
cal.set(Calendar.MINUTE, minute());
cal.set(Calendar.SECOND, second());
cal.set(Calendar.MILLISECOND, 0);
if (cal.getTimeInMillis() <= now.getTime())
cal.add(Calendar.DAY_OF_MONTH, 7);
return new SwitchTransition(_switch, cal.getTime(), level);
if (onDuration > 0) {
if ((timeOfDay > 0) && now.before(startToday))
now = startToday;
if (timeOfDayEnd > 0) {
if (now.after(endToday(_tz)))
now = DateUtils.addDays(startToday, (dayOfWeek > 0)?7:1, _tz);
}
long progress = now.getTime()%((onDuration+offDuration)*1000L);
if (progress < onDuration*1000L)
return new SwitchTransition(_switch, new Date(now.getTime() + (onDuration*1000L)-progress), 0);
return new SwitchTransition(_switch, new Date(now.getTime()+((onDuration+offDuration)*1000L)-progress), level == 0 ? 255 : level);
}
return new SwitchTransition(_switch, startToday.after(now)?startToday:DateUtils.addDays(startToday, (dayOfWeek > 0)?7:1, _tz), level);
}
@Override
public boolean equals(Object _o) {
if (this == _o) return true;
if (_o == null || getClass() != _o.getClass()) return false;
SwitchSchedule that = (SwitchSchedule) _o;
return dayOfWeek == that.dayOfWeek && timeOfDay == that.timeOfDay && timeOfDayEnd == that.timeOfDayEnd && onDuration == that.onDuration && offDuration == that.offDuration && level == that.level;
}
@Override
public int hashCode() {
return Objects.hash(dayOfWeek, timeOfDay, timeOfDayEnd, onDuration, offDuration, level);
}
public SwitchSchedule duplicate() {
SwitchSchedule s = new SwitchSchedule();
s.setDayOfWeek(getDayOfWeek());
s.setTimeOfDay(getTimeOfDay());
s.setTimeOfDayEnd(getTimeOfDayEnd());
s.setOnDuration(getOnDuration());
s.setOffDuration(getOffDuration());
s.setLevel(getLevel());
return s;
}
}

View File

@ -0,0 +1,11 @@
package com.lanternsoftware.datamodel.zwave;
public enum SwitchType {
BINARY,
DIMMER,
THERMOSTAT,
SPACE_HEATER_THERMOSTAT,
THERMOMETER,
RELAY,
SECURITY
}

View File

@ -1,5 +1,7 @@
package com.lanternsoftware.datamodel.zwave;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import com.lanternsoftware.util.dao.annotations.PrimaryKey;
@ -7,8 +9,10 @@ import java.util.List;
@DBSerializable(autogen = false)
public class ZWaveConfig {
@PrimaryKey
private int accountId;
@PrimaryKey private int accountId;
private String commPort;
private String url;
private String masterUrl;
private List<Switch> switches;
public int getAccountId() {
@ -19,6 +23,30 @@ public class ZWaveConfig {
accountId = _accountId;
}
public String getCommPort() {
return commPort;
}
public void setCommPort(String _commPort) {
commPort = _commPort;
}
public String getUrl() {
return url;
}
public void setUrl(String _url) {
url = _url;
}
public String getMasterUrl() {
return masterUrl;
}
public void setMasterUrl(String _masterUrl) {
masterUrl = _masterUrl;
}
public List<Switch> getSwitches() {
return switches;
}
@ -26,4 +54,16 @@ public class ZWaveConfig {
public void setSwitches(List<Switch> _switches) {
switches = _switches;
}
public boolean isMaster() {
return NullUtils.isEqual(url, masterUrl);
}
public List<Switch> getSwitchesForThisController() {
return CollectionUtils.filter(switches, this::isMySwitch);
}
public boolean isMySwitch(Switch _sw) {
return (isMaster() && NullUtils.isEmpty(_sw.getControllerUrl())) || _sw.isControlledBy(getUrl());
}
}

View File

@ -5,7 +5,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;
@ -28,7 +27,9 @@ public class SwitchScheduleSerializer extends AbstractDaoSerializer<SwitchSchedu
DaoEntity d = new DaoEntity();
d.put("day_of_week", _o.getDayOfWeek());
d.put("time_of_day", _o.getTimeOfDay());
d.put("minutes_per_hour", _o.getMinutesPerHour());
d.put("time_of_day_end", _o.getTimeOfDayEnd());
d.put("on_duration", _o.getOnDuration());
d.put("off_duration", _o.getOffDuration());
d.put("level", _o.getLevel());
return d;
}
@ -39,7 +40,9 @@ public class SwitchScheduleSerializer extends AbstractDaoSerializer<SwitchSchedu
SwitchSchedule o = new SwitchSchedule();
o.setDayOfWeek(DaoSerializer.getInteger(_d, "day_of_week"));
o.setTimeOfDay(DaoSerializer.getInteger(_d, "time_of_day"));
o.setMinutesPerHour(DaoSerializer.getInteger(_d, "minutes_per_hour"));
o.setTimeOfDayEnd(DaoSerializer.getInteger(_d, "time_of_day_end"));
o.setOnDuration(DaoSerializer.getInteger(_d, "on_duration"));
o.setOffDuration(DaoSerializer.getInteger(_d, "off_duration"));
o.setLevel(DaoSerializer.getInteger(_d, "level"));
return o;
}

View File

@ -2,12 +2,12 @@ package com.lanternsoftware.datamodel.zwave.dao;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.SwitchType;
import com.lanternsoftware.datamodel.zwave.ThermostatMode;
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;
@ -28,16 +28,19 @@ public class SwitchSerializer extends AbstractDaoSerializer<Switch>
public DaoEntity toDaoEntity(Switch _o)
{
DaoEntity d = new DaoEntity();
d.put("type", DaoSerializer.toEnumName(_o.getType()));
d.put("room", _o.getRoom());
d.put("name", _o.getName());
d.put("node_id", _o.getNodeId());
d.put("level", _o.getLevel());
d.put("gpio_pin", _o.getGpioPin());
d.put("primary", _o.isPrimary());
d.put("multilevel", _o.isMultilevel());
d.put("hold", _o.isHold());
d.put("thermostat_source", _o.getThermostatSource());
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;
}
@ -46,16 +49,19 @@ public class SwitchSerializer extends AbstractDaoSerializer<Switch>
public Switch fromDaoEntity(DaoEntity _d)
{
Switch o = new Switch();
o.setType(DaoSerializer.getEnum(_d, "type", SwitchType.class));
o.setRoom(DaoSerializer.getString(_d, "room"));
o.setName(DaoSerializer.getString(_d, "name"));
o.setNodeId(DaoSerializer.getInteger(_d, "node_id"));
o.setLevel(DaoSerializer.getInteger(_d, "level"));
o.setGpioPin(DaoSerializer.getInteger(_d, "gpio_pin"));
o.setPrimary(DaoSerializer.getBoolean(_d, "primary"));
o.setMultilevel(DaoSerializer.getBoolean(_d, "multilevel"));
o.setHold(DaoSerializer.getBoolean(_d, "hold"));
o.setThermostatSource(DaoSerializer.getString(_d, "thermostat_source"));
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

@ -28,6 +28,9 @@ public class ZWaveConfigSerializer extends AbstractDaoSerializer<ZWaveConfig>
{
DaoEntity d = new DaoEntity();
d.put("_id", String.valueOf(_o.getAccountId()));
d.put("comm_port", _o.getCommPort());
d.put("url", _o.getUrl());
d.put("master_url", _o.getMasterUrl());
d.put("switches", DaoSerializer.toDaoEntities(_o.getSwitches(), DaoProxyType.MONGO));
return d;
}
@ -37,6 +40,9 @@ public class ZWaveConfigSerializer extends AbstractDaoSerializer<ZWaveConfig>
{
ZWaveConfig o = new ZWaveConfig();
o.setAccountId(DaoSerializer.getInteger(_d, "_id"));
o.setCommPort(DaoSerializer.getString(_d, "comm_port"));
o.setUrl(DaoSerializer.getString(_d, "url"));
o.setMasterUrl(DaoSerializer.getString(_d, "master_url"));
o.setSwitches(DaoSerializer.getList(_d, "switches", Switch.class));
return o;
}

View File

@ -20,9 +20,14 @@
</dependency>
<dependency>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-dataaccess-currentmonitor</artifactId>
<artifactId>lantern-datamodel-currentmonitor</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.pi4j</groupId>
<artifactId>pi4j-gpio-extension</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.zwave</groupId>
<artifactId>lantern-zwave</artifactId>

View File

@ -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")));
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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");
}*/
}

View File

@ -12,6 +12,7 @@ public class MongoZWaveDao implements ZWaveDao {
proxy = new MongoProxy(_config);
}
@Override
public void shutdown() {
proxy.shutdown();
}

View File

@ -5,4 +5,5 @@ import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
public interface ZWaveDao {
void putConfig(ZWaveConfig _config);
ZWaveConfig getConfig(int _accountId);
void shutdown();
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,5 @@
package com.lanternsoftware.zwave.security;
public interface SecurityListener {
void onStateChanged(int nodeId, boolean _open);
}

View File

@ -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;
}

View File

@ -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));
}
}
}

View File

@ -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()), "/");

View File

@ -15,7 +15,7 @@
<dependency>
<groupId>com.neuronrobotics</groupId>
<artifactId>nrjavaserial</artifactId>
<version>3.15.0</version>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@ -60,6 +60,46 @@
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>lantern-zwave</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>com.lanternsoftware.zwave.PortEnum</Main-Class>
<Specification-Title>Lantern ZWave</Specification-Title>
<Specification-Version>${project.version}</Specification-Version>
<Specification-Vendor>Lantern Software, Inc.</Specification-Vendor>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,17 @@
package com.lanternsoftware.zwave;
import gnu.io.CommPortIdentifier;
import java.util.Enumeration;
public class PortEnum {
public static void main(String[] args) {
Enumeration<CommPortIdentifier> e = CommPortIdentifier.getPortIdentifiers();
while (e.hasMoreElements()) {
CommPortIdentifier id = e.nextElement();
if (id != null) {
System.out.println(id.getName());
}
}
}
}

View File

@ -70,7 +70,7 @@ public enum CommandClass {
TIME_PARAMETERS((byte)0x8B, "TIME_PARAMETERS"),
GEOGRAPHIC_LOCATION((byte)0x8C, "GEOGRAPHIC_LOCATION"),
COMPOSITE((byte)0x8D, "COMPOSITE"),
MULTI_INSTANCE_ASSOCIATION((byte)0x8E, "MULTI_INSTANCE_ASSOCIATION"),
MULTI_CHANNEL_ASSOCIATION((byte)0x8E, "MULTI_CHANNEL_ASSOCIATION"),
MULTI_CMD((byte)0x8F, "MULTI_CMD"),
ENERGY_PRODUCTION((byte)0x90, "ENERGY_PRODUCTION"),
MANUFACTURER_PROPRIETARY((byte)0x91, "MANUFACTURER_PROPRIETARY"),

View File

@ -0,0 +1,20 @@
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 AssociationGetRequest extends RequestMessage {
public AssociationGetRequest() {
this((byte)0);
}
public AssociationGetRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.SendData, CommandClass.ASSOCIATION, (byte)0x02);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@ -0,0 +1,56 @@
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 AssociationReportRequest extends RequestMessage {
private byte groupIdx;
private byte maxAssociations;
private byte numReportsToFollow;
public AssociationReportRequest() {
this((byte) 0);
}
public AssociationReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.ASSOCIATION, (byte) 0x03);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
groupIdx = _payload[8];
maxAssociations = _payload[9];
numReportsToFollow = _payload[10];
}
public byte getGroupIdx() {
return groupIdx;
}
public void setGroupIdx(byte _groupIdx) {
groupIdx = _groupIdx;
}
public byte getMaxAssociations() {
return maxAssociations;
}
public void setMaxAssociations(byte _maxAssociations) {
maxAssociations = _maxAssociations;
}
public byte getNumReportsToFollow() {
return numReportsToFollow;
}
public void setNumReportsToFollow(byte _numReportsToFollow) {
numReportsToFollow = _numReportsToFollow;
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@ -0,0 +1,45 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class AssociationSetRequest extends SendDataRequestMessage {
private byte groupIdx;
private byte targetNodeId;
public AssociationSetRequest() {
this((byte)0, (byte)0, (byte)0);
}
public AssociationSetRequest(byte _nodeId, byte _groupIdx, byte _targetNodeId) {
super(_nodeId, CommandClass.ASSOCIATION, (byte) 0x01);
groupIdx = _groupIdx;
targetNodeId = _targetNodeId;
}
public byte getGroupIdx() {
return groupIdx;
}
public void setGroupIdx(byte _groupIdx) {
groupIdx = _groupIdx;
}
public byte getTargetNodeId() {
return targetNodeId;
}
public void setTargetNodeId(byte _targetNodeId) {
targetNodeId = _targetNodeId;
}
@Override
public byte[] getPayload() {
return asByteArray(groupIdx, targetNodeId);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " groupIdx: " + groupIdx + " targetNodeIdx: " + targetNodeId;
}
}

View File

@ -1,4 +1,6 @@
com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest
com.lanternsoftware.zwave.message.impl.AssociationGetRequest
com.lanternsoftware.zwave.message.impl.AssociationReportRequest
com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest
com.lanternsoftware.zwave.message.impl.BinarySwitchReportRequest
com.lanternsoftware.zwave.message.impl.ByteMessage