Fixed build dependency issues by moving hal core into hal-core folder/subproject

This commit is contained in:
Ziver Koc 2020-11-10 18:36:02 +01:00
parent f1da2c5a4d
commit ded778fd11
138 changed files with 195 additions and 183 deletions

View file

@ -0,0 +1,464 @@
package se.hal;
import se.hal.intf.*;
import se.hal.struct.Event;
import se.hal.struct.Sensor;
import zutil.db.DBConnection;
import zutil.log.LogUtil;
import zutil.plugin.PluginManager;
import zutil.ui.Configurator;
import zutil.ui.Configurator.PostConfigurationActionListener;
import zutil.ui.Configurator.PreConfigurationActionListener;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class manages all SensorController and EventController objects
*/
@SuppressWarnings("RedundantCast")
public class ControllerManager implements HalSensorReportListener,
HalEventReportListener,
PreConfigurationActionListener,
PostConfigurationActionListener {
private static final Logger logger = LogUtil.getLogger();
private static ControllerManager instance;
/** All available sensor plugins **/
private List<Class<? extends HalSensorConfig>> availableSensors = new ArrayList<>();
/** List of all registered sensors **/
private List<Sensor> registeredSensors = Collections.synchronizedList(new ArrayList<Sensor>());
/** List of auto detected sensors **/
private List<Sensor> detectedSensors = Collections.synchronizedList(new ArrayList<Sensor>());
/** List of sensors that are currently being reconfigured **/
private List<Sensor> limboSensors = Collections.synchronizedList(new LinkedList<Sensor>());
/** All available event plugins **/
private List<Class<? extends HalEventConfig>> availableEvents = new ArrayList<>();
/** List of all registered events **/
private List<Event> registeredEvents = Collections.synchronizedList(new ArrayList<Event>());
/** List of auto detected events **/
private List<Event> detectedEvents = Collections.synchronizedList(new ArrayList<Event>());
/** List of all registered events **/
private List<Event> limboEvents = Collections.synchronizedList(new LinkedList<Event>());
/** A map of all instantiated controllers **/
private HashMap<Class,HalAbstractController> controllerMap = new HashMap<>();
/////////////////////////////// SENSORS ///////////////////////////////////
/**
* Register a Sensor instance on the manager.
* The manager will start to save reported data for the registered Sensor.
*/
public void register(Sensor sensor) {
if(sensor.getDeviceConfig() == null) {
logger.warning("Sensor config is null: "+ sensor);
return;
}
if(!availableSensors.contains(sensor.getDeviceConfig().getClass())) {
logger.warning("Sensor data plugin not available: "+ sensor.getDeviceConfig().getClass());
return;
}
logger.info("Registering new sensor(id: "+ sensor.getId() +"): "+ sensor.getDeviceConfig().getClass());
Class<? extends HalSensorController> c = sensor.getController();
HalSensorController controller = getControllerInstance(c);
if(controller != null)
controller.register(sensor.getDeviceConfig());
registeredSensors.add(sensor);
detectedSensors.remove(findSensor(sensor.getDeviceConfig(), detectedSensors)); // Remove if this device was detected
}
/**
* Deregisters a Sensor from the manager.
* Data reported on the Sensor will no longer be saved but already saved data will not be modified.
* The Controller that owns the Sensor will be deallocated if it has no more registered devices.
*/
public void deregister(Sensor sensor){
if(sensor.getDeviceConfig() == null) {
logger.warning("Sensor config is null: "+ sensor);
return;
}
Class<? extends HalSensorController> c = sensor.getController();
HalSensorController controller = (HalSensorController) controllerMap.get(c);
if (controller != null) {
logger.info("Deregistering sensor(id: "+ sensor.getId() +"): "+ sensor.getDeviceConfig().getClass());
controller.deregister(sensor.getDeviceConfig());
registeredSensors.remove(sensor);
removeControllerIfEmpty(controller);
} else {
logger.warning("Controller not instantiated:"+ sensor.getController());
}
}
/**
* Registers a Sensor class type as usable by the manager
*/
public void addAvailableSensor(Class<? extends HalSensorConfig> sensorClass) {
if ( ! availableSensors.contains(sensorClass))
availableSensors.add(sensorClass);
}
/**
* @return a List of all available Sensors that can be registered to this manager
*/
public List<Class<? extends HalSensorConfig>> getAvailableSensors(){
return availableSensors;
}
/**
* @return a List of Sensor instances that have been registered to this manager
*/
public List<Sensor> getRegisteredSensors(){
return registeredSensors;
}
/**
* @return a List of Sensor instances that have been reported but not registered on the manager
*/
public List<Sensor> getDetectedSensors(){
return detectedSensors;
}
/**
* Removes all auto detected sensors.
*/
public void clearDetectedSensors(){
detectedSensors.clear();
}
/**
* Called by Controllers to report received Sensor data
*/
@Override
public void reportReceived(HalSensorConfig sensorConfig, HalSensorData sensorData) {
try{
DBConnection db = HalContext.getDB();
Sensor sensor = findSensor(sensorConfig, registeredSensors);
if (sensor != null) {
logger.finest("Received report from sensor("+sensorConfig.getClass().getSimpleName()+"): "+ sensorConfig);
PreparedStatement stmt =
db.getPreparedStatement("INSERT INTO sensor_data_raw (timestamp, sensor_id, data) VALUES(?, ?, ?)");
stmt.setLong(1, sensorData.getTimestamp());
stmt.setLong(2, sensor.getId());
stmt.setDouble(3, sensorData.getData());
DBConnection.exec(stmt);
}
else { // unknown sensor
logger.finest("Received report from unregistered sensor" +
"("+sensorConfig.getClass().getSimpleName()+"): "+ sensorConfig);
sensor = findSensor(sensorConfig, detectedSensors);
if(sensor == null) {
sensor = new Sensor();
detectedSensors.add(sensor);
}
sensor.setDeviceConfig(sensorConfig);
}
sensor.setDeviceData(sensorData);
// call listeners
for(HalDeviceReportListener<Sensor> listener : sensor.getReportListeners())
listener.receivedReport(sensor);
}catch (SQLException e){
logger.log(Level.WARNING, "Unable to store sensor report", e);
}
}
private static Sensor findSensor(HalSensorConfig sensorData, List<Sensor> list){
for (int i=0; i<list.size(); ++i) { // Don't use foreach for concurrency reasons
Sensor s = list.get(i);
if (sensorData.equals(s.getDeviceConfig())) {
return s;
}
}
return null;
}
//////////////////////////////// EVENTS ///////////////////////////////////
/**
* Register a Event instance on the manager.
* The manager will start to save reported data for the registered Event.
*/
public void register(Event event) {
if(event.getDeviceConfig() == null) {
logger.warning("Event config is null: "+ event);
return;
}
if(!availableEvents.contains(event.getDeviceConfig().getClass())) {
logger.warning("Event data plugin not available: "+ event.getDeviceConfig().getClass());
return;
}
logger.info("Registering new event(id: "+ event.getId() +"): "+ event.getDeviceConfig().getClass());
Class<? extends HalEventController> c = event.getController();
HalEventController controller = getControllerInstance(c);
if(controller != null)
controller.register(event.getDeviceConfig());
registeredEvents.add(event);
detectedEvents.remove(findEvent(event.getDeviceConfig(), detectedEvents)); // Remove if this device was detected
}
/**
* Deregisters a Event from the manager.
* Data reported on the Event will no longer be saved but already saved data will not be modified.
* The Controller that owns the Event will be deallocated if it has no more registered devices.
*/
public void deregister(Event event){
if(event.getDeviceConfig() == null) {
logger.warning("Event config is null: "+ event);
return;
}
Class<? extends HalEventController> c = event.getController();
HalEventController controller = (HalEventController) controllerMap.get(c);
if (controller != null) {
logger.info("Deregistering event(id: "+ event.getId() +"): "+ event.getDeviceConfig().getClass());
controller.deregister(event.getDeviceConfig());
registeredEvents.remove(event);
removeControllerIfEmpty(controller);
} else {
logger.warning("Controller not instantiated: "+ event.getController());
}
}
/**
* Registers a Event class type as usable by the manager
*/
public void addAvailableEvent(Class<? extends HalEventConfig> eventClass) {
if ( ! availableEvents.contains(eventClass))
availableEvents.add(eventClass);
}
/**
* @return a List of all available Events that can be registered to this manager
*/
public List<Class<? extends HalEventConfig>> getAvailableEvents(){
return availableEvents;
}
/**
* @return a List of Sensor instances that have been registered to this manager
*/
public List<Event> getRegisteredEvents(){
return registeredEvents;
}
/**
* @return a List of Event instances that have been reported but not registered on the manager
*/
public List<Event> getDetectedEvents(){
return detectedEvents;
}
/**
* Removes all auto detected events.
*/
public void clearDetectedEvents(){
detectedEvents.clear();
}
/**
* Called by Controllers to report received Event data
*/
@Override
public void reportReceived(HalEventConfig eventConfig, HalEventData eventData) {
try {
DBConnection db = HalContext.getDB();
Event event = findEvent(eventConfig, registeredEvents);
if (event != null) {
logger.finest("Received report from event("+eventConfig.getClass().getSimpleName()+"): "+ eventConfig);
PreparedStatement stmt =
db.getPreparedStatement("INSERT INTO event_data_raw (timestamp, event_id, data) VALUES(?, ?, ?)");
stmt.setLong(1, eventData.getTimestamp());
stmt.setLong(2, event.getId());
stmt.setDouble(3, eventData.getData());
DBConnection.exec(stmt);
}
else { // unknown sensor
logger.info("Received report from unregistered event" +
"("+eventConfig.getClass().getSimpleName()+"): "+ eventConfig);
event = findEvent(eventConfig, detectedEvents);
if(event == null) {
event = new Event();
detectedEvents.add(event);
}
event.setDeviceConfig(eventConfig);
}
event.setDeviceData(eventData);
// call listeners
for(HalDeviceReportListener<Event> listener : event.getReportListeners())
listener.receivedReport(event);
}catch (SQLException e){
logger.log(Level.WARNING, "Unable to store event report", e);
}
}
private static Event findEvent(HalEventConfig eventData, List<Event> list){
for (int i=0; i<list.size(); ++i) { // Don't use foreach for concurrency reasons
Event e = list.get(i);
if (eventData.equals(e.getDeviceConfig())) {
return e;
}
}
return null;
}
public void send(Event event){
HalEventController controller = getControllerInstance(event.getController());
if(controller != null) {
controller.send(event.getDeviceConfig(), event.getDeviceData());
reportReceived(event.getDeviceConfig(), event.getDeviceData()); // save action to db
}
else
logger.warning("No controller found for event id: "+ event.getId());
}
/////////////////////////////// GENERAL ///////////////////////////////////
/**
* @return all instantiated controllers.
*/
public List<HalAbstractController> getControllers() {
return new ArrayList(controllerMap.values());
}
@Override
public void preConfigurationAction(Configurator configurator, Object obj) {
if(obj instanceof HalSensorConfig) {
Sensor sensor = findSensor((HalSensorConfig) obj, registeredSensors);
if(sensor != null){
deregister(sensor);
limboSensors.add(sensor);
}
}
else if(obj instanceof HalEventConfig) {
Event event = findEvent((HalEventConfig) obj, registeredEvents);
if(event != null){
deregister(event);
limboEvents.add(event);
}
}
}
@Override
public void postConfigurationAction(Configurator configurator, Object obj) {
if(obj instanceof HalSensorConfig) {
Sensor sensor = findSensor((HalSensorConfig) obj, limboSensors);
if(sensor != null){
register(sensor);
limboSensors.remove(sensor);
}
}
else if(obj instanceof HalEventConfig) {
Event event = findEvent((HalEventConfig) obj, limboEvents);
if(event != null){
register(event);
limboEvents.remove(event);
}
}
}
private <T extends HalAbstractController> T getControllerInstance(Class<T> c){
HalAbstractController controller;
if (controllerMap.containsKey(c)) {
controller = controllerMap.get(c);
} else {
try {
// Instantiate controller
controller = c.newInstance();
if (controller instanceof HalAutoScannableController &&
! ((HalAutoScannableController) controller).isAvailable()) {
logger.warning("Controller is not ready: " + c.getName());
return null;
}
logger.info("Instantiating new controller: " + c.getName());
controller.initialize();
if(controller instanceof HalSensorController) {
((HalSensorController) controller).setListener(this);
}
if(controller instanceof HalEventController) {
((HalEventController) controller).setListener(this);
}
controllerMap.put(c, controller);
} catch (Exception e){
logger.log(Level.SEVERE, "Unable to instantiate controller: " + c.getName(), e);
return null;
}
}
return (T) controller;
}
private void removeControllerIfEmpty(Object controller){
if (controller instanceof HalAutoScannableController)
return; // Don't do anything if controller is scannable
int size = Integer.MAX_VALUE;
if(controller instanceof HalSensorController)
size = ((HalSensorController) controller).size();
else if(controller instanceof HalEventController)
size = ((HalEventController) controller).size();
if(size < 0){
// Remove controller as it has no more registered sensors
logger.info("Closing controller as it has no more registered devices: " + controller.getClass().getName());
controllerMap.remove(controller.getClass());
if(controller instanceof HalSensorController)
((HalSensorController) controller).close();
else if(controller instanceof HalEventController)
((HalEventController) controller).close();
}
}
public static void initialize(PluginManager pluginManager){
ControllerManager manager = new ControllerManager();
for (Iterator<Class<? extends HalSensorConfig>> it = pluginManager.getClassIterator(HalSensorConfig.class);
it.hasNext(); ){
manager.addAvailableSensor(it.next());
}
for (Iterator<Class<? extends HalEventConfig>> it = pluginManager.getClassIterator(HalEventConfig.class);
it.hasNext(); ){
manager.addAvailableEvent(it.next());
}
for (Iterator<Class<? extends HalAutoScannableController>> it = pluginManager.getClassIterator(HalAutoScannableController.class);
it.hasNext(); ){
Class controller = it.next();
if (controller.isAssignableFrom(HalAbstractController.class))
manager.getControllerInstance(controller); // Instantiate controller
}
instance = manager;
}
public static ControllerManager getInstance(){
return instance;
}
}

View file

@ -0,0 +1,246 @@
package se.hal;
import se.hal.struct.User;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.db.DBUpgradeHandler;
import zutil.db.SQLResultHandler;
import zutil.db.handler.PropertiesSQLResult;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import java.io.File;
import java.io.FileReader;
import java.sql.*;
import java.util.*;
import java.util.logging.Logger;
public class HalContext {
private static final Logger logger = LogUtil.getLogger();
// Constants
public static final String PROPERTY_DB_VERSION = "hal.db_version";
public static final String PROPERTY_HTTP_PORT = "hal.http_port";
public static final String PROPERTY_MAP_BACKGROUND_IMAGE = "hal.map_bgimage";
private static final String CONF_FILE = "hal.conf";
private static final String DB_FILE = "hal.db";
private static final String DEFAULT_DB_FILE = "hal-default.db";
// Variables
private static DBConnection db; // TODO: Should probably be a db pool as we have multiple threads accessing the DB
private static HashMap<String,String> registeredConf = new HashMap<>();
private static Properties fileConf = new Properties();
private static Properties dbConf = new Properties();;
static {
// Set default values to get Hal up and running
fileConf.setProperty(PROPERTY_HTTP_PORT, "" + 8080);
}
public static void initialize(){
try {
// Read conf
if (FileUtil.find(CONF_FILE) != null) {
FileReader in = new FileReader(CONF_FILE);
fileConf.load(in);
in.close();
} else {
logger.info("No hal.conf file found");
}
if (FileUtil.find(DEFAULT_DB_FILE) == null){
logger.severe("Unable to find default DB: " + DEFAULT_DB_FILE);
System.exit(1);
}
// Init DB
File dbFile = FileUtil.find(DB_FILE);
db = new DBConnection(DBConnection.DBMS.SQLite, DB_FILE);
if(dbFile == null){
logger.info("No database file found, creating new DB...");
} else {
dbConf = db.exec("SELECT * FROM conf", new PropertiesSQLResult());
}
// Upgrade DB needed?
DBConnection referenceDB = new DBConnection(DBConnection.DBMS.SQLite, DEFAULT_DB_FILE);
Properties defaultDBConf =
referenceDB.exec("SELECT * FROM conf", new PropertiesSQLResult());
// Check DB version
final int defaultDBVersion = Integer.parseInt(defaultDBConf.getProperty(PROPERTY_DB_VERSION));
final int dbVersion = (dbConf.getProperty(PROPERTY_DB_VERSION) != null ?
Integer.parseInt(dbConf.getProperty(PROPERTY_DB_VERSION)) :
-1);
logger.info("DB version: "+ dbVersion);
if(defaultDBVersion > dbVersion ) {
logger.info("Starting DB upgrade from v" + dbVersion + " to v" + defaultDBVersion + "...");
if(dbFile != null){
File backupDB = FileUtil.getNextFile(dbFile);
logger.fine("Backing up DB to: "+ backupDB);
FileUtil.copy(dbFile, backupDB);
}
logger.fine(String.format("Upgrading DB (from: v%s, to: v%s)...", dbVersion, defaultDBVersion));
final DBUpgradeHandler handler = new DBUpgradeHandler(referenceDB);
handler.addIgnoredTable("db_version_history");
handler.addIgnoredTable("sqlite_sequence"); // sqlite internal
handler.setTargetDB(db);
logger.fine("Performing pre-upgrade activities");
// Read upgrade path preferences from the reference database
referenceDB.exec("SELECT * FROM db_version_history"
+ " WHERE db_version <= " + defaultDBVersion
+ " AND db_version > " + dbVersion,
new SQLResultHandler<Object>() {
@Override
public Object handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
while(result.next()){
if(result.getBoolean("force_upgrade")){
logger.fine("Forced upgrade enabled");
handler.setForcedDBUpgrade(true); //set to true if any of the intermediate db version requires it.
}
}
return null;
}
});
handler.upgrade();
logger.fine("Performing post-upgrade activities");
// Read upgrade path preferences from the reference database
referenceDB.exec("SELECT * FROM db_version_history"
+ " WHERE db_version <= " + defaultDBVersion
+ " AND db_version > " + dbVersion,
new SQLResultHandler<Object>() {
@Override
public Object handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
boolean clearExternalAggrData = false;
boolean clearInternalAggrData = false;
while(result.next()){
if(result.getBoolean("clear_external_aggr_data"))
clearExternalAggrData = true;
if(result.getBoolean("clear_internal_aggr_data"))
clearInternalAggrData = true;
}
if(clearExternalAggrData){
logger.fine("Clearing external aggregate data");
db.exec("DELETE FROM sensor_data_aggr WHERE sensor_id = "
+ "(SELECT sensor.id FROM user, sensor WHERE user.external == 1 AND sensor.user_id = user.id)");
}
if(clearInternalAggrData){
logger.fine("Clearing local aggregate data");
db.exec("DELETE FROM sensor_data_aggr WHERE sensor_id IN "
+ "(SELECT sensor.id FROM user, sensor WHERE user.external == 0 AND sensor.user_id = user.id)");
//update all internal sensors aggregation version to indicate for peers that they need to re-sync all data
db.exec("UPDATE sensor SET aggr_version = (aggr_version+1) WHERE id = "
+ "(SELECT sensor.id FROM user, sensor WHERE user.external == 0 AND sensor.user_id = user.id)");
}
return null;
}
});
// Check if there is a local user
User localUser = User.getLocalUser(db);
if (localUser == null){
logger.fine("Creating local user.");
localUser = new User();
localUser.setExternal(false);
localUser.save(db);
}
logger.info("DB upgrade done");
setProperty(PROPERTY_DB_VERSION, defaultDBConf.getProperty(PROPERTY_DB_VERSION));
}
referenceDB.close();
} catch (Exception e){
throw new RuntimeException(e);
}
}
public static Map<String,String> getProperties() {
HashMap map = new HashMap();
map.putAll(registeredConf);
map.putAll(dbConf);
map.putAll(fileConf);
return map;
}
public static void registerProperty(String key) {
registeredConf.put(key, "");
}
public static boolean containsProperty(String key) {
return !ObjectUtil.isEmpty(getStringProperty(key));
}
public static String getStringProperty(String key){
registerProperty(key);
String value = null;
if (fileConf != null)
value = fileConf.getProperty(key);
if (dbConf != null && value == null)
value = dbConf.getProperty(key);
return value;
}
public static String getStringProperty(String key, String defaultValue){
if (!HalContext.containsProperty(key))
return defaultValue;
return getStringProperty(key);
}
public static int getIntegerProperty(String key){
return Integer.parseInt(getStringProperty(key));
}
public static int getIntegerProperty(String key, int defaultValue){
if (!HalContext.containsProperty(key))
return defaultValue;
return getIntegerProperty(key);
}
public static boolean getBooleanProperty(String key) {
return Boolean.parseBoolean(getStringProperty(key));
}
public static boolean getBooleanProperty(String key, boolean defaultValue) {
if (!HalContext.containsProperty(key))
return defaultValue;
return getBooleanProperty(key);
}
public static void setProperty(String key, String value) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement("REPLACE INTO conf (key, value) VALUES (?, ?)");
stmt.setObject(1, key);
stmt.setObject(2, value);
DBConnection.exec(stmt);
dbConf.setProperty(key, value);
}
public static DBConnection getDB(){
return db;
}
/**
* For testing purposes.
*/
public static void setDB(DBConnection db){
HalContext.db = db;
}
}

View file

@ -0,0 +1,166 @@
package se.hal;
import se.hal.intf.HalDaemon;
import se.hal.intf.HalWebPage;
import se.hal.intf.HalJsonPage;
import se.hal.page.*;
import se.hal.struct.Event;
import se.hal.struct.PluginConfig;
import se.hal.struct.Sensor;
import se.hal.struct.TriggerFlow;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.net.http.HttpServer;
import zutil.net.http.page.HttpFilePage;
import zutil.net.http.page.HttpRedirectPage;
import zutil.plugin.PluginData;
import zutil.plugin.PluginManager;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Main class for Hal
*/
public class HalServer {
private static final Logger logger = LogUtil.getLogger();
private static ScheduledExecutorService daemonExecutor;
private static List<HalDaemon> daemons = new ArrayList<>();
private static HttpServer http;
private static List<HalWebPage> pages = new ArrayList<>();
private static PluginManager pluginManager;
public static void main(String[] args) throws InterruptedException {
try {
// init logging
LogUtil.readConfiguration("logging.properties");
// init DB and other configurations
HalContext.initialize();
DBConnection db = HalContext.getDB();
// ------------------------------------
// Init Plugins
// ------------------------------------
logger.info("Looking for plugins.");
pluginManager = new PluginManager();
// Disable plugins based on settings
for (PluginData plugin : getPlugins()) {
PluginConfig pluginConfig = PluginConfig.getPluginConfig(db, plugin.getName());
if (pluginConfig != null && !pluginConfig.isEnabled()) {
logger.info("Disabling plugin '" + plugin.getName() + "'.");
plugin.setEnabled(false);
}
}
// ------------------------------------
// Init Managers
// ------------------------------------
logger.info("Initializing managers.");
HalAlertManager.initialize();
ControllerManager.initialize(pluginManager);
TriggerManager.initialize(pluginManager);
// ------------------------------------
// Import sensors,events and triggers
// ------------------------------------
logger.info("Initializing Sensors and Events.");
for (Sensor sensor : Sensor.getLocalSensors(db)) {
ControllerManager.getInstance().register(sensor);
}
for (Event event : Event.getLocalEvents(db)) {
ControllerManager.getInstance().register(event);
}
// Import triggers
for (TriggerFlow flow : TriggerFlow.getTriggerFlows(db)) {
TriggerManager.getInstance().register(flow);
}
// ------------------------------------
// Init daemons
// ------------------------------------
logger.info("Initializing daemons.");
// We set only one thread for easier troubleshooting
daemonExecutor = Executors.newScheduledThreadPool(1);
for (Iterator<HalDaemon> it = pluginManager.getSingletonIterator(HalDaemon.class); it.hasNext(); ) {
HalDaemon daemon = it.next();
logger.info("Registering daemon: " + daemon.getClass());
registerDaemon(daemon);
}
// ------------------------------------
// Init http server
// ------------------------------------
logger.info("Initializing HTTP Server.");
HalWebPage.getRootNav().createSubNav("Sensors");
HalWebPage.getRootNav().createSubNav("Events").setWeight(100);
HalWebPage.getRootNav().createSubNav("Settings").setWeight(200);
http = new HttpServer(HalContext.getIntegerProperty(HalContext.PROPERTY_HTTP_PORT));
http.setDefaultPage(new HttpFilePage(FileUtil.find("resource/web/")));
http.setPage("/", new HttpRedirectPage("/map"));
http.setPage(HalAlertManager.getInstance().getUrl(), HalAlertManager.getInstance());
for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalJsonPage.class); it.hasNext(); )
registerPage(it.next());
for (Iterator<HalWebPage> it = pluginManager.getSingletonIterator(HalWebPage.class); it.hasNext(); )
registerPage(it.next());
http.start();
} catch (Exception e) {
logger.log(Level.SEVERE, "Startup failed.", e);
System.exit(1);
}
}
public static void setPluginEnabled(String name, boolean enabled) throws SQLException {
DBConnection db = HalContext.getDB();
PluginConfig pluginConfig = PluginConfig.getPluginConfig(db, name);
if (pluginConfig == null)
pluginConfig = new PluginConfig(name);
logger.info("Plugin '" + name + "' has been " + (enabled ? "enabled" : "disabled") + ", change will take affect after restart.");
pluginManager.getPluginData(name).setEnabled(enabled);
pluginConfig.setEnabled(enabled);
pluginConfig.save(db);
}
public static List<PluginData> getPlugins() {
return pluginManager.toArray();
}
public static void registerDaemon(HalDaemon daemon){
daemons.add(daemon);
daemon.initiate(daemonExecutor);
}
public static void registerPage(HalWebPage page){
pages.add(page);
http.setPage(page.getId(), page);
}
}

View file

@ -0,0 +1,109 @@
package se.hal;
import se.hal.intf.HalAction;
import se.hal.intf.HalTrigger;
import se.hal.struct.TriggerFlow;
import zutil.log.LogUtil;
import zutil.plugin.PluginManager;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handles all triggers registered on Hal
*/
public class TriggerManager {
private static final Logger logger = LogUtil.getLogger();
private static final long EVALUATION_INTERVAL = 5 * 1000;
private static TriggerManager instance;
private ArrayList<Class<? extends HalTrigger>> availableTriggers = new ArrayList<>();
private ArrayList<Class<? extends HalAction>> availableActions = new ArrayList<>();
private ArrayList<TriggerFlow> triggerFlows = new ArrayList<>();
private ScheduledExecutorService executor;
public void setEvaluationInterval(long interval) {
if (executor != null)
executor.shutdownNow();
executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
evaluateAndExecute();
} catch (Exception e){ logger.log(Level.SEVERE, "Trigger Evaluation Thread has Crashed", e); }
}
}, 0, interval, TimeUnit.MILLISECONDS);
}
public void addAvailableTrigger(Class<? extends HalTrigger> clazz) {
if ( ! availableTriggers.contains(clazz))
availableTriggers.add(clazz);
}
public List<Class<? extends HalTrigger>> getAvailableTriggers() {
return availableTriggers;
}
public void addAvailableAction(Class<? extends HalAction> clazz) {
if ( ! availableActions.contains(clazz))
availableActions.add(clazz);
}
public List<Class<? extends HalAction>> getAvailableActions() {
return availableActions;
}
public void register(TriggerFlow flow){
if ( ! triggerFlows.contains(flow))
triggerFlows.add(flow);
}
/**
* Main execution method.
* This method will go through all flows and evaluate them. If the
* evaluation of a trigger returns true then its execute method will be called.
*/
public synchronized void evaluateAndExecute() {
for (int i = 0; i < triggerFlows.size(); i++) { // avoid foreach as triggerFlows can change while we are running
TriggerFlow flow = triggerFlows.get(i);
if (flow.evaluate()) {
logger.fine("Flow "+ flow.getId() +" evaluated true, executing actions");
flow.execute();
flow.reset();
}
}
}
public static void initialize(PluginManager pluginManager) {
TriggerManager manager = new TriggerManager();
for (Iterator<Class<? extends HalTrigger>> it = pluginManager.getClassIterator(HalTrigger.class);
it.hasNext(); ) {
manager.addAvailableTrigger(it.next());
}
for (Iterator<Class<? extends HalAction>> it = pluginManager.getClassIterator(HalAction.class);
it.hasNext(); ) {
manager.addAvailableAction(it.next());
}
manager.setEvaluationInterval(EVALUATION_INTERVAL);
instance = manager;
}
public static TriggerManager getInstance(){
return instance;
}
}

View file

@ -0,0 +1,55 @@
package se.hal.action;
import se.hal.ControllerManager;
import se.hal.HalContext;
import se.hal.intf.HalAction;
import se.hal.intf.HalEventData;
import se.hal.struct.Event;
import zutil.db.DBConnection;
import zutil.log.LogUtil;
import zutil.ui.Configurator;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*/
public class SendEventAction implements HalAction {
private static final Logger logger = LogUtil.getLogger();
@Configurator.Configurable("Event Device ID")
private int eventId;
@Configurator.Configurable("Data to Send")
private double data;
@Override
public void execute() {
try {
DBConnection db = HalContext.getDB();
Event event = Event.getEvent(db, eventId);
if (event != null) {
HalEventData dataObj = event.getDeviceConfig().getEventDataClass().newInstance();
dataObj.setData(data);
event.setDeviceData(dataObj);
// Send
ControllerManager.getInstance().send(event);
}
else
logger.warning("Unable to find event with id: "+ eventId);
} catch (Exception e) {
logger.log(Level.SEVERE, null, e);
}
}
public String toString(){
DBConnection db = HalContext.getDB();
Event event = null;
try{ event = Event.getEvent(db, eventId); } catch (Exception e){} //ignore exception
return "Send event: "+ eventId +
" ("+(event!=null ? event.getName() : null)+")" +
" with data: "+ data;
}
}

View file

@ -0,0 +1,293 @@
package se.hal.daemon;
import se.hal.HalContext;
import se.hal.intf.HalDaemon;
import se.hal.intf.HalSensorConfig.AggregationMethod;
import se.hal.page.HalAlertManager;
import se.hal.page.HalAlertManager.AlertLevel;
import se.hal.page.HalAlertManager.AlertTTL;
import se.hal.page.HalAlertManager.HalAlert;
import se.hal.struct.Sensor;
import se.hal.util.UTCTimePeriod;
import se.hal.util.UTCTimeUtility;
import zutil.db.DBConnection;
import zutil.db.SQLResultHandler;
import zutil.db.handler.SimpleSQLResult;
import zutil.log.LogUtil;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SensorDataAggregatorDaemon implements HalDaemon {
private static final Logger logger = LogUtil.getLogger();
public enum AggregationPeriodLength{
SECOND,
MINUTE,
FIVE_MINUTES,
FIFTEEN_MINUTES,
HOUR,
DAY,
WEEK,
MONTH,
YEAR
}
private HashMap<Long, HalAlert> alertMap = new HashMap<>();
public void initiate(ScheduledExecutorService executor){
executor.scheduleAtFixedRate(this, 0, UTCTimeUtility.FIVE_MINUTES_IN_MS, TimeUnit.MILLISECONDS);
}
@Override
public void run(){
try {
List<Sensor> sensorList = Sensor.getLocalSensors(HalContext.getDB());
for(Sensor sensor : sensorList){
logger.fine("Aggregating sensor_id: " + sensor.getId());
aggregateSensor(sensor);
}
logger.fine("Aggregation Done");
} catch (Exception e) {
logger.log(Level.SEVERE, "Thread has crashed", e);
}
}
public void aggregateSensor(Sensor sensor) {
if(sensor.getDeviceConfig() == null){
logger.fine("The sensor config is not available - ignoring it");
return;
}
logger.fine("The sensor is of type: " + sensor.getDeviceConfig().getClass().getName());
long aggregationStartTime = System.currentTimeMillis();
logger.fine("Aggregating raw data up to a day old into five minute periods");
aggregateRawData(sensor, AggregationPeriodLength.FIVE_MINUTES, UTCTimeUtility.DAY_IN_MS, 5, aggregationStartTime);
logger.fine("Aggregating raw data up to a week old into one hour periods");
aggregateRawData(sensor, AggregationPeriodLength.HOUR, UTCTimeUtility.WEEK_IN_MS, 60, aggregationStartTime);
logger.fine("Aggregating raw data into one day periods");
aggregateRawData(sensor, AggregationPeriodLength.DAY, UTCTimeUtility.INFINITY, 60*24, aggregationStartTime);
logger.fine("Aggregating raw data into one week periods");
aggregateRawData(sensor, AggregationPeriodLength.WEEK, UTCTimeUtility.INFINITY, 60*24*7, aggregationStartTime);
}
/**
* Aggregate data from the raw DB table to the aggregated table
* @param sensor The sensor for to aggregate data
* @param ageLimitInMs Only aggregate up to this age
*/
private void aggregateRawData(Sensor sensor, AggregationPeriodLength aggrPeriodLength, long ageLimitInMs, int expectedSampleCount, long aggregationStartTime){
long sensorId = sensor.getId();
AggregationMethod aggrMethod = sensor.getDeviceConfig().getAggregationMethod();
DBConnection db = HalContext.getDB();
PreparedStatement stmt;
try {
// DB timestamps
long dbMaxRawTimestamp = getLatestRawTimestamp(db, sensor);
long dbMaxAggrEndTimestamp = getLatestAggrEndTimestamp(db, sensor, aggrPeriodLength);
// Periods
long periodLatestEndTimestamp = new UTCTimePeriod(aggregationStartTime, aggrPeriodLength).getPreviosPeriod().getEndTimestamp();
long periodOldestStartTimestamp = new UTCTimePeriod(aggregationStartTime-ageLimitInMs, aggrPeriodLength).getStartTimestamp();
// Check if the sensor has stopped responding for 15 min or 3 times the data interval if so alert the user
if (aggrPeriodLength == AggregationPeriodLength.FIVE_MINUTES) {
long dataInterval = sensor.getDeviceConfig().getDataInterval();
if (dataInterval < UTCTimeUtility.FIVE_MINUTES_IN_MS)
dataInterval = UTCTimeUtility.FIVE_MINUTES_IN_MS;
if (dbMaxRawTimestamp > 0 &&
dbMaxRawTimestamp + (dataInterval * 3) < System.currentTimeMillis()) {
logger.fine("Sensor \"" + sensorId + "\" stopped sending data at: "+ dbMaxRawTimestamp);
if (alertMap.containsKey(sensor.getId()))
alertMap.get(sensor.getId()).dismiss();
HalAlert alert = new HalAlert(AlertLevel.WARNING,
"Sensor \"" + sensor.getName() + "\" stopped responding",
"at <span class=\"timestamp\">"+dbMaxRawTimestamp+"</span>",
AlertTTL.DISMISSED);
alertMap.put(sensor.getId(), alert);
HalAlertManager.getInstance().addAlert(alert);
}
else {
// Sensor has responded remove alert
if (alertMap.containsKey(sensor.getId()))
alertMap.get(sensor.getId()).dismiss();
}
}
// Is there any new data to evaluate?
if(dbMaxRawTimestamp < dbMaxAggrEndTimestamp || dbMaxRawTimestamp < periodOldestStartTimestamp){
logger.fine("No new data to evaluate - aggregation is up to date");
return;
}
// Start processing
logger.fine("evaluating period: "+
(dbMaxAggrEndTimestamp+1) + "=>" + periodLatestEndTimestamp +
" (" + UTCTimeUtility.getDateString(dbMaxAggrEndTimestamp+1) + "=>" +
UTCTimeUtility.getDateString(periodLatestEndTimestamp) + ") " +
"with expected sample count: " + expectedSampleCount);
stmt = db.getPreparedStatement("SELECT *, 1 AS confidence FROM sensor_data_raw"
+" WHERE sensor_id == ?"
+ " AND timestamp > ?"
+ " AND timestamp <= ? "
+ " AND timestamp >= ? "
+" ORDER BY timestamp ASC");
stmt.setLong(1, sensorId);
stmt.setLong(2, dbMaxAggrEndTimestamp);
stmt.setLong(3, periodLatestEndTimestamp);
stmt.setLong(4, periodOldestStartTimestamp);
DBConnection.exec(stmt, new DataAggregator(sensorId, aggrPeriodLength, expectedSampleCount, aggrMethod, aggregationStartTime));
} catch (SQLException e) {
logger.log(Level.SEVERE, null, e);
}
}
private long getLatestAggrEndTimestamp(DBConnection db, Sensor sensor, AggregationPeriodLength aggrPeriodLength) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement("SELECT MAX(timestamp_end) FROM sensor_data_aggr"
+ " WHERE sensor_id == ?"
+ " AND timestamp_end-timestamp_start == ?");
stmt.setLong(1, sensor.getId());
switch(aggrPeriodLength){
case SECOND: stmt.setLong(2, UTCTimeUtility.SECOND_IN_MS-1); break;
case MINUTE: stmt.setLong(2, UTCTimeUtility.MINUTE_IN_MS-1); break;
case FIVE_MINUTES: stmt.setLong(2, UTCTimeUtility.FIVE_MINUTES_IN_MS-1); break;
case FIFTEEN_MINUTES: stmt.setLong(2, UTCTimeUtility.FIFTEEN_MINUTES_IN_MS-1); break;
case HOUR: stmt.setLong(2, UTCTimeUtility.HOUR_IN_MS-1); break;
case DAY: stmt.setLong(2, UTCTimeUtility.DAY_IN_MS-1); break;
case WEEK: stmt.setLong(2, UTCTimeUtility.WEEK_IN_MS-1); break;
default:
throw new IllegalArgumentException("aggregation period length is not supported.");
}
Long timestamp = DBConnection.exec(stmt, new SimpleSQLResult<Long>());
return (timestamp == null ? 0l : timestamp);
}
private long getLatestRawTimestamp(DBConnection db, Sensor sensor) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement(
"SELECT MAX(timestamp) FROM sensor_data_raw WHERE sensor_id == ?");
stmt.setLong(1, sensor.getId());
Long timestamp = DBConnection.exec(stmt, new SimpleSQLResult<Long>());
return (timestamp == null ? 0l : timestamp);
}
/**
* Internal class for aggregating data to the aggregated DB table
*/
private class DataAggregator implements SQLResultHandler<Object>{
private final long sensorId;
private final AggregationPeriodLength aggrPeriodLength;
private final int expectedSampleCount;
private final AggregationMethod aggrMethod;
private final long aggregationStartTime;
public DataAggregator(long sensorId, AggregationPeriodLength aggrPeriodLength, int expectedSampleCount, AggregationMethod aggrMethod, long aggregationStartTime) {
this.sensorId = sensorId;
this.aggrPeriodLength = aggrPeriodLength;
this.expectedSampleCount = expectedSampleCount;
this.aggrMethod = aggrMethod;
this.aggregationStartTime = aggregationStartTime;
}
@Override
public Object handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
try{
HalContext.getDB().getConnection().setAutoCommit(false);
UTCTimePeriod currentPeriod = null;
float sum = 0;
float confidenceSum = 0;
int samples = 0;
long highestSequenceId = Sensor.getHighestSequenceId(sensorId);
PreparedStatement preparedInsertStmt = HalContext.getDB().getPreparedStatement(
"INSERT INTO sensor_data_aggr" +
"(sensor_id, sequence_id, timestamp_start, timestamp_end, data, confidence) " +
"VALUES(?, ?, ?, ?, ?, ?)");
while(result.next()){
if(sensorId != result.getInt("sensor_id")){
throw new IllegalArgumentException("found entry for aggregation for the wrong sensorId " +
"(expecting: "+sensorId+", but was: "+result.getInt("sensor_id")+")");
}
long timestamp = result.getLong("timestamp");
UTCTimePeriod dataPeriod = new UTCTimePeriod(timestamp, this.aggrPeriodLength);
if(currentPeriod == null)
currentPeriod = dataPeriod;
if(!dataPeriod.equals(currentPeriod)){
saveData(preparedInsertStmt, confidenceSum, sum, samples, currentPeriod, ++highestSequenceId);
// Reset variables
currentPeriod = dataPeriod;
confidenceSum = 0;
sum = 0;
samples = 0;
}
sum += result.getFloat("data");
confidenceSum += result.getFloat("confidence");
++samples;
}
//check if the last period is complete and also should be aggregated
if(currentPeriod != null &&
currentPeriod.getEndTimestamp() <= new UTCTimePeriod(aggregationStartTime, aggrPeriodLength).getPreviosPeriod().getEndTimestamp()){
saveData(preparedInsertStmt, confidenceSum, sum, samples, currentPeriod, ++highestSequenceId);
}
DBConnection.execBatch(preparedInsertStmt);
HalContext.getDB().getConnection().commit();
}catch(Exception e){
HalContext.getDB().getConnection().rollback();
throw e;
}finally{
HalContext.getDB().getConnection().setAutoCommit(true);
}
return null;
}
private void saveData(PreparedStatement preparedInsertStmt, float confidenceSum, float sum, int samples, UTCTimePeriod currentPeriod, long sequenceId) throws SQLException{
float aggrConfidence = -1;
float data = -1;
switch(aggrMethod){
case SUM:
data = sum;
aggrConfidence = confidenceSum / (float)this.expectedSampleCount;
break;
case AVERAGE:
data = sum/samples;
aggrConfidence = 1; // ignore confidence for average
break;
}
logger.finer("Saved period: " + currentPeriod +
", data: " + data +
", confidence: " + aggrConfidence +
", samples: " + samples +
", aggrMethod: " + aggrMethod);
preparedInsertStmt.setLong(1, sensorId);
preparedInsertStmt.setLong(2, sequenceId);
preparedInsertStmt.setLong(3, currentPeriod.getStartTimestamp());
preparedInsertStmt.setLong(4, currentPeriod.getEndTimestamp());
preparedInsertStmt.setFloat(5, data);
preparedInsertStmt.setFloat(6, aggrConfidence);
preparedInsertStmt.addBatch();
}
}
}

View file

@ -0,0 +1,124 @@
package se.hal.daemon;
import se.hal.HalContext;
import se.hal.daemon.SensorDataAggregatorDaemon.AggregationPeriodLength;
import se.hal.intf.HalDaemon;
import se.hal.struct.Sensor;
import se.hal.util.UTCTimeUtility;
import zutil.db.DBConnection;
import zutil.db.SQLResultHandler;
import zutil.log.LogUtil;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SensorDataCleanupDaemon implements HalDaemon {
private static final Logger logger = LogUtil.getLogger();
public void initiate(ScheduledExecutorService executor){
executor.scheduleAtFixedRate(this, 5000, UTCTimeUtility.FIVE_MINUTES_IN_MS, TimeUnit.MILLISECONDS);
}
@Override
public void run(){
try {
List<Sensor> sensorList = Sensor.getSensors(HalContext.getDB());
for(Sensor sensor : sensorList){
logger.fine("Deleting old aggregated data for sensor id: " + sensor.getId());
cleanupSensor(sensor);
}
logger.fine("Data cleanup done");
} catch (Exception e) {
logger.log(Level.SEVERE, "Thread has crashed", e);
}
}
public void cleanupSensor(Sensor sensor) {
if (sensor.getUser() != null) {
cleanupSensorData(sensor.getId(), AggregationPeriodLength.FIVE_MINUTES, UTCTimeUtility.DAY_IN_MS); //clear 5-minute data older than a day
cleanupSensorData(sensor.getId(), AggregationPeriodLength.HOUR, UTCTimeUtility.WEEK_IN_MS); //clear 1-hour data older than a week
//cleanupSensorData(sensor.getId(), AggregationPeriodLength.day, TimeUtility.INFINITY); //clear 1-day data older than infinity
//cleanupSensorData(sensor.getId(), AggregationPeriodLength.week, TimeUtility.INFINITY); //clear 1-week data older than infinity
}
}
/**
* Will clear periods if they are too old.
*
* @param sensorId
* @Param clearPeriodlength Will clear periods with this length
* @param olderThan Data must be older than this many ms to be cleared from the DB
*/
private void cleanupSensorData(long sensorId, AggregationPeriodLength cleanupPeriodlength, long olderThan){
DBConnection db = HalContext.getDB();
PreparedStatement stmt = null;
try {
stmt = db.getPreparedStatement("SELECT * FROM sensor_data_aggr"
+" WHERE sensor_id == ? "
+ "AND timestamp_end-timestamp_start == ?"
+ "AND timestamp_end < ?");
stmt.setLong(1, sensorId);
switch(cleanupPeriodlength){
case SECOND: stmt.setLong(2, UTCTimeUtility.SECOND_IN_MS-1); break;
case MINUTE: stmt.setLong(2, UTCTimeUtility.MINUTE_IN_MS-1); break;
case FIVE_MINUTES: stmt.setLong(2, UTCTimeUtility.FIVE_MINUTES_IN_MS-1); break;
case FIFTEEN_MINUTES: stmt.setLong(2, UTCTimeUtility.FIFTEEN_MINUTES_IN_MS-1); break;
case HOUR: stmt.setLong(2, UTCTimeUtility.HOUR_IN_MS-1); break;
case DAY: stmt.setLong(2, UTCTimeUtility.DAY_IN_MS-1); break;
case WEEK: stmt.setLong(2, UTCTimeUtility.WEEK_IN_MS-1); break;
default: logger.warning("cleanup period length is not supported."); return;
}
stmt.setLong(3, System.currentTimeMillis()-olderThan);
DBConnection.exec(stmt, new AggregateDataDeleter(sensorId));
} catch (SQLException e) {
logger.log(Level.SEVERE, null, e);
}
}
private class AggregateDataDeleter implements SQLResultHandler<Long>{
private long sensorId = -1;
public AggregateDataDeleter(long sensorId){
this.sensorId = sensorId;
}
@Override
public Long handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
long count = 0;
try{
HalContext.getDB().getConnection().setAutoCommit(false);
PreparedStatement preparedDeleteStmt = HalContext.getDB().getPreparedStatement("DELETE FROM sensor_data_aggr WHERE sensor_id == ? AND sequence_id == ?");
while(result.next()){
if(sensorId != result.getInt("sensor_id")){
throw new IllegalArgumentException("Found entry for aggregation for the wrong sensorId (expecting: "+sensorId+", but was: "+result.getInt("sensor_id")+")");
}
logger.finer("Deleting sensor(id: "+ sensorId +") aggregate entry timestamp: "+ result.getLong("timestamp_start") +" - "+ result.getLong("timestamp_end") + " (" + UTCTimeUtility.timeInMsToString(1+result.getLong("timestamp_end")-result.getLong("timestamp_start")) + ")");
preparedDeleteStmt.setInt(1, result.getInt("sensor_id"));
preparedDeleteStmt.setLong(2, result.getLong("sequence_id"));
preparedDeleteStmt.addBatch();
count++;
}
DBConnection.execBatch(preparedDeleteStmt);
HalContext.getDB().getConnection().commit();
}catch(Exception e){
HalContext.getDB().getConnection().rollback();
throw e;
}finally{
HalContext.getDB().getConnection().setAutoCommit(true);
}
return count;
}
}
}

View file

@ -0,0 +1,49 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Ziver Koc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.intf;
public interface HalAbstractController {
/**
* The framework might create dummy objects so any type of
* resource initialization should be handled in this method
* and not in the constructor.
*/
void initialize() throws Exception;
/**
* @return the number of registered devices.
*/
int size();
/**
* Close any resources associated with this controller.
* This method could be called multiple times, first time
* should be handled as normal any subsequent calls should be ignored.
*/
void close();
}

View file

@ -0,0 +1,14 @@
package se.hal.intf;
/**
* Defines a action that will be executed
*/
public interface HalAction{
/**
* Executes this specific action
*/
void execute();
}

View file

@ -0,0 +1,14 @@
package se.hal.intf;
/**
* A interface that indicates that the implementing
* controller can be auto started when HalServer starts up.
*/
public interface HalAutoScannableController {
/**
* Indicates if the controller has all the configuration
* data and resources needed to be able to initialize correctly
*/
boolean isAvailable();
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2015 Ziver
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.intf;
public interface HalBot {
void initialize();
String respond(String question);
}

View file

@ -0,0 +1,17 @@
package se.hal.intf;
import java.util.concurrent.ScheduledExecutorService;
/**
* Defines a stand alone process that will run parallel to Hal
*/
public interface HalDaemon extends Runnable{
/**
* Initialize the daemon.
*
* @param executor The sceduler that the daemon should register to.
*/
void initiate(ScheduledExecutorService executor);
}

View file

@ -0,0 +1,25 @@
package se.hal.intf;
/**
* Interface representing one report from an event
*/
public abstract class HalDeviceData {
private long timestamp = -1;
public long getTimestamp(){
return timestamp;
}
public void setTimestamp(long timestamp){
this.timestamp = timestamp;
}
/**
* @return serialized event data converted to double that will be saved in DB.
*/
public abstract double getData();
public abstract void setData(double data);
}

View file

@ -0,0 +1,14 @@
package se.hal.intf;
import se.hal.struct.AbstractDevice;
/**
* A listener interface that will be called when the
* Event or Sensor that it is registered to receives a report
*
* @param <T> is the device type
*/
public interface HalDeviceReportListener<T extends AbstractDevice> {
void receivedReport(T device);
}

View file

@ -0,0 +1,21 @@
package se.hal.intf;
/**
* Interface representing event type specific configuration data.
*/
public interface HalEventConfig {
Class<? extends HalEventController> getEventControllerClass();
/**
* @return the class that should be instantiated and used for data received from this event
*/
Class<? extends HalEventData> getEventDataClass();
/**
* This method needs to be implemented.
* NOTE: it should not compare data and timestamp, only static or unique data for the event type.
*/
boolean equals(Object obj);
}

View file

@ -0,0 +1,26 @@
package se.hal.intf;
public interface HalEventController extends HalAbstractController {
/**
* Will register an event type to be handled by this controller
*/
void register(HalEventConfig eventConfig);
/**
* Deregisters an event from this controller, the controller
* will no longer handle that type of event
*/
void deregister(HalEventConfig eventConfig);
/**
* @param eventConfig the event configuration to target when sending
* @param eventData the data to send
*/
void send(HalEventConfig eventConfig, HalEventData eventData);
/**
* Set a listener that will receive all reports from the the registered Events
*/
void setListener(HalEventReportListener listener);
}

View file

@ -0,0 +1,8 @@
package se.hal.intf;
/**
* Interface representing one report from an event
*/
public abstract class HalEventData extends HalDeviceData{
}

View file

@ -0,0 +1,10 @@
package se.hal.intf;
/**
* Listener to be called by the {@link HalEventController} to report that a event has been received.
*/
public interface HalEventReportListener {
void reportReceived(HalEventConfig e, HalEventData d);
}

View file

@ -0,0 +1,64 @@
package se.hal.intf;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPrintStream;
import zutil.parser.DataNode;
import zutil.parser.Templator;
import zutil.parser.json.JSONWriter;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A interface defining a Hal json endpoint
*/
public abstract class HalJsonPage extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
public HalJsonPage(String id) {
super(id);
}
@Override
public void respond(HttpPrintStream out,
HttpHeader headers,
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws IOException {
out.setHeader("Content-Type", "application/json");
out.setHeader("Access-Control-Allow-Origin", "*");
out.setHeader("Pragma", "no-cache");
JSONWriter writer = new JSONWriter(out);
try{
writer.write(
jsonRespond(session, cookie, request));
} catch (Exception e){
logger.log(Level.SEVERE, null, e);
DataNode root = new DataNode(DataNode.DataType.Map);
root.set("error", e.getMessage());
writer.write(root);
}
writer.close();
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws Exception {
return null;
}
protected abstract DataNode jsonRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws Exception;
}

View file

@ -0,0 +1,39 @@
package se.hal.intf;
/**
* Interface representing sensor type specific configuration data.
*/
public interface HalSensorConfig {
enum AggregationMethod{
SUM,
AVERAGE
}
/**
* @return the intended data reporting interval in milliseconds.
*/
long getDataInterval();
/**
* @return which aggregation method that should be used to aggregate the reported data.
*/
AggregationMethod getAggregationMethod();
/**
* @return the Controller class where SensorData should be registered on
*/
Class<? extends HalSensorController> getSensorControllerClass();
/**
* @return the class that should be instantiated and used for data received from this sensor
*/
Class<? extends HalSensorData> getSensorDataClass();
/**
* NOTE: it should only static or unique data for the sensor type.
* This method is used to associate reported data with registered sensors
*/
boolean equals(Object obj);
}

View file

@ -0,0 +1,20 @@
package se.hal.intf;
public interface HalSensorController extends HalAbstractController {
/**
* Will register a sensor type to be handled by this controller
*/
void register(HalSensorConfig sensorConfig);
/**
* Deregisters a sensor from this controller, the controller
* will no longer handle that type of sensor
*/
void deregister(HalSensorConfig sensorConfig);
/**
* Set a listener that will receive all reports from the the registered Sensors
*/
void setListener(HalSensorReportListener listener);
}

View file

@ -0,0 +1,8 @@
package se.hal.intf;
/**
* Interface representing one data report from a sensor.
*/
public abstract class HalSensorData extends HalDeviceData{
}

View file

@ -0,0 +1,10 @@
package se.hal.intf;
/**
* Listener to be called by the {@link HalSensorController} to report that sensor data has been received.
*/
public interface HalSensorReportListener {
void reportReceived(HalSensorConfig s, HalSensorData d);
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2013 ezivkoc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.intf;
/**
* A generic interface for Speech to Text analysis
*/
public interface HalSpeechToText {
void initialize();
String listen();
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2013 ezivkoc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.intf;
/**
* A Common interface for Text to Speech generation
*/
public interface HalTextToSpeech {
void initialize();
void speak(String msg);
}

View file

@ -0,0 +1,22 @@
package se.hal.intf;
/**
* A interface that declares a trigger/condition that
* needs to be validated before an action can be run
*/
public interface HalTrigger{
/**
* Evaluates if this trigger has passed. If the trigger is
* true then this method will return true until the {@link #reset()}
* method is called.
*/
boolean evaluate();
/**
* Reset the evaluation to false.
*/
void reset();
}

View file

@ -0,0 +1,84 @@
package se.hal.intf;
import se.hal.HalContext;
import se.hal.page.HalAlertManager;
import se.hal.struct.User;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream;
import zutil.parser.Templator;
import zutil.parser.json.JSONWriter;
import zutil.ui.Navigation;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public abstract class HalWebPage implements HttpPage{
private static final String TEMPLATE = "resource/web/main_index.tmpl";
private static Navigation rootNav = Navigation.createRootNav();
private static Navigation userNav = Navigation.createRootNav();
private String pageId;
private boolean showSubNav;
public HalWebPage(String id){
this.pageId = id;
this.showSubNav = true;
}
public String getId(){
return pageId;
}
@Override
public void respond(HttpPrintStream out, HttpHeader header,
Map<String, Object> session, Map<String, String> cookie,
Map<String, String> request) throws IOException {
try {
DBConnection db = HalContext.getDB();
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("user", User.getLocalUser(db));
tmpl.set("showSubNav", showSubNav);
if (showSubNav) {
List<Navigation> breadcrumb = Navigation.getBreadcrumb(Navigation.getPagedNavigation(header));
if (!breadcrumb.isEmpty())
tmpl.set("subNav", breadcrumb.get(1).createPagedNavInstance(header).getSubNavs());
}
tmpl.set("rootNav", rootNav.createPagedNavInstance(header).getSubNavs());
tmpl.set("userNav", userNav.createPagedNavInstance(header).getSubNavs());
tmpl.set("content", httpRespond(session, cookie, request));
tmpl.set("alerts", HalAlertManager.getInstance().generateAlerts()); // do last so we don't miss any alerts
out.print(tmpl.compile());
} catch (Exception e) {
throw new IOException(e);
}
}
/**
* Defines if the sub-navigation should be shown on the page
*/
protected void showSubNav(boolean show) {
this.showSubNav = show;
}
public static Navigation getRootNav(){
return rootNav;
}
public static Navigation getUserNav(){
return userNav;
}
public abstract Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception;
}

View file

@ -0,0 +1,122 @@
package se.hal.page;
import se.hal.ControllerManager;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import se.hal.page.HalAlertManager.AlertLevel;
import se.hal.page.HalAlertManager.AlertTTL;
import se.hal.page.HalAlertManager.HalAlert;
import se.hal.struct.ClassConfigurationData;
import se.hal.struct.Event;
import se.hal.struct.User;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.parser.Templator;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Logger;
public class EventConfigWebPage extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
private static final String TEMPLATE = "resource/web/event_config.tmpl";
private ArrayList<ClassConfigurationData> eventConfigurations;
public EventConfigWebPage() {
super("event_config");
super.getRootNav().createSubNav("Settings").createSubNav(this.getId(), "Event Settings").setWeight(200);
eventConfigurations = new ArrayList<>();
for(Class c : ControllerManager.getInstance().getAvailableEvents())
eventConfigurations.add(new ClassConfigurationData(c));
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception{
DBConnection db = HalContext.getDB();
User localUser = User.getLocalUser(db);
// Save new input
if(request.containsKey("action")){
int id = (ObjectUtil.isEmpty(request.get("id")) ? -1 : Integer.parseInt(request.get("id")));
Event event;
switch(request.get("action")) {
// Local events
case "create_local_event":
logger.info("Creating new event: " + request.get("name"));
event = new Event();
event.setName(request.get("name"));
event.setType(request.get("type"));
event.setUser(localUser);
event.getDeviceConfigurator().setValues(request).applyConfiguration();
event.save(db);
ControllerManager.getInstance().register(event);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully created new event: " + event.getName(), AlertTTL.ONE_VIEW));
break;
case "modify_local_event":
event = Event.getEvent(db, id);
if (event != null) {
logger.info("Modifying event: " + event.getName());
event.setName(request.get("name"));
event.setType(request.get("type"));
event.setUser(localUser);
event.getDeviceConfigurator().setValues(request).applyConfiguration();
event.save(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully saved event: "+event.getName(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown event id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown event id: " + id, AlertTTL.ONE_VIEW));
}
break;
case "remove_local_event":
event = Event.getEvent(db, id);
if (event != null) {
logger.info("Removing event: " + event.getName());
ControllerManager.getInstance().deregister(event);
event.delete(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully removed event: "+event.getName(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown event id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown event id: "+id, AlertTTL.ONE_VIEW));
}
break;
case "remove_all_detected_events":
ControllerManager.getInstance().clearDetectedEvents();
break;
}
}
// Output
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("user", localUser);
tmpl.set("localEvents", Event.getLocalEvents(db));
tmpl.set("localEventConf", eventConfigurations);
tmpl.set("detectedEvents", ControllerManager.getInstance().getDetectedEvents());
tmpl.set("availableEvents", ControllerManager.getInstance().getAvailableEvents());
return tmpl;
}
}

View file

@ -0,0 +1,87 @@
package se.hal.page;
import se.hal.ControllerManager;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import se.hal.struct.Event;
import se.hal.struct.devicedata.SwitchEventData;
import se.hal.util.DeviceNameComparator;
import se.hal.util.HistoryDataListSqlResult;
import se.hal.util.HistoryDataListSqlResult.HistoryData;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.parser.Templator;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class EventOverviewWebPage extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
private static final int HISTORY_LIMIT = 200;
private static final String OVERVIEW_TEMPLATE = "resource/web/event_overview.tmpl";
private static final String DETAIL_TEMPLATE = "resource/web/event_detail.tmpl";
public EventOverviewWebPage(){
super("event_overview");
super.getRootNav().createSubNav("Events").createSubNav(this.getId(), "Overview");
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception{
DBConnection db = HalContext.getDB();
if (request.containsKey("action")) {
int id = (ObjectUtil.isEmpty(request.get("action_id")) ? -1 : Integer.parseInt(request.get("action_id")));
// change event data
SwitchEventData eventData = new SwitchEventData();
if (request.containsKey("enabled") && "on".equals(request.get("enabled")))
eventData.turnOn();
else
eventData.turnOff();
logger.info("Modifying Event(" + id + ") state: " + eventData.toString());
Event event = Event.getEvent(db, id);
event.setDeviceData(eventData);
ControllerManager.getInstance().send(event);
}
int id = (ObjectUtil.isEmpty(request.get("id")) ? -1 : Integer.parseInt(request.get("id")));
// Save new input
if (id >= 0) {
Event event = Event.getEvent(db, id);
// get history data
PreparedStatement stmt = db.getPreparedStatement(
"SELECT * FROM event_data_raw WHERE event_id == ? ORDER BY timestamp DESC LIMIT ?");
stmt.setLong(1, event.getId());
stmt.setLong(2, HISTORY_LIMIT);
List<HistoryData> history = DBConnection.exec(stmt, new HistoryDataListSqlResult());
Templator tmpl = new Templator(FileUtil.find(DETAIL_TEMPLATE));
tmpl.set("event", event);
tmpl.set("history", history);
return tmpl;
}
else {
Event[] events = Event.getLocalEvents(db).toArray(new Event[0]);
Arrays.sort(events, DeviceNameComparator.getInstance());
Templator tmpl = new Templator(FileUtil.find(OVERVIEW_TEMPLATE));
tmpl.set("events", events);
return tmpl;
}
}
}

View file

@ -0,0 +1,163 @@
package se.hal.page;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream;
import zutil.parser.Templator;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class HalAlertManager implements HttpPage {
private static final Logger logger = LogUtil.getLogger();
private static final String TMPL_PATH = "resource/web/main_alerts.tmpl";
private static final String PAGE_NAME = "alert";
private static HalAlertManager instance;
public enum AlertLevel{
ERROR,
WARNING,
SUCCESS,
INFO
}
public enum AlertTTL{
ONE_VIEW,
DISMISSED
}
private List<HalAlert> alerts = new LinkedList<>();
private HalAlertManager(){}
public String getUrl(){
return "/" + PAGE_NAME;
}
public void addAlert(HalAlert alert) {
alerts.remove(alert); // We don't want to flood the user with duplicate alerts
alerts.add(alert);
}
public Templator generateAlerts() {
try {
// clone alert list and update ttl of alerts
List<HalAlert> alertsClone = new ArrayList<>(alerts.size());
for(Iterator<HalAlert> it = alerts.iterator(); it.hasNext(); ) {
HalAlert alert = it.next();
alertsClone.add(alert);
alert.ttl--;
if (alert.ttl <= 0) { // if alert is to old, remove it
logger.fine("Alert dismissed with end of life, alert id: "+ alert.id);
it.remove();
}
}
Templator tmpl = new Templator(FileUtil.find(TMPL_PATH));
tmpl.set("serviceUrl", getUrl());
tmpl.set("alerts", alertsClone);
return tmpl;
} catch (IOException e) {
logger.log(Level.SEVERE, null, e);
}
return null;
}
@Override
public void respond(HttpPrintStream out,
HttpHeader headers,
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws IOException {
if (request.containsKey("action")) {
if (request.get("action").equals("dismiss")) {
// parse alert id
int id = Integer.parseInt(request.get("id"));
// Find alert
for(Iterator<HalAlert> it = alerts.iterator(); it.hasNext(); ) {
HalAlert alert = it.next();
if (alert.getId() == id) {
logger.fine("User dismissed alert id: "+ id);
it.remove();
break;
}
}
}
}
}
public static void initialize(){
instance = new HalAlertManager();
}
public static HalAlertManager getInstance(){
return instance;
}
public static class HalAlert {
private static int nextId = 0;
private int id;
private AlertLevel level;
private String title;
private String msg;
private int ttl;
public HalAlert(AlertLevel level, String title, AlertTTL ttl) {
this(level, title, null, ttl);
}
public HalAlert(AlertLevel level, String title, String msg, AlertTTL ttl) {
this.id = nextId++;
this.level = level;
this.title = title;
this.msg = msg;
setTTL(ttl);
}
public int getId() {
return id;
}
public AlertLevel getLevel() {
return level;
}
public boolean isError(){ return level == AlertLevel.ERROR; }
public boolean isWarning(){ return level == AlertLevel.WARNING; }
public boolean isSuccess(){ return level == AlertLevel.SUCCESS; }
public boolean isInfo(){ return level == AlertLevel.INFO; }
public String getTitle() {
return title;
}
public String getMessage() {
return msg;
}
public void setTTL(AlertTTL ttl) {
switch (ttl){
case ONE_VIEW: this.ttl = 1; break;
case DISMISSED: this.ttl = Integer.MAX_VALUE; break;
}
}
public void dismiss(){
ttl = -1;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof HalAlert)
return level == ((HalAlert) obj).level &&
title.equals(((HalAlert) obj).title);
return super.equals(obj);
}
}
}

View file

@ -0,0 +1,88 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.intf.HalJsonPage;
import se.hal.struct.AbstractDevice;
import se.hal.struct.Event;
import se.hal.struct.Sensor;
import zutil.db.DBConnection;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPrintStream;
import zutil.parser.DataNode;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
/**
* TODO: This json endpoint might not be needed as we have SensorJsonPage?
*/
public class MapJsonPage extends HalJsonPage {
private static final Logger logger = LogUtil.getLogger();
public MapJsonPage() {
super("data/map");
}
@Override
public DataNode jsonRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws Exception {
DBConnection db = HalContext.getDB();
DataNode root = new DataNode(DataNode.DataType.Map);
if ("getdata".equals(request.get("action"))) {
getDeviceNode(db, root);
} else if ("save".equals(request.get("action"))) {
int id = Integer.parseInt(request.get("id"));
AbstractDevice device = null;
logger.info("Saving Sensor coordinates.");
if ("sensor".equals(request.get("type")))
device = Sensor.getSensor(db, id);
else if ("event".equals(request.get("type")))
device = Event.getEvent(db, id);
device.setX(Float.parseFloat(request.get("x")));
device.setY(Float.parseFloat(request.get("y")));
device.save(db);
}
return root;
}
private void getDeviceNode(DBConnection db, DataNode root) throws SQLException {
DataNode sensorsNode = new DataNode(DataNode.DataType.List);
for (Sensor sensor : Sensor.getLocalSensors(db)) {
DataNode sensorNode = getDeviceNode(sensor);
sensorNode.set("data", ""+sensor.getDeviceData());
sensorsNode.add(sensorNode);
}
root.set("sensors", sensorsNode);
DataNode eventsNode = new DataNode(DataNode.DataType.List);
for (Event event : Event.getLocalEvents(db)) {
DataNode eventNode = getDeviceNode(event);
eventNode.set("data", ""+event.getDeviceData());
eventsNode.add(eventNode);
}
root.set("events", eventsNode);
}
private DataNode getDeviceNode(AbstractDevice device) {
DataNode deviceNode = new DataNode(DataNode.DataType.Map);
deviceNode.set("id", device.getId());
deviceNode.set("name", device.getName());
deviceNode.set("x", device.getX());
deviceNode.set("y", device.getY());
return deviceNode;
}
}

View file

@ -0,0 +1,91 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import zutil.io.file.FileUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPrintStream;
import zutil.net.http.multipart.MultipartField;
import zutil.net.http.multipart.MultipartFileField;
import zutil.net.http.multipart.MultipartParser;
import zutil.parser.Base64Decoder;
import zutil.parser.Base64Encoder;
import zutil.parser.Templator;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
public class MapWebPage extends HalWebPage {
private static final String TEMPLATE = "resource/web/map.tmpl";
private String bgType;
private byte[] bgImage;
public MapWebPage() {
super("map");
super.getRootNav().createSubNav(this.getId(), "Map").setWeight(-100);
super.showSubNav(false);
}
@Override
public void respond(HttpPrintStream out, HttpHeader header,
Map<String, Object> session, Map<String, String> cookie,
Map<String, String> request) throws IOException {
if ("POST".equals(header.getRequestType())) {
for (MultipartField field : new MultipartParser(header)) {
if (field instanceof MultipartFileField) {
MultipartFileField file = (MultipartFileField) field;
String ext = FileUtil.getFileExtension(file.getFilename());
if (ext.equals("jpg") || ext.equals("png") || ext.equals("svg") || ext.equals("gif")) {
try {
saveBgImage(ext, file.getContent());
out.println("Upload successful: " + file.getFilename());
} catch (SQLException e) {
e.printStackTrace();
out.println("Upload error: " + e.getMessage());
}
bgImage = null; // reload image from db
}
}
}
} else if (request.containsKey("bgimage")) {
if (bgImage == null)
loadBgImage();
if (bgImage == null)
out.setResponseStatusCode(404);
else {
out.setHeader("Content-Type", "image/" + bgType);
out.setHeader("Content-Length", "" + bgImage.length);
out.write(bgImage);
}
} else { // Run default Hal behaviour
super.respond(out, header, session, cookie, request);
}
}
@Override
public Templator httpRespond(Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws Exception {
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
return tmpl;
}
private void loadBgImage() {
String property = HalContext.getStringProperty(HalContext.PROPERTY_MAP_BACKGROUND_IMAGE);
if (property != null) {
String[] split = property.split(",", 2);
bgType = split[0];
bgImage = Base64Decoder.decodeToByte(split[1]);
}
}
private void saveBgImage(String type, byte[] data) throws SQLException {
HalContext.setProperty(HalContext.PROPERTY_MAP_BACKGROUND_IMAGE, type + "," + Base64Encoder.encode(data));
}
}

View file

@ -0,0 +1,50 @@
package se.hal.page;
import se.hal.ControllerManager;
import se.hal.HalContext;
import se.hal.HalServer;
import se.hal.intf.HalWebPage;
import se.hal.page.HalAlertManager.AlertLevel;
import se.hal.page.HalAlertManager.AlertTTL;
import se.hal.page.HalAlertManager.HalAlert;
import se.hal.struct.devicedata.SwitchEventData;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.parser.Templator;
import zutil.plugin.PluginManager;
import java.util.HashMap;
import java.util.Map;
public class PluginConfigWebPage extends HalWebPage {
private static final String TEMPLATE = "resource/web/plugin_config.tmpl";
public PluginConfigWebPage(){
super("plugins");
super.getRootNav().createSubNav("Settings").createSubNav(this.getId(), "Plugins").setWeight(500);
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception{
if (request.containsKey("action")) {
String name = request.get("action_id");
HalServer.setPluginEnabled(name,
(request.containsKey("enabled") && "on".equals(request.get("enabled"))));
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully updated plugin " + name + ", change will take affect after restart.", AlertTTL.ONE_VIEW));
}
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("plugins", HalServer.getPlugins());
tmpl.set("controllers", ControllerManager.getInstance().getControllers());
return tmpl;
}
}

View file

@ -0,0 +1,34 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.parser.Templator;
import java.util.*;
public class PropertyConfigWebPage extends HalWebPage {
private static final String TEMPLATE = "resource/web/property_config.tmpl";
public PropertyConfigWebPage(){
super("properties");
super.getRootNav().createSubNav("Settings").createSubNav(this.getId(), "Properties");
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception{
Map<String,String> properties = HalContext.getProperties();
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("properties", properties.entrySet());
return tmpl;
}
}

View file

@ -0,0 +1,187 @@
package se.hal.page;
import se.hal.ControllerManager;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import se.hal.page.HalAlertManager.AlertLevel;
import se.hal.page.HalAlertManager.AlertTTL;
import se.hal.page.HalAlertManager.HalAlert;
import se.hal.struct.ClassConfigurationData;
import se.hal.struct.Sensor;
import se.hal.struct.User;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.parser.Templator;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Logger;
public class SensorConfigWebPage extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
private static final String TEMPLATE = "resource/web/sensor_config.tmpl";
private ArrayList<ClassConfigurationData> sensorConfigurations;
public SensorConfigWebPage() {
super("sensor_config");
super.getRootNav().createSubNav("Settings").createSubNav(this.getId(), "Sensor Settings").setWeight(100);
sensorConfigurations = new ArrayList<>();
for(Class c : ControllerManager.getInstance().getAvailableSensors())
sensorConfigurations.add(new ClassConfigurationData(c));
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception{
DBConnection db = HalContext.getDB();
User localUser = User.getLocalUser(db);
// Save new input
if(request.containsKey("action")){
int id = (ObjectUtil.isEmpty(request.get("id")) ? -1 : Integer.parseInt(request.get("id")));
Sensor sensor;
User user;
switch(request.get("action")) {
// Local Sensors
case "create_local_sensor":
logger.info("Creating sensor: " + request.get("name"));
sensor = new Sensor();
sensor.setName(request.get("name"));
sensor.setType(request.get("type"));
sensor.setSynced(Boolean.parseBoolean(request.get("sync")));
sensor.setUser(localUser);
sensor.getDeviceConfigurator().setValues(request).applyConfiguration();
sensor.save(db);
ControllerManager.getInstance().register(sensor);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully created new sensor: "+sensor.getName(), AlertTTL.ONE_VIEW));
break;
case "modify_local_sensor":
sensor = Sensor.getSensor(db, id);
if(sensor != null){
logger.info("Modifying sensor: " + sensor.getName());
sensor.setName(request.get("name"));
sensor.setType(request.get("type"));
sensor.setSynced(Boolean.parseBoolean(request.get("sync")));
sensor.getDeviceConfigurator().setValues(request).applyConfiguration();
sensor.save(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully saved sensor: "+sensor.getName(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown sensor id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown sensor id: " + id, AlertTTL.ONE_VIEW));
}
break;
case "remove_local_sensor":
sensor = Sensor.getSensor(db, id);
if(sensor != null) {
logger.warning("Removing sensor: " + sensor.getName());
ControllerManager.getInstance().deregister(sensor);
sensor.delete(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully removed sensor: "+sensor.getName(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown sensor id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown sensor id: " + id, AlertTTL.ONE_VIEW));
}
break;
case "remove_all_detected_sensors":
ControllerManager.getInstance().clearDetectedSensors();
break;
// External Users
case "create_external_user":
logger.info("Creating external user: " + request.get("hostname"));
user = new User();
user.setHostname(request.get("hostname"));
user.setPort(Integer.parseInt(request.get("port")));
user.setExternal(true);
user.save(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully created new external user with host: "+user.getHostname(), AlertTTL.ONE_VIEW));
break;
case "modify_external_user":
user = User.getUser(db, id);
if(user != null){
logger.info("Modifying external user: " + user.getHostname());
user.setHostname(request.get("hostname"));
user.setPort(Integer.parseInt(request.get("port")));
user.save(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully saved external user with host: "+user.getHostname(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown user id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown user id: " + id, AlertTTL.ONE_VIEW));
}
break;
case "remove_external_user":
user = User.getUser(db, id);
if (user != null) {
logger.info("Removing external user: " + user.getHostname());
user.delete(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully removed user with host: "+user.getHostname(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown user id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown user id: "+id, AlertTTL.ONE_VIEW));
}
break;
// External Sensors
case "modify_external_sensor":
sensor = Sensor.getSensor(db, id);
if(sensor != null){
logger.warning("Modifying external sensor: " + sensor.getName());
sensor.setSynced(Boolean.parseBoolean(request.get("sync")));
sensor.save(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully saved external sensor: "+sensor.getName(), AlertTTL.ONE_VIEW));
} else {
logger.warning("Unknown user id: " + id);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.ERROR, "Unknown sensor id: "+id, AlertTTL.ONE_VIEW));
}
break;
}
}
// Output
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("user", localUser);
tmpl.set("localSensors", Sensor.getLocalSensors(db));
tmpl.set("localSensorConf", sensorConfigurations);
tmpl.set("detectedSensors", ControllerManager.getInstance().getDetectedSensors());
tmpl.set("extUsers", User.getExternalUsers(db));
tmpl.set("extSensor", Sensor.getExternalSensors(db));
tmpl.set("availableSensors", ControllerManager.getInstance().getAvailableSensors());
return tmpl;
}
}

View file

@ -0,0 +1,137 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.daemon.SensorDataAggregatorDaemon;
import se.hal.intf.HalJsonPage;
import se.hal.struct.Sensor;
import se.hal.util.AggregateDataListSqlResult;
import se.hal.util.UTCTimeUtility;
import zutil.ArrayUtil;
import zutil.db.DBConnection;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPrintStream;
import zutil.parser.DataNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Available HTTP Get Request parameters:
* <pre>
* Sensor filtering parameters:
* id: comma separated numeric id for specific sensors
* type: sensor data type name
*
* Data filtering parameters:
* aggr: Aggregation periods, needs to be provided to retrieve data. Possible values: minute,hour,day,week
* </pre>
*/
public class SensorJsonPage extends HalJsonPage {
private static final Logger logger = LogUtil.getLogger();
public SensorJsonPage() {
super("data/sensor");
}
@Override
public DataNode jsonRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws Exception{
DBConnection db = HalContext.getDB();
DataNode root = new DataNode(DataNode.DataType.List);
//// Get sensors
String[] req_ids = null;
if (request.get("id") != null)
req_ids = request.get("id").split(",");
String req_type = request.get("type");
List<Sensor> sensors = new ArrayList<>();
for (Sensor sensor : Sensor.getSensors(db)) {
if (sensor.getDeviceConfig() == null) // Show all sensors for now
continue;
if (req_ids == null && req_type==null) // no options defined, then add all sensors
sensors.add(sensor);
else if (req_ids != null && ArrayUtil.contains(req_ids, ""+sensor.getId())) // id filtering
sensors.add(sensor);
else if (req_type != null && !req_type.isEmpty() &&
sensor.getDeviceConfig().getSensorDataClass().getSimpleName().contains(req_type)) // device type filtering
sensors.add(sensor);
}
//// Figure out aggregation period
SensorDataAggregatorDaemon.AggregationPeriodLength aggrType = null;
long aggrLength = -1;
if (request.get("aggr") != null) {
switch (request.get("aggr")) {
case "minute":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.FIVE_MINUTES;
aggrLength = UTCTimeUtility.DAY_IN_MS;
break;
case "hour":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.HOUR;
aggrLength = UTCTimeUtility.WEEK_IN_MS;
break;
case "day":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.DAY;
aggrLength = UTCTimeUtility.INFINITY;
break;
case "week":
aggrType = SensorDataAggregatorDaemon.AggregationPeriodLength.WEEK;
aggrLength = UTCTimeUtility.INFINITY;
break;
}
}
/// Generate DataNode
for (Sensor sensor : sensors) {
DataNode deviceNode = new DataNode(DataNode.DataType.Map);
deviceNode.set("id", sensor.getId());
deviceNode.set("name", sensor.getName());
deviceNode.set("user", sensor.getUser().getUsername());
deviceNode.set("type", sensor.getDeviceConfig().getSensorDataClass().getSimpleName());
deviceNode.set("x", sensor.getX());
deviceNode.set("y", sensor.getY());
if (aggrLength > 0) {
addAggregateDataToDataNode(deviceNode, aggrLength,
AggregateDataListSqlResult.getAggregateDataForPeriod(db, sensor, aggrType, aggrLength));
}
root.add(deviceNode);
}
return root;
}
private void addAggregateDataToDataNode(DataNode deviceNode, long endTime, List<AggregateDataListSqlResult.AggregateData> dataList) {
DataNode timestampNode = new DataNode(DataNode.DataType.List);
DataNode dataNode = new DataNode(DataNode.DataType.List);
// end timestamp
if (endTime != UTCTimeUtility.INFINITY) {
timestampNode.add(System.currentTimeMillis() - endTime);
dataNode.add((String)null);
}
// actual data
for (AggregateDataListSqlResult.AggregateData data : dataList) {
timestampNode.add(data.timestamp);
if (data.data == null || Float.isNaN(data.data))
dataNode.add((String)null);
else
dataNode.add(data.data);
}
// start timestamp
timestampNode.add(System.currentTimeMillis());
dataNode.add((String)null);
deviceNode.set("timestamps", timestampNode);
deviceNode.set("data", dataNode);
}
}

View file

@ -0,0 +1,65 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import se.hal.struct.Sensor;
import se.hal.util.DeviceNameComparator;
import se.hal.util.HistoryDataListSqlResult;
import se.hal.util.HistoryDataListSqlResult.HistoryData;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.parser.Templator;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class SensorOverviewWebPage extends HalWebPage {
private static final int HISTORY_LIMIT = 200;
private static final String OVERVIEW_TEMPLATE = "resource/web/sensor_overview.tmpl";
private static final String DETAIL_TEMPLATE = "resource/web/sensor_detail.tmpl";
public SensorOverviewWebPage(){
super("sensor_overview");
super.getRootNav().createSubNav("Sensors").createSubNav(this.getId(), "Overview");
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception{
DBConnection db = HalContext.getDB();
int id = (ObjectUtil.isEmpty(request.get("id")) ? -1 : Integer.parseInt(request.get("id")));
// Save new input
if (id >= 0) {
Sensor sensor = Sensor.getSensor(db, id);
// get history data
PreparedStatement stmt = db.getPreparedStatement(
"SELECT * FROM sensor_data_raw WHERE sensor_id == ? ORDER BY timestamp DESC LIMIT ?");
stmt.setLong(1, sensor.getId());
stmt.setLong(2, HISTORY_LIMIT);
List<HistoryData> history = DBConnection.exec(stmt, new HistoryDataListSqlResult());
Templator tmpl = new Templator(FileUtil.find(DETAIL_TEMPLATE));
tmpl.set("sensor", sensor);
tmpl.set("history", history);
return tmpl;
} else {
Sensor[] sensors = Sensor.getLocalSensors(db).toArray(new Sensor[0]);
Arrays.sort(sensors, DeviceNameComparator.getInstance());
Templator tmpl = new Templator(FileUtil.find(OVERVIEW_TEMPLATE));
tmpl.set("sensors", sensors);
return tmpl;
}
}
}

View file

@ -0,0 +1,144 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.TriggerManager;
import se.hal.intf.HalWebPage;
import se.hal.struct.Action;
import se.hal.struct.ClassConfigurationData;
import se.hal.struct.Trigger;
import se.hal.struct.TriggerFlow;
import zutil.ObjectUtil;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.parser.Templator;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Logger;
public class TriggerWebPage extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
private static final String TEMPLATE = "resource/web/trigger.tmpl";
private ArrayList<ClassConfigurationData> triggerConfigurators;
private ArrayList<ClassConfigurationData> actionConfigurators;
public TriggerWebPage() {
super("trigger");
super.getRootNav().createSubNav("Events").createSubNav(this.getId(), "Triggers");
triggerConfigurators = new ArrayList<>();
for(Class c : TriggerManager.getInstance().getAvailableTriggers())
triggerConfigurators.add(new ClassConfigurationData(c));
actionConfigurators = new ArrayList<>();
for(Class c : TriggerManager.getInstance().getAvailableActions())
actionConfigurators.add(new ClassConfigurationData(c));
}
@Override
public Templator httpRespond (
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception {
DBConnection db = HalContext.getDB();
if (request.containsKey("action")) {
TriggerFlow flow = (ObjectUtil.isEmpty(request.get("flow-id")) ? null :
TriggerFlow.getTriggerFlow(db, Integer.parseInt(request.get("flow-id"))));
Trigger trigger = (ObjectUtil.isEmpty(request.get("trigger-id")) ? null :
Trigger.getTrigger(db, Integer.parseInt(request.get("trigger-id"))));
Action action = (ObjectUtil.isEmpty(request.get("action-id")) ? null :
Action.getAction(db, Integer.parseInt(request.get("action-id"))));
switch(request.get("action")) {
// Flows
case "create_flow":
logger.info("Creating new flow.");
flow = new TriggerFlow();
flow.save(db);
break;
case "modify_flow":
logger.info("Modifying flow: " + flow.getName());
flow.setEnabled("on".equals(request.get("enabled")));
flow.setName(request.get("name"));
flow.save(db);
break;
case "remove_flow":
logger.info("Removing flow: " + flow.getName());
flow.delete(db);
break;
// Triggers
case "create_trigger":
if (flow == null){
logger.warning("Invalid flow id: " + request.get("flow-id"));
HalAlertManager.getInstance().addAlert(new HalAlertManager.HalAlert(
HalAlertManager.AlertLevel.ERROR, "Invalid flow id: " + request.get("flow-id"), HalAlertManager.AlertTTL.ONE_VIEW));
break;
}
logger.info("Creating trigger associated to flow: " + flow.getName());
trigger = new Trigger();
flow.addTrigger(trigger);
flow.save(db);
/* FALLTHROUGH */
case "modify_trigger":
logger.info("Modifying trigger: " + trigger.getId());
trigger.setObjectClass(request.get("type"));
trigger.getObjectConfigurator().setValues(request).applyConfiguration();
trigger.save(db); // will save all sub beans also
break;
case "remove_trigger":
if (flow == null)
flow = TriggerFlow.getTriggerFlow(db, trigger);
logger.info("Removing trigger: " + trigger.getId());
flow.removeTrigger(trigger);
trigger.delete(db);
break;
// Triggers
case "create_action":
if (flow == null){
HalAlertManager.getInstance().addAlert(new HalAlertManager.HalAlert(
HalAlertManager.AlertLevel.ERROR, "Invalid flow id", HalAlertManager.AlertTTL.ONE_VIEW));
break;
}
logger.info("Creating action associated with flow: " + flow.getName());
action = new Action();
flow.addAction(action);
flow.save(db);
/* FALLTHROUGH */
case "modify_action":
logger.info("Modifying action: " + action.getId());
action.setObjectClass(request.get("type"));
action.getObjectConfigurator().setValues(request).applyConfiguration();
action.save(db); // will save all sub beans also
break;
case "remove_action":
if (flow == null)
flow = TriggerFlow.getTriggerFlow(db, action);
logger.info("Removing action: " + action.getId());
flow.removeAction(action);
action.delete(db);
break;
}
}
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("triggerConf", triggerConfigurators);
tmpl.set("actionConf", actionConfigurators);
tmpl.set("availableTriggers", TriggerManager.getInstance().getAvailableTriggers());
tmpl.set("availableActions", TriggerManager.getInstance().getAvailableActions());
tmpl.set("flows", TriggerFlow.getTriggerFlows(db));
return tmpl;
}
}

View file

@ -0,0 +1,67 @@
package se.hal.page;
import se.hal.HalContext;
import se.hal.intf.HalWebPage;
import se.hal.page.HalAlertManager.AlertLevel;
import se.hal.page.HalAlertManager.AlertTTL;
import se.hal.page.HalAlertManager.HalAlert;
import se.hal.struct.User;
import zutil.db.DBConnection;
import zutil.io.file.FileUtil;
import zutil.log.LogUtil;
import zutil.parser.Templator;
import java.util.Map;
import java.util.logging.Logger;
public class UserConfigWebPage extends HalWebPage {
private static final Logger logger = LogUtil.getLogger();
private static final String TEMPLATE = "resource/web/user_config.tmpl";
public UserConfigWebPage() {
super("user_profile");
super.getUserNav().createSubNav(this.getId(), "Profile");
}
@Override
public Templator httpRespond(
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request)
throws Exception {
DBConnection db = HalContext.getDB();
User localUser = User.getLocalUser(db);
// Save new input
if (request.containsKey("action")) {
User user;
switch(request.get("action")) {
// Local User
case "modify_local_user":
if (localUser == null) {
localUser = new User();
localUser.setExternal(false);
}
logger.info("Modifying user: " + localUser.getUsername());
localUser.setUsername(request.get("username"));
localUser.setEmail(request.get("email"));
localUser.setAddress(request.get("address"));
localUser.save(db);
HalAlertManager.getInstance().addAlert(new HalAlert(
AlertLevel.SUCCESS, "Successfully saved profile changes", AlertTTL.ONE_VIEW));
break;
}
}
// Output
Templator tmpl = new Templator(FileUtil.find(TEMPLATE));
tmpl.set("user", localUser);
return tmpl;
}
}

View file

@ -0,0 +1,29 @@
{
"version": 1.0,
"name": "Hal Core",
"interfaces": [
{"se.hal.intf.HalDaemon": "se.hal.daemon.SensorDataAggregatorDaemon"},
{"se.hal.intf.HalDaemon": "se.hal.daemon.SensorDataCleanupDaemon"},
{"se.hal.intf.HalJsonPage": "se.hal.page.MapJsonPage"},
{"se.hal.intf.HalJsonPage": "se.hal.page.SensorJsonPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.MapWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.SensorOverviewWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.SensorConfigWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.EventOverviewWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.EventConfigWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.TriggerWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.UserConfigWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.PropertyConfigWebPage"},
{"se.hal.intf.HalWebPage": "se.hal.page.PluginConfigWebPage"},
{"se.hal.intf.HalTrigger": "se.hal.trigger.DateTimeTrigger"},
{"se.hal.intf.HalTrigger": "se.hal.trigger.EventTrigger"},
{"se.hal.intf.HalTrigger": "se.hal.trigger.SensorTrigger"},
{"se.hal.intf.HalTrigger": "se.hal.trigger.TimerTrigger"},
{"se.hal.intf.HalAction": "se.hal.action.SendEventAction"}
]
}

View file

@ -0,0 +1,203 @@
package se.hal.struct;
import se.hal.ControllerManager;
import se.hal.HalContext;
import se.hal.intf.HalDeviceData;
import se.hal.intf.HalDeviceReportListener;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.log.LogUtil;
import zutil.parser.json.JSONParser;
import zutil.parser.json.JSONWriter;
import zutil.ui.Configurator;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Contains logic and data common to devices (Events and Sensors)
*
* @param <T> is the device type
* @param <C> is the device configuration class
* @param <D> is the device data class
*/
public abstract class AbstractDevice<T extends AbstractDevice, C,D extends HalDeviceData> extends DBBean {
private static final Logger logger = LogUtil.getLogger();
// Sensor specific data
private String name;
private String type;
private String config; // only used to store the deviceConfig configuration in DB
/** Sensor specific configuration **/
private transient C deviceConfig;
/** latest device data received **/
private transient D deviceData;
// User configuration
@DBColumn("user_id")
private User user;
// UI variables
@DBColumn("map_x")
private double x;
@DBColumn("map_y")
private double y;
protected transient List<HalDeviceReportListener<T>> listeners = new LinkedList<>();
/**************** DEVICE CONFIG ******************/
public Configurator<C> getDeviceConfigurator() {
C obj = getDeviceConfig();
if (obj != null) {
Configurator<C> configurator = new Configurator<>(obj);
configurator.setPreConfigurationListener(ControllerManager.getInstance());
configurator.setPostConfigurationListener(ControllerManager.getInstance());
return configurator;
}
return null;
}
public C getDeviceConfig() {
if (deviceConfig == null || !deviceConfig.getClass().getName().equals(type)) {
try {
Class c = Class.forName(type);
deviceConfig = (C) c.newInstance();
applyConfig();
deviceData = getLatestDeviceData(HalContext.getDB());
} catch (Exception e) {
logger.log(Level.SEVERE, "Unable instantiate DeviceConfig: "+type, e);
}
}
return deviceConfig;
}
/**
* Will replace the current DeviceData.
* And the current config will be applied on the new DeviceData.
* DeviceData will be reset if the input is set as null.
*/
public void setDeviceConfig(C data) {
if(data != null) {
type = data.getClass().getName();
deviceConfig = data;
deviceData = getLatestDeviceData(HalContext.getDB());
} else {
deviceConfig = null;
deviceData = null;
type = null;
config = null;
}
}
@Override
public void save(DBConnection db) throws SQLException {
if (deviceConfig != null)
updateConfigString();
else
this.config = null;
super.save(db);
}
/**
* Will update the config String that will be stored in DB.
*/
private void updateConfigString() {
Configurator<C> configurator = getDeviceConfigurator();
this.config = JSONWriter.toString(configurator.getValuesAsNode());
}
/**
* This method will configure the current DeviceData with the
* configuration from the config String.
*/
private void applyConfig(){
if (config != null && !config.isEmpty()) {
Configurator<C> configurator = getDeviceConfigurator();
configurator.setValues(JSONParser.read(config));
configurator.applyConfiguration();
}
}
public abstract Class<?> getController();
/**************** DEVICE DATA ******************/
/**
* @return the latest known data from the device
*/
public D getDeviceData(){
return deviceData;
}
public void setDeviceData(D latest){
this.deviceData = latest;
}
/**
* Reads latest device data from DB
*/
protected abstract D getLatestDeviceData(DBConnection db);
/**************** OTHER ******************/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* @return a String containing the class name of the DeviceData
*/
public String getType() {
return type;
}
/**
* Will set the DeviceData class type. This method will
* reset set the current DeviceData if the input type is
* null or a different type from the current DeviceData class.
*/
public void setType(String type) {
if (this.type == null || !this.type.equals(type)) {
setDeviceConfig(null); // reset
this.type = type;
}
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void addReportListener(HalDeviceReportListener<T> listener){
listeners.add(listener);
}
public void removeReportListener(HalDeviceReportListener<T> listener){
listeners.remove(listener);
}
public List<HalDeviceReportListener<T>> getReportListeners(){
return listeners;
}
}

View file

@ -0,0 +1,42 @@
package se.hal.struct;
import se.hal.intf.HalAction;
import se.hal.intf.HalTrigger;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanObjectDSO;
import zutil.db.bean.DBBeanSQLResultHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Defines a action that will be executed
*/
@DBBean.DBTable(value = "action", superBean = true)
public class Action extends DBBeanObjectDSO<HalAction>{
public static Action getAction(DBConnection db, long id) throws SQLException {
return DBBean.load(db, Action.class, id);
}
public Action() { }
public Action(HalAction action) {
this.setObject(action);
}
/**
* Executes this specific action
*/
public void execute(){
if (getObject() != null)
getObject().execute();
}
}

View file

@ -0,0 +1,18 @@
package se.hal.struct;
import zutil.ui.Configurator;
import zutil.ui.Configurator.ConfigurationParam;
/**
* A Data class used by the dynamic class configuration pages
*/
public class ClassConfigurationData {
public Class clazz;
public ConfigurationParam[] params;
public ClassConfigurationData(Class clazz) {
this.clazz = clazz;
this.params = Configurator.getConfiguration(clazz);
}
}

View file

@ -0,0 +1,64 @@
package se.hal.struct;
import se.hal.intf.HalDeviceData;
import se.hal.intf.HalEventController;
import se.hal.intf.HalEventConfig;
import se.hal.intf.HalEventData;
import se.hal.util.DeviceDataSqlResult;
import zutil.db.DBConnection;
import zutil.db.SQLResultHandler;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanSQLResultHandler;
import zutil.db.handler.SimpleSQLResult;
import zutil.log.LogUtil;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@DBBean.DBTable(value="event", superBean=true)
public class Event extends AbstractDevice<Event, HalEventConfig,HalEventData>{
private static final Logger logger = LogUtil.getLogger();
public static Event getEvent(DBConnection db, long id) throws SQLException{
return DBBean.load(db, Event.class, id);
}
public static List<Event> getLocalEvents(DBConnection db) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement(
"SELECT event.* FROM event,user WHERE user.external == 0 AND user.id == event.user_id");
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Event.class, db));
}
@Override
public Class<? extends HalEventController> getController(){
return getDeviceConfig().getEventControllerClass();
}
@Override
protected HalEventData getLatestDeviceData(DBConnection db) {
try {
Class deviceDataClass = getDeviceConfig().getEventDataClass();
if (deviceDataClass == null)
throw new ClassNotFoundException("Unknown event data class for: " + getDeviceConfig().getClass());
if (getId() != null) {
PreparedStatement stmt = db.getPreparedStatement(
"SELECT * FROM event_data_raw WHERE event_id == ? ORDER BY timestamp DESC LIMIT 1");
stmt.setLong(1, getId());
return (HalEventData)
DBConnection.exec(stmt, new DeviceDataSqlResult(deviceDataClass));
}
} catch (Exception e){
logger.log(Level.WARNING, null, e);
}
return null;
}
}

View file

@ -0,0 +1,68 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2020 Ziver Koc
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.struct;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanSQLResultHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@DBBean.DBTable(value="plugin")
public class PluginConfig extends DBBean {
private String name;
private boolean enabled;
/**
* @return a PluginConfig bean for the specific plugin name.
*/
public static PluginConfig getPluginConfig(DBConnection db, String name) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement( "SELECT plugin.* FROM plugin WHERE name == ?" );
stmt.setString(1, name);
return DBConnection.exec(stmt, DBBeanSQLResultHandler.create(PluginConfig.class, db) );
}
public PluginConfig() {}
public PluginConfig(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

View file

@ -0,0 +1,137 @@
package se.hal.struct;
import se.hal.HalContext;
import se.hal.intf.HalDeviceReportListener;
import se.hal.intf.HalSensorController;
import se.hal.intf.HalSensorConfig;
import se.hal.intf.HalSensorData;
import se.hal.util.DeviceDataSqlResult;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanSQLResultHandler;
import zutil.db.handler.SimpleSQLResult;
import zutil.log.LogUtil;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@DBBean.DBTable(value="sensor", superBean=true)
public class Sensor extends AbstractDevice<Sensor, HalSensorConfig,HalSensorData>{
private static final Logger logger = LogUtil.getLogger();
private long external_id = -1;
/** local sensor= if sensor should be public. external sensor= if sensor should be requested from host **/
private boolean sync = false;
private long aggr_version;
public static List<Sensor> getExternalSensors(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 1 AND user.id == sensor.user_id" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) );
}
public static Sensor getExternalSensor(DBConnection db, User user, long external_id) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor WHERE ? == sensor.user_id AND sensor.external_id == ?" );
stmt.setLong(1, user.getId());
stmt.setLong(2, external_id);
return DBConnection.exec(stmt, DBBeanSQLResultHandler.create(Sensor.class, db) );
}
public static List<Sensor> getLocalSensors(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT sensor.* FROM sensor,user WHERE user.external == 0 AND user.id == sensor.user_id" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) );
}
public static List<Sensor> getSensors(DBConnection db, User user) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM sensor WHERE user_id == ?" );
stmt.setLong(1, user.getId());
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) );
}
public static List<Sensor> getSensors(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM sensor" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(Sensor.class, db) );
}
public static Sensor getSensor(DBConnection db, long id) throws SQLException{
return DBBean.load(db, Sensor.class, id);
}
public static long getHighestSequenceId(long sensorId) throws SQLException{
PreparedStatement stmt = HalContext.getDB().getPreparedStatement("SELECT MAX(sequence_id) FROM sensor_data_aggr WHERE sensor_id == ?");
stmt.setLong(1, sensorId);
Integer id = DBConnection.exec(stmt, new SimpleSQLResult<Integer>());
return (id != null ? id : 0);
}
/**
* Will delete this Sensor and its aggregate data
* (raw data will never be deleted as a safety precaution!)
*/
@Override
public void delete(DBConnection db) throws SQLException {
clearAggregatedData(db);
super.delete(db);
}
/**
* Will clear all aggregated data for this Sensor and increment the AggregationVersion
*/
public void clearAggregatedData(DBConnection db) throws SQLException{
logger.fine("Clearing all aggregate data for sensor id: "+this.getId());
PreparedStatement stmt = db.getPreparedStatement( "DELETE FROM sensor_data_aggr WHERE sensor_id == ?" );
stmt.setLong(1, getId());
DBConnection.exec(stmt);
aggr_version++;
}
public long getExternalId() {
return external_id;
}
public void setExternalId(long external_id) {
this.external_id = external_id;
}
public boolean isSynced() {
return sync;
}
public void setSynced(boolean synced) {
this.sync = synced;
}
public long getAggregationVersion(){
return this.aggr_version;
}
public void setAggregationVersion(long aggr_version){
this.aggr_version = aggr_version;
}
@Override
public Class<? extends HalSensorController> getController(){
return getDeviceConfig().getSensorControllerClass();
}
@Override
protected HalSensorData getLatestDeviceData(DBConnection db) {
try {
Class deviceDataClass = getDeviceConfig().getSensorDataClass();
if (deviceDataClass == null)
throw new ClassNotFoundException("Unknown sensor data class for: " + getDeviceConfig().getClass());
if (getId() != null) {
PreparedStatement stmt = db.getPreparedStatement(
"SELECT * FROM sensor_data_raw WHERE sensor_id == ? ORDER BY timestamp DESC LIMIT 1");
stmt.setLong(1, getId());
return (HalSensorData)
DBConnection.exec(stmt, new DeviceDataSqlResult(deviceDataClass));
}
} catch (Exception e){
logger.log(Level.WARNING, null, e);
}
return null;
}
}

View file

@ -0,0 +1,54 @@
package se.hal.struct;
import se.hal.intf.HalTrigger;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanObjectDSO;
import zutil.db.bean.DBBeanSQLResultHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* A class that declares a trigger/condition that
* needs to be validated before an action can be run
*/
@DBBean.DBTable(value = "trigger", superBean = true)
public class Trigger extends DBBeanObjectDSO<HalTrigger>{
public static Trigger getTrigger(DBConnection db, long id) throws SQLException {
return DBBean.load(db, Trigger.class, id);
}
public Trigger() { }
public Trigger(HalTrigger trigger) {
this.setObject(trigger);
}
/**
* Evaluates if this trigger has passed. If the trigger is
* true then this method will return true until the {@link #reset()}
* method is called.
*/
public boolean evaluate(){
if (getObject() != null)
return getObject().evaluate();
return false;
}
/**
* Reset the evaluation to false.
*/
public void reset(){
if (getObject() != null)
getObject().reset();
}
}

View file

@ -0,0 +1,130 @@
package se.hal.struct;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanSQLResultHandler;
import zutil.db.handler.SimpleSQLResult;
import zutil.log.LogUtil;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* A class that encapsulates triggerList and their actionList.
* TODO: Bad class name, should be renamed when we come up with a better one
*/
@DBBean.DBTable("trigger_flow")
public class TriggerFlow extends DBBean {
private static final Logger logger = LogUtil.getLogger();
private boolean enabled = true;
private String name = "";
@DBLinkTable(beanClass=Trigger.class, table="trigger", idColumn = "flow_id")
private List<Trigger> triggerList = new ArrayList<>();
@DBLinkTable(beanClass=Action.class, table="action", idColumn = "flow_id")
private List<Action> actionList = new ArrayList<>();
public static List<TriggerFlow> getTriggerFlows(DBConnection db) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement("SELECT * FROM trigger_flow");
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(TriggerFlow.class, db));
}
public static TriggerFlow getTriggerFlow(DBConnection db, int id) throws SQLException {
return DBBean.load(db, TriggerFlow.class, id);
}
/**
* Looks up the parent TriggerFlow for the specified Trigger
*/
public static TriggerFlow getTriggerFlow(DBConnection db, Trigger trigger) throws SQLException {
return getParentFlow(db, "trigger", trigger);
}
/**
* Looks up the parent TriggerFlow for the specified Action
*/
public static TriggerFlow getTriggerFlow(DBConnection db, Action action) throws SQLException {
return getParentFlow(db, "action", action);
}
private static TriggerFlow getParentFlow(DBConnection db, String table, DBBean subObj) throws SQLException {
if (subObj.getId() == null)
return null;
Integer flowId = db.exec("SELECT flow_id FROM "+table+" WHERE id=="+subObj.getId(),
new SimpleSQLResult<Integer>());
if (flowId == null)
return null;
return TriggerFlow.getTriggerFlow(db, flowId);
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void addTrigger(Trigger trigger) {
triggerList.add(trigger);
}
public List<Trigger> getTriggers() {
return triggerList;
}
public void removeTrigger(Trigger trigger) {
triggerList.remove(trigger);
}
public void addAction(Action action) {
actionList.add(action);
}
public List<Action> getActions() {
return actionList;
}
public void removeAction(Action action) {
actionList.remove(action);
}
/**
* @return true if any one of the triggerList evaluate to true,
* false if there are no triggerList added.
* Note: this method will not execute any actionList
*/
public boolean evaluate(){
if (triggerList.isEmpty() || !enabled)
return false;
for(Trigger trigger : triggerList){
if (!trigger.evaluate())
return false;
}
return true;
}
/**
* Executes the associated actionList in this flow
*/
public void execute(){
if (!enabled)
return;
for(Action action : actionList){
action.execute();
}
}
/**
* Resets all trigger evaluations
*/
public void reset() {
for(Trigger trigger : triggerList){
trigger.reset();
}
}
}

View file

@ -0,0 +1,101 @@
package se.hal.struct;
import zutil.api.Gravatar;
import zutil.db.DBConnection;
import zutil.db.bean.DBBean;
import zutil.db.bean.DBBeanSQLResultHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
@DBBean.DBTable("user")
public class User extends DBBean{
private String username;
private String email;
private String address;
private int external;
private String hostname;
private int port;
public static List<User> getExternalUsers(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM user WHERE user.external == 1" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(User.class, db) );
}
public static User getLocalUser(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM user WHERE user.external == 0" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.create(User.class, db) );
}
public static List<User> getUsers(DBConnection db) throws SQLException{
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM user" );
return DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(User.class, db) );
}
public static User getUser(DBConnection db, int id) throws SQLException {
return DBBean.load(db, User.class, id);
}
/**
* Will delete this user and all its Sensors
*/
@Override
public void delete(DBConnection db) throws SQLException {
List<Sensor> sensorList = Sensor.getSensors(db, this);
for(Sensor sensor : sensorList){
sensor.delete(db);
}
super.delete(db);
}
public String getUsername() {
return username;
}
public void setUsername(String name) {
this.username = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAvatarUrl(){
return Gravatar.getImageUrl(email, 130);
}
public String getLargeAvatarUrl(){
return Gravatar.getImageUrl(email, 250);
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public boolean isExternal() {
return external > 0;
}
public void setExternal(boolean external) {
this.external = (external? 1:0 );
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2015 Ziver
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.struct.devicedata;
import se.hal.intf.HalEventData;
public class DimmerEventData extends HalEventData {
private double dimmValue;
public DimmerEventData() { }
public DimmerEventData(double dimmValue, long timestamp) {
this.dimmValue = dimmValue;
this.setTimestamp(timestamp);
}
/**
* @return the dim level from 0.0 to 1.0
*/
@Override
public double getData() {
return dimmValue;
}
@Override
public void setData(double dimmValue) {
this.dimmValue = dimmValue;
}
@Override
public String toString(){
return dimmValue+"%";
}
}

View file

@ -0,0 +1,31 @@
package se.hal.struct.devicedata;
import se.hal.intf.HalSensorData;
public class HumiditySensorData extends HalSensorData {
private double humidity;
public HumiditySensorData() { }
public HumiditySensorData(double humidity, long timestamp) {
this.humidity = humidity;
this.setTimestamp(timestamp);
}
@Override
public double getData() {
return humidity;
}
@Override
public void setData(double humidity) {
this.humidity = humidity;
}
@Override
public String toString(){
return humidity+"%";
}
}

View file

@ -0,0 +1,36 @@
package se.hal.struct.devicedata;
import se.hal.intf.HalSensorData;
public class LightSensorData extends HalSensorData {
private double lux;
public LightSensorData(){}
public LightSensorData(double lux, long timestamp){
this.lux = lux;
this.setTimestamp(timestamp);
}
/**
* @return the light intensity in lux
*/
@Override
public double getData() {
return lux;
}
/**
* @param lux set the light intensity in lux
*/
@Override
public void setData(double lux) {
this.lux = lux;
}
@Override
public String toString(){
return lux+" lux";
}
}

View file

@ -0,0 +1,34 @@
package se.hal.struct.devicedata;
import se.hal.intf.HalSensorData;
public class PowerConsumptionSensorData extends HalSensorData {
private double wattHours;
public PowerConsumptionSensorData() { }
public PowerConsumptionSensorData(double wattHours, long timestamp) {
this.wattHours = wattHours;
super.setTimestamp(timestamp);
}
/**
* @return int representing Watt/Hour
*/
@Override
public double getData() {
return wattHours;
}
@Override
public void setData(double wattHours){
this.wattHours = wattHours;
}
@Override
public String toString(){
return wattHours+" Wh";
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2015 Ziver
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package se.hal.struct.devicedata;
import se.hal.intf.HalEventData;
public class SwitchEventData extends HalEventData {
private boolean enabled;
public SwitchEventData() { }
public SwitchEventData(boolean enabled, long timestamp) {
this.enabled = enabled;
this.setTimestamp(timestamp);
}
public void turnOn(){
enabled = true;
}
public void turnOff(){
enabled = false;
}
public boolean isOn(){
return enabled;
}
@Override
public double getData() {
return (enabled ? 1.0 : 0.0);
}
@Override
public void setData(double enabled) {
this.enabled = enabled > 0;
}
@Override
public String toString(){
return enabled ? "ON" : "OFF";
}
}

View file

@ -0,0 +1,37 @@
package se.hal.struct.devicedata;
import se.hal.intf.HalSensorData;
public class TemperatureSensorData extends HalSensorData {
private double temperature;
public TemperatureSensorData(){}
public TemperatureSensorData(double temperature, long timestamp){
this.temperature = temperature;
super.setTimestamp(timestamp);
}
/**
* @return temperature in degrees C
*/
@Override
public double getData() {
return temperature;
}
/**
* @param temperature the temperature to set in degrees C
*/
@Override
public void setData(double temperature) {
this.temperature = temperature;
}
@Override
public String toString(){
return temperature+" \u00b0C";
}
}

View file

@ -0,0 +1,57 @@
package se.hal.trigger;
import se.hal.intf.HalTrigger;
import zutil.CronTimer;
import zutil.ui.Configurator;
import zutil.ui.Configurator.PreConfigurationActionListener;
import java.text.SimpleDateFormat;
public class DateTimeTrigger implements HalTrigger,Configurator.PostConfigurationActionListener {
@Configurator.Configurable("Minute (Cron format)")
private String minute = "00";
@Configurator.Configurable("Hour (Cron format)")
private String hour = "12";
@Configurator.Configurable("Day of the Month (Cron format)")
private String dayOfMonth = "*";
@Configurator.Configurable("Month (1-12 or Cron format)")
private String month = "*";
@Configurator.Configurable("Day of the Week (1-7 or Cron format)")
private String dayOfWeek = "*";
@Configurator.Configurable("Year (Cron format)")
private String year = "*";
private transient CronTimer cronTimer;
private transient long timeOut = -1;
@Override
public void postConfigurationAction(Configurator configurator, Object obj) {
cronTimer = new CronTimer(minute, hour, dayOfMonth, month, dayOfWeek, year);
reset();
}
@Override
public boolean evaluate() {
if (cronTimer == null)
return false;
// have we passed the majority of the minute? then get next timeout
if (System.currentTimeMillis()-timeOut > 50*1000)
reset();
return timeOut <= System.currentTimeMillis();
}
@Override
public void reset() {
if (cronTimer != null)
timeOut = cronTimer.next();
}
public String toString(){
return //"Cron: \""+minute+" "+hour+" "+dayOfMonth+" "+month+" "+dayOfWeek+" "+year+"\" "+
"Next timeout: "+
(timeOut>0 ? new SimpleDateFormat("yyyy-MM-dd HH:mm").format(timeOut) : timeOut);
}
}

View file

@ -0,0 +1,69 @@
package se.hal.trigger;
import se.hal.TriggerManager;
import se.hal.intf.HalDeviceData;
import se.hal.intf.HalDeviceReportListener;
import se.hal.intf.HalTrigger;
import se.hal.struct.AbstractDevice;
import zutil.ui.Configurator;
import zutil.ui.Configurator.PostConfigurationActionListener;
import zutil.ui.Configurator.PreConfigurationActionListener;
/**
* An abstract class that implements generic device data logic
*/
public abstract class DeviceTrigger implements HalTrigger,
PreConfigurationActionListener,
PostConfigurationActionListener, HalDeviceReportListener<AbstractDevice> {
@Configurator.Configurable("Device ID")
protected int deviceId = -1;
@Configurator.Configurable("Trigger only on change")
protected boolean triggerOnChange = true;
@Configurator.Configurable("Data to compare to")
protected double expectedData;
private transient HalDeviceData receivedData;
@Override
public void preConfigurationAction(Configurator configurator, Object obj) {
AbstractDevice device = getDevice(deviceId);
if (device != null)
device.removeReportListener(this);
reset();
}
@Override
public void postConfigurationAction(Configurator configurator, Object obj) {
AbstractDevice device = getDevice(deviceId);
if (device != null)
device.addReportListener(this);
}
@Override
public void receivedReport(AbstractDevice device) {
receivedData = device.getDeviceData();
// Instant trigger evaluation
if (triggerOnChange)
TriggerManager.getInstance().evaluateAndExecute();
}
@Override
public boolean evaluate() {
if (receivedData != null)
return expectedData == receivedData.getData();
return false;
}
@Override
public void reset() {
if (triggerOnChange) // only reset if we want to trigger on change
receivedData = null;
}
protected abstract AbstractDevice getDevice(long id);
}

View file

@ -0,0 +1,31 @@
package se.hal.trigger;
import se.hal.HalContext;
import se.hal.struct.Event;
import zutil.log.LogUtil;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class EventTrigger extends DeviceTrigger{
private static final Logger logger = LogUtil.getLogger();
@Override
protected Event getDevice(long id) {
try {
if (id >= 0)
return Event.getEvent(HalContext.getDB(), id);
} catch (SQLException e){ logger.log(Level.SEVERE, null, e);}
return null;
}
@Override
public String toString(){
Event event = getDevice(deviceId);
return "Trigger " + (triggerOnChange ? "on" : "when") +
" event: "+ deviceId +" ("+(event != null ? event.getName() : null) + ")" +
" == "+ expectedData;
}
}

View file

@ -0,0 +1,32 @@
package se.hal.trigger;
import se.hal.HalContext;
import se.hal.struct.Event;
import se.hal.struct.Sensor;
import zutil.log.LogUtil;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SensorTrigger extends DeviceTrigger{
private static final Logger logger = LogUtil.getLogger();
@Override
protected Sensor getDevice(long id) {
try {
if (id >= 0)
return Sensor.getSensor(HalContext.getDB(), id);
} catch (SQLException e){ logger.log(Level.SEVERE, null, e);}
return null;
}
@Override
public String toString(){
Sensor sensor = getDevice(deviceId);
return "Trigger " + (triggerOnChange ? "on" : "when") +
" sensor: "+ deviceId +" ("+(sensor != null ? sensor.getName() : null) + ")" +
" == "+ expectedData;
}
}

View file

@ -0,0 +1,27 @@
package se.hal.trigger;
import se.hal.intf.HalTrigger;
import zutil.Timer;
import zutil.ui.Configurator;
public class TimerTrigger implements HalTrigger {
@Configurator.Configurable("Countdown time (in seconds)")
private int timerTime = 10; // default 10s
private Timer timer;
@Override
public boolean evaluate() {
return timer == null || timer.hasTimedOut();
}
@Override
public void reset() {
timer = new Timer(timerTime * 1000).start();
}
public String toString(){
return "Timer: "+ timerTime +"s";
}
}

View file

@ -0,0 +1,106 @@
package se.hal.util;
import se.hal.daemon.SensorDataAggregatorDaemon.AggregationPeriodLength;
import se.hal.struct.Sensor;
import se.hal.struct.devicedata.PowerConsumptionSensorData;
import zutil.db.DBConnection;
import zutil.db.SQLResultHandler;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class AggregateDataListSqlResult implements SQLResultHandler<ArrayList<AggregateDataListSqlResult.AggregateData>> {
public static class AggregateData {
public int id;
public long timestamp;
public Float data;
public String username;
public AggregateData(int id, long time, Float data, String uname) {
this.id = id;
this.timestamp = time;
this.data = data;
this.username = uname;
}
}
public static List<AggregateData> getAggregateDataForPeriod(DBConnection db, Sensor sensor, AggregationPeriodLength aggrPeriodLength, long ageLimitInMs) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement(
"SELECT user.username as username,"
+ " sensor.*,"
+ " sensor_data_aggr.*"
+ " FROM sensor_data_aggr, user, sensor"
+ " WHERE sensor.id = sensor_data_aggr.sensor_id"
+ " AND sensor.id = ?"
+ " AND user.id = sensor.user_id"
+ " AND user.id = ?"
+ " AND timestamp_end-timestamp_start == ?"
+ " AND timestamp_start > ?"
+ " ORDER BY timestamp_start ASC");
stmt.setLong(1, sensor.getId());
stmt.setLong(2, sensor.getUser().getId());
switch(aggrPeriodLength){
case SECOND: stmt.setLong(3, UTCTimeUtility.SECOND_IN_MS-1); break;
case MINUTE: stmt.setLong(3, UTCTimeUtility.MINUTE_IN_MS-1); break;
case FIVE_MINUTES: stmt.setLong(3, UTCTimeUtility.FIVE_MINUTES_IN_MS-1); break;
case FIFTEEN_MINUTES: stmt.setLong(3, UTCTimeUtility.FIFTEEN_MINUTES_IN_MS-1); break;
case HOUR: stmt.setLong(3, UTCTimeUtility.HOUR_IN_MS-1); break;
case DAY: stmt.setLong(3, UTCTimeUtility.DAY_IN_MS-1); break;
case WEEK: stmt.setLong(3, UTCTimeUtility.WEEK_IN_MS-1); break;
default: throw new IllegalArgumentException("selected aggrPeriodLength is not supported");
}
stmt.setLong(4, (System.currentTimeMillis() - ageLimitInMs) );
return DBConnection.exec(stmt , new AggregateDataListSqlResult(sensor));
}
private Sensor sensor;
private AggregateDataListSqlResult(Sensor sensor){
this.sensor = sensor;
}
@Override
public ArrayList<AggregateData> handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
ArrayList<AggregateData> list = new ArrayList<>();
long previousTimestampEnd = -1;
while (result.next()){
int id = result.getInt("id");
long timestampStart = result.getLong("timestamp_start");
long timestampEnd = result.getLong("timestamp_end");
String username = result.getString("username");
float confidence = result.getFloat("confidence");
// Calculate the data point
float data = result.getFloat("data"); //the "raw" recorded data
float estimatedData = data/confidence; //estimate the "real" value of the data by looking at the confidence value
// Add null data point to list if one or more periods of data is missing before this
if (previousTimestampEnd != -1 && sensor.getDeviceConfig() != null){
boolean shortInterval = timestampEnd-timestampStart < sensor.getDeviceConfig().getDataInterval();
long distance = timestampStart - (previousTimestampEnd + 1);
if (// Only add nulls if the report interval is smaller than the aggregated interval
!shortInterval && distance > 0 ||
// Only add nulls if space between aggr is larger than sensor report interval
shortInterval && distance > sensor.getDeviceConfig().getDataInterval())
list.add(new AggregateData(id, previousTimestampEnd + 1, null /*Float.NaN*/, username));
}
if (sensor.getDeviceConfig().getSensorDataClass() == PowerConsumptionSensorData.class)
estimatedData = (estimatedData/1000f);
list.add(new AggregateData(id, timestampEnd, estimatedData, username)); //add this data point to list
// Update previous end timestamp
previousTimestampEnd = timestampEnd;
}
return list;
}
}

View file

@ -0,0 +1,37 @@
package se.hal.util;
import se.hal.intf.HalDeviceData;
import zutil.ClassUtil;
import zutil.db.SQLResultHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DeviceDataSqlResult implements SQLResultHandler<HalDeviceData> {
private Class<? extends HalDeviceData> clazz;
public DeviceDataSqlResult(Class<? extends HalDeviceData> clazz){
this.clazz = clazz;
}
@Override
public HalDeviceData handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
try {
if (result.next()) {
HalDeviceData dataObj = clazz.newInstance();
dataObj.setData(result.getDouble("data"));
dataObj.setTimestamp(result.getLong("timestamp"));
return dataObj;
}
} catch (SQLException e){
throw e;
} catch (Exception e){
throw new SQLException(e);
}
return null;
}
}

View file

@ -0,0 +1,23 @@
package se.hal.util;
import se.hal.struct.AbstractDevice;
import java.util.Comparator;
/**
* A comparator that compares on the device name.
*/
public class DeviceNameComparator implements Comparator<AbstractDevice> {
private static DeviceNameComparator instance;
@Override
public int compare(AbstractDevice device1, AbstractDevice device2) {
return device1.getName().compareTo(device2.getName());
}
public static DeviceNameComparator getInstance() {
if (instance == null)
instance = new DeviceNameComparator();
return instance;
}
}

View file

@ -0,0 +1,28 @@
package se.hal.util;
import zutil.db.SQLResultHandler;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class HistoryDataListSqlResult implements SQLResultHandler<List<HistoryDataListSqlResult.HistoryData>> {
public static class HistoryData{
public long timestamp;
public float data;
}
@Override
public List<HistoryData> handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
ArrayList<HistoryData> list = new ArrayList<HistoryData>();
while(result.next()){
HistoryData data = new HistoryData();
data.timestamp = result.getLong("timestamp");
data.data = result.getFloat("data");
list.add(data);
}
return list;
}
}

View file

@ -0,0 +1,52 @@
package se.hal.util;
import se.hal.daemon.SensorDataAggregatorDaemon.AggregationPeriodLength;
public class UTCTimePeriod{
private final long start;
private final long end;
private final AggregationPeriodLength periodLength;
public UTCTimePeriod(long timestamp, AggregationPeriodLength periodLength){
start = UTCTimeUtility.getTimestampPeriodStart(periodLength, timestamp);
end = UTCTimeUtility.getTimestampPeriodEnd(periodLength, timestamp);
this.periodLength = periodLength;
}
public long getStartTimestamp(){
return start;
}
public long getEndTimestamp(){
return end;
}
public UTCTimePeriod getNextPeriod(){
return new UTCTimePeriod(end+1, periodLength);
}
public UTCTimePeriod getPreviosPeriod(){
return new UTCTimePeriod(start-1, periodLength);
}
public boolean containsTimestamp(long timestamp){
return start <= timestamp && timestamp <= end;
}
public boolean equals(Object other){
if(other == null)
return false;
if(other instanceof UTCTimePeriod){
UTCTimePeriod o = (UTCTimePeriod)other;
return start == o.start
&& end == o.end
&& periodLength == o.periodLength;
}
return false;
}
public String toString(){
return start + "=>" + end + " (" + UTCTimeUtility.getDateString(start) + "=>" + UTCTimeUtility.getDateString(end) + ")";
}
}

View file

@ -0,0 +1,250 @@
package se.hal.util;
import se.hal.daemon.SensorDataAggregatorDaemon.AggregationPeriodLength;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
public class UTCTimeUtility {
public static final TimeZone TIMEZONE = TimeZone.getTimeZone("UTC");
public static final Locale LOCALE = new Locale("sv","SE");
public static final long SECOND_IN_MS = 1000;
public static final long MINUTE_IN_MS = SECOND_IN_MS * 60;
public static final long FIVE_MINUTES_IN_MS = MINUTE_IN_MS * 5;
public static final long FIFTEEN_MINUTES_IN_MS = MINUTE_IN_MS * 15;
public static final long HOUR_IN_MS = MINUTE_IN_MS * 60;
public static final long DAY_IN_MS = HOUR_IN_MS * 24;
public static final long WEEK_IN_MS = DAY_IN_MS * 7;
public static final long INFINITY = Long.MAX_VALUE; //sort of true
public static long getTimestampPeriodStart(AggregationPeriodLength aggrPeriodLength, long timestamp) throws NumberFormatException{
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(timestamp);
cal.setFirstDayOfWeek(Calendar.MONDAY);
switch(aggrPeriodLength){
case YEAR:
cal.set(Calendar.DAY_OF_YEAR, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case MONTH:
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case WEEK:
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case DAY:
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case HOUR:
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case FIVE_MINUTES:
cal.set(Calendar.MINUTE, (cal.get(Calendar.MINUTE)/5)*5);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case FIFTEEN_MINUTES:
cal.set(Calendar.MINUTE, (cal.get(Calendar.MINUTE)/15)*15);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case MINUTE:
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
break;
case SECOND:
cal.set(Calendar.MILLISECOND, 0);
break;
}
return cal.getTimeInMillis();
}
public static long getTimestampPeriodEnd(AggregationPeriodLength aggrPeriodLength, long timestamp) throws NumberFormatException{
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(timestamp);
cal.setFirstDayOfWeek(Calendar.MONDAY);
switch(aggrPeriodLength){
case YEAR:
cal.set(Calendar.DAY_OF_YEAR, cal.getActualMaximum(Calendar.DAY_OF_YEAR));
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case MONTH:
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case WEEK:
cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case DAY:
cal.set(Calendar.HOUR_OF_DAY, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case HOUR:
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case FIVE_MINUTES:
cal.set(Calendar.MINUTE, 4+(cal.get(Calendar.MINUTE)/5)*5);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case FIFTEEN_MINUTES:
cal.set(Calendar.MINUTE, 14+(cal.get(Calendar.MINUTE)/15)*15);
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case MINUTE:
cal.set(Calendar.SECOND, 59);
cal.set(Calendar.MILLISECOND, 1000);
break;
case SECOND:
cal.set(Calendar.MILLISECOND, 1000);
break;
}
return cal.getTimeInMillis()-1; //subtract one
}
public static int getMillisecondInSecondFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.MILLISECOND);
}
public static int getSecondOfMinuteFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.SECOND);
}
public static int getMinuteOfHourFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.MINUTE);
}
public static int getHourOfDayFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.HOUR_OF_DAY);
}
public static int getDayOfWeekFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.DAY_OF_WEEK);
}
public static int getDayOfMonthFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.DAY_OF_MONTH);
}
public static int getDayOfYearFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.DAY_OF_YEAR);
}
public static int getWeekOfYearFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.WEEK_OF_YEAR);
}
public static int getMonthOfYearFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.MONTH);
}
public static int getYearFromTimestamp(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(ms);
return cal.get(Calendar.YEAR);
}
public static String timeInMsToString(long ms) throws NumberFormatException{
if(ms < 0)
throw new NumberFormatException("argument must be positive");
String retval = "";
int weeks = (int) (ms / WEEK_IN_MS);
if(weeks > 0){
retval += weeks + "w+";
}
int days = ((int) (ms / DAY_IN_MS)) % 7;
if(days > 0){
retval += days + "d+";
}
int hours = (int) ((ms % DAY_IN_MS) / HOUR_IN_MS);
retval += (hours<10?"0"+hours:hours);
int minutes = (int) ((ms % HOUR_IN_MS) / MINUTE_IN_MS);
retval += ":" + (minutes<10?"0"+minutes:minutes);
int seconds = (int) ((ms % MINUTE_IN_MS) / SECOND_IN_MS);
retval += ":" + (seconds<10?"0"+seconds:seconds);
int milliseconds = (int) (ms % SECOND_IN_MS);
retval += "." + (milliseconds<100?"0"+(milliseconds<10?"0"+milliseconds:milliseconds):milliseconds);
return retval;
}
public static String getDateString(long timestamp){
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
format.setTimeZone(TIMEZONE);
Calendar cal = Calendar.getInstance(TIMEZONE, LOCALE);
cal.setTimeInMillis(timestamp);
return format.format(cal.getTime());
}
}