[insteon] Add modem database backup restore console commands (#17958)

Signed-off-by: jsetton <jeremy.setton@gmail.com>
This commit is contained in:
Jeremy 2025-01-04 15:10:41 -05:00 committed by GitHub
parent 36cff511b9
commit d54a51b732
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 266 additions and 81 deletions

View File

@ -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 @@ import org.openhab.core.thing.ThingTypeUID;
@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");

View File

@ -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.Collectors;
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 class DebugCommand extends InsteonCommand implements PortListener {
} 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 class DebugCommand extends InsteonCommand implements PortListener {
}
}
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 @@ public class DebugCommand extends InsteonCommand implements PortListener {
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.");

View File

@ -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 java.util.stream.Stream;
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 @@ public abstract class InsteonCommand implements ConsoleCommandCompleter {
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<Path> getBindingDataFilePaths(String prefix) {
try {
return Files.list(InsteonBindingConstants.BINDING_DATA_DIR)
.filter(path -> path.getFileName().toString().startsWith(prefix));
} catch (IOException e) {
return Stream.of();
}
}
}

View File

@ -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.NonNullByDefault;
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<String> 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<String> 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 class ModemCommand extends InsteonCommand {
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 + " <filename> " + CONFIRM_OPTION,
"restore all-link database to the Insteon modem from a specific file"),
buildCommandUsage(ADD_DATABASE_CONTROLLER + " <address> <group> [<devCat> <subCat> <firmware>]",
"add a controller record to all-link database for the Insteon modem"),
buildCommandUsage(ADD_DATABASE_RESPONDER + " <address> <group>",
@ -83,6 +98,7 @@ public class ModemCommand extends InsteonCommand {
"add an Insteon device to the modem, optionally providing its address"),
buildCommandUsage(REMOVE_DEVICE + " <address> [" + FORCE_OPTION + "]",
"remove an Insteon device from the modem"),
buildCommandUsage(RESET + " " + CONFIRM_OPTION, "reset the Insteon modem to factory defaults"),
buildCommandUsage(SWITCH + " <thingId>",
"switch Insteon modem bridge to use if more than one configured and enabled"));
}
@ -118,6 +134,20 @@ public class ModemCommand extends InsteonCommand {
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 class ModemCommand extends InsteonCommand {
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 class ModemCommand extends InsteonCommand {
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 class ModemCommand extends InsteonCommand {
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 class ModemCommand extends InsteonCommand {
} 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 @@ public class ModemCommand extends InsteonCommand {
} 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 @@ public class ModemCommand extends InsteonCommand {
} 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 @@ public class ModemCommand extends InsteonCommand {
private void listDatabaseChanges(Console console) {
InsteonAddress address = getModem().getAddress();
List<String> changes = getModem().getDB().getChanges().stream().map(String::valueOf).toList();
List<String> 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 @@ public class ModemCommand extends InsteonCommand {
}
}
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<ModemDBRecord> 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.");
@ -313,7 +430,6 @@ public class ModemCommand extends InsteonCommand {
ModemDBRecord record = getModem().getDB().getRecord(address, group, isController);
if (record == null) {
getModem().getDB().markRecordForAdd(address, group, isController, data);
} else {
getModem().getDB().markRecordForModify(record, data);
}
@ -405,6 +521,19 @@ public class ModemCommand extends InsteonCommand {
}
}
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) {

View File

@ -153,7 +153,6 @@ public class SceneCommand extends InsteonCommand {
}
break;
}
} else if (cursorArgumentIndex == 4) {
InsteonDevice device = getInsteonDevice(args[2]);
DeviceFeature feature = device != null ? device.getFeature(args[3]) : null;

View File

@ -980,7 +980,7 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
device.setFlags(deviceType.getFlags());
}
int location = productData.getFirstRecordLocation();
if (location != LinkDBRecord.LOCATION_ZERO) {
if (location != 0) {
device.getLinkDB().setFirstRecordLocation(location);
}
device.setProductData(productData);

View File

@ -270,6 +270,26 @@ public class InsteonModem extends BaseDevice<InsteonAddress, InsteonBridgeHandle
}
}
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 class InsteonModem extends BaseDevice<InsteonAddress, InsteonBridgeHandle
}
}
/**
* 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 class InsteonModem extends BaseDevice<InsteonAddress, InsteonBridgeHandle
}
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);
}

View File

@ -26,7 +26,7 @@ import org.openhab.binding.insteon.internal.utils.HexUtils;
*/
@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 class DatabaseRecord {
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 class DatabaseRecord {
@Override
public String toString() {
String s = "";
if (location != LOCATION_ZERO) {
if (location != 0) {
s += HexUtils.getHexString(location, 4) + " ";
}
s += address + " " + type;

View File

@ -36,8 +36,6 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class LinkDB {
public static final int RECORD_BYTE_SIZE = 8;
private static enum DatabaseStatus {
EMPTY,
COMPLETE,
@ -258,7 +256,7 @@ public class LinkDB {
*/
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 class LinkDB {
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 class LinkDB {
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);

View File

@ -229,12 +229,12 @@ public class LinkDBReader implements PortListener {
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();
}
}

View File

@ -113,7 +113,7 @@ public class LinkDBWriter implements PortListener {
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 @@ public class LinkDBWriter implements PortListener {
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());

View File

@ -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 class ModemDB {
}
}
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<ModemDBRecord> getRecords(@Nullable InsteonAddress address, @Nullable Integer group,
@Nullable Boolean isController) {
return getRecords().stream()

View File

@ -175,9 +175,6 @@ public class ModemDBReader implements PortListener {
} 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 @@ public class ModemDBReader implements PortListener {
productQueries.remove(address);
}
}
private void handleIMReset() {
modem.resetInitiated();
}
}

View File

@ -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 @@ import org.openhab.binding.insteon.internal.transport.message.Msg;
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 class ModemDBRecord extends DatabaseRecord {
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<ModemDBRecord> fromRecordDump(InputStream stream) throws IllegalArgumentException, IOException {
List<ModemDBRecord> 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
*

View File

@ -94,7 +94,7 @@ public class ModemDBWriter implements PortListener {
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();