From 446387911a608b4a7da3f34bb91bc4ba456b907d Mon Sep 17 00:00:00 2001 From: jsetton Date: Sun, 22 Dec 2024 23:38:11 -0500 Subject: [PATCH] [insteon] Add modem database backup restore console commands Signed-off-by: jsetton --- .../internal/InsteonBindingConstants.java | 4 +- .../internal/command/DebugCommand.java | 55 +++---- .../internal/command/InsteonCommand.java | 21 +++ .../internal/command/ModemCommand.java | 142 +++++++++++++++++- .../internal/device/InsteonDevice.java | 2 +- .../insteon/internal/device/InsteonModem.java | 38 +++-- .../device/database/DatabaseRecord.java | 14 +- .../internal/device/database/LinkDB.java | 8 +- .../device/database/LinkDBReader.java | 4 +- .../device/database/LinkDBWriter.java | 4 +- .../internal/device/database/ModemDB.java | 9 ++ .../device/database/ModemDBReader.java | 7 - .../device/database/ModemDBRecord.java | 35 ++++- .../device/database/ModemDBWriter.java | 2 +- 14 files changed, 266 insertions(+), 79 deletions(-) diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBindingConstants.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBindingConstants.java index 7394194e79a26..31f868dd404b6 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBindingConstants.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBindingConstants.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.insteon.internal; -import java.io.File; +import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; @@ -34,7 +34,7 @@ @NonNullByDefault public class InsteonBindingConstants { public static final String BINDING_ID = "insteon"; - public static final String BINDING_DATA_DIR = OpenHAB.getUserDataFolder() + File.separator + BINDING_ID; + public static final Path BINDING_DATA_DIR = Path.of(OpenHAB.getUserDataFolder(), BINDING_ID); // List of all thing type uids public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device"); diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/DebugCommand.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/DebugCommand.java index 4e899fed3b204..714b2e4aed7f1 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/DebugCommand.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/DebugCommand.java @@ -13,11 +13,11 @@ package org.openhab.binding.insteon.internal.command; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -27,7 +27,6 @@ import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.insteon.internal.InsteonBindingConstants; import org.openhab.binding.insteon.internal.device.InsteonAddress; import org.openhab.binding.insteon.internal.device.InsteonScene; import org.openhab.binding.insteon.internal.device.X10Address; @@ -71,7 +70,7 @@ public class DebugCommand extends InsteonCommand implements PortListener { private static final String ALL_OPTION = "--all"; - private static final String MSG_EVENTS_FILE_PREFIX = "messageEvents"; + private static final String MSG_EVENTS_FILE_PREFIX = "message-events"; private static enum MessageType { STANDARD, @@ -195,9 +194,16 @@ public boolean complete(String[] args, int cursorArgumentIndex, int cursorPositi } else if (cursorArgumentIndex == 1) { switch (args[0]) { case START_MONITORING: + strings = monitorAllDevices ? List.of() + : Stream.concat(Stream.of(ALL_OPTION), + getModem().getDB().getDevices().stream() + .filter(address -> !monitoredAddresses.contains(address)) + .map(InsteonAddress::toString)) + .toList(); + break; case STOP_MONITORING: - strings = Stream.concat(Stream.of(ALL_OPTION), - getModem().getDB().getDevices().stream().map(InsteonAddress::toString)).toList(); + strings = monitorAllDevices ? List.of(ALL_OPTION) + : monitoredAddresses.stream().map(InsteonAddress::toString).toList(); break; case SEND_BROADCAST_MESSAGE: strings = getModem().getDB().getBroadcastGroups().stream().map(String::valueOf).toList(); @@ -251,40 +257,25 @@ public void messageSent(Msg msg) { } } - private String getMsgEventsFileName(String address) { - return MSG_EVENTS_FILE_PREFIX + "-" + address.replace(".", "") + ".log"; - } - - private String getMsgEventsFilePath(String address) { - return InsteonBindingConstants.BINDING_DATA_DIR + File.separator + getMsgEventsFileName(address); + private Path getMsgEventsFilePath(String address) { + return getBindingDataFilePath(MSG_EVENTS_FILE_PREFIX + "-" + address.toLowerCase().replace(".", "") + ".log"); } private void clearMonitorFiles(String address) { - File folder = new File(InsteonBindingConstants.BINDING_DATA_DIR); - String prefix = ALL_OPTION.equals(address) ? MSG_EVENTS_FILE_PREFIX : getMsgEventsFileName(address); + String prefix = ALL_OPTION.equals(address) ? MSG_EVENTS_FILE_PREFIX + : getMsgEventsFilePath(address).getFileName().toString(); - if (folder.isDirectory()) { - Arrays.asList(folder.listFiles()).stream().filter(file -> file.getName().startsWith(prefix)) - .forEach(File::delete); - } + getBindingDataFilePaths(prefix).map(Path::toFile).forEach(File::delete); } private void logMessageEvent(InsteonAddress address, Msg msg) { String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); - String pathname = getMsgEventsFilePath(address.toString()); + String line = timestamp + " " + msg + System.lineSeparator(); + Path path = getMsgEventsFilePath(address.toString()); try { - File file = new File(pathname); - File parent = file.getParentFile(); - if (parent == null) { - throw new IOException(pathname + " does not name a parent directory"); - } - parent.mkdirs(); - file.createNewFile(); - - PrintStream ps = new PrintStream(new FileOutputStream(file, true)); - ps.println(timestamp + " " + msg.toString()); - ps.close(); + Files.createDirectories(path.getParent()); + Files.writeString(path, line, StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (IOException e) { logger.warn("failed to write to message event file", e); } @@ -307,7 +298,7 @@ private void startMonitoring(Console console, String address) { monitorAllDevices = true; monitoredAddresses.clear(); console.println("Started monitoring all devices."); - console.println("Message events logged in " + InsteonBindingConstants.BINDING_DATA_DIR); + console.println("Message events logged in " + getBindingDataDirPath()); clearMonitorFiles(address); } else { console.println("Already monitoring all devices."); diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommand.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommand.java index 7c068067fa294..bb74ff4c164be 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommand.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommand.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.insteon.internal.command; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -20,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.insteon.internal.InsteonBindingConstants; import org.openhab.binding.insteon.internal.device.Device; import org.openhab.binding.insteon.internal.device.InsteonAddress; import org.openhab.binding.insteon.internal.device.InsteonDevice; @@ -171,4 +175,21 @@ protected InsteonEngine getInsteonEngine(String thingId) { InsteonDevice device = getInsteonDevice(thingId); return device != null ? device.getInsteonEngine() : InsteonEngine.UNKNOWN; } + + protected Path getBindingDataDirPath() { + return InsteonBindingConstants.BINDING_DATA_DIR; + } + + protected Path getBindingDataFilePath(String filename) { + return InsteonBindingConstants.BINDING_DATA_DIR.resolve(filename); + } + + protected Stream getBindingDataFilePaths(String prefix) { + try { + return Files.list(InsteonBindingConstants.BINDING_DATA_DIR) + .filter(path -> path.getFileName().toString().startsWith(prefix)); + } catch (IOException e) { + return Stream.of(); + } + } } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/ModemCommand.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/ModemCommand.java index 089338bb07306..eb06145c84702 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/ModemCommand.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/ModemCommand.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.insteon.internal.command; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -20,6 +26,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.insteon.internal.device.InsteonAddress; import org.openhab.binding.insteon.internal.device.ProductData; +import org.openhab.binding.insteon.internal.device.database.ModemDBChange; import org.openhab.binding.insteon.internal.device.database.ModemDBEntry; import org.openhab.binding.insteon.internal.device.database.ModemDBRecord; import org.openhab.binding.insteon.internal.handler.InsteonBridgeHandler; @@ -41,6 +48,8 @@ public class ModemCommand extends InsteonCommand { private static final String LIST_ALL = "listAll"; private static final String LIST_DATABASE = "listDatabase"; private static final String RELOAD_DATABASE = "reloadDatabase"; + private static final String BACKUP_DATABASE = "backupDatabase"; + private static final String RESTORE_DATABASE = "restoreDatabase"; private static final String ADD_DATABASE_CONTROLLER = "addDatabaseController"; private static final String ADD_DATABASE_RESPONDER = "addDatabaseResponder"; private static final String DELETE_DATABASE_RECORD = "deleteDatabaseRecord"; @@ -48,16 +57,19 @@ public class ModemCommand extends InsteonCommand { private static final String CLEAR_DATABASE_CHANGES = "clearDatabaseChanges"; private static final String ADD_DEVICE = "addDevice"; private static final String REMOVE_DEVICE = "removeDevice"; + private static final String RESET = "reset"; private static final String SWITCH = "switch"; - private static final List SUBCMDS = List.of(LIST_ALL, LIST_DATABASE, RELOAD_DATABASE, - ADD_DATABASE_CONTROLLER, ADD_DATABASE_RESPONDER, DELETE_DATABASE_RECORD, APPLY_DATABASE_CHANGES, - CLEAR_DATABASE_CHANGES, ADD_DEVICE, REMOVE_DEVICE, SWITCH); + private static final List SUBCMDS = List.of(LIST_ALL, LIST_DATABASE, RELOAD_DATABASE, BACKUP_DATABASE, + RESTORE_DATABASE, ADD_DATABASE_CONTROLLER, ADD_DATABASE_RESPONDER, DELETE_DATABASE_RECORD, + APPLY_DATABASE_CHANGES, CLEAR_DATABASE_CHANGES, ADD_DEVICE, REMOVE_DEVICE, RESET, SWITCH); private static final String CONFIRM_OPTION = "--confirm"; private static final String FORCE_OPTION = "--force"; private static final String RECORDS_OPTION = "--records"; + private static final String MODEM_DATABASE_FILE_PREFIX = "modem-database"; + public ModemCommand(InsteonCommandExtension commandExtension) { super(NAME, DESCRIPTION, commandExtension); } @@ -69,6 +81,9 @@ public List getUsages() { buildCommandUsage(LIST_DATABASE + " [" + RECORDS_OPTION + "]", "list all-link database summary or records and pending changes for the Insteon modem"), buildCommandUsage(RELOAD_DATABASE, "reload all-link database from the Insteon modem"), + buildCommandUsage(BACKUP_DATABASE, "backup all-link database from the Insteon modem to a file"), + buildCommandUsage(RESTORE_DATABASE + " " + CONFIRM_OPTION, + "restore all-link database to the Insteon modem from a specific file"), buildCommandUsage(ADD_DATABASE_CONTROLLER + "
[ ]", "add a controller record to all-link database for the Insteon modem"), buildCommandUsage(ADD_DATABASE_RESPONDER + "
", @@ -83,6 +98,7 @@ public List getUsages() { "add an Insteon device to the modem, optionally providing its address"), buildCommandUsage(REMOVE_DEVICE + "
[" + FORCE_OPTION + "]", "remove an Insteon device from the modem"), + buildCommandUsage(RESET + " " + CONFIRM_OPTION, "reset the Insteon modem to factory defaults"), buildCommandUsage(SWITCH + " ", "switch Insteon modem bridge to use if more than one configured and enabled")); } @@ -118,6 +134,20 @@ public void execute(String[] args, Console console) { printUsage(console, args[0]); } break; + case BACKUP_DATABASE: + if (args.length == 1) { + backupDatabase(console); + } else { + printUsage(console, args[0]); + } + break; + case RESTORE_DATABASE: + if (args.length == 2 || args.length == 3 && CONFIRM_OPTION.equals(args[2])) { + restoreDatabase(console, args[1], args.length == 3); + } else { + printUsage(console, args[0]); + } + break; case ADD_DATABASE_CONTROLLER: if (args.length == 3 || args.length == 6) { addDatabaseRecord(console, args, true); @@ -167,6 +197,13 @@ public void execute(String[] args, Console console) { printUsage(console, args[0]); } break; + case RESET: + if (args.length == 1 || args.length == 2 && CONFIRM_OPTION.equals(args[1])) { + resetModem(console, args.length == 2); + } else { + printUsage(console, args[0]); + } + break; case SWITCH: if (args.length == 2) { switchModem(console, args[1]); @@ -191,6 +228,10 @@ public boolean complete(String[] args, int cursorArgumentIndex, int cursorPositi case LIST_DATABASE: strings = List.of(RECORDS_OPTION); break; + case RESTORE_DATABASE: + strings = getBindingDataFilePaths(MODEM_DATABASE_FILE_PREFIX).map(Path::getFileName) + .map(Path::toString).toList(); + break; case ADD_DATABASE_CONTROLLER: case ADD_DATABASE_RESPONDER: case REMOVE_DEVICE: @@ -200,6 +241,10 @@ public boolean complete(String[] args, int cursorArgumentIndex, int cursorPositi strings = getModem().getDB().getRecords().stream().map(record -> record.getAddress().toString()) .distinct().toList(); break; + case APPLY_DATABASE_CHANGES: + case RESET: + strings = List.of(CONFIRM_OPTION); + break; case SWITCH: strings = getBridgeHandlers().map(InsteonBridgeHandler::getThingId).toList(); break; @@ -207,6 +252,9 @@ public boolean complete(String[] args, int cursorArgumentIndex, int cursorPositi } else if (cursorArgumentIndex == 2) { InsteonAddress address = InsteonAddress.isValid(args[1]) ? new InsteonAddress(args[1]) : null; switch (args[0]) { + case RESTORE_DATABASE: + strings = List.of(CONFIRM_OPTION); + break; case DELETE_DATABASE_RECORD: if (address != null) { strings = getModem().getDB().getRecords(address).stream() @@ -242,7 +290,8 @@ private void listDatabaseSummary(Console console) { } else if (entries.isEmpty()) { console.println("The all-link database for modem " + address + " is empty"); } else { - console.println("The all-link database for modem " + address + " contains " + entries.size() + " devices:"); + console.println("The all-link database for modem " + address + " contains " + entries.size() + " devices:" + + (!getModem().getDB().isComplete() ? " (Partial)" : "")); print(console, entries); } } @@ -255,7 +304,8 @@ private void listDatabaseRecords(Console console) { } else if (records.isEmpty()) { console.println("The all-link database for modem " + address + " is empty"); } else { - console.println("The all-link database for modem " + address + " contains " + records.size() + " records:"); + console.println("The all-link database for modem " + address + " contains " + records.size() + " records:" + + (!getModem().getDB().isComplete() ? " (Partial)" : "")); print(console, records); listDatabaseChanges(console); } @@ -263,7 +313,7 @@ private void listDatabaseRecords(Console console) { private void listDatabaseChanges(Console console) { InsteonAddress address = getModem().getAddress(); - List changes = getModem().getDB().getChanges().stream().map(String::valueOf).toList(); + List changes = getModem().getDB().getChanges().stream().map(ModemDBChange::toString).toList(); if (InsteonAddress.UNKNOWN.equals(address)) { console.println("No modem found!"); } else if (!changes.isEmpty()) { @@ -285,6 +335,73 @@ private void reloadDatabase(Console console) { } } + private void backupDatabase(Console console) { + InsteonAddress address = getModem().getAddress(); + if (InsteonAddress.UNKNOWN.equals(address)) { + console.println("No modem found!"); + } else if (!getModem().getDB().isComplete()) { + console.println("The all-link database for modem " + address + " is not loaded yet."); + } else if (getModem().getDB().getRecords().isEmpty()) { + console.println("The all-link database for modem " + address + " is empty"); + } else { + String timestamp = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()); + String id = address.toString().toLowerCase().replace(".", ""); + Path path = getBindingDataFilePath(MODEM_DATABASE_FILE_PREFIX + "-" + id + "-" + timestamp + ".dmp"); + byte[] bytes = getModem().getDB().getRecordDump(); + int count = bytes.length / ModemDBRecord.SIZE; + + try { + Files.createDirectories(path.getParent()); + Files.write(path, bytes); + console.println("Backed up " + count + " database records from modem " + address + " to " + path); + } catch (IOException e) { + console.println("Failed to write backup file: " + e.getMessage()); + } + } + } + + private void restoreDatabase(Console console, String filename, boolean isConfirmed) { + InsteonAddress address = getModem().getAddress(); + if (InsteonAddress.UNKNOWN.equals(address)) { + console.println("No modem found!"); + } else if (!getModem().getDB().isComplete()) { + console.println("The all-link database for modem " + address + " is not loaded yet."); + } else { + Path path = Path.of(filename); + if (!path.isAbsolute()) { + path = getBindingDataFilePath(filename); + } + + try { + if (!Files.isReadable(path)) { + console.println("The restore file " + path + " does not exist or is not readable."); + } else if (Files.size(path) == 0) { + console.println("The restore file " + path + " is empty."); + } else { + InputStream stream = Files.newInputStream(path); + List records = ModemDBRecord.fromRecordDump(stream); + if (!isConfirmed) { + console.println( + "The restore file " + path + " contains " + records.size() + " database records:"); + print(console, records.stream().map(ModemDBRecord::toString).toList()); + console.println("Please run the same command with " + CONFIRM_OPTION + + " option to have these database records restored to modem " + address + "."); + } else { + console.println("Restoring " + records.size() + " database records to modem " + address + + " from " + path + "..."); + records.forEach(record -> getModem().getDB().markRecordForAddOrModify(record.getAddress(), + record.getGroup(), record.isController(), record.getData())); + getModem().getDB().update(); + } + } + } catch (IllegalArgumentException e) { + console.println("The restore file " + path + " is invalid."); + } catch (IOException e) { + console.println("Failed to read restore file: " + e.getMessage()); + } + } + } + private void addDatabaseRecord(Console console, String[] args, boolean isController) { if (!getModem().getDB().isComplete()) { console.println("The modem database is not loaded yet."); @@ -405,6 +522,19 @@ private void removeDevice(Console console, String address, boolean force) { } } + private void resetModem(Console console, boolean isConfirmed) { + InsteonAddress address = getModem().getAddress(); + if (InsteonAddress.UNKNOWN.equals(address)) { + console.println("No modem found!"); + } else if (!isConfirmed) { + console.println("Please run the same command with " + CONFIRM_OPTION + " option to reset modem " + address + + " to factory defaults."); + } else { + console.println("Resetting modem " + address + " to factory defaults..."); + getModem().reset(); + } + } + private void switchModem(Console console, String thingId) { InsteonBridgeHandler handler = getBridgeHandler(thingId); if (handler == null) { diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonDevice.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonDevice.java index 0364ad979b53f..18d6acc138be0 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonDevice.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonDevice.java @@ -980,7 +980,7 @@ public static InsteonDevice makeDevice(InsteonAddress address, @Nullable Insteon device.setFlags(deviceType.getFlags()); } int location = productData.getFirstRecordLocation(); - if (location != LinkDBRecord.LOCATION_ZERO) { + if (location != 0) { device.getLinkDB().setFirstRecordLocation(location); } device.setProductData(productData); diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonModem.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonModem.java index 9c22c24c83167..30948e4407cce 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonModem.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/InsteonModem.java @@ -270,6 +270,26 @@ private void handleModemInfo(Msg msg) throws FieldException { } } + public void reset() { + try { + Msg msg = Msg.makeMessage("ResetIM"); + writeMessage(msg); + } catch (IOException e) { + logger.warn("error sending modem reset query", e); + } catch (InvalidMessageTypeException e) { + logger.warn("invalid message ", e); + } + } + + private void handleModemReset() { + logger.debug("modem reset initiated"); + + InsteonBridgeHandler handler = getHandler(); + if (handler != null) { + handler.reset(RESET_TIME); + } + } + public void logDeviceStatistics() { logger.debug("devices: {} configured, {} polling, msgs received: {}", getDevices().size(), getPollManager().getSizeOfQueue(), msgsReceived); @@ -384,18 +404,6 @@ public void databaseProductDataUpdated(InsteonAddress address, ProductData produ } } - /** - * Notifies that the modem reset process has been initiated - */ - public void resetInitiated() { - logger.debug("modem reset initiated"); - - InsteonBridgeHandler handler = getHandler(); - if (handler != null) { - handler.reset(RESET_TIME); - } - } - /** * Notifies that the modem port has disconnected */ @@ -462,8 +470,12 @@ public void messageSent(Msg msg) { } private void handleIMMessage(Msg msg) throws FieldException { - if (msg.getCommand() == 0x60) { + if (msg.getCommand() == 0x60 && msg.isReplyAck()) { + // we got an im info reply ack handleModemInfo(msg); + } else if (msg.getCommand() == 0x55 || msg.getCommand() == 0x67 && msg.isReplyAck()) { + // we got a user reset detected message or im reset reply ack + handleModemReset(); } else { handleMessage(msg); } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/DatabaseRecord.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/DatabaseRecord.java index 4d779fac4d358..5f3182c546d5b 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/DatabaseRecord.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/DatabaseRecord.java @@ -26,7 +26,7 @@ */ @NonNullByDefault public class DatabaseRecord { - public static final int LOCATION_ZERO = 0; + public static final int SIZE = 8; private final int location; private final RecordType type; @@ -42,12 +42,12 @@ public DatabaseRecord(int location, RecordType type, int group, InsteonAddress a this.data = data; } + public DatabaseRecord(RecordType type, int group, InsteonAddress address, byte[] data) { + this(0, type, group, address, data); + } + public DatabaseRecord(DatabaseRecord record) { - this.location = record.location; - this.type = record.type; - this.group = record.group; - this.address = record.address; - this.data = record.data; + this(record.location, record.type, record.group, record.address, record.data); } public int getLocation() { @@ -114,7 +114,7 @@ public byte[] getBytes() { @Override public String toString() { String s = ""; - if (location != LOCATION_ZERO) { + if (location != 0) { s += HexUtils.getHexString(location, 4) + " "; } s += address + " " + type; diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDB.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDB.java index 014010d267b1e..065bbcb0d21ee 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDB.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDB.java @@ -36,8 +36,6 @@ */ @NonNullByDefault public class LinkDB { - public static final int RECORD_BYTE_SIZE = 8; - private static enum DatabaseStatus { EMPTY, COMPLETE, @@ -258,7 +256,7 @@ public int getChangeLocation(InsteonAddress address, int group, boolean isContro */ public int getNextAvailableLocation() { return getRecords().stream().filter(LinkDBRecord::isAvailable).map(LinkDBRecord::getLocation).findFirst() - .orElse(Math.min(getLastRecordLocation(), getLastChangeLocation() - RECORD_BYTE_SIZE)); + .orElse(Math.min(getLastRecordLocation(), getLastChangeLocation() - LinkDBRecord.SIZE)); } /** @@ -355,7 +353,7 @@ public void update(long delay) { LinkDBRecord prevRecord = records.put(record.getLocation(), record); // move last record if overwritten if (prevRecord != null && prevRecord.isLast()) { - int location = prevRecord.getLocation() - RECORD_BYTE_SIZE; + int location = prevRecord.getLocation() - LinkDBRecord.SIZE; records.put(location, LinkDBRecord.withNewLocation(location, prevRecord)); if (logger.isTraceEnabled()) { logger.trace("moved last record for {} to location {}", device.getAddress(), @@ -531,7 +529,7 @@ public synchronized void updateStatus() { int firstLocation = records.firstKey(); int lastLocation = records.lastKey(); - int expected = (firstLocation - lastLocation) / RECORD_BYTE_SIZE + 1; + int expected = (firstLocation - lastLocation) / LinkDBRecord.SIZE + 1; if (firstLocation != getFirstRecordLocation()) { logger.debug("got unexpected first record location for {}", device.getAddress()); setStatus(DatabaseStatus.PARTIAL); diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBReader.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBReader.java index fa3837dbc7e0c..27c2de495506d 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBReader.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBReader.java @@ -229,12 +229,12 @@ private void handleRecordByte(byte b) { stream.write(b); // get next peek byte if stream size below the record byte size // otherwise add record and get next peek record if not done - if (stream.size() < LinkDB.RECORD_BYTE_SIZE) { + if (stream.size() < LinkDBRecord.SIZE) { getNextPeekByte(); } else { addRecord(LinkDBRecord.fromRecordData(stream.toByteArray(), location)); if (!done) { - location -= LinkDB.RECORD_BYTE_SIZE; + location -= LinkDBRecord.SIZE; getNextPeekRecord(); } } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBWriter.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBWriter.java index acc8bb2ecbc03..b620b5f77a8f8 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBWriter.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/LinkDBWriter.java @@ -113,7 +113,7 @@ private void done() { private void setNextAllLinkRecord() { LinkDBChange change = device.getLinkDB().pollNextChange(); if (change == null) { - logger.trace("all link db changes written using standard mode for {}", device.getAddress()); + logger.debug("all link db changes written using standard mode for {}", device.getAddress()); done(); } else { setAllLinkRecord(change.getRecord()); @@ -123,7 +123,7 @@ private void setNextAllLinkRecord() { private void setNextPokeRecord() { LinkDBChange change = device.getLinkDB().pollNextChange(); if (change == null) { - logger.trace("all link db changes written using peek/poke mode for {}", device.getAddress()); + logger.debug("all link db changes written using peek/poke mode for {}", device.getAddress()); done(); } else { setPokeRecord(change.getRecord()); diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDB.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDB.java index 420388e79df92..3d83601ffc09b 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDB.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDB.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.insteon.internal.device.database; +import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -83,6 +84,14 @@ public List getRecords() { } } + public byte[] getRecordDump() { + return getRecords().stream().distinct().map(ModemDBRecord::getBytes) + .flatMapToInt(bytes -> IntStream.range(0, bytes.length).map(i -> bytes[i])) + .collect(ByteArrayOutputStream::new, ByteArrayOutputStream::write, + (out1, out2) -> out1.write(out2.toByteArray(), 0, out2.size())) + .toByteArray(); + } + private Stream getRecords(@Nullable InsteonAddress address, @Nullable Integer group, @Nullable Boolean isController) { return getRecords().stream() diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBReader.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBReader.java index 07c9e4aa6cd7b..1f0392a770b79 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBReader.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBReader.java @@ -175,9 +175,6 @@ public void messageReceived(Msg msg) { } else if (msg.getCommand() == 0x53) { // we got a linking completed message handleLinkingCompleted(msg); - } else if (msg.getCommand() == 0x55 || msg.getCommand() == 0x67 && msg.isReplyAck()) { - // we got a user reset detected message or im reset reply ack - handleIMReset(); } else if (msg.getCommand() == 0x57) { // we got a link record response handleLinkRecord(msg); @@ -305,8 +302,4 @@ private void handleProductDataAck(Msg msg) throws FieldException { productQueries.remove(address); } } - - private void handleIMReset() { - modem.resetInitiated(); - } } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBRecord.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBRecord.java index 2d42c0a107d7e..60f5ac17d5aac 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBRecord.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBRecord.java @@ -12,6 +12,11 @@ */ package org.openhab.binding.insteon.internal.device.database; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.insteon.internal.device.InsteonAddress; import org.openhab.binding.insteon.internal.transport.message.FieldException; @@ -26,7 +31,7 @@ public class ModemDBRecord extends DatabaseRecord { public ModemDBRecord(RecordType type, int group, InsteonAddress address, byte[] data) { - super(LOCATION_ZERO, type, group, address, data); + super(type, group, address, data); } public ModemDBRecord(DatabaseRecord record) { @@ -99,6 +104,34 @@ public static ModemDBRecord fromLinkingMsg(Msg msg) throws FieldException { return new ModemDBRecord(type, group, address, data); } + /** + * Factory method for creating a list of ModemDBRecord from an Insteon record dump + * + * @param stream the Insteon record dump input stream to use + * @return the list of modem db records + * @throws IllegalArgumentException + * @throws IOException + */ + public static List fromRecordDump(InputStream stream) throws IllegalArgumentException, IOException { + List records = new ArrayList<>(); + + if (stream.available() % ModemDBRecord.SIZE != 0) { + throw new IllegalArgumentException("Invalid record dump length"); + } + + while (stream.available() > 0) { + byte[] buf = stream.readNBytes(ModemDBRecord.SIZE); + RecordType type = new RecordType(Byte.toUnsignedInt(buf[0])); + int group = Byte.toUnsignedInt(buf[1]); + InsteonAddress address = new InsteonAddress(buf[2], buf[3], buf[4]); + byte[] data = new byte[] { buf[5], buf[6], buf[7] }; + + records.add(new ModemDBRecord(type, group, address, data)); + } + + return records; + } + /** * Factory method for creating a new ModemDBRecord from another instance with new data * diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBWriter.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBWriter.java index 53ff4b3a9d6c5..e3ef905f11957 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBWriter.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/device/database/ModemDBWriter.java @@ -94,7 +94,7 @@ private void done() { private void manageNextModemLinkRecord() { ModemDBChange change = modem.getDB().pollNextChange(); if (change == null) { - logger.trace("all modem database changes written"); + logger.debug("all modem database changes written"); done(); } else { ModemDBRecord record = change.getRecord();