Initial Commit

This commit is contained in:
Mark Milligan
2021-01-14 16:28:24 -06:00
parent 21c28201c5
commit 1334c110ff
318 changed files with 24160 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
<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>
<artifactId>lantern-datamodel-zwave</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>lantern-datamodel-zwave</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<index>true</index>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,146 @@
package com.lanternsoftware.datamodel.zwave;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.List;
@DBSerializable
public class Switch {
private String room;
private String name;
private int nodeId;
private int level;
private boolean primary;
private boolean multilevel;
private boolean hold;
private String thermostatSource;
private ThermostatMode thermostatMode;
private int lowLevel;
private List<SwitchSchedule> schedule;
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, int _level, boolean _primary, boolean _multilevel, boolean _hold, String _thermostatSource, int _lowLevel, List<SwitchSchedule> _schedule) {
room = _room;
name = _name;
nodeId = _nodeId;
level = _level;
primary = _primary;
multilevel = _multilevel;
hold = _hold;
thermostatSource = _thermostatSource;
lowLevel = _lowLevel;
schedule = _schedule;
}
public String getRoom() {
return room;
}
public void setRoom(String _room) {
room = _room;
}
public String getName() {
return name;
}
public void setName(String _name) {
name = _name;
}
public int getNodeId() {
return nodeId;
}
public void setNodeId(int _nodeId) {
nodeId = _nodeId;
}
public int getLevel() {
return level;
}
public void setLevel(int _level) {
level = _level;
}
public boolean isPrimary() {
return primary;
}
public void setPrimary(boolean _primary) {
primary = _primary;
}
public boolean isMultilevel() {
return multilevel;
}
public void setMultilevel(boolean _multilevel) {
multilevel = _multilevel;
}
public boolean isHold() {
return hold;
}
public void setHold(boolean _hold) {
hold = _hold;
}
public String getThermostatSource() {
return thermostatSource;
}
public void setThermostatSource(String _thermostatSource) {
thermostatSource = _thermostatSource;
}
public boolean isThermostat() {
return NullUtils.isNotEmpty(thermostatSource) && (nodeId < 100);
}
public boolean isThermometer() {
return isUrlThermostat() && (nodeId > 99);
}
public boolean isUrlThermostat() {
return NullUtils.makeNotNull(thermostatSource).startsWith("http");
}
public boolean isZWaveThermostat() {
return NullUtils.isEqual(thermostatSource, "ZWAVE");
}
public ThermostatMode getThermostatMode() {
return thermostatMode;
}
public void setThermostatMode(ThermostatMode _thermostatMode) {
thermostatMode = _thermostatMode;
}
public int getLowLevel() {
return lowLevel;
}
public void setLowLevel(int _lowLevel) {
lowLevel = _lowLevel;
}
public List<SwitchSchedule> getSchedule() {
return schedule;
}
public void setSchedule(List<SwitchSchedule> _schedule) {
schedule = _schedule;
}
}

View File

@@ -0,0 +1,118 @@
package com.lanternsoftware.datamodel.zwave;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
@DBSerializable
public class SwitchSchedule {
private int dayOfWeek;
private int timeOfDay;
private int minutesPerHour;
private int level;
public SwitchSchedule() {
}
public SwitchSchedule(int _minutesPerHour) {
minutesPerHour = _minutesPerHour;
}
public SwitchSchedule(int _dayOfWeek, int _timeOfDay, int _level) {
dayOfWeek = _dayOfWeek;
timeOfDay = _timeOfDay;
level = _level;
}
public SwitchSchedule(int _dayOfWeek, int _hour, int _minute, int _level) {
dayOfWeek = _dayOfWeek;
setTimeOfDay(_hour, _minute);
level = _level;
}
public SwitchSchedule(int _dayOfWeek, int _hour, int _minute, int _second, int _level) {
dayOfWeek = _dayOfWeek;
setTimeOfDay(_hour, _minute, _second);
level = _level;
}
public int getDayOfWeek() {
return dayOfWeek;
}
public void setDayOfWeek(int _dayOfWeek) {
dayOfWeek = _dayOfWeek;
}
public int getTimeOfDay() {
return timeOfDay;
}
public void setTimeOfDay(int _timeOfDayInSeconds) {
timeOfDay = _timeOfDayInSeconds;
}
public void setTimeOfDay(int _hour, int _minute) {
timeOfDay = (_hour * 3600) + (_minute * 60);
}
public void setTimeOfDay(int _hour, int _minute, int _second) {
timeOfDay = (_hour * 3600) + (_minute * 60) + _second;
}
public int getMinutesPerHour() {
return minutesPerHour;
}
public void setMinutesPerHour(int _minutesPerHour) {
minutesPerHour = _minutesPerHour;
}
public int getLevel() {
return level;
}
public void setLevel(int _level) {
level = _level;
}
public int hour() {
return timeOfDay/3600;
}
public int minute() {
return (timeOfDay/60)%60;
}
public int second() {
return timeOfDay%60;
}
private boolean isOn() {
return GregorianCalendar.getInstance().get(Calendar.MINUTE) < minutesPerHour;
}
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 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);
}
}

View File

@@ -0,0 +1,27 @@
package com.lanternsoftware.datamodel.zwave;
import java.util.Date;
public class SwitchTransition {
private final Switch sw;
private final Date transitionTime;
private final int level;
SwitchTransition(Switch _sw, Date _transitionTime, int _level) {
sw = _sw;
transitionTime = _transitionTime;
level = _level;
}
public Switch getSwitch() {
return sw;
}
public Date getTransitionTime() {
return transitionTime;
}
public int getLevel() {
return level;
}
}

View File

@@ -0,0 +1,24 @@
package com.lanternsoftware.datamodel.zwave;
public enum ThermostatMode {
OFF((byte)0, "Off"),
HEAT((byte)1, "Heat"),
COOL((byte)2, "Cool"),
AUXILIARY((byte)4, "E-Heat");
public final byte data;
public final String display;
ThermostatMode(byte _data, String _display) {
data = _data;
display = _display;
}
public static ThermostatMode fromByte(byte _bt) {
for (ThermostatMode mode : values()) {
if (mode.data == _bt)
return mode;
}
return null;
}
}

View File

@@ -0,0 +1,29 @@
package com.lanternsoftware.datamodel.zwave;
import com.lanternsoftware.util.dao.annotations.DBSerializable;
import com.lanternsoftware.util.dao.annotations.PrimaryKey;
import java.util.List;
@DBSerializable(autogen = false)
public class ZWaveConfig {
@PrimaryKey
private int accountId;
private List<Switch> switches;
public int getAccountId() {
return accountId;
}
public void setAccountId(int _accountId) {
accountId = _accountId;
}
public List<Switch> getSwitches() {
return switches;
}
public void setSwitches(List<Switch> _switches) {
switches = _switches;
}
}

View File

@@ -0,0 +1,46 @@
package com.lanternsoftware.datamodel.zwave.dao;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class SwitchScheduleSerializer extends AbstractDaoSerializer<SwitchSchedule>
{
@Override
public Class<SwitchSchedule> getSupportedClass()
{
return SwitchSchedule.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(SwitchSchedule _o)
{
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("level", _o.getLevel());
return d;
}
@Override
public SwitchSchedule fromDaoEntity(DaoEntity _d)
{
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.setLevel(DaoSerializer.getInteger(_d, "level"));
return o;
}
}

View File

@@ -0,0 +1,62 @@
package com.lanternsoftware.datamodel.zwave.dao;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
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;
public class SwitchSerializer extends AbstractDaoSerializer<Switch>
{
@Override
public Class<Switch> getSupportedClass()
{
return Switch.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(Switch _o)
{
DaoEntity d = new DaoEntity();
d.put("room", _o.getRoom());
d.put("name", _o.getName());
d.put("node_id", _o.getNodeId());
d.put("level", _o.getLevel());
d.put("primary", _o.isPrimary());
d.put("multilevel", _o.isMultilevel());
d.put("hold", _o.isHold());
d.put("thermostat_source", _o.getThermostatSource());
d.put("thermostat_mode", DaoSerializer.toEnumName(_o.getThermostatMode()));
d.put("low_level", _o.getLowLevel());
d.put("schedule", DaoSerializer.toDaoEntities(_o.getSchedule(), DaoProxyType.MONGO));
return d;
}
@Override
public Switch fromDaoEntity(DaoEntity _d)
{
Switch o = new Switch();
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.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.setThermostatMode(DaoSerializer.getEnum(_d, "thermostat_mode", ThermostatMode.class));
o.setLowLevel(DaoSerializer.getInteger(_d, "low_level"));
o.setSchedule(DaoSerializer.getList(_d, "schedule", SwitchSchedule.class));
return o;
}
}

View File

@@ -0,0 +1,43 @@
package com.lanternsoftware.datamodel.zwave.dao;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
import com.lanternsoftware.util.dao.AbstractDaoSerializer;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoProxyType;
import com.lanternsoftware.util.dao.DaoSerializer;
import java.util.Collections;
import java.util.List;
public class ZWaveConfigSerializer extends AbstractDaoSerializer<ZWaveConfig>
{
@Override
public Class<ZWaveConfig> getSupportedClass()
{
return ZWaveConfig.class;
}
@Override
public List<DaoProxyType> getSupportedProxies() {
return Collections.singletonList(DaoProxyType.MONGO);
}
@Override
public DaoEntity toDaoEntity(ZWaveConfig _o)
{
DaoEntity d = new DaoEntity();
d.put("_id", String.valueOf(_o.getAccountId()));
d.put("switches", DaoSerializer.toDaoEntities(_o.getSwitches(), DaoProxyType.MONGO));
return d;
}
@Override
public ZWaveConfig fromDaoEntity(DaoEntity _d)
{
ZWaveConfig o = new ZWaveConfig();
o.setAccountId(DaoSerializer.getInteger(_d, "_id"));
o.setSwitches(DaoSerializer.getList(_d, "switches", Switch.class));
return o;
}
}

View File

@@ -0,0 +1,3 @@
com.lanternsoftware.datamodel.zwave.dao.SwitchScheduleSerializer
com.lanternsoftware.datamodel.zwave.dao.SwitchSerializer
com.lanternsoftware.datamodel.zwave.dao.ZWaveConfigSerializer

View File

@@ -0,0 +1,79 @@
<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.thermometer</groupId>
<artifactId>lantern-service-thermometer</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<name>lantern-service-thermometer</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-common</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.hid4java</groupId>
<artifactId>hid4java</artifactId>
<version>0.5.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,16 @@
package com.lanternsoftware.thermometer;
import com.lanternsoftware.thermometer.context.ThermometerApp;
public class TestThermo {
public static void main(String[] args) {
ThermometerApp app = new ThermometerApp();
app.start();
try {
Thread.sleep(20000);
} catch (InterruptedException _e) {
_e.printStackTrace();
}
app.stop();
}
}

View File

@@ -0,0 +1,22 @@
package com.lanternsoftware.thermometer.context;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class Globals implements ServletContextListener {
public static ThermometerApp app;
@Override
public void contextInitialized(ServletContextEvent sce) {
app = new ThermometerApp();
app.start();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (app != null) {
app.stop();
app = null;
}
}
}

View File

@@ -0,0 +1,131 @@
package com.lanternsoftware.thermometer.context;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import org.hid4java.HidDevice;
import org.hid4java.HidManager;
import org.hid4java.HidServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
public class ThermometerApp {
private static final Logger LOG = LoggerFactory.getLogger(ThermometerApp.class);
private HidDevice device;
private final Timer timer = new Timer();
private double lastTemp;
public void start() {
HidServices hs = HidManager.getHidServices();
for (HidDevice d : hs.getAttachedHidDevices()) {
if (NullUtils.isEqual(d.getVendorId(), (short) 0x413d) && NullUtils.isEqual(d.getProductId(), (short) 0x2107)) {
if (d.getInterfaceNumber() == 1)
device = d;
}
}
if ((device != null) && device.open()) {
synchronized (device) {
read(hexToByte("0182770100000000"));
read(hexToByte("0186ff0100000000"));
read(hexToByte("0182770100000000"));
read(hexToByte("0182770100000000"));
}
} else {
LOG.error("Failed to open HID Device");
return;
}
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
lastTemp = readTemperature();
}
}, 0L, 10000L);
}
public void stop() {
timer.cancel();
ConcurrencyUtils.sleep(10000);
if (device != null) {
device.close();
device = null;
}
HidServices hs = HidManager.getHidServices();
hs.stop();
hs.shutdown();
}
private byte[] read(byte[] _request) {
int RETRIES = 8;
int stat = -1;
int attempts = 0;
while ((stat <= 0) && (attempts < RETRIES)) {
attempts++;
try {
stat = device.write(_request, _request.length*8, (byte) 0);
}
catch (Exception _e) {
LOG.error("Exception while writing", _e);
}
if (stat <= 0) {
if (attempts == RETRIES) {
LOG.error("Failed max number of retires, returning null");
return null;
}
LOG.error("Write attempt " + attempts + " failed, waiting 250ms to retry");
ConcurrencyUtils.sleep(250);
}
}
byte[] response = new byte[32];
stat = -1;
attempts = 0;
while ((stat <= 0) && (attempts < RETRIES)) {
attempts++;
try {
stat = device.read(response, 500);
}
catch (Exception _e) {
LOG.error("Exception while reading", _e);
}
if (stat <= 0) {
if (attempts == RETRIES) {
LOG.error("Failed max number of retires, returning null");
return null;
}
LOG.error("Read attempt " + attempts + " failed, waiting 250ms to retry");
ConcurrencyUtils.sleep(250);
}
}
return response;
}
public double getTemperature() {
return lastTemp;
}
public double readTemperature() {
if (device != null) {
synchronized (device) {
byte[] response = read(hexToByte("0180330100000000"));
if (response == null)
return 5.0;
int rawReading = ((response[3] & 0xFF) + (response[2] << 8));
if (rawReading == 0)
return 5.0;
return rawReading / 100.0;
}
}
return 5.0;
}
private static byte[] hexToByte(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}

View File

@@ -0,0 +1,18 @@
package com.lanternsoftware.thermometer.servlet;
import com.lanternsoftware.thermometer.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("/temp")
public class TempServlet extends ThermoServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
setResponseEntity(resp, "application/json", "{\"temp\": "+ Globals.app.getTemperature() + "}");
}
}

View File

@@ -0,0 +1,45 @@
package com.lanternsoftware.thermometer.servlet;
import com.lanternsoftware.util.NullUtils;
import org.apache.commons.io.IOUtils;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
public abstract class ThermoServlet extends HttpServlet {
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) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(os);
}
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/opt/currentmonitor/log/log.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/opt/currentmonitor/log/log.%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<maxFileSize>20MB</maxFileSize>
<maxHistory>20</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<logger name="com.lanternsoftware" level="INFO"/>
<root level="OFF">
<appender-ref ref="FILE"/>
</root>
</configuration>

View File

@@ -0,0 +1,9 @@
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<listener>
<listener-class>com.lanternsoftware.thermometer.context.Globals</listener-class>
</listener>
</web-app>

View File

@@ -0,0 +1,16 @@
package com.lanternsoftware.thermometer;
import com.lanternsoftware.thermometer.context.ThermometerApp;
public class TestStartup {
public static void main(String[] args) {
ThermometerApp app = new ThermometerApp();
app.start();
try {
Thread.sleep(20000);
} catch (InterruptedException _e) {
_e.printStackTrace();
}
app.stop();
}
}

View File

@@ -0,0 +1,94 @@
<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.zwave</groupId>
<artifactId>lantern-service-zwave</artifactId>
<packaging>war</packaging>
<version>1.0.0</version>
<name>lantern-service-zwave</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.services</groupId>
<artifactId>lantern-datamodel-zwave</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.currentmonitor</groupId>
<artifactId>lantern-dataaccess-currentmonitor</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.zwave</groupId>
<artifactId>lantern-zwave</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao-mongo</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-http</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,30 @@
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();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
if (app != null) {
app.stop();
app = null;
}
if (cmDao != null)
cmDao.shutdown();
}
}

View File

@@ -0,0 +1,334 @@
package com.lanternsoftware.zwave.context;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.SwitchTransition;
import com.lanternsoftware.datamodel.zwave.ThermostatMode;
import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.DateUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.http.HttpPool;
import com.lanternsoftware.zwave.controller.Controller;
import com.lanternsoftware.zwave.dao.MongoZWaveDao;
import com.lanternsoftware.zwave.message.IMessageSubscriber;
import com.lanternsoftware.zwave.message.MessageEngine;
import com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest;
import com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest;
import com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest;
import com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest;
import com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest;
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 org.apache.http.client.methods.HttpGet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
public class ZWaveApp {
private static final Logger logger = LoggerFactory.getLogger(ZWaveApp.class);
private MongoZWaveDao dao;
private ZWaveConfig config;
private Controller controller;
private final Map<Integer, Switch> switches = new HashMap<>();
private final Map<Integer, List<Integer>> peers = new HashMap<>();
private Timer timer;
private HttpPool pool;
private SwitchScheduleTask nextScheduleTask;
private final Map<Integer, Double> temperatures = 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");
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}));
// 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 = dao.getConfig(1);
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(), Switch::isUrlThermostat) != 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();
MessageEngine.subscribe(new IMessageSubscriber<MultilevelSensorReportRequest>() {
@Override
public Class<MultilevelSensorReportRequest> getHandledMessageClass() {
return MultilevelSensorReportRequest.class;
}
@Override
public void onMessage(MultilevelSensorReportRequest _message) {
synchronized (temperatures) {
temperatures.put((int) _message.getNodeId(), _message.getTemperatureCelsius());
temperatures.notify();
}
}
});
MessageEngine.subscribe(new IMessageSubscriber<ThermostatSetPointReportRequest>() {
@Override
public Class<ThermostatSetPointReportRequest> getHandledMessageClass() {
return ThermostatSetPointReportRequest.class;
}
@Override
public void onMessage(ThermostatSetPointReportRequest _message) {
synchronized (switches) {
Switch sw = switches.get((int) _message.getNodeId());
if (sw != null) {
if (NullUtils.isOneOf(_message.getIndex(), ThermostatSetPointIndex.HEATING, ThermostatSetPointIndex.COOLING)) {
sw.setLevel((int) Math.round(_message.getTemperatureCelsius() * 1.8) + 32);
persistConfig();
}
}
}
}
});
MessageEngine.subscribe(new IMessageSubscriber<MultilevelSwitchReportRequest>() {
@Override
public Class<MultilevelSwitchReportRequest> getHandledMessageClass() {
return MultilevelSwitchReportRequest.class;
}
@Override
public void onMessage(MultilevelSwitchReportRequest _message) {
synchronized (switches) {
Switch sw = switches.get((int) _message.getNodeId());
if (sw != null) {
sw.setLevel(_message.getLevel());
for (Integer node : CollectionUtils.makeNotNull(peers.get((int) _message.getNodeId()))) {
sw = switches.get(node);
sw.setLevel(_message.getLevel());
logger.info("Mirror Event from node {} to node {}", _message.getNodeId(), node);
controller.send(new MultilevelSwitchSetRequest(node.byteValue(), _message.getLevel()));
}
persistConfig();
}
}
}
});
// controller.send(new MultilevelSensorGetRequest((byte)11));
// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.HEATING));
// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.COOLING));
// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.HEATING_ECON));
// controller.send(new ThermostatSetPointGetRequest((byte)11, ThermostatSetPointIndex.COOLING_ECON));
// controller.send(new ThermostatSetPointSupportedGetRequest((byte)11));
// controller.send(new ThermostatSetPointCapabilitiesGetRequest((byte)11));
// controller.send(new ThermostatSetPointSetRequest((byte)11, ThermostatSetPointIndex.HEATING_ECON, 72));
// controller.send(new ThermostatModeSetRequest((byte)11, ThermostatMode.HEAT));
// controller.send(new ThermostatModeGetRequest((byte)11));
}
private void scheduleNextTransition() {
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));
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()));
}
nextScheduleTask = new SwitchScheduleTask(nextTransitions);
timer.schedule(nextScheduleTask, CollectionUtils.getFirst(nextTransitions).getTransitionTime());
} 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 (!sw.isThermostat()) {
setGroupSwitchLevel(_nodeId, _level, sw.isMultilevel());
} 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();
}
}
public void setThermostatMode(int _nodeId, ThermostatMode _mode) {
Switch sw = switches.get(_nodeId);
if ((sw == null) || !sw.isPrimary() || !sw.isZWaveThermostat())
return;
controller.send(new ThermostatModeSetRequest((byte) sw.getNodeId(), com.lanternsoftware.zwave.message.thermostat.ThermostatMode.fromByte(_mode.data)));
sw.setThermostatMode(_mode);
persistConfig();
}
public void setSwitchSchedule(int _nodeId, List<SwitchSchedule> _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) {
dao.putConfig(config);
}
}
public int getSwitchLevel(int _nodeId) {
Switch sw = switches.get(_nodeId);
return (sw != null) ? sw.getLevel() : 0;
}
public ZWaveConfig getConfig() {
return config;
}
public void stop() {
controller.stop();
if (timer != null) {
timer.cancel();
timer = null;
}
if (pool != null) {
pool.shutdown();
pool = null;
}
if (dao != null) {
dao.shutdown();
dao = null;
}
}
private void setGroupSwitchLevel(int _primary, int _level, boolean _multilevel) {
List<Integer> nodes = CollectionUtils.asArrayList(_primary);
nodes.addAll(CollectionUtils.makeNotNull(peers.get(_primary)));
for (int node : nodes) {
controller.send(_multilevel ? new MultilevelSwitchSetRequest((byte) node, _level) : new BinarySwitchSetRequest((byte) node, _level > 0));
}
}
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.getNodeId(), 0, false);
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.getNodeId(), (byte) 0xf, false);
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());
}
}
}
}
private class SwitchScheduleTask extends TimerTask {
private final List<SwitchTransition> transitions;
SwitchScheduleTask(List<SwitchTransition> _transitions) {
transitions = _transitions;
}
@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());
}
else
logger.info("Skipping scheduled transition of node {} to level {}, switch is on hold", tr.getSwitch().getNodeId(), tr.getLevel());
ConcurrencyUtils.sleep(100);
}
nextScheduleTask = null;
Globals.app.scheduleNextTransition();
}
}
public double getTemperatureCelsius(int _nodeId) {
return getTemperatureCelsius(switches.get(_nodeId));
}
private double getTemperatureCelsius(Switch _sw) {
if ((pool == null) || (_sw == null) || !(_sw.isThermometer() || _sw.isThermostat()))
return 0.0;
if (_sw.isUrlThermostat())
return DaoSerializer.getDouble(DaoSerializer.parse(pool.executeToString(new HttpGet(_sw.getThermostatSource()))), "temp");
else if (_sw.isZWaveThermostat()) {
synchronized (ZWAVE_MUTEX) {
synchronized (temperatures) {
controller.send(new MultilevelSensorGetRequest((byte) _sw.getNodeId()));
try {
temperatures.wait(5000);
} catch (InterruptedException _e) {
_e.printStackTrace();
}
Double temp = temperatures.get(_sw.getNodeId());
return (temp == null) ? 0.0 : temp;
}
}
}
return 0.0;
}
}

View File

@@ -0,0 +1,230 @@
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

@@ -0,0 +1,29 @@
package com.lanternsoftware.zwave.dao;
import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
import com.lanternsoftware.util.dao.DaoQuery;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.util.dao.mongo.MongoProxy;
public class MongoZWaveDao implements ZWaveDao {
private MongoProxy proxy;
public MongoZWaveDao(MongoConfig _config) {
proxy = new MongoProxy(_config);
}
public void shutdown() {
proxy.shutdown();
}
@Override
public void putConfig(ZWaveConfig _config) {
proxy.save(_config);
}
@Override
public ZWaveConfig getConfig(int _accountId) {
return proxy.queryOne(ZWaveConfig.class, new DaoQuery("_id", String.valueOf(_accountId)));
}
}

View File

@@ -0,0 +1,8 @@
package com.lanternsoftware.zwave.dao;
import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
public interface ZWaveDao {
void putConfig(ZWaveConfig _config);
ZWaveConfig getConfig(int _accountId);
}

View File

@@ -0,0 +1,18 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.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 {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
jsonResponse(_rep, Globals.app.getConfig());
}
}

View File

@@ -0,0 +1,35 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.zwave.context.Globals;
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)) {
_rep.setStatus(401);
return;
}
get(authCode, _req, _rep);
}
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
}
@Override
protected void doPost(HttpServletRequest _req, HttpServletResponse _rep) {
AuthCode authCode = Globals.cmDao.decryptAuthCode(_req.getHeader("auth_code"));
if ((authCode == null) || (authCode.getAccountId() != 1)) {
_rep.setStatus(401);
return;
}
post(authCode, _req, _rep);
}
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
}
}

View File

@@ -0,0 +1,53 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.ThermostatMode;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.zwave.context.Globals;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@WebServlet("/switch/*")
public class SwitchServlet extends SecureServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);
int nodeId = NullUtils.toInteger(CollectionUtils.get(path, 0));
if (path.length == 1) {
setResponseEntity(_rep, "application/json", "{level:" + Globals.app.getSwitchLevel(nodeId) + "}");
} else {
if (nodeId > 0) {
String command = CollectionUtils.get(path, 1);
if ("hold".equals(command))
Globals.app.setSwitchHold(nodeId, true);
else if ("run".equals(command))
Globals.app.setSwitchHold(nodeId, false);
else if ("mode".equals(command))
Globals.app.setThermostatMode(nodeId, ThermostatMode.fromByte(Byte.parseByte(CollectionUtils.get(path, 2))));
else {
Globals.app.setSwitchLevel(nodeId, NullUtils.toInteger(command));
}
}
}
}
@Override
protected void post(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);
int nodeId = NullUtils.toInteger(CollectionUtils.get(path, 0));
if (path.length > 1) {
String command = CollectionUtils.get(path, 1);
if ("schedule".equals(command)) {
String json = getRequestPayloadAsString(_req);
List<SwitchSchedule> transitions = DaoSerializer.parseList(json, SwitchSchedule.class);
Globals.app.setSwitchSchedule(nodeId, transitions);
}
}
}
}

View File

@@ -0,0 +1,22 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.datamodel.currentmonitor.AuthCode;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoEntity;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.zwave.context.Globals;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/temperature/*")
public class TemperatureServlet extends SecureServlet {
@Override
protected void get(AuthCode _authCode, HttpServletRequest _req, HttpServletResponse _rep) {
String[] path = path(_req);
DaoEntity json = new DaoEntity("temp", Globals.app.getTemperatureCelsius(NullUtils.toInteger(CollectionUtils.get(path, 0))));
jsonResponse(_rep, DaoSerializer.toJson(json));
}
}

View File

@@ -0,0 +1,87 @@
package com.lanternsoftware.zwave.servlet;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.dao.DaoSerializer;
import org.apache.commons.io.IOUtils;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.io.InputStream;
import java.io.OutputStream;
public abstract class ZWaveServlet extends HttpServlet {
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) {
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 String[] path(HttpServletRequest _req) {
return NullUtils.cleanSplit(NullUtils.makeNotNull(_req.getPathInfo()), "/");
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<logger name="com.lanternsoftware" level="INFO"/>
<root level="OFF">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

View File

@@ -0,0 +1,59 @@
Power,Mode,Temperature,Code
Off,Heat,72,0000 006C 0000 0083 0010 00D1 0010 0010 0010 0011 0010 00F1 0010 0030 0010 0030 0010 0071 0010 0030 0010 0010 0010 0031 0010 0031 0010 0030 0010 0011 0010 01B2 0080 0041 0010 0031 0010 0030 0010 0011 0010 0010 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0011 0010 0031 0010 0011 0010 0011 0010 0031 0010 0031 0010 0010 0010 0031 0010 0031 0010 0010 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0011 0010 0010 0010 0010 0010 0071 0010 0091 0010 0011 0010 00B1 0010 0091 0010 0071 0010 0011 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0010 0010 0031 0010 0011 0010 0011 0010 0011 0010 0030 0010 0010 0010 0051 0010 0010 0010 0010 0010 0011 0010 0011 0010 0071 0010 0030 0010 0010 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0031 0010 0010 0010 0031 0010 0031 0010 0010 0010 0010 0010 0010 0010 0010 0010 0011 0010 0011 0010 0011 0010 0010 0010 0010 0010 0011 0010 0010 0010 0031 0010 0010 0010 0010 0010 0010 0010 0011 0010 0011 0010 0011 0010 0011 0010 0030 0010 0010 0010 0011 0010 0051 0010 0010 0010 0010 0010 0010 0010 0031 0010 0011 0010 0010 0010 0030 0010 0010 0010 0031 0010 0010 0010 0010 0010 0010 0010 0071 0010 0010 0010 0031 0010 0031 0010 0010 0010 0011 0010 0010 0010 0010 0010 0030 0010 0031 0010 0010 0010 0030 0010 0031 0010 0031 0010 0011 0010 02CF
On,Heat,61,
On,Heat,62,
On,Heat,63,
On,Heat,64,
On,Heat,65,
On,Heat,66,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B1 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8E
On,Heat,67,0000 006D 0000 00BF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F83
On,Heat,68,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8D
On,Heat,69,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8B
On,Heat,70,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8A
On,Heat,71,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F86
On,Heat,72,0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B0 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F89
On,Heat,73,
On,Heat,74,
On,Heat,75,
On,Heat,76,
On,Heat,77,
On,Heat,78,
On,Heat,79,
On,Heat,80,
On,Heat,81,
On,Heat,82,
On,Heat,83,
On,Heat,84,
On,Heat,85,
On,Heat,86,
On,Heat,87,
On,Heat,88,
Off,Cool,72,
On,Cool,61,
On,Cool,62,
On,Cool,63,
On,Cool,64,
On,Cool,65,
On,Cool,66,
On,Cool,67,
On,Cool,68,
On,Cool,69,
On,Cool,70,
On,Cool,71,
On,Cool,72,
On,Cool,73,
On,Cool,74,
On,Cool,75,
On,Cool,76,
On,Cool,77,
On,Cool,78,
On,Cool,79,
On,Cool,80,
On,Cool,81,
On,Cool,82,
On,Cool,83,
On,Cool,84,
On,Cool,85,
On,Cool,86,
On,Cool,87,
On,Cool,88,
1 Power Mode Temperature Code
2 Off Heat 72 0000 006C 0000 0083 0010 00D1 0010 0010 0010 0011 0010 00F1 0010 0030 0010 0030 0010 0071 0010 0030 0010 0010 0010 0031 0010 0031 0010 0030 0010 0011 0010 01B2 0080 0041 0010 0031 0010 0030 0010 0011 0010 0010 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0011 0010 0031 0010 0011 0010 0011 0010 0031 0010 0031 0010 0010 0010 0031 0010 0031 0010 0010 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0011 0010 0010 0010 0010 0010 0071 0010 0091 0010 0011 0010 00B1 0010 0091 0010 0071 0010 0011 0010 0011 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0010 0010 0031 0010 0011 0010 0011 0010 0011 0010 0030 0010 0010 0010 0051 0010 0010 0010 0010 0010 0011 0010 0011 0010 0071 0010 0030 0010 0010 0010 0031 0010 0010 0010 0010 0010 0031 0010 0031 0010 0031 0010 0010 0010 0031 0010 0031 0010 0010 0010 0010 0010 0010 0010 0010 0010 0011 0010 0011 0010 0011 0010 0010 0010 0010 0010 0011 0010 0010 0010 0031 0010 0010 0010 0010 0010 0010 0010 0011 0010 0011 0010 0011 0010 0011 0010 0030 0010 0010 0010 0011 0010 0051 0010 0010 0010 0010 0010 0010 0010 0031 0010 0011 0010 0010 0010 0030 0010 0010 0010 0031 0010 0010 0010 0010 0010 0010 0010 0071 0010 0010 0010 0031 0010 0031 0010 0010 0010 0011 0010 0010 0010 0010 0010 0030 0010 0031 0010 0010 0010 0030 0010 0031 0010 0031 0010 0011 0010 02CF
3 On Heat 61
4 On Heat 62
5 On Heat 63
6 On Heat 64
7 On Heat 65
8 On Heat 66 0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B1 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8E
9 On Heat 67 0000 006D 0000 00BF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F83
10 On Heat 68 0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8D
11 On Heat 69 0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8B
12 On Heat 70 0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B0 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F8A
13 On Heat 71 0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F86
14 On Heat 72 0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01B0 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F89
15 On Heat 73
16 On Heat 74
17 On Heat 75
18 On Heat 76
19 On Heat 77
20 On Heat 78
21 On Heat 79
22 On Heat 80
23 On Heat 81
24 On Heat 82
25 On Heat 83
26 On Heat 84
27 On Heat 85
28 On Heat 86
29 On Heat 87
30 On Heat 88
31 Off Cool 72
32 On Cool 61
33 On Cool 62
34 On Cool 63
35 On Cool 64
36 On Cool 65
37 On Cool 66
38 On Cool 67
39 On Cool 68
40 On Cool 69
41 On Cool 70
42 On Cool 71
43 On Cool 72
44 On Cool 73
45 On Cool 74
46 On Cool 75
47 On Cool 76
48 On Cool 77
49 On Cool 78
50 On Cool 79
51 On Cool 80
52 On Cool 81
53 On Cool 82
54 On Cool 83
55 On Cool 84
56 On Cool 85
57 On Cool 86
58 On Cool 87
59 On Cool 88

View File

@@ -0,0 +1,9 @@
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<listener>
<listener-class>com.lanternsoftware.zwave.context.Globals</listener-class>
</listener>
</web-app>

View File

@@ -0,0 +1,74 @@
package com.lanternsoftware.zwave;
import com.lanternsoftware.dataaccess.currentmonitor.CurrentMonitorDao;
import com.lanternsoftware.dataaccess.currentmonitor.MongoCurrentMonitorDao;
import com.lanternsoftware.datamodel.zwave.Switch;
import com.lanternsoftware.datamodel.zwave.SwitchSchedule;
import com.lanternsoftware.datamodel.zwave.ZWaveConfig;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.LanternFiles;
import com.lanternsoftware.util.ResourceLoader;
import com.lanternsoftware.util.dao.DaoSerializer;
import com.lanternsoftware.util.dao.mongo.MongoConfig;
import com.lanternsoftware.zwave.dao.MongoZWaveDao;
public class CreateConfig {
public static void main(String[] args) {
MongoZWaveDao dao = new MongoZWaveDao(MongoConfig.fromDisk(LanternFiles.OPS_PATH + "mongo.cfg"));
ZWaveConfig config = dao.getConfig(1);
// ZWaveConfig cconfig = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config - christmas lights.dat"), ZWaveConfig.class);
// Switch c = CollectionUtils.filterOne(config.getSwitches(), _s->_s.getName().contains("hristm"));
// CollectionUtils.filterMod(config.getSwitches(), _s->!_s.getRoom().equals("Treehouse"));
// Switch treehouse = new Switch("Treehouse", "Interior", 14, true, true, null, 0);
// Switch to = new Switch("Treehouse", "Floods", 15, true, true, null, 0);
// Switch out = new Switch("Outside", "Repeater Outlet", 10, true, false, null, 0);
// config.getSwitches().add(out);
Switch c = CollectionUtils.filterOne(config.getSwitches(), _s->_s.getName().contains("Agitator"));
c.setName("Septic Aerator");
dao.putConfig(config);
// if (c != null) {
// c.setNodeId(8);
// dao.putConfig(config);
// }
// ZWaveConfig config = DaoSerializer.parse(ResourceLoader.loadFile(LanternFiles.OPS_PATH + "config.dat"), ZWaveConfig.class);
// config.setAccountId(1);
// config.getSwitches().add(new Switch("Garage", "Septic Agitator", 12, 0, true, false, false, null, 0, Arrays.asList(new SwitchTransition(20))));
// Switch thermo = CollectionUtils.filterOne(config.getSwitches(), _sw->_sw.getNodeId() == 0);
// if (thermo != null)
// thermo.setNodeId(100);
// config.getSwitches().add(new Switch("Basement", "Temperature", 101, true, false, "https://basement.lanternsoftware.com/thermometer/temp", 0));
/* ZWaveConfig config = new ZWaveConfig();
List<Switch> switches = new ArrayList<>();
switches.add(new Switch("Basement", "Main", 3, true, true, null, 0));
switches.add(new Switch("Basement", "Main", 5, false, true, null, 0));
switches.add(new Switch("Basement", "Bar", 4, true, true, null, 0));
switches.add(new Switch("Basement", "Bar", 6, false, true, null, 0));
switches.add(new Switch("Master Bedroom", "Heater", 7, true, true, "https://thermometer.lanternsoftware.com/thermometer/temp", 0));
switches.add(new Switch("Bruce's Room", "Heater", 8, true, true, "https://bruce.lanternsoftware.com/thermometer/temp", 0));
switches.add(new Switch("Master Bedroom", "Heater", 9, false, true, "", 0));
Switch out = new Switch("Outside", "Christmas Lights", 10, true, false, "", 0);
out.setSchedule(CollectionUtils.asArrayList(
new SwitchTransition(Calendar.FRIDAY, 12, 42, 0, 0),
new SwitchTransition(Calendar.FRIDAY, 12, 42, 10, 0xFF),
new SwitchTransition(Calendar.FRIDAY, 12, 42, 20, 0),
new SwitchTransition(Calendar.FRIDAY, 12, 42, 30, 0xFF),
new SwitchTransition(Calendar.FRIDAY, 12, 42, 40, 0),
new SwitchTransition(Calendar.FRIDAY, 12, 42, 50, 0xFF)));
switches.add(out);
config.setSwitches(switches);
*/
// config.setSwitches(switches);
// Switch sump = CollectionUtils.filterOne(config.getSwitches(), _c->_c.getNodeId() == 12);
// SwitchSchedule transition = CollectionUtils.getFirst(sump.getSchedule());
// transition.setLevel(0xFF);
// transition.setMinutesPerHour(15);
// dao.putConfig(config);
dao.shutdown();
// TimeZone tz = TimeZone.getTimeZone("America/Chicago");
// Date next = transition.getNextTransition(tz);
// System.out.println("Next Transition: " + DateUtils.format(tz, next, "hh:mm:ssa"));
// ResourceLoader.writeFile(LanternFiles.OPS_PATH + "config.dat", DaoSerializer.toJson(config));
}
}

View File

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

View File

@@ -0,0 +1,16 @@
package com.lanternsoftware.zwave;
import com.lanternsoftware.zwave.context.ZWaveApp;
public class TestStartup {
public static void main(String[] args) {
ZWaveApp app = new ZWaveApp();
app.start();
try {
Thread.sleep(20000);
} catch (InterruptedException _e) {
_e.printStackTrace();
}
app.stop();
}
}

View File

@@ -0,0 +1,78 @@
<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>
<artifactId>lantern-uirt</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>lantern-uirt</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.5.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.5</version>
<configuration>
<archive>
<index>true</index>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,94 @@
package com.lanternsoftware.uirt;
import com.lanternsoftware.uirt.model.UIRTConfig;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
public class UsbUirt {
private static String off = "0000 006D 0000 00BF 0080 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 01AF 007F 0040 0010 0030 0010 0030 0010 0010 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0030 0010 0010 0010 0030 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0030 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0010 0F85";
private UirtLibrary lib;
private Pointer handle;
public interface UirtLibrary extends Library {
interface ReceiveProc extends StdCallLibrary.StdCallCallback {
boolean callback(Pointer _event, Pointer _userData);
}
interface LearnProc extends StdCallLibrary.StdCallCallback {
boolean callback(int progress, int _sigQuality, long carrierFreq, Pointer _userData);
}
Pointer UUIRTOpen();
boolean UUIRTClose(Pointer _handle);
boolean UUIRTGetDrvVersion(IntByReference _version);
boolean UUIRTGetUUIRTConfig(Pointer _handle, UIRTConfig.ByReference _config);
boolean UUIRTSetReceiveCallback(Pointer _handle, ReceiveProc _proc, Pointer _userData);
boolean UUIRTSetRawReceiveCallback(Pointer _handle, ReceiveProc _proc, Pointer _userData);
boolean UUIRTLearnIR(Pointer _handle, int codeFormat, PointerByReference irCode, LearnProc progressProc, Pointer _userData, IntByReference _abort, int _param1, Pointer reserved0, Pointer reserved1);
boolean UUIRTTransmitIR(Pointer _handle, String _code, int _codeFormat, int repeatCount_, int _inactivityWaitTime, Pointer _event, Pointer reserved0, Pointer reserved1);
}
public void startup() {
lib = Native.load("uuirtdrv", UirtLibrary.class, W32APIOptions.ASCII_OPTIONS);
handle = lib.UUIRTOpen();
}
public void shutdown() {
if (isStarted())
lib.UUIRTClose(handle);
lib = null;
}
public int getDriverVersion() {
if (!isStarted())
return 0;
IntByReference version = new IntByReference();
lib.UUIRTGetDrvVersion(version);
return version.getValue();
}
public UIRTConfig getConfig() {
if (!isStarted())
return null;
UIRTConfig.ByReference configRef = new UIRTConfig.ByReference();
lib.UUIRTGetUUIRTConfig(handle, configRef);
return configRef;
}
public void setReceiveCallback(UirtLibrary.ReceiveProc _proc) {
if (isStarted())
lib.UUIRTSetReceiveCallback(handle, _proc, null);
}
public void setRawReceiveCallback(UirtLibrary.ReceiveProc _proc) {
if (isStarted())
lib.UUIRTSetRawReceiveCallback(handle, _proc, null);
}
public String learnCode(UirtLibrary.LearnProc _proc) {
PointerByReference codeRef = new PointerByReference();
lib.UUIRTLearnIR(handle, 0, codeRef, _proc, null, null, 0, null, null);
Pointer code = codeRef.getValue();
return code == null ? null : code.getString(0);
}
public boolean transmitIR(boolean _pronto, int _repeatCnt, int _inactivityWaitTime, String _code) {
PointerByReference result = new PointerByReference();
return lib.UUIRTTransmitIR(handle, _code, _pronto?0x0010:0x0000, _repeatCnt, _inactivityWaitTime, null, null, null);
}
private boolean isStarted() {
return (lib != null) && (handle != null);
}
public static void main(String[] args) {
UsbUirt uirt = new UsbUirt();
uirt.startup();
uirt.transmitIR(true, 3, 0, off);
uirt.shutdown();
}
}

View File

@@ -0,0 +1,51 @@
package com.lanternsoftware.uirt.model;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class UIRTConfig extends Structure {
public static class ByReference extends UIRTConfig implements Structure.ByReference {}
public boolean ledRX;
public boolean ledTX;
public boolean legacyRX;
public UIRTConfig() {
}
public UIRTConfig(Pointer _value) {
super(_value);
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("ledRX", "ledTX", "legacyRX");
}
public boolean isLedRX() {
return ledRX;
}
public void setLedRX(boolean _ledRX) {
ledRX = _ledRX;
}
public boolean isLedTX() {
return ledTX;
}
public void setLedTX(boolean _ledTX) {
ledTX = _ledTX;
}
public boolean isLegacyRX() {
return legacyRX;
}
public void setLegacyRX(boolean _legacyRX) {
legacyRX = _legacyRX;
}
}

View File

@@ -0,0 +1,26 @@
package com.lanternsoftware.uirt.model;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class UIRTEvent extends Structure {
public static class ByReference extends UIRTEvent implements Structure.ByReference {}
public int code;
public String data;
public UIRTEvent() {
}
public UIRTEvent(Pointer p) {
super(p);
}
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("code", "data");
}
}

View File

@@ -0,0 +1,65 @@
<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.zwave</groupId>
<artifactId>lantern-zwave</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>lantern-zwave</name>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.neuronrobotics</groupId>
<artifactId>nrjavaserial</artifactId>
<version>3.15.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.29</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>com.lanternsoftware.util</groupId>
<artifactId>lantern-util-dao</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<executions>
<execution>
<goals>
<goal>testCompile</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<optimize>true</optimize>
<showDeprecation>true</showDeprecation>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,237 @@
package com.lanternsoftware.zwave.controller;
import com.lanternsoftware.util.NullUtils;
import com.lanternsoftware.util.concurrency.ConcurrencyUtils;
import com.lanternsoftware.zwave.message.IMessageSubscriber;
import com.lanternsoftware.zwave.message.Message;
import com.lanternsoftware.zwave.message.MessageEngine;
import com.lanternsoftware.zwave.message.RequestMessage;
import com.lanternsoftware.zwave.message.ResponseMessage;
import com.lanternsoftware.zwave.message.impl.ByteMessage;
import com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest;
import com.lanternsoftware.zwave.message.impl.ControllerInitialDataRequest;
import com.lanternsoftware.zwave.message.impl.GetControllerIdRequest;
import com.lanternsoftware.zwave.message.impl.SendDataRequest;
import com.lanternsoftware.zwave.node.NodeManager;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Controller {
private static final byte SOF = 0x01;
private static final byte ACK = 0x06;
private static final byte NAK = 0x15;
private static final byte CAN = 0x18;
private static final Logger logger = LoggerFactory.getLogger(Controller.class);
private SerialPort serialPort;
private OutputStream os;
private boolean running = false;
private AtomicInteger callbackId = new AtomicInteger(0);
private final Object ackMutex = new Object();
private final Object responseMutex = new Object();
private final Object callbackMutex = new Object();
private boolean responseReceived;
private final Map<Byte, Byte> callbacks = new HashMap<>();
private ExecutorService executor = Executors.newFixedThreadPool(2);
private NodeManager nodeManager;
public boolean start(String _port) {
try {
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(_port);
serialPort = portIdentifier.open("zwaveport", 2000);
serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
serialPort.enableReceiveThreshold(1);
serialPort.enableReceiveTimeout(1000);
os = serialPort.getOutputStream();
running = true;
executor.submit(new MessageReceiver());
MessageEngine.subscribe(new SendDataRequestHandler());
nodeManager = new NodeManager(this);
send(new ControllerCapabilitiesRequest());
send(new ControllerInitialDataRequest());
send(new GetControllerIdRequest());
nodeManager.waitForStartup();
logger.debug("Finishing Controller Start");
return true;
} catch (Exception _e) {
if (serialPort != null) {
serialPort.close();
serialPort = null;
}
logger.error("Exception while starting controller", _e);
return false;
}
}
public void stop() {
running = false;
ConcurrencyUtils.sleep(2000);
IOUtils.closeQuietly(os);
if (serialPort != null) {
serialPort.close();
}
executor.shutdown();
}
public void send(Message _message) {
executor.submit(new MessageSender(_message));
}
private class MessageReceiver implements Runnable {
@Override
public void run() {
InputStream is = null;
try {
is = serialPort.getInputStream();
int nextByte = 0;
int offset = 0;
while (running) {
nextByte = is.read();
if (nextByte == -1)
continue;
switch (nextByte) {
case SOF:
int messageLength = is.read();
byte[] buffer = new byte[messageLength + 2];
buffer[0] = SOF;
buffer[1] = (byte) messageLength;
offset = 2;
while (offset < messageLength + 2) {
offset += is.read(buffer, offset, messageLength + 2 - offset);
}
processIncomingMessage(buffer);
break;
case ACK:
synchronized (ackMutex) {
logger.debug("Received ACK");
ackMutex.notify();
}
MessageEngine.publish(new ByteMessage((byte) nextByte));
break;
case NAK:
case CAN:
synchronized (ackMutex) {
logger.debug("Received: {}", NullUtils.toHex(new byte[]{(byte) nextByte}));
ackMutex.notify();
}
MessageEngine.publish(new ByteMessage((byte) nextByte));
break;
default:
sendRaw(new byte[]{NAK});
break;
}
}
} catch (IOException _e) {
logger.error("Exception while receiving inbound, stopping controller", _e);
stop();
} finally {
IOUtils.closeQuietly(is);
}
}
}
private class MessageSender implements Runnable {
private final Message message;
MessageSender(Message _message) {
message = _message;
}
@Override
public void run() {
try {
synchronized (Controller.this) {
byte callback = 0;
String log = "Sending message outbound: " + message.describe();
if (message.isCallbackExpected()) {
callback = (byte) (callbackId.getAndIncrement() % 126 + 1);
callbacks.put(callback, message.getNodeId());
log += " callback: " + callback;
}
logger.info(log);
byte[] data = message.toByteArray((byte) 0, callback);
logger.debug("Sending outbound: {}", NullUtils.toHexBytes(data));
responseReceived = false;
sendRaw(data);
synchronized (ackMutex) {
ackMutex.wait(1000);
}
logger.debug("Finished outbound of: {}", message.describe());
}
if (message instanceof RequestMessage) {
logger.debug("Waiting for response from: {}", message.describe());
synchronized (responseMutex) {
responseMutex.wait(1000);
logger.debug("Response received: {}", responseReceived);
responseReceived = false;
}
}
if (message.isCallbackExpected()) {
logger.debug("Waiting for callback from: {}", message.describe());
synchronized (callbackMutex) {
callbackMutex.wait(1000);
}
}
} catch (InterruptedException _e) {
logger.error("Interrupted while sending outbound", _e);
}
}
}
private void sendRaw(byte[] _data) {
try {
os.write(_data);
os.flush();
} catch (IOException _e) {
logger.error("IO exception while sending outbound", _e);
}
}
private void processIncomingMessage(byte[] _buffer) {
logger.debug("Received inbound: {}", NullUtils.toHexBytes(_buffer));
logger.debug("Sending ACK");
sendRaw(new byte[]{ACK});
Message message = MessageEngine.decode(_buffer);
if (message != null) {
logger.info("Received message inbound: {}", message.describe());
MessageEngine.publish(message);
if (message instanceof ResponseMessage) {
synchronized (responseMutex) {
responseReceived = true;
responseMutex.notify();
}
}
}
}
private class SendDataRequestHandler implements IMessageSubscriber<SendDataRequest> {
@Override
public Class<SendDataRequest> getHandledMessageClass() {
return SendDataRequest.class;
}
@Override
public void onMessage(SendDataRequest _message) {
Byte nodeId = callbacks.remove(_message.getCallbackId());
if (nodeId != null) {
logger.debug("Received callback for node: {} callback id: {}", nodeId, _message.getCallbackId());
synchronized (callbackMutex) {
callbackMutex.notify();
}
}
}
}
}

View File

@@ -0,0 +1,119 @@
package com.lanternsoftware.zwave.message;
import java.util.HashMap;
import java.util.Map;
public enum CommandClass {
NO_OPERATION((byte)0x00, "NO_OPERATION"),
BASIC((byte)0x20, "BASIC"),
CONTROLLER_REPLICATION((byte)0x21, "CONTROLLER_REPLICATION"),
APPLICATION_STATUS((byte)0x22, "APPLICATION_STATUS"),
ZIP_SERVICES((byte)0x23, "ZIP_SERVICES"),
ZIP_SERVER((byte)0x24, "ZIP_SERVER"),
SWITCH_BINARY((byte)0x25, "SWITCH_BINARY", true, 0),
SWITCH_MULTILEVEL((byte)0x26, "SWITCH_MULTILEVEL", true, 0),
SWITCH_ALL((byte)0x27, "SWITCH_ALL"),
SWITCH_TOGGLE_BINARY((byte)0x28, "SWITCH_TOGGLE_BINARY"),
SWITCH_TOGGLE_MULTILEVEL((byte)0x29, "SWITCH_TOGGLE_MULTILEVEL"),
CHIMNEY_FAN((byte)0x2A, "CHIMNEY_FAN"),
SCENE_ACTIVATION((byte)0x2B, "SCENE_ACTIVATION"),
SCENE_ACTUATOR_CONF((byte)0x2C, "SCENE_ACTUATOR_CONF"),
SCENE_CONTROLLER_CONF((byte)0x2D, "SCENE_CONTROLLER_CONF"),
ZIP_CLIENT((byte)0x2E, "ZIP_CLIENT"),
ZIP_ADV_SERVICES((byte)0x2F, "ZIP_ADV_SERVICES"),
SENSOR_BINARY((byte)0x30, "SENSOR_BINARY"),
SENSOR_MULTILEVEL((byte)0x31, "SENSOR_MULTILEVEL"),
METER((byte)0x32, "METER", true, 60),
ZIP_ADV_SERVER((byte)0x33, "ZIP_ADV_SERVER"),
ZIP_ADV_CLIENT((byte)0x34, "ZIP_ADV_CLIENT"),
METER_PULSE((byte)0x35, "METER_PULSE"),
METER_TBL_CONFIG((byte)0x3C, "METER_TBL_CONFIG"),
METER_TBL_MONITOR((byte)0x3D, "METER_TBL_MONITOR"),
METER_TBL_PUSH((byte)0x3E, "METER_TBL_PUSH"),
THERMOSTAT_HEATING((byte)0x38, "THERMOSTAT_HEATING"),
THERMOSTAT_MODE((byte)0x40, "THERMOSTAT_MODE"),
THERMOSTAT_OPERATING_STATE((byte)0x42, "THERMOSTAT_OPERATING_STATE"),
THERMOSTAT_SETPOINT((byte)0x43, "THERMOSTAT_SETPOINT"),
THERMOSTAT_FAN_MODE((byte)0x44, "THERMOSTAT_FAN_MODE"),
THERMOSTAT_FAN_STATE((byte)0x45, "THERMOSTAT_FAN_STATE"),
CLIMATE_CONTROL_SCHEDULE((byte)0x46, "CLIMATE_CONTROL_SCHEDULE"),
THERMOSTAT_SETBACK((byte)0x47, "THERMOSTAT_SETBACK"),
DOOR_LOCK_LOGGING((byte)0x4C, "DOOR_LOCK_LOGGING"),
SCHEDULE_ENTRY_LOCK((byte)0x4E, "SCHEDULE_ENTRY_LOCK"),
BASIC_WINDOW_COVERING((byte)0x50, "BASIC_WINDOW_COVERING"),
MTP_WINDOW_COVERING((byte)0x51, "MTP_WINDOW_COVERING"),
MULTI_INSTANCE((byte)0x60, "MULTI_INSTANCE"),
DOOR_LOCK((byte)0x62, "DOOR_LOCK"),
USER_CODE((byte)0x63, "USER_CODE"),
CONFIGURATION((byte)0x70, "CONFIGURATION"),
ALARM((byte)0x71, "ALARM"),
MANUFACTURER_SPECIFIC((byte)0x72, "MANUFACTURER_SPECIFIC"),
POWERLEVEL((byte)0x73, "POWERLEVEL"),
PROTECTION((byte)0x75, "PROTECTION"),
LOCK((byte)0x76, "LOCK"),
NODE_NAMING((byte)0x77, "NODE_NAMING"),
FIRMWARE_UPDATE_MD((byte)0x7A, "FIRMWARE_UPDATE_MD"),
GROUPING_NAME((byte)0x7B, "GROUPING_NAME"),
REMOTE_ASSOCIATION_ACTIVATE((byte)0x7C, "REMOTE_ASSOCIATION_ACTIVATE"),
REMOTE_ASSOCIATION((byte)0x7D, "REMOTE_ASSOCIATION"),
BATTERY((byte)0x80, "BATTERY", true, 3600),
CLOCK((byte)0x81, "CLOCK"),
HAIL((byte)0x82, "HAIL"),
WAKE_UP((byte)0x84, "WAKE_UP"),
ASSOCIATION((byte)0x85, "ASSOCIATION"),
VERSION((byte)0x86, "VERSION"),
INDICATOR((byte)0x87, "INDICATOR"),
PROPRIETARY((byte)0x88, "PROPRIETARY"),
LANGUAGE((byte)0x89, "LANGUAGE"),
TIME((byte)0x8A, "TIME"),
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_CMD((byte)0x8F, "MULTI_CMD"),
ENERGY_PRODUCTION((byte)0x90, "ENERGY_PRODUCTION"),
MANUFACTURER_PROPRIETARY((byte)0x91, "MANUFACTURER_PROPRIETARY"),
SCREEN_MD((byte)0x92, "SCREEN_MD"),
SCREEN_ATTRIBUTES((byte)0x93, "SCREEN_ATTRIBUTES"),
SIMPLE_AV_CONTROL((byte)0x94, "SIMPLE_AV_CONTROL"),
AV_CONTENT_DIRECTORY_MD((byte)0x95, "AV_CONTENT_DIRECTORY_MD"),
AV_RENDERER_STATUS((byte)0x96, "AV_RENDERER_STATUS"),
AV_CONTENT_SEARCH_MD((byte)0x97, "AV_CONTENT_SEARCH_MD"),
SECURITY((byte)0x98, "SECURITY"),
AV_TAGGING_MD((byte)0x99, "AV_TAGGING_MD"),
IP_CONFIGURATION((byte)0x9A, "IP_CONFIGURATION"),
ASSOCIATION_COMMAND_CONFIGURATION((byte)0x9B, "ASSOCIATION_COMMAND_CONFIGURATION"),
SENSOR_ALARM((byte)0x9C, "SENSOR_ALARM"),
SILENCE_ALARM((byte)0x9D, "SILENCE_ALARM"),
SENSOR_CONFIGURATION((byte)0x9E, "SENSOR_CONFIGURATION"),
MARK((byte)0xEF, "MARK"),
NON_INTEROPERABLE((byte)0xF0, "NON_INTEROPERABLE"),
ALL((byte)0xFF, null);
public final byte data;
public final String label;
public final boolean supportsPolling;
public final int secondsRefreshInterval;
private static final Map<Byte, CommandClass> classes = new HashMap<>();
static {
for (CommandClass cls : values()) {
classes.put(cls.data, cls);
}
}
CommandClass(byte _data, String _label) {
this(_data, _label, false, 0);
}
CommandClass(byte _data, String _label, boolean _supportsPolling, int _secondsRefreshInterval) {
data = _data;
label = _label;
supportsPolling = _supportsPolling;
secondsRefreshInterval = _secondsRefreshInterval;
}
public static CommandClass fromByte(byte _code) {
CommandClass cls = classes.get(_code);
return cls == null ? CommandClass.NO_OPERATION : cls;
}
}

View File

@@ -0,0 +1,92 @@
package com.lanternsoftware.zwave.message;
import com.lanternsoftware.util.CollectionUtils;
import java.util.Arrays;
import java.util.Map;
public enum ControllerMessageType {
None((byte)0x0,"None"),
SerialApiGetInitData((byte)0x02,"SerialApiGetInitData"), // Request initial information about devices in network
SerialApiApplicationNodeInfo((byte)0x03,"SerialApiApplicationNodeInfo"), // Set controller node information
ApplicationCommandHandler((byte)0x04,"ApplicationCommandHandler"), // Handle application command
GetControllerCapabilities((byte)0x05,"GetControllerCapabilities"), // Request controller capabilities (primary role, SUC/SIS availability)
SerialApiSetTimeouts((byte)0x06,"SerialApiSetTimeouts"), // Set Serial API timeouts
GetCapabilities((byte)0x07,"GetCapabilities"), // Request Serial API capabilities from the controller
SerialApiSoftReset((byte)0x08,"SerialApiSoftReset"), // Soft reset. Restarts Z-Wave chip
RfReceiveMode((byte)0x10,"RfReceiveMode"), // Power down the RF section of the stick
SetSleepMode((byte)0x11,"SetSleepMode"), // Set the CPU into sleep mode
SendNodeInfo((byte)0x12,"SendNodeInfo"), // Send Node Information Frame of the stick
SendData((byte)0x13,"SendData"), // Send data.
SendDataMulti((byte)0x14, "SendDataMulti"),
GetVersion((byte)0x15,"GetVersion"), // Request controller hardware version
SendDataAbort((byte)0x16,"SendDataAbort"), // Abort Send data.
RfPowerLevelSet((byte)0x17,"RfPowerLevelSet"), // Set RF Power level
SendDataMeta((byte)0x18, "SendDataMeta"),
GetRandom((byte)0x1c,"GetRandom"), // ???
MemoryGetId((byte)0x20,"MemoryGetId"), // ???
MemoryGetByte((byte)0x21,"MemoryGetByte"), // Get a byte of memory.
MemoryPutByte((byte)0x22, "MemoryPutByte"),
ReadMemory((byte)0x23,"ReadMemory"), // Read memory.
WriteMemory((byte)0x24, "WriteMemory"),
SetLearnNodeState((byte)0x40,"SetLearnNodeState"), // ???
IdentifyNode((byte)0x41,"IdentifyNode"), // Get protocol info (baud rate, listening, etc.) for a given node
SetDefault((byte)0x42,"SetDefault"), // Reset controller and node info to default (original) values
NewController((byte)0x43,"NewController"), // ???
ReplicationCommandComplete((byte)0x44,"ReplicationCommandComplete"), // Replication send data complete
ReplicationSendData((byte)0x45,"ReplicationSendData"), // Replication send data
AssignReturnRoute((byte)0x46,"AssignReturnRoute"), // Assign a return route from the specified node to the controller
DeleteReturnRoute((byte)0x47,"DeleteReturnRoute"), // Delete all return routes from the specified node
RequestNodeNeighborUpdate((byte)0x48,"RequestNodeNeighborUpdate"), // Ask the specified node to update its neighbors (then read them from the controller)
ApplicationUpdate((byte)0x49,"ApplicationUpdate"), // Get a list of supported (and controller) command classes
AddNodeToNetwork((byte)0x4a,"AddNodeToNetwork"), // Control the addnode (or addcontroller) process...start, stop, etc.
RemoveNodeFromNetwork((byte)0x4b,"RemoveNodeFromNetwork"), // Control the removenode (or removecontroller) process...start, stop, etc.
CreateNewPrimary((byte)0x4c,"CreateNewPrimary"), // Control the createnewprimary process...start, stop, etc.
ControllerChange((byte)0x4d,"ControllerChange"), // Control the transferprimary process...start, stop, etc.
SetLearnMode((byte)0x50,"SetLearnMode"), // Put a controller into learn mode for replication/ receipt of configuration info
AssignSucReturnRoute((byte)0x51,"AssignSucReturnRoute"), // Assign a return route to the SUC
EnableSuc((byte)0x52,"EnableSuc"), // Make a controller a Static Update Controller
RequestNetworkUpdate((byte)0x53,"RequestNetworkUpdate"), // Network update for a SUC(?)
SetSucNodeID((byte)0x54,"SetSucNodeID"), // Identify a Static Update Controller node id
DeleteSUCReturnRoute((byte)0x55,"DeleteSUCReturnRoute"), // Remove return routes to the SUC
GetSucNodeId((byte)0x56,"GetSucNodeId"), // Try to retrieve a Static Update Controller node id (zero if no SUC present)
SendSucId((byte)0x57, "SendSucId"),
RequestNodeNeighborUpdateOptions((byte)0x5a,"RequestNodeNeighborUpdateOptions"), // Allow options for request node neighbor update
RequestNodeInfo((byte)0x60,"RequestNodeInfo"), // Get info (supported command classes) for the specified node
RemoveFailedNodeID((byte)0x61,"RemoveFailedNodeID"), // Mark a specified node id as failed
IsFailedNodeID((byte)0x62,"IsFailedNodeID"), // Check to see if a specified node has failed
ReplaceFailedNode((byte)0x63,"ReplaceFailedNode"), // Remove a failed node from the controller's list (?)
GetRoutingInfo((byte)0x80,"GetRoutingInfo"), // Get a specified node's neighbor information from the controller
LockRoute((byte)0x90, "LockRoute"),
SerialApiSlaveNodeInfo((byte)0xA0,"SerialApiSlaveNodeInfo"), // Set application virtual slave node information
ApplicationSlaveCommandHandler((byte)0xA1,"ApplicationSlaveCommandHandler"), // Slave command handler
SendSlaveNodeInfo((byte)0xA2,"ApplicationSlaveCommandHandler"), // Send a slave node information frame
SendSlaveData((byte)0xA3,"SendSlaveData"), // Send data from slave
SetSlaveLearnMode((byte)0xA4,"SetSlaveLearnMode"), // Enter slave learn mode
GetVirtualNodes((byte)0xA5,"GetVirtualNodes"), // Return all virtual nodes
IsVirtualNode((byte)0xA6,"IsVirtualNode"), // Virtual node test
WatchDogEnable((byte)0xB6, "WatchDogEnable"),
WatchDogDisable((byte)0xB7, "WatchDogDisable"),
WatchDogKick((byte)0xB6, "WatchDogKick"),
RfPowerLevelGet((byte)0xBA,"RfPowerLevelSet"), // Get RF Power level
GetLibraryType((byte)0xBD, "GetLibraryType"), // Gets the type of ZWave library on the stick
SendTestFrame((byte)0xBE, "SendTestFrame"), // Send a test frame to a node
GetProtocolStatus((byte)0xBF, "GetProtocolStatus"),
SetPromiscuousMode((byte)0xD0,"SetPromiscuousMode"), // Set controller into promiscuous mode to listen to all frames
PromiscuousApplicationCommandHandler((byte)0xD1,"PromiscuousApplicationCommandHandler"),
ALL((byte)0xFF, null);
public final byte data;
public final String label;
ControllerMessageType(byte _data, String _label) {
data = _data;
label = _label;
}
private static Map<Byte, ControllerMessageType> types = CollectionUtils.transformToMap(Arrays.asList(values()), _type->_type.data);
public static ControllerMessageType fromByte(byte _data) {
ControllerMessageType type = types.get(_data);
return type == null ? ControllerMessageType.None : type;
}
}

View File

@@ -0,0 +1,6 @@
package com.lanternsoftware.zwave.message;
public interface IMessageSubscriber<T extends Message> {
Class<T> getHandledMessageClass();
void onMessage(T _message);
}

View File

@@ -0,0 +1,126 @@
package com.lanternsoftware.zwave.message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public abstract class Message {
protected byte nodeId;
protected final ControllerMessageType controllerMessageType;
protected final MessageType messageType;
protected final CommandClass commandClass;
protected final byte command;
public Message(ControllerMessageType _controllerMessageType, MessageType _messageType, CommandClass _commandClass, byte _command) {
this((byte) 0, _controllerMessageType, _messageType, _commandClass, _command);
}
public Message(byte _nodeId, ControllerMessageType _controllerMessageType, MessageType _messageType, CommandClass _commandClass, byte _command) {
nodeId = _nodeId;
controllerMessageType = _controllerMessageType;
messageType = _messageType;
commandClass = _commandClass;
command = _command;
}
public byte getNodeId() {
return nodeId;
}
public void setNodeId(byte _nodeId) {
nodeId = _nodeId;
}
public byte[] toPayload() {
try {
byte[] payload = getPayload();
ByteArrayOutputStream os = new ByteArrayOutputStream();
if (nodeId > 0)
os.write(nodeId);
if (commandClass != CommandClass.NO_OPERATION) {
os.write(payload.length + 2);
os.write(commandClass.data);
os.write(command);
}
if (payload.length > 0)
os.write(payload);
os.close();
return os.toByteArray();
} catch (IOException _e) {
_e.printStackTrace();
return new byte[0];
}
}
public void fromPayload(byte[] _payload) {
}
public byte[] getPayload() {
return new byte[0];
}
public byte[] toByteArray(byte _transmitOptions, byte _callbackId) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] payload = toPayload();
os.write((byte) 0x01);
int messageLength = payload.length + (isCallbackExpected() ? 5 : 3);
os.write((byte) messageLength);
os.write(messageType.data);
os.write(controllerMessageType.data);
if (payload.length > 0)
os.write(payload);
if (isCallbackExpected()) {
os.write(_transmitOptions);
os.write(_callbackId);
}
os.write((byte) 1);
byte[] msg = os.toByteArray();
msg[msg.length - 1] = calculateChecksum(msg);
return msg;
} catch (IOException _e) {
_e.printStackTrace();
return null;
}
}
public String getKey() {
return toKey(controllerMessageType.data, messageType.data, commandClass.data, command);
}
public static String toKey(byte _controllerMessageType, byte _messageType, byte _commandClass, byte _command) {
return String.format("%02X%02X%02X%02X", _controllerMessageType, _messageType, _commandClass, _command);
}
public static byte calculateChecksum(byte[] buffer) {
byte checkSum = (byte) 0xFF;
for (int i = 1; i < buffer.length - 1; i++) {
checkSum = (byte) (checkSum ^ buffer[i]);
}
return checkSum;
}
protected byte[] asByteArray(int _byte) {
byte[] ret = new byte[1];
ret[0] = (byte) _byte;
return ret;
}
protected byte[] asByteArray(byte... _bytes) {
return _bytes;
}
public String name() {
return getClass().getSimpleName();
}
public boolean isCallbackExpected() {
return (controllerMessageType == ControllerMessageType.SendData) && (messageType == MessageType.REQUEST);
}
public String describe() {
return name();
}
}

View File

@@ -0,0 +1,63 @@
package com.lanternsoftware.zwave.message;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.util.NullUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
public abstract class MessageEngine {
private static final Logger logger = LoggerFactory.getLogger(MessageEngine.class);
private static final Map<String, Message> messages = new HashMap<>();
private static final Map<Class<?>, List<IMessageSubscriber<?>>> subscribers = new HashMap<>();
static {
for (Message m : ServiceLoader.load(Message.class)) {
messages.put(m.getKey(), m);
}
for (IMessageSubscriber s : ServiceLoader.load(IMessageSubscriber.class)) {
subscribe(s);
}
}
public static Message decode(byte[] _data) {
byte messageCheckSum = Message.calculateChecksum(_data);
byte messageCheckSumReceived = _data[_data.length - 1];
if (messageCheckSum != messageCheckSumReceived) {
logger.debug("Invalid checksum for message: {}", NullUtils.toHex(_data));
return null;
}
MessageType messageType = _data[2] == 0x00 ? MessageType.REQUEST : MessageType.RESPONSE;
ControllerMessageType controllerMessageType = ControllerMessageType.fromByte((byte)(_data[3] & 0xFF));
int offset = ((messageType == MessageType.REQUEST) && NullUtils.isOneOf(controllerMessageType, ControllerMessageType.SendData, ControllerMessageType.ApplicationCommandHandler))?7:5;
CommandClass commandClass = _data.length > offset + 1 ? CommandClass.fromByte((byte)(_data[offset] & 0xFF)):CommandClass.NO_OPERATION;
byte command = ((commandClass == CommandClass.NO_OPERATION) || (_data.length <= offset+2))?0:(byte)(_data[offset+1] & 0xFF);
Message message = messages.get(Message.toKey(controllerMessageType.data, messageType.data, commandClass.data, command));
if (message == null) {
logger.debug("Could not find message class for message: {} {} {} {}", controllerMessageType.label, messageType.name(), commandClass.label, command);
return null;
}
try {
Message ret = message.getClass().newInstance();
ret.fromPayload(_data);
return ret;
} catch (Exception _e) {
_e.printStackTrace();
return null;
}
}
public static void publish(Message _m) {
for (IMessageSubscriber s : CollectionUtils.makeNotNull(subscribers.get(_m.getClass()))) {
s.onMessage(_m);
}
}
public static void subscribe(IMessageSubscriber<?> _subscriber) {
CollectionUtils.addToMultiMap(_subscriber.getHandledMessageClass(), _subscriber, subscribers);
}
}

View File

@@ -0,0 +1,12 @@
package com.lanternsoftware.zwave.message;
public enum MessageType {
REQUEST((byte)0),
RESPONSE((byte)1);
public final byte data;
MessageType(byte _data) {
data = _data;
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.zwave.message;
import java.math.BigDecimal;
import java.math.RoundingMode;
public abstract class MessageUtil {
private static final int SIZE_MASK = 0x07;
private static final int SCALE_MASK = 0x18;
private static final int SCALE_SHIFT = 0x03;
private static final int PRECISION_MASK = 0xe0;
private static final int PRECISION_SHIFT = 0x05;
public static double getTemperatureCelsius(byte[] _payload, int _offset) {
int size = _payload[_offset] & SIZE_MASK;
int scale = (_payload[_offset] & SCALE_MASK) >> SCALE_SHIFT;
int precision = (_payload[_offset] & PRECISION_MASK) >> PRECISION_SHIFT;
if ((size+_offset) >= _payload.length)
return 0.0;
int value = 0;
for (int i = 0; i < size; ++i) {
value <<= 8;
value |= _payload[_offset + i + 1] & 0xFF;
}
BigDecimal result;
if ((_payload[_offset + 1] & 0x80) == 0x80) {
if (size == 1)
value |= 0xffffff00;
else if (size == 2)
value |= 0xffff0000;
}
result = BigDecimal.valueOf(value);
BigDecimal divisor = BigDecimal.valueOf(Math.pow(10, precision));
double temp = result.divide(divisor, RoundingMode.HALF_EVEN).doubleValue();
return (scale == 1) ? (temp-32)/1.8 : temp;
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class NoCommandRequestMessage extends RequestMessage {
public NoCommandRequestMessage(ControllerMessageType _controllerMessageType) {
this((byte)0, _controllerMessageType);
}
public NoCommandRequestMessage(byte _nodeId, ControllerMessageType _controllerMessageType) {
super(_nodeId, _controllerMessageType, CommandClass.NO_OPERATION, (byte) 0);
}
}

View File

@@ -0,0 +1,7 @@
package com.lanternsoftware.zwave.message;
public abstract class NoCommandResponseMessage extends ResponseMessage {
public NoCommandResponseMessage(ControllerMessageType _controllerMessageType) {
super(_controllerMessageType, CommandClass.NO_OPERATION, (byte) 0);
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class RequestMessage extends Message {
public RequestMessage(ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
this((byte)0, _controllerMessageType, _commandClass, _command);
}
public RequestMessage(byte _nodeId, ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
super(_nodeId, _controllerMessageType, MessageType.REQUEST, _commandClass, _command);
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class ResponseMessage extends Message {
public ResponseMessage(ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
this((byte) 0, _controllerMessageType, _commandClass, _command);
}
public ResponseMessage(byte _nodeId, ControllerMessageType _controllerMessageType, CommandClass _commandClass, byte _command) {
super(_nodeId, _controllerMessageType, MessageType.RESPONSE, _commandClass, _command);
}
}

View File

@@ -0,0 +1,11 @@
package com.lanternsoftware.zwave.message;
public abstract class SendDataRequestMessage extends RequestMessage {
public SendDataRequestMessage(CommandClass _commandClass, byte _command) {
this((byte)0, _commandClass, _command);
}
public SendDataRequestMessage(byte _nodeId, CommandClass _commandClass, byte _command) {
super(_nodeId, ControllerMessageType.SendData, _commandClass, _command);
}
}

View File

@@ -0,0 +1,42 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
public class ApplicationUpdateRequest extends RequestMessage {
private static final Logger logger = LoggerFactory.getLogger(ApplicationUpdateRequest.class);
private List<CommandClass> commandClasses;
public ApplicationUpdateRequest() {
super(ControllerMessageType.ApplicationUpdate, CommandClass.NO_OPERATION, (byte) 0);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
if (_payload[4] == (byte) 0x84)
logger.debug("Received node information for node: {}", nodeId);
int length = _payload[6];
commandClasses = new ArrayList<>();
for (int i = 7; i < length + 7; i++) {
if (_payload[i] != (byte) 0xef) {
CommandClass commandClass = CommandClass.fromByte(_payload[i]);
if (commandClass != CommandClass.NO_OPERATION) {
logger.debug("Received command class: {} for node: {}", commandClass.name(), nodeId);
commandClasses.add(commandClass);
}
}
}
}
public List<CommandClass> getCommandClasses() {
return commandClasses;
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class BinarySwitchSetRequest extends SendDataRequestMessage {
private boolean on;
public BinarySwitchSetRequest() {
this(true);
}
public BinarySwitchSetRequest(boolean _on) {
this((byte)0, _on);
}
public BinarySwitchSetRequest(byte _nodeId, boolean _on) {
super(_nodeId, CommandClass.SWITCH_BINARY, (byte)0x01);
on = _on;
}
public boolean isOn() {
return on;
}
public void setOn(boolean _on) {
on = _on;
}
@Override
public byte[] getPayload() {
return asByteArray(on?0xFF:0);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " on: " + on;
}
}

View File

@@ -0,0 +1,30 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class ByteMessage extends NoCommandRequestMessage {
private byte b;
public ByteMessage() {
super(ControllerMessageType.None);
}
public ByteMessage(byte _b) {
super(ControllerMessageType.None);
b = _b;
}
public byte getByte() {
return b;
}
public void setByte(byte _b) {
b = _b;
}
@Override
public byte[] toByteArray(byte _transmitOptions, byte _callbackId) {
return asByteArray(b);
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class ControllerCapabilitiesRequest extends NoCommandRequestMessage {
public ControllerCapabilitiesRequest() {
super(ControllerMessageType.GetCapabilities);
}
}

View File

@@ -0,0 +1,75 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
public class ControllerCapabilitiesResponse extends NoCommandResponseMessage {
private static final Logger logger = LoggerFactory.getLogger(ControllerCapabilitiesResponse.class);
private String serialAPIVersion;
private int manufacturerId;
private int deviceType;
private int deviceId;
private Set<CommandClass> supportedCommandClasses;
public ControllerCapabilitiesResponse() {
super(ControllerMessageType.GetCapabilities);
}
@Override
public void fromPayload(byte[] _payload) {
serialAPIVersion = String.format("%d.%d", _payload[4], _payload[5]);
manufacturerId = getShort(_payload, 6);
deviceType = getShort(_payload, 8);
deviceId = getShort(_payload, 10);
supportedCommandClasses = new HashSet<>();
for (int by = 12; by < _payload.length-1; by++) {
for (int bi = 0; bi < 8; bi++) {
if ((_payload[by] & (0x01 << bi)) != 0) {
byte commandClassByte = (byte) (((by - 12) << 3) + bi + 1);
CommandClass commandClass = CommandClass.fromByte(commandClassByte);
if (commandClass != CommandClass.NO_OPERATION) {
logger.debug("Supports command class: {}", commandClass.label);
supportedCommandClasses.add(commandClass);
} else {
logger.debug("Supports unknown command class: {}", commandClassByte);
}
}
}
}
}
private int getShort(byte[] _data, int _offset) {
return (toShort(_data[_offset]) << 8) | toShort(_data[_offset + 1]);
}
private int toShort(byte _bt) {
return _bt & 0xFF;
}
public String getSerialAPIVersion() {
return serialAPIVersion;
}
public int getManufacturerId() {
return manufacturerId;
}
public int getDeviceType() {
return deviceType;
}
public int getDeviceId() {
return deviceId;
}
public Set<CommandClass> getSupportedCommandClasses() {
return supportedCommandClasses;
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class ControllerInitialDataRequest extends NoCommandRequestMessage {
public ControllerInitialDataRequest() {
super(ControllerMessageType.SerialApiGetInitData);
}
}

View File

@@ -0,0 +1,50 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
import java.util.ArrayList;
import java.util.List;
public class ControllerInitialDataResponse extends NoCommandResponseMessage {
private boolean master;
private boolean primary;
private List<Byte> nodeIds;
public ControllerInitialDataResponse() {
super(ControllerMessageType.SerialApiGetInitData);
}
@Override
public void fromPayload(byte[] _payload) {
int length = _payload[6];
if (length == 29) {
byte nodeId = 1;
nodeIds = new ArrayList<>();
for (int i = 7; i < 7 + length; i++) {
byte curByte = _payload[i];
for (int j = 0; j < 8; j++) {
int bit = 1 << j;
if ((curByte & bit) == bit) {
nodeIds.add(nodeId);
}
nodeId++;
}
}
master = (_payload[5] & 0x1) == 0;
primary = (_payload[5] & 0x4) == 0;
}
}
public boolean isMaster() {
return master;
}
public boolean isPrimary() {
return primary;
}
public List<Byte> getNodeIds() {
return nodeIds;
}
}

View File

@@ -0,0 +1,15 @@
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 DeviceManufacturerActionRequest extends RequestMessage {
public DeviceManufacturerActionRequest() {
this((byte) 0);
}
public DeviceManufacturerActionRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.SendData, CommandClass.MANUFACTURER_SPECIFIC, (byte) 0x04);
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class GetControllerIdRequest extends NoCommandRequestMessage {
public GetControllerIdRequest() {
super(ControllerMessageType.MemoryGetId);
}
}

View File

@@ -0,0 +1,39 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
public class GetControllerIdResponse extends NoCommandResponseMessage {
private long homeId;
private int controllerId;
public GetControllerIdResponse() {
super(ControllerMessageType.MemoryGetId);
}
@Override
public void fromPayload(byte[] _payload) {
homeId = (getByte(_payload, 4) << 24) | (getByte(_payload, 5) << 16) | (getByte(_payload, 6) << 8) | getByte(_payload, 7);
controllerId = _payload[8];
}
public long getHomeId() {
return homeId;
}
public void setHomeId(int _homeId) {
homeId = _homeId;
}
public int getControllerId() {
return controllerId;
}
public void setControllerId(int _controllerId) {
controllerId = _controllerId;
}
private long getByte(byte[] _data, int _offset) {
return _data[_offset] & 0xFF;
}
}

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

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.MessageUtil;
import com.lanternsoftware.zwave.message.RequestMessage;
public class MultilevelSensorReportRequest extends RequestMessage {
private double temperature;
public MultilevelSensorReportRequest() {
this((byte) 0);
}
public MultilevelSensorReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.SENSOR_MULTILEVEL, (byte) 0x05);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
if (_payload[9] == (byte) 1)
temperature = MessageUtil.getTemperatureCelsius(_payload, 10);
}
public double getTemperatureCelsius() {
return temperature;
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + temperature;
}
}

View File

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

View File

@@ -0,0 +1,31 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class MultilevelSwitchSetRequest extends SendDataRequestMessage {
private int level;
public MultilevelSwitchSetRequest() {
this(99);
}
public MultilevelSwitchSetRequest(int _level) {
this((byte)0, _level);
}
public MultilevelSwitchSetRequest(byte _nodeId, int _level) {
super(_nodeId, CommandClass.SWITCH_MULTILEVEL, (byte) 0x01);
level = _level;
}
@Override
public byte[] getPayload() {
return asByteArray(level);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + level;
}
}

View File

@@ -0,0 +1,14 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandRequestMessage;
public class NodeInfoRequest extends NoCommandRequestMessage {
public NodeInfoRequest() {
super(ControllerMessageType.RequestNodeInfo);
}
public NodeInfoRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.RequestNodeInfo);
}
}

View File

@@ -0,0 +1,10 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.NoCommandResponseMessage;
public class NodeInfoResponse extends NoCommandResponseMessage {
public NodeInfoResponse() {
super(ControllerMessageType.RequestNodeInfo);
}
}

View File

@@ -0,0 +1,60 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
public class SendDataRequest extends RequestMessage {
public enum TransmissionState {
COMPLETE_OK((byte) 0x00, "Transmission complete and ACK received"),
COMPLETE_NO_ACK((byte) 0x01, "Transmission complete, no ACK received"),
COMPLETE_FAIL((byte) 0x02, "Transmission failed"),
COMPLETE_NOT_IDLE((byte) 0x03, "Transmission failed, network busy"),
COMPLETE_NOROUTE((byte) 0x04, "Tranmission complete, no return route");
public byte key;
public String label;
TransmissionState(byte _key, String _label) {
key = _key;
label = _label;
}
public static TransmissionState fromKey(byte _key) {
for (TransmissionState state : values()) {
if (state.key == _key)
return state;
}
return COMPLETE_FAIL;
}
}
private TransmissionState state;
private byte callbackId;
public SendDataRequest() {
super(ControllerMessageType.SendData, CommandClass.NO_OPERATION, (byte) 0);
}
@Override
public void fromPayload(byte[] _payload) {
if (CollectionUtils.length(_payload) > 5) {
callbackId = _payload[4];
state = TransmissionState.fromKey(_payload[5]);
}
}
public TransmissionState getState() {
return state;
}
public byte getCallbackId() {
return callbackId;
}
@Override
public String describe() {
return name() + " callbackId: " + callbackId + " state: " + state.name();
}
}

View File

@@ -0,0 +1,33 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.ResponseMessage;
public class SendDataResponse extends ResponseMessage {
private byte response;
public SendDataResponse() {
this((byte) 0);
}
public SendDataResponse(byte _response) {
super(ControllerMessageType.SendData, CommandClass.NO_OPERATION, (byte) 0);
response = _response;
}
@Override
public void fromPayload(byte[] _payload) {
if (CollectionUtils.length(_payload) > 0)
response = _payload[0];
}
public boolean isSuccess() {
return response != 0;
}
public String describe() {
return name() + ": " + (isSuccess() ? "SUCCESS" : "FAILURE");
}
}

View File

@@ -0,0 +1,19 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class ThermostatModeGetRequest extends SendDataRequestMessage {
public ThermostatModeGetRequest() {
this((byte) 0);
}
public ThermostatModeGetRequest(byte _nodeId) {
super(_nodeId, CommandClass.THERMOSTAT_MODE, (byte) 0x02);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,28 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatMode;
public class ThermostatModeSetRequest extends SendDataRequestMessage {
private ThermostatMode mode;
public ThermostatModeSetRequest() {
this((byte) 0, ThermostatMode.OFF);
}
public ThermostatModeSetRequest(byte _nodeId, ThermostatMode _mode) {
super(_nodeId, CommandClass.THERMOSTAT_MODE, (byte) 0x01);
mode = _mode;
}
@Override
public byte[] getPayload() {
return asByteArray(mode.data);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,28 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
public class ThermostatSetPointGetRequest extends SendDataRequestMessage {
private ThermostatSetPointIndex index;
public ThermostatSetPointGetRequest() {
this((byte) 0, ThermostatSetPointIndex.HEATING);
}
public ThermostatSetPointGetRequest(byte _nodeId, ThermostatSetPointIndex _index) {
super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x02);
index = _index;
}
@Override
public byte[] getPayload() {
return asByteArray(index.index);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,41 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.MessageUtil;
import com.lanternsoftware.zwave.message.RequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
public class ThermostatSetPointReportRequest extends RequestMessage {
private ThermostatSetPointIndex index;
private double temperature;
public ThermostatSetPointReportRequest() {
this((byte) 0);
}
public ThermostatSetPointReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x03);
}
@Override
public void fromPayload(byte[] _payload) {
nodeId = _payload[5];
index = ThermostatSetPointIndex.fromIndex(_payload[9]);
if (index != null)
temperature = MessageUtil.getTemperatureCelsius(_payload, 10);
}
public ThermostatSetPointIndex getIndex() {
return index;
}
public double getTemperatureCelsius() {
return temperature;
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + temperature;
}
}

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
import com.lanternsoftware.zwave.message.thermostat.ThermostatSetPointIndex;
public class ThermostatSetPointSetRequest extends SendDataRequestMessage {
private ThermostatSetPointIndex index;
private int level;
public ThermostatSetPointSetRequest() {
this(ThermostatSetPointIndex.HEATING, 72);
}
public ThermostatSetPointSetRequest(ThermostatSetPointIndex _index, int _level) {
this((byte)0, _index, _level);
}
public ThermostatSetPointSetRequest(byte _nodeId, ThermostatSetPointIndex _index, int _level) {
super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x01);
index = _index;
level = _level;
}
@Override
public byte[] getPayload() {
return asByteArray(index.index, (byte)9, (byte)level);
}
@Override
public String describe() {
return name() + " node: " + nodeId + " level: " + level;
}
}

View File

@@ -0,0 +1,19 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.SendDataRequestMessage;
public class ThermostatSetPointSupportedGetRequest extends SendDataRequestMessage {
public ThermostatSetPointSupportedGetRequest() {
this((byte) 0);
}
public ThermostatSetPointSupportedGetRequest(byte _nodeId) {
super(_nodeId, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x04);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,46 @@
package com.lanternsoftware.zwave.message.impl;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.message.CommandClass;
import com.lanternsoftware.zwave.message.ControllerMessageType;
import com.lanternsoftware.zwave.message.RequestMessage;
import java.util.Set;
import java.util.TreeSet;
public class ThermostatSetPointSupportedReportRequest extends RequestMessage {
private Set<Byte> supportedSetPointIndices;
public ThermostatSetPointSupportedReportRequest() {
this((byte) 0);
}
public ThermostatSetPointSupportedReportRequest(byte _nodeId) {
super(_nodeId, ControllerMessageType.ApplicationCommandHandler, CommandClass.THERMOSTAT_SETPOINT, (byte) 0x05);
}
@Override
public void fromPayload(byte[] _payload) {
supportedSetPointIndices = new TreeSet<>();
for (int i = 9; i < _payload.length - 1; ++i) {
for (int bit = 0; bit < 8; ++bit) {
if ((_payload[i] & (1 << bit)) != 0) {
supportedSetPointIndices.add((byte)(((i - 9) << 3) + bit));
}
}
}
}
public Set<Byte> getSupportedSetPointIndices() {
return supportedSetPointIndices;
}
public boolean isSupportedIndex(byte _index) {
return CollectionUtils.contains(supportedSetPointIndices, _index);
}
@Override
public String describe() {
return name() + " node: " + nodeId;
}
}

View File

@@ -0,0 +1,23 @@
package com.lanternsoftware.zwave.message.thermostat;
public enum ThermostatMode {
OFF((byte)0),
HEAT((byte)1),
COOL((byte)2),
AUTO((byte)3),
AUXILIARY((byte)4);
public final byte data;
ThermostatMode(byte _data) {
data = _data;
}
public static ThermostatMode fromByte(byte _bt) {
for (ThermostatMode mode : values()) {
if (mode.data == _bt)
return mode;
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
package com.lanternsoftware.zwave.message.thermostat;
public enum ThermostatSetPointIndex {
HEATING((byte)1),
COOLING((byte)2),
FURNACE((byte)7),
DRY_AIR((byte)8),
MOIST_AIR((byte)9),
AUTO_CHANGEOVER((byte)10),
HEATING_ECON((byte)11),
COOLING_ECON((byte)12),
AWAY_HEATING((byte)13),
COOLING_HEATING((byte)14),
HEATING_MINIMUM((byte)101),
COOLING_MINIMUM((byte)102),
FURNACE_MINIMUM((byte)107),
DRY_AIR_MINIMUM((byte)108),
MOIST_AIR_MINIMUM((byte)109),
AUTO_CHANGEOVER_MINIMUM((byte)110),
HEATING_ECON_MINIMUM((byte)111),
COOLING_ECON_MINIMUM((byte)112),
AWAY_HEATING_MINIMUM((byte)113),
COOLING_HEATING_MINIMUM((byte)114),
HEATING_MAXIMUM((byte)201),
COOLING_MAXIMUM((byte)202),
FURNACE_MAXIMUM((byte)207),
DRY_AIR_MAXIMUM((byte)208),
MOIST_AIR_MAXIMUM((byte)209),
AUTO_CHANGEOVER_MAXIMUM((byte)210),
HEATING_ECON_MAXIMUM((byte)211),
COOLING_ECON_MAXIMUM((byte)212),
AWAY_HEATING_MAXIMUM((byte)213),
COOLING_HEATING_MAXIMUM((byte)214);
public final byte index;
ThermostatSetPointIndex(byte _index) {
index = _index;
}
public static ThermostatSetPointIndex fromIndex(byte _index) {
for (ThermostatSetPointIndex idx : values()) {
if (idx.index == _index)
return idx;
}
return null;
}
}

View File

@@ -0,0 +1,34 @@
package com.lanternsoftware.zwave.node;
import com.lanternsoftware.zwave.message.CommandClass;
import java.util.Set;
public class Node {
private byte id;
private Set<CommandClass> commandClasses;
public Node() {
}
public Node(byte _id, Set<CommandClass> _commandClasses) {
id = _id;
commandClasses = _commandClasses;
}
public byte getId() {
return id;
}
public void setId(byte _id) {
id = _id;
}
public Set<CommandClass> getCommandClasses() {
return commandClasses;
}
public void setCommandClasses(Set<CommandClass> _commandClasses) {
commandClasses = _commandClasses;
}
}

View File

@@ -0,0 +1,108 @@
package com.lanternsoftware.zwave.node;
import com.lanternsoftware.util.CollectionUtils;
import com.lanternsoftware.zwave.controller.Controller;
import com.lanternsoftware.zwave.message.IMessageSubscriber;
import com.lanternsoftware.zwave.message.MessageEngine;
import com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest;
import com.lanternsoftware.zwave.message.impl.ControllerInitialDataResponse;
import com.lanternsoftware.zwave.message.impl.GetControllerIdResponse;
import com.lanternsoftware.zwave.message.impl.NodeInfoRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NodeManager {
private static final Logger logger = LoggerFactory.getLogger(NodeManager.class);
private Controller controller;
private ControllerInitialDataResponse initialDataResponse;
private GetControllerIdResponse controllerIdResponse;
private final Set<Byte> missingNodes = new HashSet<>();
private Map<Byte, Node> nodes = new HashMap<>();
public NodeManager(final Controller _controller) {
controller = _controller;
MessageEngine.subscribe(new IMessageSubscriber<ControllerInitialDataResponse>() {
@Override
public Class<ControllerInitialDataResponse> getHandledMessageClass() {
return ControllerInitialDataResponse.class;
}
@Override
public void onMessage(ControllerInitialDataResponse _response) {
synchronized (NodeManager.this) {
initialDataResponse = _response;
init();
}
}
});
MessageEngine.subscribe(new IMessageSubscriber<GetControllerIdResponse>() {
@Override
public Class<GetControllerIdResponse> getHandledMessageClass() {
return GetControllerIdResponse.class;
}
@Override
public void onMessage(GetControllerIdResponse _response) {
synchronized (NodeManager.this) {
controllerIdResponse = _response;
init();
}
}
});
MessageEngine.subscribe(new IMessageSubscriber<ApplicationUpdateRequest>() {
@Override
public Class<ApplicationUpdateRequest> getHandledMessageClass() {
return ApplicationUpdateRequest.class;
}
@Override
public void onMessage(ApplicationUpdateRequest _request) {
synchronized (NodeManager.this) {
if (missingNodes.remove(_request.getNodeId()))
nodes.put(_request.getNodeId(), new Node(_request.getNodeId(), CollectionUtils.asHashSet(_request.getCommandClasses())));
logger.debug("Received command classes for node: {}", _request.getNodeId());
requestNodeInfo();
}
}
});
}
private void init() {
if (!isStarted())
return;
missingNodes.clear();
logger.info("Node Ids:{}", CollectionUtils.transformToCommaSeparated(initialDataResponse.getNodeIds(), String::valueOf));
// missingNodes.addAll(CollectionUtils.filter(initialDataResponse.getNodeIds(), _b->_b != controllerIdResponse.getControllerId()));
requestNodeInfo();
}
private void requestNodeInfo() {
if (!missingNodes.isEmpty())
controller.send(new NodeInfoRequest(CollectionUtils.getFirst(missingNodes)));
else
notify();
}
private boolean isStarted() {
return ((initialDataResponse != null) && (controllerIdResponse != null));
}
public void waitForStartup() {
synchronized (this) {
if (!isStarted()) {
try {
wait();
} catch (InterruptedException _e) {
_e.printStackTrace();
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
com.lanternsoftware.zwave.message.impl.ApplicationUpdateRequest
com.lanternsoftware.zwave.message.impl.BinarySwitchSetRequest
com.lanternsoftware.zwave.message.impl.ByteMessage
com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesRequest
com.lanternsoftware.zwave.message.impl.ControllerCapabilitiesResponse
com.lanternsoftware.zwave.message.impl.ControllerInitialDataRequest
com.lanternsoftware.zwave.message.impl.ControllerInitialDataResponse
com.lanternsoftware.zwave.message.impl.DeviceManufacturerActionRequest
com.lanternsoftware.zwave.message.impl.GetControllerIdRequest
com.lanternsoftware.zwave.message.impl.GetControllerIdResponse
com.lanternsoftware.zwave.message.impl.MultilevelSensorGetRequest
com.lanternsoftware.zwave.message.impl.MultilevelSensorReportRequest
com.lanternsoftware.zwave.message.impl.MultilevelSwitchReportRequest
com.lanternsoftware.zwave.message.impl.MultilevelSwitchSetRequest
com.lanternsoftware.zwave.message.impl.NodeInfoRequest
com.lanternsoftware.zwave.message.impl.NodeInfoResponse
com.lanternsoftware.zwave.message.impl.SendDataRequest
com.lanternsoftware.zwave.message.impl.SendDataResponse
com.lanternsoftware.zwave.message.impl.ThermostatModeGetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointGetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointReportRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointSetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointSupportedGetRequest
com.lanternsoftware.zwave.message.impl.ThermostatSetPointSupportedReportRequest

View File

@@ -0,0 +1,8 @@
package com.lanternsoftware.zwave;
public class TestBits {
public static void main(String[] args) {
System.out.println(Integer.toBinaryString(6));
System.out.println(Integer.toBinaryString(0x18));
}
}

21
zwave/pom.xml Normal file
View File

@@ -0,0 +1,21 @@
<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>
<packaging>pom</packaging>
<groupId>com.lanternsoftware.zwave</groupId>
<artifactId>zwave-reactor</artifactId>
<name>zwave-reactor</name>
<version>1.0.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<modules>
<module>lantern-datamodel-zwave</module>
<module>lantern-service-thermometer</module>
<module>lantern-service-zwave</module>
<module>lantern-uirt</module>
<module>lantern-zwave</module>
</modules>
</project>