static void Run(string[] args) { bool esp32mode = false; if (esp32mode) { Console.WriteLine("ESP32 image tool v1.1 [http://sysprogs.com/]"); } else { Console.WriteLine("ESP8266 image tool v1.1 [http://sysprogs.com/]"); } if (args.Length < 1) { PrintUsage(); return; } string port = null; string bootloader = null; int otaPort = 0; int baud = 115200; bool erase = false; List <string> files = new List <string>(); string frequency = null, mode = null, size = null; for (int i = 0; i < args.Length; i++) { if (args[i] == "--esp32") { esp32mode = true; } else if (args[i] == "--boot") { if (i >= (args.Length - 1)) { throw new Exception("--boot must be followed by the bootloader image"); } bootloader = args[++i]; } else if (args[i] == "--program") { if (i >= (args.Length - 1)) { throw new Exception("--program must be followed by port number"); } port = args[++i]; if ((i + 1) < args.Length && !args[i + 1].StartsWith("-")) { baud = int.Parse(args[++i]); } } else if (args[i] == "--mode") { if (i >= (args.Length - 1)) { throw new Exception("--mode must be followed by FLASH mode"); } mode = args[++i]; } else if (args[i] == "--size") { if (i >= (args.Length - 1)) { throw new Exception("--size must be followed by FLASH mode"); } size = args[++i]; } else if (args[i] == "--freq") { if (i >= (args.Length - 1)) { throw new Exception("--freq must be followed by FLASH mode"); } frequency = args[++i]; } else if (args[i].ToLower() == "--ota") { if (i >= (args.Length - 1)) { throw new Exception("--OTA must be followed by port number"); } otaPort = int.Parse(args[++i]); } else if (args[i] == "--erase") { erase = true; } else { files.Add(args[i]); } } ESP8266BinaryImage.IESPxxImageHeader hdr; if (esp32mode) { var hdr2 = new ESP8266BinaryImage.ESP32ImageHeader(frequency, mode, size); Console.WriteLine("FLASH Parameters:"); Console.WriteLine("\tFrequency: " + DumpEnumValue(hdr2.Frequency)); Console.WriteLine("\tMode: " + DumpEnumValue(hdr2.Mode)); Console.WriteLine("\tSize: " + DumpEnumValue(hdr2.Size)); hdr = hdr2; } else { var hdr2 = new ESP8266BinaryImage.ESP8266ImageHeader(frequency, mode, size); Console.WriteLine("FLASH Parameters:"); Console.WriteLine("\tFrequency: " + DumpEnumValue(hdr2.Frequency)); Console.WriteLine("\tMode: " + DumpEnumValue(hdr2.Mode)); Console.WriteLine("\tSize: " + DumpEnumValue(hdr2.Size)); hdr = hdr2; } if (otaPort != 0) { OTAServer.ServeOTAFiles(otaPort, (ESP8266BinaryImage.ESP8266ImageHeader)hdr, files.ToArray()); return; } foreach (var elf in files) { string pathBase = Path.ChangeExtension(elf, ".").TrimEnd('.'); List <ProgrammableRegion> regions = new List <ProgrammableRegion>(); Console.WriteLine("Processing " + elf + "..."); using (var elfFile = new ELFFile(elf)) { string status; if (esp32mode) { var img = ESP8266BinaryImage.MakeESP32ImageFromELFFile(elfFile, (ESP8266BinaryImage.ESP32ImageHeader)hdr); string fn = pathBase + "-esp32.bin"; using (var fs = new FileStream(fn, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { img.Save(fs); regions.Add(new ProgrammableRegion { FileName = fn, Offset = 0x10000, Size = (int)fs.Length }); } } else { int appMode = ESP8266BinaryImage.DetectAppMode(elfFile, out status); Console.WriteLine(status); if (appMode == 0) { var img = ESP8266BinaryImage.MakeNonBootloaderImageFromELFFile(elfFile, (ESP8266BinaryImage.ESP8266ImageHeader)hdr); string fn = pathBase + "-0x00000.bin"; using (var fs = new FileStream(fn, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { img.Save(fs); regions.Add(new ProgrammableRegion { FileName = fn, Offset = 0, Size = (int)fs.Length }); } foreach (var sec in ESP8266BinaryImage.GetFLASHSections(elfFile)) { fn = string.Format("{0}-0x{1:x5}.bin", pathBase, sec.OffsetInFLASH); using (var fs = new FileStream(fn, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { fs.Write(sec.Data, 0, sec.Data.Length); regions.Add(new ProgrammableRegion { FileName = fn, Offset = (int)sec.OffsetInFLASH, Size = sec.Data.Length }); } } } else { string fn; var hdr2 = (ESP8266BinaryImage.ESP8266ImageHeader)hdr; var img = ESP8266BinaryImage.MakeBootloaderBasedImageFromELFFile(elfFile, hdr2, appMode); if (bootloader == null) { Console.WriteLine("Warning: no bootloader specified. Skipping bootloader..."); } else { if (!File.Exists(bootloader)) { throw new Exception(bootloader + " not found. Cannot program OTA images."); } byte[] data = File.ReadAllBytes(bootloader); data[2] = (byte)hdr2.Mode; data[3] = (byte)(((byte)hdr2.Size << 4) | (byte)hdr2.Frequency); fn = string.Format("{0}-boot.bin", pathBase); File.WriteAllBytes(fn, data); regions.Add(new ProgrammableRegion { FileName = fn, Offset = 0, Size = File.ReadAllBytes(fn).Length }); } fn = string.Format("{0}-user{1}.bin", pathBase, appMode); using (var fs = new FileStream(fn, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite)) { img.Save(fs); regions.Add(new ProgrammableRegion { FileName = fn, Offset = (int)img.BootloaderImageOffset, Size = (int)fs.Length }); } } } } if (port != null) { using (var serialPort = new SerialPortStream(port, baud, System.IO.Ports.Handshake.None) { AllowTimingOutWithZeroBytes = true }) { ESP8266BootloaderClient client = new ESP8266BootloaderClient(serialPort, 50, null); Console.WriteLine("Connecting to bootloader on {0}...", port); client.Sync(); if (erase) { Console.WriteLine("Erasing FLASH..."); client.EraseFLASH(); Console.WriteLine("FLASH erased. Please restart your ESP8266 into the bootloader mode again.\r\nPress any key when done..."); Console.ReadKey(); client.Sync(); } foreach (var region in regions) { DateTime start = DateTime.Now; Console.WriteLine("Programming " + Path.GetFileName(region.FileName) + "..."); var tracker = new ProgressTracker(region); client.BlockWritten += tracker.BlockWritten; client.ProgramFLASH((uint)region.Offset, File.ReadAllBytes(region.FileName)); client.BlockWritten -= tracker.BlockWritten; Console.WriteLine("\rProgrammed in {0} seconds ", (int)(DateTime.Now - start).TotalSeconds); } } } else { int fileNameLen = Path.GetFileName(args[0]).Length + 10; Console.WriteLine("\r\nCreated the following files:"); Console.WriteLine("File".PadRight(fileNameLen) + " FLASH Offset Size"); foreach (var region in regions) { Console.WriteLine(Path.GetFileName(region.FileName).PadRight(fileNameLen) + " " + string.Format("0x{0:x8} {1}KB", region.Offset, region.Size / 1024)); } } } }
public static void ServeOTAFiles(int port, ESP8266BinaryImage.ESP8266ImageHeader hdr, params string[] elfFiles) { TcpListener listener = new TcpListener(port); byte[] buffer = new byte[1024]; OTAImage[] images = new OTAImage[2]; foreach (var fn in elfFiles) { if (fn != null) { using (var elfFile = new ELFFile(fn)) { string status; int appMode = ESP8266BinaryImage.DetectAppMode(elfFile, out status); if (appMode == 0) { Console.WriteLine(fn + " is not an OTA ELF file. Skipping..."); continue; } var img = ESP8266BinaryImage.MakeBootloaderBasedImageFromELFFile(elfFile, hdr, appMode); using (var ms = new MemoryStream()) { img.Save(ms); images[appMode - 1].Data = ms.ToArray(); images[appMode - 1].File = fn; } } } } Console.WriteLine($"Ready to serve the following files:"); Console.WriteLine($"APP1: {images[0].File ?? "(none)"}"); Console.WriteLine($"APP2: {images[1].File ?? "(none)"}"); Console.WriteLine($"Waiting for connection on port {port}..."); listener.Start(); for (;;) { using (var sock = listener.AcceptSocket()) { Console.WriteLine($"Incoming connection from {(sock.RemoteEndPoint as IPEndPoint).Address}"); StringBuilder requestBuilder = new StringBuilder(); while (!requestBuilder.ToString().Contains("\r\n\r")) { int done = sock.Receive(buffer); requestBuilder.Append(Encoding.UTF8.GetString(buffer, 0, done)); } string request = requestBuilder.ToString(); string[] parts = request.Split(' '); if (parts.Length < 3) { throw new Exception("Invalid HTTP request: " + request); } string url = parts[1]; Console.WriteLine("Received request for " + url); int otaIndex = (url.ToLower().Contains("user2") ? 1 : 0); if (images[otaIndex].Data == null) { throw new Exception($"No OTA image for app{otaIndex + 1} is provided. Please check your linker scripts."); } string reply = string.Format("HTTP/1.0 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: {0}\r\n\r\n", images[otaIndex].Data.Length); var r = Encoding.UTF8.GetBytes(reply); sock.Send(r); if (parts[0] == "GET") { Console.Write($"Serving {Path.GetFileName(images[otaIndex].File)}...\r\n"); using (var ms = new MemoryStream(images[otaIndex].Data)) { int totalDone = 0; for (;;) { int done = ms.Read(buffer, 0, buffer.Length); if (done == 0) { break; } sock.Send(buffer, done, SocketFlags.None); totalDone += done; int percent = (int)((totalDone * 100) / ms.Length); int progress = percent / 5; Console.Write($"\r[{new string('#', progress).PadRight(20)}] {percent}%"); } } Console.WriteLine("\r\nFile sent successfully\n"); break; } } } listener.Stop(); }