package me.legrange.panstamp.tools.store; import com.github.jsonj.JsonElement; import com.github.jsonj.JsonObject; import static com.github.jsonj.tools.JsonBuilder.field; import static com.github.jsonj.tools.JsonBuilder.object; import com.github.jsonj.tools.JsonParser; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.math.BigInteger; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import me.legrange.panstamp.DeviceStateStore; import me.legrange.panstamp.Network; import me.legrange.panstamp.NetworkException; import me.legrange.panstamp.NetworkListener; import me.legrange.panstamp.PanStamp; import me.legrange.panstamp.Register; import me.legrange.panstamp.definition.CompoundDeviceLibrary; import me.legrange.panstamp.event.AbstractNetworkListener; import me.legrange.panstamp.xml.ClassLoaderLibrary; import me.legrange.panstamp.xml.FileLibrary; import me.legrange.swap.ModemSetup; import me.legrange.swap.SwapException; import me.legrange.swap.SwapModem; import me.legrange.swap.SerialModem; import me.legrange.swap.tcp.TcpModem; /** * Storage for tool configuration data and for network discovery data. * * Data is saved to a JSON file. * * @author gideon */ public class Store { public static final String STORE_VERSION = "1.0"; public static Store openFile(String fileName) throws DataStoreException { return new Store(fileName); } public void addGateway(Network gw) throws DataStoreException { addJsonGateway(gw); gw.addListener(networkListener); gw.setDeviceStore(new JsonStateStore(gw)); flush(); } public void removeGateway(Network gw) throws NetworkException { root.getObject(NETWORKS).remove(makeKey(gw)); flush(); } /** * Load all gateways stored in the data store * * @return The list of gateways loaded from storage. * @throws me.legrange.panstamp.tools.store.DataStoreException If there is * an error loading the definitions. */ public List<Network> loadNetworks() throws DataStoreException { List<Network> gateways = new LinkedList<>(); JsonObject networksO = root.getObject(NETWORKS); for (String key : networksO.keySet()) { JsonObject networkO = networksO.getObject(key); gateways.add(loadNetwork(networkO)); } return gateways; } /** * Load all device libraries configured from storage. * * @return The device library implementation, or null if it isn't defined. . * @throws DataStoreException If there is an error loading the library. */ public FileLibrary getLibary() throws DataStoreException { String libraryO = root.getString(XML_LIBRARY); if (libraryO != null) { return new FileLibrary(new File(libraryO)); } return null; } /** * Set the file library used by the application, if configured. * * @param lib The library */ public void setLibrary(FileLibrary lib) throws DataStoreException { if (lib != null) { root.put(XML_LIBRARY, lib.getDirectory()); } else { root.remove(XML_LIBRARY); } flush(); } private JsonObject addDevice(PanStamp ps) throws DataStoreException { Network nw = ps.getNetwork(); JsonObject networkO = getGateway(nw); JsonObject devicesO = getDevices(networkO); JsonObject deviceO = new JsonObject(); devicesO.put("" + ps.getAddress(), deviceO); flush(); return deviceO; } private JsonElement removeDevice(PanStamp ps) { Network nw = ps.getNetwork(); JsonObject networkO = getGateway(nw); JsonObject devicesO = getDevices(networkO); return devicesO.remove("" + ps.getAddress()); } /** * Load a network from JSON */ private Network loadNetwork(JsonObject gatewayO) throws DataStoreException { try { SwapModem modem = loadModem(gatewayO.getObject(SWAP_MODEM)); Network gw = Network.create(modem); FileLibrary lib = getLibary(); if (lib != null) { gw.setDeviceLibrary(new CompoundDeviceLibrary(lib, new ClassLoaderLibrary())); } gw.setNetworkId(gatewayO.getInt(NETWORK_ID)); gw.setChannel(gatewayO.getInt(CHANNEL)); gw.setDeviceAddress(gatewayO.getInt(DEVICE_ADDRESS)); gw.setSecurityOption(gatewayO.getInt(SECURITY_OPTION)); loadDevices(gw, gatewayO.getObject(DEVICES)); gw.setDeviceStore(new JsonStateStore(gw)); return gw; } catch (NetworkException ex) { throw new DataStoreException(ex.getMessage(), ex); } } /** * Load the devices from JSOM */ private void loadDevices(Network gw, JsonObject devicesO) throws NetworkException { for (String key : devicesO.keySet()) { JsonObject deviceO = devicesO.getObject(key); PanStamp ps = new PanStamp((Network) gw, Integer.parseInt(key)); for (String regKey : deviceO.keySet()) { int id = Integer.parseInt(regKey); Register reg = ps.getRegister(id); reg.setValue(hexToBytes(deviceO.getString(regKey))); } gw.addDevice(ps); } } /** * Load a SWAP modem */ private SwapModem loadModem(JsonObject modemO) throws DataStoreException { String type = modemO.getString(TYPE); switch (type) { case SERIAL: return loadSerialModem(modemO); case TCP_IP: return loadTcpModem(modemO); default: throw new DataStoreException(String.format("Unknown modem type '%s'. BUG!", type)); } } /** * Load a serial modem */ private SerialModem loadSerialModem(JsonObject modemO) { return new SerialModem(modemO.getString(SERIAL_PORT), modemO.getInt(SERIAL_SPEED)); } /** * Load a TCP/IP modem */ private TcpModem loadTcpModem(JsonObject modemO) { return new TcpModem(modemO.getString(TCP_HOST), modemO.getInt(TCP_PORT)); } /** * store a SWAP modem */ private JsonElement storeModem(SwapModem modem) throws DataStoreException { JsonObject modemO; switch (modem.getType()) { case SERIAL: modemO = storeSerialModem((SerialModem) modem); break; case TCP_IP: modemO = storeTcpModem((TcpModem) modem); break; default: throw new DataStoreException(String.format("Unknown modem type '%s'. BUG!", modem.getType())); } try { modemO.add(field(MODEM_SETUP, storeSetup(modem.getSetup()))); } catch (SwapException ex) { throw new DataStoreException(ex.getMessage(), ex); } return modemO; } /** * convert a serial modem to JSON */ private JsonObject storeSerialModem(SerialModem modem) { return object( field(TYPE, SERIAL), field(SERIAL_PORT, modem.getPort()), field(SERIAL_SPEED, modem.getBaud()) ); } /** * convert a TCP/IP modem to JSON */ private JsonObject storeTcpModem(TcpModem modem) { return object( field(TYPE, TCP_IP), field(TCP_HOST, modem.getHost()), field(TCP_PORT, modem.getPort()) ); } /** * convert ModemSetup to JSON */ private JsonObject storeSetup(ModemSetup setup) { return object( field(NETWORK_ID, setup.getNetworkID()), field(DEVICE_ADDRESS, setup.getDeviceAddress()), field(CHANNEL, setup.getChannel()) ); } private JsonObject addJsonGateway(Network gw) { try { JsonObject networksO = root.getObject(NETWORKS); String key = makeKey(gw); JsonObject gwO = object( field(NETWORK_ID, gw.getNetworkId()), field(DEVICE_ADDRESS, gw.getDeviceAddress()), field(CHANNEL, gw.getChannel()), field(SECURITY_OPTION, gw.getSecurityOption()), field(SWAP_MODEM, storeModem(gw.getSWAPModem())), field(DEVICES, new JsonObject())); networksO.put(key, gwO); return gwO; } catch (NetworkException ex) { return new JsonObject(); } } private JsonObject getGateway(Network gw) { try { String key = makeKey(gw); JsonObject networksO = root.getObject(NETWORKS); if (!networksO.containsKey(key)) { return addJsonGateway(gw); } return networksO.getObject(key); } catch (NetworkException ex) { return new JsonObject(); } } private JsonObject getDevices(JsonObject gwO) { JsonObject devicesO = gwO.getObject(DEVICES); if (devicesO == null) { devicesO = new JsonObject(); gwO.put(DEVICES, devicesO); } return devicesO; } private JsonObject getDevice(JsonObject gwO, int address) { JsonObject devicesO = getDevices(gwO); JsonObject deviceO = devicesO.getObject("" + address); if (deviceO == null) { deviceO = new JsonObject(); devicesO.put("" + address, deviceO); } return deviceO; } /** * flush the JSON data to disk */ private void flush() throws DataStoreException { try (PrintWriter out = new PrintWriter(fileName)) { out.println(root.prettyPrint()); out.flush(); out.close(); } catch (FileNotFoundException ex) { throw new DataStoreException(ex.getMessage(), ex); } } /** * make a key for the given gateway */ private String makeKey(Network gw) throws NetworkException { String path = ""; switch (gw.getSWAPModem().getType()) { case SERIAL: path = ((SerialModem) gw.getSWAPModem()).getPort(); break; case TCP_IP: path = ((TcpModem) gw.getSWAPModem()).getHost() + ":" + ((TcpModem) gw.getSWAPModem()).getPort(); break; } return String.format("%s->%d", path, gw.getNetworkId()); } /** * Create new instance with a backing file */ private Store(String fileName) throws DataStoreException { try { this.fileName = fileName; File file = new File(fileName); if (file.exists()) { root = new JsonParser().parseObject(new FileReader(file)); if (root.containsKey(VERSION)) { String ver = root.getString(VERSION); if (!STORE_VERSION.equals(ver)) { throw new DataStoreException(String.format("Incompatible store version: Expected '%s' but found '%s'", STORE_VERSION, ver)); } } } else { root = object( field(VERSION, STORE_VERSION), field(NETWORKS, object()) ); } } catch (IOException ex) { throw new DataStoreException(ex.getMessage(), ex); } } private byte[] hexToBytes(String text) { text = text.trim(); if ((text.length() % 2) == 1) { text = "0" + text; } byte data[] = new byte[text.length() / 2]; for (int i = 0; i < text.length(); i = i + 2) { int val = Integer.parseInt("" + text.charAt(i) + text.charAt(i + 1), 16); if (val > 127) { val = 256 - val; } data[i / 2] = (byte) val; } return data; } private final String fileName; private JsonObject root; private static final String SWAP_MODEM = "swapModem"; private static final String TYPE = "type"; private static final String SERIAL = "serial"; private static final String TCP_IP = "tcpIp"; private static final String SERIAL_PORT = "serialPort"; private static final String SERIAL_SPEED = "serialSpeed"; private static final String TCP_HOST = "tcpHost"; private static final String TCP_PORT = "tcpPort"; private static final String MODEM_SETUP = "modemSetup"; private static final String NETWORK_ID = "networkId"; private static final String DEVICE_ADDRESS = "deviceAddress"; private static final String CHANNEL = "channel"; private static final String SECURITY_OPTION = "securityOption"; private static final String DEVICES = "devices"; private static final String VERSION = "storeVersion"; private static final String NETWORKS = "gateways"; private static final String XML_LIBRARY = "xml-library"; private NetworkListener networkListener = new AbstractNetworkListener() { @Override public void deviceDetected(Network gw, PanStamp dev) { try { addDevice(dev); flush(); } catch (DataStoreException ex) { Logger.getLogger(Store.class.getName()).log(Level.SEVERE, null, ex); } } @Override public void deviceRemoved(Network gw, PanStamp dev) { removeDevice(dev); try { flush(); } catch (DataStoreException ex) { Logger.getLogger(Store.class.getName()).log(Level.SEVERE, null, ex); } } }; private class JsonStateStore implements DeviceStateStore { public JsonStateStore(Network gw) { this.gw = gw; } @Override public boolean hasRegisterValue(Register reg) { JsonObject devO = getDevice(getGateway(gw), reg.getDevice().getAddress()); return devO.containsKey("" + reg.getId()); } @Override public byte[] getRegisterValue(Register reg) { JsonObject devO = getDevice(getGateway(gw), reg.getDevice().getAddress()); return new BigInteger(devO.getString("" + reg.getId()), 16).toByteArray(); } @Override public void setRegisterValue(Register reg, byte[] value) { JsonObject devO = getDevice(getGateway(gw), reg.getDevice().getAddress()); StringBuilder val = new StringBuilder(); for (byte b : value) { val.append(String.format("%02x", ((int) b) & 0xFF)); } devO.put("" + reg.getId(), val.toString()); try { flush(); } catch (DataStoreException ex) { Logger.getLogger(Store.class.getName()).log(Level.SEVERE, null, ex); } } private final Network gw; } }