diff --git a/Installer-System/Installer-System.aip b/Installer-System/Installer-System.aip index d9204c1..047cd90 100644 --- a/Installer-System/Installer-System.aip +++ b/Installer-System/Installer-System.aip @@ -1,8 +1,9 @@ - + - + + @@ -21,10 +22,10 @@ - + - + @@ -89,6 +90,7 @@ + @@ -144,6 +146,7 @@ + @@ -176,6 +179,7 @@ + @@ -187,7 +191,10 @@ + + + diff --git a/Installer/Installer.aip b/Installer/Installer.aip index c1c73b5..6257c43 100644 --- a/Installer/Installer.aip +++ b/Installer/Installer.aip @@ -1,8 +1,9 @@ - + - + + @@ -21,10 +22,10 @@ - + - + @@ -90,6 +91,7 @@ + @@ -145,6 +147,7 @@ + @@ -177,6 +180,7 @@ + @@ -188,7 +192,10 @@ + + + diff --git a/README.md b/README.md index 2baab9f..b664fe4 100644 --- a/README.md +++ b/README.md @@ -66,11 +66,19 @@ If you have multiple COM ports, multiple CircuitPython devices connected, or nee `-a, --autoconnect` sets the desired auto-(re)connect behaviour (ex. `a:NONE`, `--autoconnect:ANY`) - `-l, --log` Logs all output to the specified file. (ex. `-l:ss.log`, `-log:"C:\Users\My Name\my log.txt"`) + `-l, --log` logs all output to the specified file (ex. `-l:ss.log`, `-log:"C:\Users\My Name\my log.txt"`) - `--logmode` Instructs SimplySerial to either `APPEND` to an existing log file, or `OVERWRITE` an existing log file. In either case, if the specified log file does not exist, it will be created. If neither option is specified, `OVERWRITE` is assumed. (ex. `--logmode:APPEND`) + `--logmode` instructs SimplySerial to either `APPEND` to an existing log file, or `OVERWRITE` an existing log file. In either case, if the specified log file does not exist, it will be created. If neither option is specified, `OVERWRITE` is assumed. (ex. `--logmode:APPEND`) - `-q, --quiet` prevents any application messages (connection banner, error messages, etc.) from printing out to the console. + `-q, --quiet` prevents any application messages (connection banner, error messages, etc.) from printing out to the console + + `-f, --forcenewline` replaces carriage returns with linefeeds in received data + + `-e, --encoding` sets the encoding to use when outputting to the terminal and log files. Defaults to `UTF8`, can also be set to `ASCII` (the default in SimplySerial versions prior to 0.8.0) or `RAW`. In `RAW` mode, all non-printable characters are displayed as `[xx]` where `xx` is the hexadecimal byte value of the character. + + `-noc --noclear` don't clear the terminal screen on connection + + `-nos --nostatus` block status/title updates generated by virtual terminal sequences (such as the CircuitPython status bar introduced in CP version 8.0.0) If you wanted to connect to a device on COM17 at 115200 baud, you would use the command `ss -c:17 -b:115200`, or if you really enjoy typing `ss --com:17 --baud:115200`. diff --git a/SimplySerial/Properties/AssemblyInfo.cs b/SimplySerial/Properties/AssemblyInfo.cs index 47de3b6..4fb425b 100644 --- a/SimplySerial/Properties/AssemblyInfo.cs +++ b/SimplySerial/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("fasteddy516")] [assembly: AssemblyProduct("SimplySerial")] -[assembly: AssemblyCopyright("Copyright © 2019 Edward Wright (fasteddy516)")] +[assembly: AssemblyCopyright("Copyright © 2023 Edward Wright (fasteddy516)")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/SimplySerial/SimplySerial.cs b/SimplySerial/SimplySerial.cs index 7ae068f..00a5ec1 100644 --- a/SimplySerial/SimplySerial.cs +++ b/SimplySerial/SimplySerial.cs @@ -1,20 +1,20 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; -using System.Linq; using System.IO; using System.IO.Ports; -using System.Text; -using System.Threading; +using System.Linq; using System.Management; -using System.Text.RegularExpressions; using System.Runtime.InteropServices; -using Newtonsoft.Json; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; namespace SimplySerial { class SimplySerial { - const string version = "0.8.0-alpha.2"; + const string version = "0.8.0-beta.1"; private const int STD_OUTPUT_HANDLE = -11; private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; @@ -52,6 +52,10 @@ class SimplySerial static int bufferSize = 102400; static DateTime lastFlush = DateTime.Now; static bool forceNewline = false; + static Encoding encoding = Encoding.UTF8; + static bool convertToPrintable = false; + static bool clearScreen = true; + static bool noStatus = false; // dictionary of "special" keys with the corresponding string to send out when they are pressed static Dictionary specialKeys = new Dictionary @@ -82,32 +86,37 @@ class SimplySerial static void Main(string[] args) { - // attempt to enable virtual terminal escape sequence processing - try - { - var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE); - GetConsoleMode(iStdOut, out uint outConsoleMode); - outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - SetConsoleMode(iStdOut, outConsoleMode); - } - catch - { - // if the above fails, it doesn't really matter - it just means escape sequences won't process nicely - } - // load and parse data in boards.json LoadBoards(); // process all command-line arguments ProcessArguments(args); + // attempt to enable virtual terminal escape sequence processing + if (!convertToPrintable) + { + try + { + var iStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleMode(iStdOut, out uint outConsoleMode); + outConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(iStdOut, outConsoleMode); + } + catch + { + // if the above fails, it doesn't really matter - it just means escape sequences won't process nicely + } + } + + Console.OutputEncoding = encoding; + // verify log-related settings if (logging) { try - { + { FileStream stream = new FileStream(logFile, logMode, FileAccess.Write); - using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8)) + using (StreamWriter writer = new StreamWriter(stream, encoding)) { writer.WriteLine($"\n----- LOGGING STARTED ({DateTime.Now}) ------------------------------------"); } @@ -118,7 +127,7 @@ static void Main(string[] args) ExitProgram($"* Error accessing log file '{logFile}'\n > {e.GetType()}: {e.Message}", exitCode: -1); } } - + // set up keyboard input for program control / relay to serial port ConsoleKeyInfo keyInfo = new ConsoleKeyInfo(); Console.TreatControlCAsInput = true; // we need to use CTRL-C to activate the REPL in CircuitPython, so it can't be used to exit the application @@ -199,7 +208,8 @@ static void Main(string[] args) ReadTimeout = 1, // minimal timeout - we don't want to wait forever for data that may not be coming! WriteTimeout = 250, // small delay - if we go too small on this it causes System.IO semaphore timeout exceptions DtrEnable = true, // without this we don't ever receive any data - RtsEnable = true // without this we don't ever receive any data + RtsEnable = true, // without this we don't ever receive any data + Encoding = encoding }; // attempt to set the baud rate, fail if the specified value is not supported by the hardware @@ -212,7 +222,7 @@ static void Main(string[] args) else baud = 9600; } - + serialPort.BaudRate = baud; } catch (ArgumentOutOfRangeException) @@ -247,11 +257,20 @@ static void Main(string[] args) continue; } + Console.Title = $"{port.name}: {port.board.make} {port.board.model}"; + // if we get this far, clear the screen and send the connection message if not in 'quiet' mode - Console.Clear(); + if (clearScreen) + { + Console.Clear(); + } + else + { + Output(""); + } Output(String.Format("<<< SimplySerial v{0} connected via {1} >>>\n" + - "Settings : {2} baud, {3} parity, {4} data bits, {5} stop bit{6}, auto-connect {7}.\n" + - "Device : {8} {9}{10}\n{11}" + + "Settings : {2} baud, {3} parity, {4} data bits, {5} stop bit{6}, {7} encoding, auto-connect {8}\n" + + "Device : {9} {10}{11}\n{12}" + "---\n\nUse CTRL-X to exit.\n", version, port.name, @@ -259,12 +278,13 @@ static void Main(string[] args) (parity == Parity.None) ? "no" : (parity.ToString()).ToLower(), dataBits, (stopBits == StopBits.None) ? "0" : (stopBits == StopBits.One) ? "1" : (stopBits == StopBits.OnePointFive) ? "1.5" : "2", (stopBits == StopBits.One) ? "" : "s", + (encoding.ToString() == "System.Text.UTF8Encoding") ? "UTF-8" : (convertToPrintable) ? "RAW" : "ASCII", (autoConnect == AutoConnect.ONE) ? "on" : (autoConnect == AutoConnect.ANY) ? "any" : "off", port.board.make, port.board.model, (port.isCircuitPython) ? " (CircuitPython-capable)" : "", - (logging == true) ? ($"Logfile : {logFile} (Mode = " + ((logMode == FileMode.Create) ? "OVERWRITE" : "APPEND") + ")\n" ) : "" - ), flush: true); + (logging == true) ? ($"Logfile : {logFile} (Mode = " + ((logMode == FileMode.Create) ? "OVERWRITE" : "APPEND") + ")\n") : "" + ), flush: true); ; lastFlush = DateTime.Now; DateTime start = DateTime.Now; @@ -304,6 +324,13 @@ static void Main(string[] args) // if anything was received, process it if (received.Length > 0) { + // if we're trying to filter out title/status updates in received data, try to ensure we've got the whole string + if (noStatus && received.Contains("\x1b")) + { + Thread.Sleep(100); + received += serialPort.ReadExisting(); + } + if (forceNewline) received = received.Replace("\r", "\n"); @@ -336,7 +363,10 @@ static void Main(string[] args) if (autoConnect == AutoConnect.NONE) ExitProgram((e.GetType() + " occurred while attempting to read/write to/from " + port.name + "."), exitCode: -1); else + { + Console.Title = $"{port.name}: (disconnected)"; Output("\n<<< Communications Interrupted >>>\n"); + } try { serialPort.Dispose(); @@ -348,11 +378,13 @@ static void Main(string[] args) Thread.Sleep(2000); // sort-of arbitrary delay - should be long enough to read the "interrupted" message if (autoConnect == AutoConnect.ANY) { + Console.Title = "SimplySerial: Searching..."; port.name = String.Empty; Output("<<< Attemping to connect to any available COM port. Use CTRL-X to cancel >>>"); } else if (autoConnect == AutoConnect.ONE) { + Console.Title = $"{port.name}: Searching..."; Output("<<< Attempting to re-connect to " + port.name + ". Use CTRL-X to cancel >>>"); } break; @@ -383,7 +415,7 @@ static void ProcessArguments(string[] args) foreach (string arg in args) { // split argument into components based on 'key:value' formatting - string[] argument = arg.Split(new [] { ':' }, 2); + string[] argument = arg.Split(new[] { ':' }, 2); argument[0] = argument[0].ToLower(); // help @@ -442,6 +474,18 @@ static void ProcessArguments(string[] args) forceNewline = true; } + // disable screen clearing + else if (argument[0].StartsWith("noc")) + { + clearScreen = false; + } + + // disable status/title updates from virtual terminal sequences + else if (argument[0].StartsWith("nos")) + { + noStatus = true; + } + // the remainder of possible command-line arguments require two parameters, so let's enforce that now else if (argument.Count() < 2) { @@ -545,6 +589,30 @@ static void ProcessArguments(string[] args) logFile = argument[1]; } + // specify encoding + else if (argument[0].StartsWith("e")) + { + argument[1] = argument[1].ToLower(); + + if (argument[1].StartsWith("a")) + { + encoding = Encoding.ASCII; + convertToPrintable = false; + } + else if (argument[1].StartsWith("r")) + { + encoding = Encoding.GetEncoding(1252); + convertToPrintable = true; + } + else if (argument[1].StartsWith("u")) + { + encoding = Encoding.UTF8; + convertToPrintable = false; + } + else + ExitProgram(("Invalid encoding specified <" + argument[1] + ">"), exitCode: -1); + } + // an invalid/incomplete argument was passed else { @@ -552,20 +620,34 @@ static void ProcessArguments(string[] args) } } - Console.Clear(); + if (clearScreen) + { + Console.Clear(); + } + if (autoConnect == AutoConnect.ANY) { + Console.Title = "SimplySerial: Searching..."; Output("<<< Attemping to connect to any available COM port. Use CTRL-X to cancel >>>"); } else if (autoConnect == AutoConnect.ONE) { - Console.Clear(); + if (clearScreen) + { + Console.Clear(); + } if (port.name == String.Empty) + { + Console.Title = "SimplySerial: Searching..."; Output("<<< Attempting to connect to first available COM port. Use CTRL-X to cancel >>>"); + } else + { + Console.Title = $"{port.name}: Searching..."; Output("<<< Attempting to connect to " + port.name + ". Use CTRL-X to cancel >>>"); + } } - + // if we made it this far, everything has been processed and we're ready to proceed! } @@ -574,7 +656,7 @@ static void ProcessArguments(string[] args) /// Writes messages using Console.WriteLine() as long as the 'Quiet' option hasn't been enabled /// /// Message to output (assuming 'Quiet' is false) - static void Output(string message, bool force=false, bool newline=true, bool flush=false) + static void Output(string message, bool force = false, bool newline = true, bool flush = false) { if (!SimplySerial.Quiet || force) { @@ -582,7 +664,27 @@ static void Output(string message, bool force=false, bool newline=true, bool flu message += "\n"; if (message.Length > 0) + { + if (noStatus) + { + Regex r = new Regex(@"\x1b\][02];.*\x1b\\"); + message = r.Replace(message, string.Empty); + } + + if (convertToPrintable) + { + string newMessage = ""; + foreach (byte c in message) + { + if ((c > 31 && c < 128) || (c == 8) || (c == 9) || (c == 10) || (c == 13)) + newMessage += (char)c; + else + newMessage += $"[{c:X2}]"; + } + message = newMessage; + } Console.Write(message); + } if (logging) { @@ -592,7 +694,7 @@ static void Output(string message, bool force=false, bool newline=true, bool flu try { FileStream stream = new FileStream(logFile, FileMode.Append, FileAccess.Write); - using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8)) + using (StreamWriter writer = new StreamWriter(stream, encoding)) { writer.Write(logData); } @@ -637,6 +739,9 @@ static void ShowHelp() Console.WriteLine(" -logmode:MODE APPEND | OVERWRITE, default is OVERWRITE"); Console.WriteLine(" -quiet don't print any application messages/errors to console"); Console.WriteLine(" -forcenewline Force linefeeds (newline) in place of carriage returns in received data."); + Console.WriteLine(" -encoding:ENC UTF8 | ASCII | RAW"); + Console.WriteLine(" -noclear Don't clear the terminal screen on connection."); + Console.WriteLine(" -nostatus Block status/title updates from virtual terminal sequences."); Console.WriteLine("\nPress CTRL-X to exit a running instance of SimplySerial.\n"); } @@ -668,7 +773,7 @@ static void ShowVersion() /// Message to display - should indicate the reason why the program is terminating. /// Code to return to parent process. Should be <0 if an error occurred, >=0 if program is terminating normally. /// Exits without displaying a message or asking for a key press when set to 'true' - static void ExitProgram(string message="", int exitCode=0, bool silent=false) + static void ExitProgram(string message = "", int exitCode = 0, bool silent = false) { // the serial port should be closed before exiting if (serialPort != null && serialPort.IsOpen) @@ -708,7 +813,7 @@ private static List GetSerialPorts() string[] cpb_descriptions = new string[] { "CircuitPython CDC ", "Sol CDC ", "StringCarM0Ex CDC " }; List detectedPorts = new List(); - + foreach (var p in new ManagementObjectSearcher("root\\CIMV2", query).Get().OfType()) { ComPort c = new ComPort(); @@ -721,7 +826,7 @@ private static List GetSerialPorts() c.name = mName.Value; c.num = int.Parse(c.name.Substring(3)); } - + // if the port name or number cannot be determined, skip this port and move on if (c.num < 1) continue; @@ -741,7 +846,7 @@ private static List GetSerialPorts() // extract the device's friendly description (caption) c.description = p.GetPropertyValue("Caption").ToString(); - + // attempt to match this device with a known board c.board = MatchBoard(c.vid, c.pid); @@ -767,7 +872,7 @@ private static List GetSerialPorts() } // add this port to our list of detected ports - detectedPorts.Add(c); + detectedPorts.Add(c); } return detectedPorts; @@ -788,7 +893,7 @@ static Board MatchBoard(string vid, string pid) if (mBoard == null) { - mBoard = new Board(vid:vid, pid:pid); + mBoard = new Board(vid: vid, pid: pid); Vendor mVendor = null; if (boardData.vendors != null) @@ -799,7 +904,7 @@ static Board MatchBoard(string vid, string pid) return mBoard; } - + static void LoadBoards() { try @@ -818,7 +923,7 @@ static void LoadBoards() } } - + /// /// Custom string array sorting logic for SimplySerial command-line arguments /// @@ -837,15 +942,15 @@ public int Compare(string x, string y) // 'l' triggers the 'list available ports' output and supersedes all other command-line arguments aside from 'help' and 'version' // 'q' enables the 'quiet' option, which needs to be enabled before something that would normally generate console output // 'c' is the 'comport' setting, which needs to be processed before 'autoconnect' - + x = x.ToLower(); if (x.StartsWith("lo")) x = "z"; // mask out logging options so that they are not interpreted as the list option - + y = y.ToLower(); if (y.StartsWith("lo")) y = "z"; // mask out logging options so that they are not interpreted as the list option - + foreach (char c in "?hvlqc") { if (x.ToLower()[0] == c) diff --git a/SimplySerial/SimplySerial.csproj b/SimplySerial/SimplySerial.csproj index b0681f2..57b30ba 100644 --- a/SimplySerial/SimplySerial.csproj +++ b/SimplySerial/SimplySerial.csproj @@ -113,6 +113,6 @@ - "C:\Program Files\7-Zip\7z.exe" a "$(TargetDir)\SimplySerial_standalone.zip" "$(TargetDir)\ss.exe" "$(ProjectDir)\..\LICENSE" "$(ProjectDir)\..\README.md" "$(TargetDir)\boards.json" "$(TargetDir)\ss.exe.config" "$(TargetDir)\Newtonsoft.Json.dll" + powershell Compress-Archive -Path '$(TargetDir)\ss.exe', '$(ProjectDir)\..\LICENSE', '$(ProjectDir)\..\README.md', '$(TargetDir)\boards.json', '$(TargetDir)\ss.exe.config', '$(TargetDir)\Newtonsoft.Json.dll' -DestinationPath '$(TargetDir)\SimplySerial_standalone.zip' -Force \ No newline at end of file diff --git a/SimplySerial/boards.json b/SimplySerial/boards.json index 9874b97..057a21d 100644 --- a/SimplySerial/boards.json +++ b/SimplySerial/boards.json @@ -1,5 +1,5 @@ { - "version": "2022-08-22T10:16:04.484338", + "version": "2023-02-14T13:18:35.393375", "vendors": [ { "vid": "04D8", @@ -67,6 +67,12 @@ } ], "boards": [ + { + "vid": "0483", + "pid": "572A", + "make": "STMicroelectronics", + "model": "NUCLEO-F446RE - CPy" + }, { "vid": "04D8", "pid": "E799", @@ -205,6 +211,12 @@ "make": "Targett", "model": "Targett Module Clip w/Wrover" }, + { + "vid": "1209", + "pid": "4203", + "make": "42. Keebs", + "model": "Frood" + }, { "vid": "1209", "pid": "4D43", @@ -283,6 +295,12 @@ "make": "takayoshiotake", "model": "Octave RP2040" }, + { + "vid": "1209", + "pid": "9000", + "make": "Hack Club", + "model": "Sprig" + }, { "vid": "1209", "pid": "A182", @@ -337,6 +355,12 @@ "make": "Betrusted", "model": "Simmel" }, + { + "vid": "1209", + "pid": "CB74", + "make": "0xCB", + "model": "Helios" + }, { "vid": "1209", "pid": "D10D", @@ -355,6 +379,12 @@ "make": "StackRduino", "model": "StackRduino M0 PRO" }, + { + "vid": "1209", + "pid": "EF00", + "make": "2231puppy", + "model": "E-Fidget" + }, { "vid": "1209", "pid": "F123", @@ -391,6 +421,12 @@ "make": "Pimoroni", "model": "PicoSystem" }, + { + "vid": "16D0", + "pid": "10ED", + "make": "Mechwild", + "model": "PillBug" + }, { "vid": "1915", "pid": "B001", @@ -577,6 +613,12 @@ "make": "WeAct Studio", "model": "Pico" }, + { + "vid": "239A", + "pid": "2030", + "make": "Czech maker", + "model": "Maker badge" + }, { "vid": "239A", "pid": "6005", @@ -1211,7 +1253,7 @@ "vid": "239A", "pid": "80EE", "make": "Adafruit", - "model": "Feather ESP32S2 TFT no PSRAM" + "model": "Feather ESP32-S2 Reverse TFT" }, { "vid": "239A", @@ -1345,6 +1387,24 @@ "make": "Adafruit", "model": "Feather ESP32-S3 TFT" }, + { + "vid": "239A", + "pid": "8120", + "make": "Raspberry Pi", + "model": "Pico W" + }, + { + "vid": "239A", + "pid": "8122", + "make": "Adafruit", + "model": "Feather RP2040 Scorpio" + }, + { + "vid": "239A", + "pid": "8124", + "make": "Adafruit", + "model": "Feather ESP32-S3 Reverse TFT" + }, { "vid": "239A", "pid": "D1ED", @@ -1585,6 +1645,42 @@ "make": "WIZnet", "model": "W5500-EVB-Pico" }, + { + "vid": "2E8A", + "pid": "102C", + "make": "Invector Labs", + "model": "Challenger RP2040 WiFi/BLE" + }, + { + "vid": "2E8A", + "pid": "102D", + "make": "Invector Labs", + "model": "Challenger RP2040 SD/RTC" + }, + { + "vid": "2E8A", + "pid": "102E", + "make": "VCC-GND Studio", + "model": "YD-RP2040" + }, + { + "vid": "2E8A", + "pid": "1032", + "make": "Invector Labs", + "model": "Challenger RP2040 SubGHz" + }, + { + "vid": "2E8A", + "pid": "1039", + "make": "Waveshare Electronics", + "model": "Waveshare RP2040-LCD-1.28" + }, + { + "vid": "2E8A", + "pid": "1048", + "make": "nullbits", + "model": "Bit-C PRO" + }, { "vid": "303A", "pid": "7001", @@ -1595,7 +1691,7 @@ "vid": "303A", "pid": "7003", "make": "Espressif", - "model": "ESP32-S3-DevKitC-1-N8" + "model": "ESP32-S3-DevKitC-1-N32R8" }, { "vid": "303A", @@ -1717,6 +1813,12 @@ "make": "Lolin", "model": "S2 Pico" }, + { + "vid": "303A", + "pid": "80C8", + "make": "BrainBoardz", + "model": "Neuron" + }, { "vid": "303A", "pid": "80D1", @@ -1741,6 +1843,18 @@ "make": "FutureKeys", "model": "HexKy_S2" }, + { + "vid": "303A", + "pid": "80E0", + "make": "BananaPi", + "model": "BPI-Leaf-S3" + }, + { + "vid": "303A", + "pid": "80E6", + "make": "BananaPi", + "model": "BPI-Bit-S2" + }, { "vid": "303A", "pid": "80E8", @@ -1801,6 +1915,18 @@ "make": "Smart Bee Designs", "model": "Bee-Motion-S3" }, + { + "vid": "303A", + "pid": "8117", + "make": "WEMOS", + "model": "LOLIN S3 16MB Flash 8MB PSRAM" + }, + { + "vid": "303A", + "pid": "812C", + "make": "BananaPi", + "model": "BPI-PicoW-S3" + }, { "vid": "30A4", "pid": "0002",