Java tutorial
/* * Copyright (C) 2013, Alliance for Sustainable Energy * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package gov.nrel.bacnet.consumer; import gov.nrel.bacnet.Config; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.InterfaceAddress; import java.net.NetworkInterface; import java.nio.charset.Charset; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import java.util.Timer; import com.serotonin.bacnet4j.LocalDevice; import com.serotonin.bacnet4j.event.DefaultExceptionListener; import com.serotonin.bacnet4j.type.primitive.UnsignedInteger; import com.serotonin.util.queue.ByteQueue; import org.apache.commons.cli.*; public class BACnet { private static final Logger logger = Logger.getLogger(Main.class.getName()); private ScheduledThreadPoolExecutor schedSvc; //used for broadcast and pollingz private ExecutorService execSvc; private ExecutorService recorderSvc; private Timer slaveDeviceTimer; private OurExecutor exec; private DatabusDataWriter writer; private Config config; private LocalDevice localDevice; private LogHandler logHandler; private BACnetDatabase database; private TaskTracker tracker; public BACnet(Config t_config) { config = t_config; tracker = new TaskTracker(); localDevice = null; logHandler = null; try { logger.info("starting. Loading logging.properties first to log to a file in logs directory"); FileInputStream configFile = new FileInputStream(config.getLoggingPropertiesFileName()); LogManager.getLogManager().readConfiguration(configFile); if (config.getVerboseLogging()) { logger.setLevel(Level.INFO); } if (config.getVeryVerboseLogging()) { logger.setLevel(Level.ALL); } logger.info("Starting now that logger properties are loaded"); LocalDevice.setExceptionListener(new MyExceptionListener()); initialize(config); } catch (Throwable e) { logger.log(Level.WARNING, "exception starting", e); } } public void setDatabase(BACnetDatabase d) { logger.info("Setting new database"); database = d; } public BACnetDatabase getDatabase() { return database; } public TaskTracker getTaskTracker() { return tracker; } public void setLogger(LogHandler h) { logger.info("Setting new loghandler"); if (logHandler != null) { Logger.getLogger("gov.nrel.bacnet").removeHandler(logHandler); } logHandler = h; Logger.getLogger("gov.nrel.bacnet").addHandler(logHandler); } public OurExecutor getOurExec() { return exec; } public LogHandler getLogger() { return logHandler; } public LocalDevice getLocalDevice() { return localDevice; } public Collection<BACnetDataWriter> getDefaultWriters() { java.util.List<BACnetDataWriter> writerlist = new java.util.ArrayList(); if (getDatabusDataWriter() != null) { writerlist.add(getDatabusDataWriter()); } if (database != null) { writerlist.add(database); } return writerlist; } private void initialize(Config config) throws IOException { LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(1000); RejectedExecutionHandler rejectedExec = new RejectedExecHandler(); // schedule polling on single threaded service because local device instance is not threadsafe execSvc = Executors.newFixedThreadPool(config.getNumThreads()); //give databus recording 2 threads to match old code recorderSvc = new ThreadPoolExecutor(20, 20, 120, TimeUnit.SECONDS, queue, rejectedExec); schedSvc = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(config.getNumThreads()); exec = new OurExecutor(schedSvc, execSvc, recorderSvc); String devname = config.getNetworkDevice(); int device_id = config.getDeviceId(); NetworkInterface networkinterface = null; try { networkinterface = java.net.NetworkInterface.getByName(devname); } catch (Exception ex) { System.out.println("Unable to open device: " + devname); System.exit(-1); } if (networkinterface == null) { System.out.println("Unable to open device: " + devname); System.exit(-1); } List<InterfaceAddress> addresses = networkinterface.getInterfaceAddresses(); String sbroadcast = null; String saddress = null; //InterfaceAddress ifaceaddr = null; for (InterfaceAddress address : addresses) { logger.fine("Evaluating address: " + address.toString()); if (address.getAddress().getAddress().length == 4) { logger.info("Address is ipv4, selecting: " + address.toString()); sbroadcast = address.getBroadcast().toString().substring(1); saddress = address.getAddress().toString().substring(1); //ifaceaddr = address; break; } else { logger.info("Address is not ipv4, not selecting: " + address.toString()); } } logger.info("Binding to: " + saddress + " " + sbroadcast); localDevice = new LocalDevice(device_id, sbroadcast); localDevice.setPort(LocalDevice.DEFAULT_PORT); localDevice.setTimeout(localDevice.getTimeout() * 3); localDevice.setSegTimeout(localDevice.getSegTimeout() * 3); try { localDevice.initialize(); localDevice.setRetries(0); //don't retry as it seems to really be a waste. } catch (IOException e) { e.printStackTrace(); return; } if (config.getSlaveDeviceEnabled()) { slaveDeviceTimer = new Timer(); slaveDeviceTimer.schedule(new gov.nrel.bacnet.SlaveDevice(localDevice, config), 1000, config.getSlaveDeviceUpdateInterval() * 1000); } int counter = 0; String username = config.getDatabusUserName(); String key = config.getDatabusKey(); logger.info("user=" + username + " key=" + key); DatabusSender sender = null; if (config.getDatabusEnabled()) { sender = new DatabusSender(username, key, execSvc, config.getDatabusUrl(), config.getDatabusPort(), true); } logger.info("databus sender: " + sender); writer = new DatabusDataWriter(new DataPointWriter(sender)); logger.info("databus writer" + writer); } public static String readFile(String file, Charset cs) throws IOException { // No real need to close the BufferedReader/InputStreamReader // as they're only wrapping the stream FileInputStream stream = new FileInputStream(file); try { Reader reader = new BufferedReader(new InputStreamReader(stream, cs)); StringBuilder builder = new StringBuilder(); char[] buffer = new char[8192]; int read; while ((read = reader.read(buffer, 0, buffer.length)) > 0) { builder.append(buffer, 0, read); } return builder.toString(); } finally { // Potential issue here: if this throws an IOException, // it will mask any others. Normally I'd use a utility // method which would log exceptions and swallow them stream.close(); } } public BACnetDataWriter getDatabusDataWriter() { return writer; } public static Config parseOptions(String[] args) throws Exception { int min_id = -1; int max_id = -1; int device_id = 11234; int scanInterval = 168; int broadcastInterval = 1; int range = 100; int numThreads = java.lang.Runtime.getRuntime().availableProcessors() + 1; String databusStreamTable = "bacnetstreamMeta"; String databusDeviceTable = "bacnetdeviceMeta"; String databusUserName = ""; String databusKey = ""; String databusURL = ""; int databusPort = 1; String devname = "eth0"; String filterFile = "config/filter.json"; String slaveDeviceConfigFile = "config/example_oid.json"; int slaveDeviceUpdateInterval = 10; boolean scan = true; boolean slaveDeviceEnabled = false; String loggingPropertiesFile = "config/logging.properties"; boolean verboseLogging = false; boolean veryVerboseLogging = false; boolean databusEnabled = true; CommandLineParser parser = new PosixParser(); Options options = new Options(); options.addOption("m", "min-device-id", true, "Minimum device ID to scan for, default: " + min_id); options.addOption("M", "max-device-id", true, "Maximum device ID to scan for, default: " + max_id); options.addOption("i", "id", true, "Device ID of this software, default: " + device_id); options.addOption("D", "device-id", true, "device ID to scan, exclusive of min-device-id and max-device-id"); options.addOption("f", "filter-file", true, "JSON filter file to use during scanning, default: " + filterFile); options.addOption("d", "dev", true, "Network device to use for broadcasts, default: " + devname); options.addOption("s", "scan", false, "Enable scanning feature, default: " + scan); options.addOption("S", "slave-device", false, "Enable slave device feature, default: " + slaveDeviceEnabled); options.addOption("T", "slave-device-interval", true, "Number of seconds between updates to slave device values, default: " + slaveDeviceUpdateInterval); options.addOption("t", "scan-interval", true, "Amount of time (in ms) to wait between finishing one scan and starting another. default: " + scanInterval); options.addOption("F", "oid-file", true, "JSON oid file to use for the slave device configuration, default: " + slaveDeviceConfigFile); options.addOption("v", "verbose", false, "Verbose logging (Info Level). Default is warning and error logging. default: " + verboseLogging); options.addOption("vv", "very-verbose", false, "Very verbose logging (All Levels). Default is warning and error logging. default: " + veryVerboseLogging); options.addOption("u", "databus-url", true, "Databus URL to send data to, default: " + databusURL); options.addOption("k", "databus-key", true, "Key for sending to Databus, default: " + databusKey); options.addOption("U", "databus-user", true, "Databus username for sending to Database, default: " + databusUserName); options.addOption("p", "databus-port", true, "Databus port for sending to Database, default: " + databusPort); options.addOption("l", "logging-properties-file", true, "File for loading logger configuration, default: " + loggingPropertiesFile); options.addOption("databus", "databus-enabled", true, "Enable writing to databus. default: " + databusEnabled); options.addOption("h", "threads", true, "Number of threads to use during processing. default: " + numThreads); try { CommandLine line = parser.parse(options, args); scanInterval = Integer.parseInt(line.getOptionValue("t", "168")); device_id = Integer.parseInt(line.getOptionValue("i", "1234")); scan = line.hasOption("s"); slaveDeviceEnabled = line.hasOption("S"); min_id = Integer.parseInt(line.getOptionValue("m", "-1")); max_id = min_id; max_id = Integer.parseInt(line.getOptionValue("M", "-1")); if (min_id == -1) { min_id = 0; } if (max_id == -1) { max_id = 4000000; } if (line.hasOption("m") && !line.hasOption("M")) { HelpFormatter hp = new HelpFormatter(); hp.printHelp("Syntax:", "Error: a max-device-id must be specified if a min-device-id is specified", options, "", true); System.exit(-1); } if (max_id < min_id) { HelpFormatter hp = new HelpFormatter(); hp.printHelp("Syntax:", "Error: max-device-id cannot be less than min-device-id", options, "", true); System.exit(-1); } if (line.hasOption("D") && (line.hasOption("m") || line.hasOption("M"))) { HelpFormatter hp = new HelpFormatter(); hp.printHelp("Syntax:", "Error: you cannot specify both a specific device-id and a min-device-id or max-device-id", options, "", true); System.exit(-1); } if (line.hasOption("D")) { min_id = max_id = Integer.parseInt(line.getOptionValue("D")); } if (line.hasOption("f")) { filterFile = line.getOptionValue("f"); } if (line.hasOption("F")) { slaveDeviceConfigFile = line.getOptionValue("F"); } devname = line.getOptionValue("dev", "eth0"); if (line.hasOption("v")) { verboseLogging = true; } if (line.hasOption("vv")) { veryVerboseLogging = true; } if (line.hasOption("u")) { databusURL = line.getOptionValue("u"); } if (line.hasOption("k")) { databusKey = line.getOptionValue("k"); } if (line.hasOption("U")) { databusUserName = line.getOptionValue("U"); } if (line.hasOption("p")) { databusPort = Integer.parseInt(line.getOptionValue("p")); } if (line.hasOption("l")) { loggingPropertiesFile = line.getOptionValue("l"); } if (line.hasOption("databus-enabled")) { databusEnabled = Boolean.parseBoolean(line.getOptionValue("databus-enabled")); } if (line.hasOption("h")) { numThreads = Integer.parseInt(line.getOptionValue("h")); } } catch (Exception e) { HelpFormatter hp = new HelpFormatter(); logger.log(Level.SEVERE, "Commmand Line Parsing Error: ", e); hp.printHelp("Syntax:", options, true); System.exit(-1); } Config config = new Config(scanInterval, broadcastInterval, range, numThreads, devname, verboseLogging, veryVerboseLogging, device_id, filterFile, loggingPropertiesFile, databusEnabled, databusDeviceTable, databusStreamTable, databusUserName, databusKey, databusURL, databusPort, slaveDeviceEnabled, slaveDeviceConfigFile, slaveDeviceUpdateInterval, min_id, max_id); return config; } static class MyExceptionListener extends DefaultExceptionListener { @Override public void unimplementedVendorService(UnsignedInteger vendorId, UnsignedInteger serviceNumber, ByteQueue queue) { if (vendorId.intValue() == 8 && serviceNumber.intValue() == 1) { // do nothing logger.info("do nothing...unimplemented vendor service=" + vendorId + " service=" + serviceNumber); } else { logger.info("Received unimplemented vendor service: vendor id=" + vendorId + ", service number=" + serviceNumber + ", bytes (with context id)=" + queue); } } public void receivedException(Exception e) { logger.log(Level.WARNING, "Exception", e); } public void receivedThrowable(Throwable t) { logger.log(Level.WARNING, "Exc", t); } } }