// Author: cirakg & claude ai // Summary: Reads every spike setting, measures lengths and characters, writes the status back, builds the screen lines. // // Per text field it reports: length in characters and UTF-8 bytes, the first // 12 and last 8 characters (the test strings start with "N=|" and // end with "END", so truncation is obvious), and which of the characters // & < > \ " { } % and non-ASCII it contains. // // Write-back test: on every settings change the watch writes a status line // into the read-only "statusField" and clears "secretField". Opening the // settings on the phone afterwards shows whether the phone displays values // the watch wrote. import Toybox.Application; import Toybox.Application.Properties; import Toybox.Lang; import Toybox.System; module SpikeKeys { const TEXT_FIELDS = ["text1", "text2", "text3", "text4", "text5", "text6", "text7", "text8"]; const SECRET = "secretField"; const STATUS = "statusField"; const OTHER_FIELDS = ["switchField", "listField", "numberField", "longField"]; const HEAD_CHARS = 12; const TAIL_CHARS = 8; const ASCII_BACKSLASH = 92; const ASCII_QUOTE = 34; } class SpikeReport { // Lines for SpikeView, rebuilt by analyse(). var lines as Array = [] as Array; // onSettingsChanged calls since the app started, and the time of the last. var changeCount as Number = 0; var lastChange as String = "never"; // Totals over the eight text fields, from the latest analysis. var totalChars as Number = 0; var totalBytes as Number = 0; // Length of the password field when it was last read (before clearing). var passwordReceived as Number = 0; // The status line the watch wrote last ("" before the first change). var lastWriteBack as String = ""; function initialize() { } // On start: no write-back, just show what the last sync delivered. function analyseOnStart() as Void { passwordReceived = readString(SpikeKeys.SECRET).length(); analyse(); } // Settings were saved on the phone: measure, write back, show. function settingsChanged() as Void { changeCount++; lastChange = clockText(); passwordReceived = readString(SpikeKeys.SECRET).length(); analyse(); lastWriteBack = "seen " + totalChars + " chars at " + lastChange + ", change #" + changeCount; Properties.setValue(SpikeKeys.STATUS, lastWriteBack); Properties.setValue(SpikeKeys.SECRET, ""); analyse(); } // Rebuild every screen line from the current property values. function analyse() as Void { lines = [] as Array; totalChars = 0; totalBytes = 0; var fieldLines = [] as Array; for (var i = 0; i < SpikeKeys.TEXT_FIELDS.size(); i++) { describeText(fieldLines, "T" + (i + 1), readString(SpikeKeys.TEXT_FIELDS[i])); } lines.add("Settings spike"); lines.add("Changes seen: " + changeCount); lines.add("Last change: " + lastChange); lines.add("Total: " + totalChars + " chars, " + totalBytes + " B"); lines.addAll(fieldLines); lines.add("Password: got " + passwordReceived + " chars,"); lines.add(" now " + readString(SpikeKeys.SECRET).length() + " (watch clears it)"); for (var i = 0; i < SpikeKeys.OTHER_FIELDS.size(); i++) { lines.add(SpikeKeys.OTHER_FIELDS[i] + ": " + valueText(SpikeKeys.OTHER_FIELDS[i])); } lines.add("Status now: " + readString(SpikeKeys.STATUS)); lines.add("Wrote: " + (lastWriteBack.equals("") ? "nothing yet" : lastWriteBack)); } // Length, ends and special characters of one text field. private function describeText(out as Array, label as String, value as String) as Void { var chars = value.length(); var bytes = value.toUtf8Array().size(); totalChars += chars; totalBytes += bytes; out.add(label + ": " + chars + " chars, " + bytes + " B"); if (chars == 0) { return; } var head = part(value, 0, chars < SpikeKeys.HEAD_CHARS ? chars : SpikeKeys.HEAD_CHARS); var tailStart = chars > SpikeKeys.TAIL_CHARS ? chars - SpikeKeys.TAIL_CHARS : 0; out.add(" " + head + " .. " + part(value, tailStart, chars)); out.add(" has: " + specialsIn(value, chars != bytes)); } // Which of the characters the plan worries about occur in the value. private function specialsIn(value as String, nonAscii as Boolean) as String { var probes = [ "&", "<", ">", SpikeKeys.ASCII_BACKSLASH.toChar().toString(), SpikeKeys.ASCII_QUOTE.toChar().toString(), "{", "}", "%" ]; var found = ""; for (var i = 0; i < probes.size(); i++) { if (value.find(probes[i]) != null) { found += probes[i] + " "; } } if (nonAscii) { found += "non-ASCII"; } return found.equals("") ? "none" : found; } // A property as a String; anything missing or of another type reads as "". private function readString(key as String) as String { var value = Properties.getValue(key); return value instanceof String ? value : ""; } // Any property as display text. private function valueText(key as String) as String { // getValue throws for unknown keys and never returns null for declared ones. return Properties.getValue(key).toString() as String; } private function part(value as String, startIndex as Number, endIndex as Number) as String { var result = value.substring(startIndex, endIndex); return result == null ? "" : result; } private function clockText() as String { var time = System.getClockTime(); return time.hour.format("%02d") + ":" + time.min.format("%02d") + ":" + time.sec.format("%02d"); } }