Skip to content

Commit

Permalink
Updated model based on new information
Browse files Browse the repository at this point in the history
Operating mode 11 is actually "machine control only" mode, not an invalid connection.
(Thanks to @johnnysako in GEMakers/gea-plugin-dishwasher#6 and GEMakers/gea-plugin-dishwasher#4)

Convert fill and drain rates to SI units.
(Thanks to @palisaide in GEMakers/gea-plugin-dishwasher#10)

resetCount is actually the count of power cycles. Also added support
for tracking uptime and boot times in the log.
(Thanks to @palisaide in GEMakers/gea-plugin-dishwasher#11)

Slight correction and clarifications to personality information.
(Thanks to @palisaide in GEMakers/gea-plugin-dishwasher#12)

Added text for error codes 0, 96, and 97.
(Thanks to @johnnysako in GEMakers/gea-plugin-dishwasher#3)

Sample output (piping the logs from the last month into the dart
script, and then letting the proxy connect to it to show live data):

```
GE GDF570SGFWW dishwasher model
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
╔═══════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Personality: machine-control-driven UI board (specified by factory-configured bootloader parametric). ║
║ Fill rate: 52.6ml/s. Drain rate: 129.5ml/s.                                                           ║
║ Dry drain state: 0 failed dry drains (maximum 1).                                                     ║
║ Continuous cycle mode: cycle 0, 0 remaining cycles. 0m between cycles.                                ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════════════╝

┌────┤ Low power • rebooted ├────────────────────────────────┐
│ AutoSense  Rinse Aid Enabled                               │
│ Cycle count: 43 cycles completed out of 53 cycles started. │
│ Power cycle events: 11.                 Uptime: 1h 59m 9s. │
│ Door open/close count: 291.          Sensors: ▁▇▁▁▁▁▁█▁▁▁▁ │
└────────────────┤ 2016-07-06 19:50:53.709 ├─────────────────┘

Event log (most recent first):
     30: Booted at: 2016-07-06 17:51:44.342
     29: Cycle started at: 2016-07-05 21:23:00.000. Temperature: 23.9°C .. 61.7°C; final: 61.1°C. Turbidity: 45.0 NTU .. 2225.0 NTU. Duration: 1h 50m.
     28: Cycle started at: 2016-07-04 00:22:00.000. Temperature: 23.9°C .. 63.3°C; final: 62.8°C. Turbidity: 40.0 NTU .. 1681.0 NTU. Duration: 1h 50m.
     27: Cycle started at: 2016-07-03 14:20:00.000. Temperature: 23.3°C .. 61.7°C; final: 61.1°C. Turbidity: 32.0 NTU .. 2733.0 NTU. Duration: 2h 1m.
     26: Cycle started at: 2016-06-30 18:53:00.000. Temperature: 23.3°C .. 60.0°C; final: 60.0°C. Turbidity: 40.0 NTU .. 1790.0 NTU. Duration: 2h 18m.
     25: Cycle started at: 2016-06-28 10:51:00.000. Temperature: 23.3°C .. 63.3°C; final: 63.3°C. Turbidity: 41.0 NTU .. 1237.0 NTU. Duration: 1h 53m.
     24: Cycle started at: 2016-06-27 14:24:00.000. Duration: 0m.
     23: Cycle started at: 2016-06-27 14:24:00.000. Temperature: 23.3°C .. 62.8°C; final: 62.8°C. Turbidity: 34.0 NTU .. 934.0 NTU. Duration: 1h 54m.
     22: Cycle started at: 2016-06-26 17:58:00.000. Temperature: 23.3°C .. 61.1°C; final: 61.1°C. Turbidity: 37.0 NTU .. 1950.0 NTU. Duration: 1h 50m.
     21: Cycle started at: 2016-06-23 12:56:00.000. Temperature: 22.8°C .. 62.2°C; final: 61.7°C. Turbidity: 51.0 NTU .. 3039.0 NTU. Duration: 2h 14m.
     20: Cycle started at: 2016-06-22 19:29:00.000. Temperature: 23.3°C .. 61.1°C; final: 60.6°C. Turbidity: 84.0 NTU .. 3316.0 NTU. Duration: 2h 14m.
     19: Cycle started at: 2016-06-20 20:46:00.000. Temperature: 23.3°C .. 68.3°C; final: 67.2°C. Turbidity: 29.0 NTU .. 2066.0 NTU. Duration: 2h 30m.
     18: Cycle started at: 2016-06-19 22:59:00.000. Temperature: 30.6°C .. 61.7°C; final: 61.1°C. Turbidity: 43.0 NTU .. 3294.0 NTU. Duration: 2h 14m.
     17: Cycle started at: 2016-06-19 19:46:00.000. Temperature: 23.3°C .. 60.0°C; final: 59.4°C. Turbidity: 63.0 NTU .. 949.0 NTU. Duration: 2h 18m.
     16: Cycle started at: 2016-06-17 15:48:00.000. Temperature: 22.8°C .. 60.0°C; final: 60.0°C. Turbidity: 70.0 NTU .. 1514.0 NTU. Duration: 2h 18m.
     15: Cycle started at: 2016-06-16 19:31:00.000. Temperature: 22.8°C .. 62.8°C; final: 62.2°C. Turbidity: 69.0 NTU .. 1134.0 NTU. Duration: 2h 21m.
     14: Cycle started at: 2016-06-15 16:36:00.000. Temperature: 22.8°C .. 62.2°C; final: 62.2°C. Turbidity: 54.0 NTU .. 849.0 NTU. Duration: 3h 25m.
     13: Cycle started at: 2016-06-14 18:35:00.000. Temperature: 23.3°C .. 61.7°C; final: 61.1°C. Turbidity: 66.0 NTU .. 936.0 NTU. Duration: 2h 46m.
     12: Cycle started at: 2016-06-12 14:09:00.000. Temperature: 23.3°C .. 59.4°C; final: 58.9°C. Turbidity: 63.0 NTU .. 3269.0 NTU. Duration: 2h 15m.
     11: Cycle started at: 2016-06-12 01:00:00.000. Duration: 0m.
     10: Cycle started at: 2016-06-12 00:34:00.000. Duration: 0m.
      9: Cycle started at: 2016-06-12 00:26:00.000. Duration: 0m.
      8: Cycle started at: 2016-06-12 00:17:00.000. Temperature: 25.0°C .. 25.6°C; final: 25.6°C. Turbidity: 149.0 NTU .. 1279.0 NTU. Duration: 2m.
      7: Cycle started at: 2016-06-12 00:12:00.000. Temperature: 23.9°C .. 25.0°C. Duration: 3m.
      6: Cycle started at: 2016-06-11 13:59:00.000. Temperature: 22.2°C .. 51.1°C; final: 51.1°C. Turbidity: 152.0 NTU .. 3485.0 NTU. Duration: 41m.
      5: Cycle started at: 2016-06-09 19:42:00.000. Temperature: 23.9°C .. 68.3°C; final: 68.3°C. Turbidity: 18.0 NTU .. 461.0 NTU. Duration: 3h 38m.
      4: Cycle started at: 2016-06-08 17:58:00.000. Temperature: 32.2°C .. 68.3°C; final: 66.7°C. Turbidity: 19.0 NTU .. 3101.0 NTU. Duration: 3h 34m.
      3: Cycle started at: 2016-06-08 13:36:00.000. Temperature: 23.3°C .. 67.8°C; final: 67.8°C. Turbidity: 18.0 NTU .. 1789.0 NTU. Duration: 3h 35m.
      2: Cycle started at: 2016-06-06 20:40:00.000. Temperature: 24.4°C .. 60.0°C; final: 59.4°C. Turbidity: 55.0 NTU .. 1322.0 NTU. Duration: 2h 18m.
      1: Cycle started at: 2016-06-05 19:28:00.000. Temperature: 23.3°C .. 60.0°C; final: 60.0°C. Turbidity: 29.0 NTU .. 626.0 NTU. Duration: 3h 22m.
```
  • Loading branch information
Hixie committed Jul 7, 2016
1 parent d321519 commit f9ed90e
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 92 deletions.
4 changes: 3 additions & 1 deletion dishwasher-to-websocket-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ function assert(condition) {
}

var reconnectTimeout = 2000; // milliseconds between connections to websocket server

var delayTime = 1000; // milliseconds between messages to dishwasher
var readAllInterval = 60 * 1000; // milliseconds between when we try to request every message at once


// WEBSOCKET LOGIC
Expand Down Expand Up @@ -179,4 +181,4 @@ greenBean.connect("dishwasher", function(dw) {
}
});

setInterval(readAll, 60 * 1000);
setInterval(readAll, readAllInterval);
104 changes: 74 additions & 30 deletions model/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,50 @@ import 'model.dart';
Stopwatch staleness = new Stopwatch();
Timer dirtyTimer;

const Duration kCoallesceDelay = const Duration(milliseconds: 1500);
const Duration kCoallesceDelay = const Duration(milliseconds: 500);
const Duration kMaxStaleness = const Duration(milliseconds: 3500);

final Dishwasher dishwasher = new Dishwasher();

void processMessage(String message) {
List<String> parts = message.split('\x00');
DateTime stamp;
try {
verify(parts.length == 3, 'Invalid message (${parts.length} parts): "$message"');
stamp = new DateTime.fromMillisecondsSinceEpoch(int.parse(parts[0], radix: 10), isUtc: true);
assert(stamp.compareTo(dishwasher.lastMessageTimestamp) >= 0);
MessageHandler handler = handlers[parts[1]] ?? new DefaultHandler(parts[1]);
handler.parse(dishwasher, stamp, parts[2]);
dishwasher.lastMessageTimestamp = stamp;
} catch (e) {
print('$stamp ${parts[1]} ${parts[2]}');
print('${ " " * stamp.toString().length } unable to parse: $e');
class LogMessage {
LogMessage(this.stamp, this.handler, this.payload, { this.messageName });
factory LogMessage.fromLogLine(String message) {
try {
final List<String> parts = message.split('\x00');
verify(parts.length == 3, 'Invalid message (${parts.length} parts): "$message"');
DateTime stamp = new DateTime.fromMillisecondsSinceEpoch(int.parse(parts[0], radix: 10), isUtc: true);
if (dishwasher.lastMessageTimestamp != null && stamp.compareTo(dishwasher.lastMessageTimestamp) < 0)
return null; // ignore old messages
final MessageHandler handler = handlers[parts[1]] ?? new DefaultHandler(parts[1]);
return new LogMessage(stamp, handler, parts[2], messageName: parts[1]);
} catch (e, stack) {
print('unable to parse: $message\n$e\n$stack');
}
return null;
}
final DateTime stamp;
final MessageHandler handler;
final String payload;
final String messageName;
void dispatch(Dishwasher dishwasher) {
try {
dishwasher.lastMessageTimestamp = stamp;
handler.parse(dishwasher, stamp, payload);
} catch (e, stack) {
print('$stamp $messageName $payload');
print('${ " " * stamp.toString().length } unable to parse: $e\n$stack');
}
}
}

void updateDisplay(bool ansiEnabled) {
if (ansiEnabled) {
if (dishwasher.isDirty) {
stdout.write('\u001B[H'); // clear screen and move cursor to top left
stdout.write('\u001B[?25l\u001B[H'); // hide cursor, and move cursor to top left
printClear('GE GDF570SGFWW dishwasher model');
printClear('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔');
dishwasher.printEverything(); // calls printClear below
stdout.write('\u001B[J');
stdout.write('\u001B[J\u001B[?25h'); // clear rest of screen, and show cursor again
}
} else {
dishwasher.printUpdates();
Expand All @@ -49,11 +64,21 @@ void printClear(String lines) {
stdout.write('$line\u001B[K\n');
}

void configureDishwasherOutput(Dishwasher dishwasher, bool ansiEnabled) {
if (ansiEnabled) {
dishwasher.enableNotifications = false;
dishwasher.onPrint = printClear;
} else {
dishwasher.enableNotifications = true;
dishwasher.onPrint = print;
}
}

void handleWebSocketMessage(dynamic message, bool ansiEnabled) {
if (message is! String)
return;
processMessage(message);
if (staleness.elapsed > kMaxStaleness || ansiEnabled) {
new LogMessage.fromLogLine(message)?.dispatch(dishwasher);
if (staleness.elapsed > kMaxStaleness) {
dirtyTimer?.cancel();
dirtyTimer = null;
updateDisplay(ansiEnabled);
Expand All @@ -63,15 +88,12 @@ void handleWebSocketMessage(dynamic message, bool ansiEnabled) {
}
}

const int port = 2000;

void startServer(bool ansiEnabled) {
print('Starting server...\n');
if (ansiEnabled) {
dishwasher.onPrint = printClear;
} else {
dishwasher.enableNotifications = true;
dishwasher.onPrint = print;
}
HttpServer.bind('127.0.0.1', 2000)
configureDishwasherOutput(dishwasher, ansiEnabled);
HttpServer.bind('127.0.0.1', port)
.then((HttpServer server) {
server.listen((HttpRequest request) {
WebSocketTransformer.upgrade(
Expand All @@ -88,7 +110,7 @@ void startServer(bool ansiEnabled) {

final RegExp logFile = new RegExp(r'^sending to model: (.+)$');

void readLogs(List<String> arguments) {
void readLogs(List<String> arguments, { bool ansiEnabled: false, bool verbose: false }) {
print('Loading archived logs...');
for (String pathName in arguments) {
final Directory directory = new Directory(pathName);
Expand All @@ -99,21 +121,43 @@ void readLogs(List<String> arguments) {
for (String line in entry.readAsLinesSync()) {
final Match parts = logFile.matchAsPrefix(line);
if (parts != null) {
processMessage(parts.group(1));
final LogMessage message = new LogMessage.fromLogLine(parts.group(1));
if (message != null) {
if (dishwasher.lastMessageTimestamp != null) {
assert(message.stamp.compareTo(dishwasher.lastMessageTimestamp) >= 0);
if (verbose) {
if (message.stamp.difference(dishwasher.lastMessageTimestamp) >= kCoallesceDelay)
updateDisplay(ansiEnabled);
}
}
message.dispatch(dishwasher);
}
}
}
}
}
}

const String kColorArgument = 'ansi';
const String kServerArgument = 'server';
const String kVerboseLogsArgument = 'show-logs';

void main(List<String> arguments) {
print('GE GDF570SGFWW dishwasher model');
print('▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔');
final ArgParser parser = new ArgParser()
..addFlag(kColorArgument, help: 'Enable ANSI codes', defaultsTo: false);
..addFlag(kColorArgument, help: 'Enable ANSI codes.', defaultsTo: false)
..addFlag(kServerArgument, help: 'Listen for further messages using a WebSocket on port $port.', defaultsTo: true)
..addFlag(kVerboseLogsArgument, help: 'Show updates when parsing logs.', defaultsTo: false);
final ArgResults parsedArguments = parser.parse(arguments);
readLogs(parsedArguments.rest);
startServer(parsedArguments[kColorArgument]);
final bool ansiEnabled = parsedArguments[kColorArgument];
if (parsedArguments[kVerboseLogsArgument]) {
configureDishwasherOutput(dishwasher, ansiEnabled);
readLogs(parsedArguments.rest, ansiEnabled: ansiEnabled, verbose: true);
} else {
readLogs(parsedArguments.rest, ansiEnabled: ansiEnabled);
}
print('Log parsing complete.');
if (parsedArguments[kServerArgument])
startServer(ansiEnabled);
}
9 changes: 5 additions & 4 deletions model/lib/messages.dart
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class OperatingModeHandler extends IntHandler<OperatingMode> {
case 7: return OperatingMode.downloadMode;
case 8: return OperatingMode.sensorCheckMode;
case 9: return OperatingMode.loadActivationMode;
case 11: return OperatingMode.invalidConnection;
case 11: return OperatingMode.machineControlOnly;
default: parseFail(); return null;
}
}
Expand Down Expand Up @@ -288,7 +288,7 @@ class CycleCountsHandler extends MessageHandler {
verify(decodedData['resetCount'] is int, 'cycleCounts.resetCount not a number');
dishwasher.countOfCyclesStarted = decodedData['startedCount'];
dishwasher.countOfCyclesCompleted = decodedData['completedCount'];
dishwasher.countOfCyclesReset = decodedData['resetCount'];
dishwasher.powerOnCounter = decodedData['resetCount'];
}
}

Expand All @@ -308,9 +308,10 @@ class RatesHandler extends MessageHandler {
verify(decodedData is Map<dynamic, dynamic>, 'rates data not a map');
verify(decodedData['fillRate'] is int, 'rates.fillRate not a number');
verify(decodedData['drainRate'] is int, 'rates.drainRate not a number');
// data is in 10000ths of a gallon per second
dishwasher.rates = new DishwasherRates(
fill: decodedData['fillRate'],
drain: decodedData['drainRate']
fill: decodedData['fillRate'] / (10000.0 * 0.26417),
drain: decodedData['drainRate'] / (10000.0 * 0.26417)
);
}
}
Expand Down
Loading

0 comments on commit f9ed90e

Please sign in to comment.