diff --git a/LumenLabInstallerForm.Designer.cs b/LumenLabInstallerForm.Designer.cs index c47e021..b03839c 100644 --- a/LumenLabInstallerForm.Designer.cs +++ b/LumenLabInstallerForm.Designer.cs @@ -39,7 +39,8 @@ private void InitializeComponent() BtnSync = new Button(); menuStrip1 = new MenuStrip(); fileToolStripMenuItem = new ToolStripMenuItem(); - loadConfigToolStripMenuItem = new ToolStripMenuItem(); + LoadControllerSettingsToolStripMenuItem = new ToolStripMenuItem(); + SaveControllerSettingsToolStripMenuItem = new ToolStripMenuItem(); toolStripSeparator1 = new ToolStripSeparator(); exitToolStripMenuItem = new ToolStripMenuItem(); viewToolStripMenuItem = new ToolStripMenuItem(); @@ -139,7 +140,7 @@ private void InitializeComponent() BtnSync.Name = "BtnSync"; BtnSync.Size = new Size(174, 26); BtnSync.TabIndex = 4; - BtnSync.Text = "Refresh Device List"; + BtnSync.Text = "Scan Devices"; BtnSync.UseVisualStyleBackColor = true; BtnSync.Click += BtnSync_Click; // @@ -154,26 +155,34 @@ private void InitializeComponent() // // fileToolStripMenuItem // - fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { loadConfigToolStripMenuItem, toolStripSeparator1, exitToolStripMenuItem }); + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { LoadControllerSettingsToolStripMenuItem, SaveControllerSettingsToolStripMenuItem, toolStripSeparator1, exitToolStripMenuItem }); fileToolStripMenuItem.Name = "fileToolStripMenuItem"; fileToolStripMenuItem.Size = new Size(37, 20); fileToolStripMenuItem.Text = "File"; // - // loadConfigToolStripMenuItem + // LoadControllerSettingsToolStripMenuItem // - loadConfigToolStripMenuItem.Name = "loadConfigToolStripMenuItem"; - loadConfigToolStripMenuItem.Size = new Size(139, 22); - loadConfigToolStripMenuItem.Text = "Load Config"; + LoadControllerSettingsToolStripMenuItem.Name = "LoadControllerSettingsToolStripMenuItem"; + LoadControllerSettingsToolStripMenuItem.Size = new Size(201, 22); + LoadControllerSettingsToolStripMenuItem.Text = "Load Controller Settings"; + LoadControllerSettingsToolStripMenuItem.Click += LoadControllerSettingsToolStripMenuItem_Click; + // + // SaveControllerSettingsToolStripMenuItem + // + SaveControllerSettingsToolStripMenuItem.Name = "SaveControllerSettingsToolStripMenuItem"; + SaveControllerSettingsToolStripMenuItem.Size = new Size(201, 22); + SaveControllerSettingsToolStripMenuItem.Text = "Save Controller Settings"; + SaveControllerSettingsToolStripMenuItem.Click += SaveControllerSettingsToolStripMenuItem_Click; // // toolStripSeparator1 // toolStripSeparator1.Name = "toolStripSeparator1"; - toolStripSeparator1.Size = new Size(136, 6); + toolStripSeparator1.Size = new Size(198, 6); // // exitToolStripMenuItem // exitToolStripMenuItem.Name = "exitToolStripMenuItem"; - exitToolStripMenuItem.Size = new Size(139, 22); + exitToolStripMenuItem.Size = new Size(201, 22); exitToolStripMenuItem.Text = "Exit"; exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; // @@ -455,8 +464,6 @@ private void InitializeComponent() LblUpdateAlert.Size = new Size(432, 21); LblUpdateAlert.TabIndex = 30; LblUpdateAlert.Text = "Plug in your LumenLab to continue."; - LblUpdateAlert.SelectAll(); - LblUpdateAlert.SelectionAlignment = HorizontalAlignment.Center; // // LumenLabInstallerForm // @@ -512,7 +519,6 @@ private void InitializeComponent() private ToolStripMenuItem exitToolStripMenuItem; private ToolStripMenuItem helpToolStripMenuItem; private ToolStripMenuItem aboutToolStripMenuItem; - private Label LblCustomizeConfig; private CheckBox ChkCustomizeConfiguration; private Label LblTotalLeds; private Label LblMacAddress; @@ -531,7 +537,7 @@ private void InitializeComponent() private RichTextBox LblInstalledVersion; private RichTextBox LblLatestRelease; private ToolStripSeparator toolStripSeparator1; - private ToolStripMenuItem loadConfigToolStripMenuItem; + private ToolStripMenuItem LoadControllerSettingsToolStripMenuItem; private Label label1; private Label label2; private RadioButton RadPS3; @@ -539,5 +545,6 @@ private void InitializeComponent() private Label LblControllerType; private CheckBox ChkClearMemory; private RichTextBox LblUpdateAlert; + private ToolStripMenuItem SaveControllerSettingsToolStripMenuItem; } } diff --git a/LumenLabInstallerForm.cs b/LumenLabInstallerForm.cs index ee9f13e..454539c 100644 --- a/LumenLabInstallerForm.cs +++ b/LumenLabInstallerForm.cs @@ -9,304 +9,384 @@ using LumenLabInstaller.Services; using LumenLabInstaller.Models; using System.Reflection; +using System.IO.Compression; -namespace LumenLabInstaller +namespace LumenLabInstaller; + +public partial class LumenLabInstallerForm : Form { - public partial class LumenLabInstallerForm : Form + private readonly InstallerContext _context; + private readonly NetworkService _networkService; + private readonly BindingSource _releaseBindingSource = []; + + + public LumenLabInstallerForm(NetworkService networkService, InstallerContext context) { - private readonly InstallerContext _context; - private readonly DeviceDiscoveryService _deviceService; - private readonly FirmwareService _firmwareService; - private readonly BinaryDownloadService _downloadService; + _networkService = networkService; + _context = context; + InitializeComponent(); + ConfigureReleaseGrid(); + } - private BindingSource _releaseBindingSource = []; + private void ConfigureReleaseGrid() + { + DgvGitHubReleases.AutoGenerateColumns = false; + DgvGitHubReleases.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + DgvGitHubReleases.MultiSelect = false; + DgvGitHubReleases.ReadOnly = true; + DgvGitHubReleases.AllowUserToAddRows = false; + DgvGitHubReleases.AllowUserToDeleteRows = false; + DgvGitHubReleases.Columns.Clear(); - public LumenLabInstallerForm(BinaryDownloadService downloadService, - InstallerContext context, - FirmwareService firmwareService, - DeviceDiscoveryService deviceService) + DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn { - _downloadService = downloadService; - _context = context; - _firmwareService = firmwareService; - _deviceService = deviceService; - InitializeComponent(); - ConfigureReleaseGrid(); - } + DataPropertyName = "TagName", + HeaderText = "Version", + Width = 60 + }); - private void ConfigureReleaseGrid() + DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn { - DgvGitHubReleases.AutoGenerateColumns = false; - DgvGitHubReleases.SelectionMode = DataGridViewSelectionMode.FullRowSelect; - DgvGitHubReleases.MultiSelect = false; - DgvGitHubReleases.ReadOnly = true; - DgvGitHubReleases.AllowUserToAddRows = false; - DgvGitHubReleases.AllowUserToDeleteRows = false; + DataPropertyName = "Name", + HeaderText = "Release Name", + Width = 230 + }); - DgvGitHubReleases.Columns.Clear(); - - DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn + DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn + { + DataPropertyName = "PublishedAt", + HeaderText = "Published", + DefaultCellStyle = new DataGridViewCellStyle { - DataPropertyName = "TagName", - HeaderText = "Version", - Width = 60 - }); + Format = "MM/dd/yyyy" + } + }); - DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn - { - DataPropertyName = "Name", - HeaderText = "Release Name", - Width = 230 - }); + DgvGitHubReleases.DataSource = _releaseBindingSource; + } - DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn - { - DataPropertyName = "PublishedAt", - HeaderText = "Published", - DefaultCellStyle = new DataGridViewCellStyle - { - Format = "MM/dd/yyyy" - } - }); - - DgvGitHubReleases.DataSource = _releaseBindingSource; + private async void LumenLabInstallerForm_Shown(object sender, EventArgs e) + { + this.UseWaitCursor = true; + BtnFlashFirmware.Enabled = false; + try + { + await QueryGitHub(); + } + catch (Exception ex) + { + MessageBox.Show($"Error connecting to server to download releases.{Environment.NewLine}{ex.Message}", "Network Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } - private async void LumenLabInstallerForm_Shown(object sender, EventArgs e) + try { - this.UseWaitCursor = true; - BtnFlashFirmware.Enabled = false; - try - { - await QueryGitHub(); - await ReadConnectedDeviceVersion(); - } - catch (Exception ex) + var successfulRead = await ReadConnectedDeviceVersion(); + if (!successfulRead) { - MessageBox.Show($"Error retrieving releases:{Environment.NewLine}{ex.Message}", - "GitHub API Error", - MessageBoxButtons.OK, - MessageBoxIcon.Error); + + // TODO: Just update the guide label, don't fire messagebox + //MessageBox.Show("LumenLab was not detected. Please make sure the external power is on and try again.", "LumenLab Not Detected", MessageBoxButtons.RetryCancel, MessageBoxIcon.Stop); } } - - private async Task QueryGitHub() + catch (Exception ex) { - var releaseService = new GitHubReleaseService(); - var releases = await releaseService.GetReleasesAsync(); + MessageBox.Show($"Error checking version installed on LumenLab.{Environment.NewLine}{ex.Message}", "Device Read Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } - _context.AvailableReleases = releases.OrderByDescending(r => r.PublishedAt).ToList(); + private async Task QueryGitHub() + { + WriteToLogs("Querying GitHub to check for updates."); + var releases = await _networkService.GetReleasesAsync(); + + _context.AvailableReleases = releases.OrderByDescending(r => r.PublishedAt).ToList(); - var gridRows = releases - .OrderByDescending(r => r.PublishedAt) - .Select(r => new ReleaseGridRow - { - TagName = r.TagName ?? string.Empty, - Name = r.Name?.Substring(r.Name.IndexOf("-") + 2) ?? string.Empty, - PublishedAt = r.PublishedAt ?? new DateTime(), - Release = r - }) - .ToList(); + var gridRows = releases + .OrderByDescending(r => r.PublishedAt) + .Select(r => new ReleaseGridRow + { + TagName = r.TagName ?? string.Empty, + Name = r.Name?.Substring(r.Name.IndexOf("-") + 2) ?? string.Empty, + PublishedAt = r.PublishedAt ?? new DateTime(), + Release = r + }) + .ToList(); - _releaseBindingSource.DataSource = gridRows; + _releaseBindingSource.DataSource = gridRows; - LblLatestRelease.Text = releases[0].TagName; + LblLatestRelease.Text = releases[0].TagName; - foreach (var release in releases) + foreach (var release in releases) + { + WriteToLogs($"{release.TagName} - {release.PublishedAt}"); + foreach (var asset in release.Assets) { - WriteToLogs($"{release.TagName} - {release.PublishedAt}"); - foreach (var asset in release.Assets) - { - WriteToLogs($" Asset: {asset.Name} -> {asset.BrowserDownloadUrl}"); - } + WriteToLogs($" Asset: {asset.Name} -> {asset.BrowserDownloadUrl}"); } - - // PopulateReleaseTable(releases); } - private async Task ReadConnectedDeviceVersion() + // PopulateReleaseTable(releases); + } + + private async Task ReadConnectedDeviceVersion() + { + BtnSync.Enabled = false; + this.UseWaitCursor = true; + BtnFlashFirmware.Enabled = false; + LblInstalledVersion.Text = "N/A"; + + WriteToLogs($"Scanning USB ports to find LumenLab."); + + string? port = await DeviceService.DetectLumenLabPortAsync(); + _context.PortName = port; + + if (port == null) { - BtnSync.Enabled = false; - this.UseWaitCursor = true; - BtnFlashFirmware.Enabled = false; - LblInstalledVersion.Text = "N/A"; + WriteToLogs("LumenLab was not detected. Please make sure the external power is on and try again."); + BtnSync.Enabled = true; + this.UseWaitCursor = false; + return false; + } - WriteToLogs($"Scanning USB ports to find LumenLab."); + WriteToLogs($"LumenLab detected on {port}"); - string? port = await _deviceService.DetectLumenLabPortAsync(); - _context.PortName = port; + string? version = await DeviceService.ReadFirmwareVersionAsync(port); + _context.FirmwareVersion = new Version(version.Substring(1)); - if (port == null) - { - WriteToLogs("LumenLab not detected."); - } - else - { - WriteToLogs($"LumenLab detected on {port}"); + LblInstalledVersion.Text = $"v{_context.FirmwareVersion.ToString()}"; + WriteToLogs($"Currently installed version: {version}"); - string version = await _firmwareService.ReadFirmwareVersionAsync(port); - _context.FirmwareVersion = new Version(version.Substring(1)); + //if (_context.FirmwareVersion == ) TODO: update the label to inform user if they're out of date or current. - LblInstalledVersion.Text = $"v{_context.FirmwareVersion.ToString()}"; + BtnFlashFirmware.Enabled = true; + WriteToLogs($"Device scan complete. Ready for firmware upgrade."); - WriteToLogs(version); - //if (_context.FirmwareVersion == ) - BtnFlashFirmware.Enabled = true; - } + BtnSync.Enabled = true; + this.UseWaitCursor = false; + return true; + } - WriteToLogs($"Scan complete."); - BtnSync.Enabled = true; - this.UseWaitCursor = false; + private void AppendLog(string text) + { + if (InvokeRequired) + { + Invoke(new Action(AppendLog), text); + return; } - private void AppendLog(string text) + WriteToLogs(text); + } + + private async void BtnFlashFirmware_Click(object sender, EventArgs e) + { + WriteToLogs("Starting LumenLab firmware upgrade."); + BtnFlashFirmware.Enabled = false; + string? port = await DeviceService.DetectLumenLabPortAsync(); + if (port == null) + { + LblInstalledVersion.Text = "N/A"; + MessageBox.Show("LumenLab was disconnected. Please connect the device and run again.", "No LumenLab detected", MessageBoxButtons.RetryCancel, MessageBoxIcon.Stop); + return; + } + + var selectedRelease = GetSelectedRelease(); + + if (selectedRelease == null) + { + MessageBox.Show("Please select a version to install.", "Select Version", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + var selectedReleaseVersionId = new Version(selectedRelease?.TagName?.Substring(1) ?? "0.0.0"); + + DialogResult userDialogSelection; + if (selectedReleaseVersionId == _context.FirmwareVersion) { - if (InvokeRequired) + userDialogSelection = MessageBox.Show($"You are about to install LumenLab version {selectedReleaseVersionId}, however you already have a that version installed.\n\nAre you sure you want to overwrite?", "Continue with overwrite?", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); + if (userDialogSelection == DialogResult.Cancel) { - Invoke(new Action(AppendLog), text); + WriteToLogs("Cancelled LumenLab firmware update."); + BtnFlashFirmware.Enabled = true; return; } - - WriteToLogs(text); } - - private async void BtnFlashFirmware_Click(object sender, EventArgs e) + else if (selectedReleaseVersionId < _context.FirmwareVersion) { - BtnFlashFirmware.Enabled = false; - string? port = await _deviceService.DetectLumenLabPortAsync(); - if (port == null) + userDialogSelection = MessageBox.Show($"You are about to install LumenLab version {selectedReleaseVersionId}, however you already have a newer version {_context.FirmwareVersion} installed.\n\nAre you sure you want to downgrade?", "Continue with downgrade?", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); + if (userDialogSelection == DialogResult.Cancel) { - LblInstalledVersion.Text = "N/A"; - MessageBox.Show("LumenLab was disconnected. Please connect the device and run again.", "No LumenLab detected", MessageBoxButtons.RetryCancel, MessageBoxIcon.Stop); + WriteToLogs("Cancelled LumenLab firmware update."); + BtnFlashFirmware.Enabled = true; return; } + } + BtnFlashFirmware.Enabled = false; - var selectedRelease = GetSelectedRelease(); + try + { + var latestRelease = _context.AvailableReleases[0] ?? null; + if (latestRelease == null) + { + // second-pass refactor: more specific error + throw new Exception("Error"); + } - if (selectedRelease == null) + string downloadPath = Path.Combine(AppPaths.BinariesPath, latestRelease.TagName ?? "v99.99.99"); + if (!Directory.Exists(downloadPath)) { - MessageBox.Show("Please select a version to install.", "Select Version", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; + WriteToLogs($"Creating directory {downloadPath}"); + Directory.CreateDirectory(downloadPath); } - var selectedReleaseVersionId = new Version(selectedRelease.TagName.Substring(1)); + WriteToLogs($"Downloading LumenLab {latestRelease.TagName}"); + var cts = new CancellationTokenSource(); + var result = await _networkService.DownloadAsync( + new Uri($"https://github.com/ericmcdaniel/lumenlab/releases/download/{latestRelease.TagName}/lumenlab-firmware.zip"), + Path.Combine(downloadPath, "lumenlab-firmware.zip") + ); + + string compressedLumenLabPath = Path.Combine(downloadPath, "lumenlab-firmware.zip"); + + ZipFile.ExtractToDirectory(compressedLumenLabPath, downloadPath, true); + File.Delete(compressedLumenLabPath); + + WriteToLogs("Download complete."); + WriteToLogs($"Downloaded {result.BytesWritten.Bytes()}."); - DialogResult userDialogSelection; - if (selectedReleaseVersionId == _context.FirmwareVersion) + string esptoolFullCmd = $"--chip esp32 --baud 921600 --port {port} write_flash -z 0x1000 {Path.Combine(downloadPath, "bootloader.bin")} 0x8000 {Path.Combine(downloadPath, "partitions.bin")} 0x10000 {Path.Combine(downloadPath, "firmware.bin")}"; + WriteToLogs($"Running esptool.exe {esptoolFullCmd}"); + var exitCode = await DeviceService.RunEsptoolAsync(esptoolFullCmd, AppendLog, cts.Token); + + if (exitCode == 0) { - userDialogSelection = MessageBox.Show($"You are about to install LumenLab version {selectedReleaseVersionId}, however you already have a that version installed.\n\nAre you sure you want to overwrite?", "Continue with overwrite?", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); - if (userDialogSelection == DialogResult.Cancel) - { - MessageBox.Show("LumenLab firmware upgrade operation cancelled.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } + WriteToLogs($"Successful installation of LumenLab {latestRelease.TagName}."); + WriteToLogs("You may safely exit this tool."); } - else if (selectedReleaseVersionId < _context.FirmwareVersion) + else { - userDialogSelection = MessageBox.Show($"You are about to install LumenLab version {selectedReleaseVersionId}, however you already have a newer version {_context.FirmwareVersion} installed.\n\nAre you sure you want to downgrade?", "Continue with downgrade?", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); - if (userDialogSelection == DialogResult.Cancel) - { - MessageBox.Show("LumenLab firmware upgrade operation cancelled.", "Operation canceled", MessageBoxButtons.OK, MessageBoxIcon.Information); - return; - } + WriteToLogs($"Failed to flash LumenLab {latestRelease.TagName}."); } - - MessageBox.Show(selectedRelease.TagName); - MessageBox.Show(selectedRelease.Assets[0].Name); - + } + catch (Exception ex) + { + MessageBox.Show(ex.Message); + } + finally + { BtnFlashFirmware.Enabled = true; - - //btnDownload.Enabled = false; - - //try - //{ - // var cts = new CancellationTokenSource(); - // WriteToLogs("Downloading LumenLab v0.3.0."); - // var result = await _downloadService.DownloadAsync( - // new Uri("https://github.com/ericmcdaniel/lumenlab/archive/refs/tags/v0.3.0.zip"), - // Path.Combine(AppPaths.BinariesPath, "lumenlab_v0-3-0.zip") - // ); - - // WriteToLogs("Download complete."); - // WriteToLogs($"Downloaded {result.BytesWritten.Bytes()}."); - // await ToolManager.RunEsptoolAsync("--chip esp32 --baud 921600 write_flash -z 0x1000 C:\\Users\\McDan\\development\\lumenlab\\.pio\\build\\release\\bootloader.bin 0x8000 C:\\Users\\McDan\\development\\lumenlab\\.pio\\build\\release\\partitions.bin 0x10000 C:\\Users\\McDan\\development\\lumenlab\\.pio\\build\\release\\firmware.bin", AppendLog, cts.Token); - - //} - //catch (Exception ex) - //{ - // MessageBox.Show(ex.Message); - //} - //finally - //{ - // btnDownload.Enabled = true; - //} } - private async void BtnSync_Click(object sender, EventArgs e) + } + private async void BtnSync_Click(object sender, EventArgs e) + { + var successfulRead = await ReadConnectedDeviceVersion(); + if (!successfulRead) { - await ReadConnectedDeviceVersion(); + MessageBox.Show("LumenLab was not detected. Please make sure the external power is on and try again.", "LumenLab Not Detected", MessageBoxButtons.RetryCancel, MessageBoxIcon.Stop); } + } - private GitHubRelease? GetSelectedRelease() - { - if (_releaseBindingSource.Current is ReleaseGridRow row) - return row.Release; + private GitHubRelease? GetSelectedRelease() + { + if (_releaseBindingSource.Current is ReleaseGridRow row) + return row.Release; - return null; - } + return null; + } + + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + Application.Exit(); + } - private void exitToolStripMenuItem_Click(object sender, EventArgs e) + private void ChkCustomizeConfiguration_CheckedChanged(object sender, EventArgs e) + { + TxtNumLeds.Enabled = !TxtNumLeds.Enabled; + LblTotalLeds.Enabled = !LblTotalLeds.Enabled; + + LblControllerType.Enabled = !LblControllerType.Enabled; + RadPS3.Enabled = !RadPS3.Enabled; + + TxtMacAddress.Enabled = !TxtMacAddress.Enabled; + LblMacAddress.Enabled = !LblMacAddress.Enabled; + + TxtSerialBaud.Enabled = !TxtSerialBaud.Enabled; + LblSerialBaud.Enabled = !LblSerialBaud.Enabled; + + TxtBound1.Enabled = !TxtBound1.Enabled; + TxtBound2.Enabled = !TxtBound2.Enabled; + TxtBound3.Enabled = !TxtBound3.Enabled; + LblBoundaries.Enabled = !LblBoundaries.Enabled; + } + + private void toggleDebugWindowToolStripMenuItem_Click(object sender, EventArgs e) + { + toggleDebugWindowToolStripMenuItem.Checked = !toggleDebugWindowToolStripMenuItem.Checked; + outputBox.Visible = !outputBox.Visible; + if (outputBox.Visible) { - Application.Exit(); + this.Height += outputBox.Height; } - - private void ChkCustomizeConfiguration_CheckedChanged(object sender, EventArgs e) + else { - TxtNumLeds.Enabled = !TxtNumLeds.Enabled; - LblTotalLeds.Enabled = !LblTotalLeds.Enabled; + this.Height -= outputBox.Height; + } + } - LblControllerType.Enabled = !LblControllerType.Enabled; - RadPS3.Enabled = !RadPS3.Enabled; + private void ChkClearMemory_CheckedChanged(object sender, EventArgs e) + { + if (ChkClearMemory.Checked) + { + MessageBox.Show("Nice try, cheater.", "Failed to erase high score ", MessageBoxButtons.OK, MessageBoxIcon.Error); + ChkClearMemory.Checked = false; + } + } - TxtMacAddress.Enabled = !TxtMacAddress.Enabled; - LblMacAddress.Enabled = !LblMacAddress.Enabled; + private void WriteToLogs(string log) + { + outputBox.AppendText($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] - {log}{Environment.NewLine}"); + } - TxtSerialBaud.Enabled = !TxtSerialBaud.Enabled; - LblSerialBaud.Enabled = !LblSerialBaud.Enabled; + private void SaveControllerSettingsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var dialog = new SaveFileDialog(); - TxtBound1.Enabled = !TxtBound1.Enabled; - TxtBound2.Enabled = !TxtBound2.Enabled; - TxtBound3.Enabled = !TxtBound3.Enabled; - LblBoundaries.Enabled = !LblBoundaries.Enabled; - } + dialog.Title = "Save Controller Configuration"; + dialog.Filter = "JSON Files (*.json)|*.json"; + dialog.DefaultExt = "json"; + dialog.AddExtension = true; + dialog.FileName = "lumenlab-controller-settings.json"; - private void toggleDebugWindowToolStripMenuItem_Click(object sender, EventArgs e) + if (dialog.ShowDialog() == DialogResult.OK) { - toggleDebugWindowToolStripMenuItem.Checked = !toggleDebugWindowToolStripMenuItem.Checked; - outputBox.Visible = !outputBox.Visible; - if (outputBox.Visible) + try { - this.Height += outputBox.Height; + File.WriteAllText(dialog.FileName, "{}"); + MessageBox.Show("Successfully saved your controller settings.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); + WriteToLogs($"Successfully saved your controller settings."); } - else + catch (Exception ex) { - this.Height -= outputBox.Height; + MessageBox.Show("Failed to save controller settings. Try again.", "Save failed", MessageBoxButtons.OK, MessageBoxIcon.Error); + WriteToLogs($"Failed to save controller settings. Error message: {ex}"); } } + } - private void ChkClearMemory_CheckedChanged(object sender, EventArgs e) - { - if (ChkClearMemory.Checked) - { - MessageBox.Show("Nice try, cheater.", "Failed to erase high score ", MessageBoxButtons.OK, MessageBoxIcon.Error); - ChkClearMemory.Checked = false; - } - } + private void LoadControllerSettingsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var dialog = new OpenFileDialog(); + + dialog.Title = "Load Controller Configuration"; + dialog.Filter = "JSON Files (*.json)|*.json"; - private void WriteToLogs(string log) + if (dialog.ShowDialog() == DialogResult.OK) { - outputBox.AppendText($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}] - {log}{Environment.NewLine}"); + string path = dialog.FileName; + + // Placeholder for future implementation + MessageBox.Show($"Selected config file:\n{path}"); } } } diff --git a/Program.cs b/Program.cs index 5040a7f..ce609f0 100644 --- a/Program.cs +++ b/Program.cs @@ -5,18 +5,23 @@ using LumenLabInstaller.Services; using LumenLabInstaller.Models; -ApplicationConfiguration.Initialize(); -var host = Host.CreateDefaultBuilder() - .ConfigureServices((context, services) => +internal static class Program +{ + [STAThread] + static void Main() { - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddHttpClient(); - services.AddTransient(); - }) - .Build(); + ApplicationConfiguration.Initialize(); + var host = Host.CreateDefaultBuilder() + .ConfigureServices((context, services) => + { + services.AddSingleton(); + services.AddHttpClient(); + services.AddTransient(); + }) + .Build(); -var form = host.Services.GetRequiredService(); -Application.Run(form); \ No newline at end of file + var form = host.Services.GetRequiredService(); + Application.Run(form); + } +} \ No newline at end of file diff --git a/README.md b/README.md index c0e1d6f..c055226 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # LumenLab Installer -A Windows-based installation tool to streamline updating the LumenLab. +A Windows-based installation tool to help keep your LumenLab up-to-date. + +![Screenshot of the main form when updating the LumenLab](./assets/main-screenshot.png) # How To Use +(More detail will be added soon, the priority is to finish the core functionality by allowing users to flash controller configuration, store settings, and re-load them. Full documention will be drafted when that is deployed.) + [To be finalized.] \ No newline at end of file diff --git a/Services/BinaryDownloadService.cs b/Services/BinaryDownloadService.cs deleted file mode 100644 index 861678b..0000000 --- a/Services/BinaryDownloadService.cs +++ /dev/null @@ -1,45 +0,0 @@ -using LumenLabInstaller.Models; - -namespace LumenLabInstaller.Services -{ - public class BinaryDownloadService - { - private readonly HttpClient _httpClient; - - public BinaryDownloadService(HttpClient httpClient) - { - _httpClient = httpClient; - } - - public async Task DownloadAsync(Uri uri, - string destinationPath, - CancellationToken cancellationToken = default) - { - using var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - - response.EnsureSuccessStatusCode(); - - var totalBytes = response.Content.Headers.ContentLength; - - await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); - await using var fileStream = new FileStream(destinationPath, - FileMode.Create, - FileAccess.Write, - FileShare.None, - bufferSize: 8192, - useAsync: true); - - var buffer = new byte[8192]; - long totalRead = 0; - int bytesRead; - - while ((bytesRead = await contentStream.ReadAsync(buffer, cancellationToken)) > 0) - { - await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); - totalRead += bytesRead; - } - - return new DownloadResult(destinationPath, totalRead); - } - } -} \ No newline at end of file diff --git a/Services/DeviceDiscoveryService.cs b/Services/DeviceDiscoveryService.cs deleted file mode 100644 index 4aef5c0..0000000 --- a/Services/DeviceDiscoveryService.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LumenLabInstaller.Services -{ - using System.Management; - - public class DeviceDiscoveryService - { - public Task DetectLumenLabPortAsync() - { - return Task.Run(() => - { - var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM%)%'"); - - foreach (var device in searcher.Get()) - { - string name = device["Name"]?.ToString() ?? ""; - - if (name.Contains("CH340") || - name.Contains("CP2102") || - name.Contains("FT232")) - { - int start = name.IndexOf("(COM") + 1; - int end = name.IndexOf(")", start); - - if (start > 0 && end > start) - return name.Substring(start, end - start); - } - } - - return null; - }); - } - } -} diff --git a/Services/DeviceService.cs b/Services/DeviceService.cs new file mode 100644 index 0000000..90ba9cb --- /dev/null +++ b/Services/DeviceService.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Management; +using LumenLabInstaller.Infrastructure; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Reflection; + +namespace LumenLabInstaller.Services; + + +public class DeviceService +{ + public static Task DetectLumenLabPortAsync() + { + return Task.Run(() => + { + var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM%)%'"); + + foreach (var device in searcher.Get()) + { + string name = device["Name"]?.ToString() ?? ""; + + if (name.Contains("CH340") || + name.Contains("CP2102") || + name.Contains("FT232")) + { + int start = name.IndexOf("(COM") + 1; + int end = name.IndexOf(')', start); + + if (start > 0 && end > start) + return name.Substring(start, end - start); + } + } + + return null; + }); + } + + public async static Task ReadFirmwareVersionAsync(string portName) + { + EnsureEsptoolExists(); + string versionTemp = Path.Combine(Path.GetTempPath(), "version_chunk.bin"); + + var psi = new ProcessStartInfo + { + FileName = AppPaths.EsptoolPath, + Arguments = $"--chip esp32 --port {portName} read_flash 0x10100 0x1000 \"{versionTemp}\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = new Process { StartInfo = psi }; + process.Start(); + + await process.WaitForExitAsync(); + + if (!File.Exists(versionTemp)) + return null; + + byte[] firmwareBytes = await File.ReadAllBytesAsync(versionTemp); + string content = Encoding.ASCII.GetString(firmwareBytes); + + var match = Regex.Match(content, @"v?\d+\.\d+\.\d+"); + return match.Success ? match.Value : null; + } + + public static void EnsureEsptoolExists() + { + if (File.Exists(AppPaths.EsptoolPath)) + return; + + Directory.CreateDirectory(AppPaths.ToolsFolder); + + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = "LumenLabInstaller.Firmware.esptool.exe"; + using var resourceStream = assembly.GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException("Embedded esptool not found."); + + using var fileStream = new FileStream(AppPaths.EsptoolPath, FileMode.Create, FileAccess.Write); + + resourceStream.CopyTo(fileStream); + } + + public static async Task RunEsptoolAsync(string arguments, Action onOutput, CancellationToken cancellationToken) + { + EnsureEsptoolExists(); + var psi = new ProcessStartInfo + { + FileName = AppPaths.EsptoolPath, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = new Process { StartInfo = psi }; + + process.OutputDataReceived += (s, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + onOutput(e.Data); + }; + + process.ErrorDataReceived += (s, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + onOutput(e.Data); + }; + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + await process.WaitForExitAsync(cancellationToken); + + return process.ExitCode; + } +} diff --git a/Services/FirmwareService.cs b/Services/FirmwareService.cs deleted file mode 100644 index acadb22..0000000 --- a/Services/FirmwareService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using LumenLabInstaller.Infrastructure; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace LumenLabInstaller.Services -{ - public class FirmwareService - { - public async Task ReadFirmwareVersionAsync(string portName) - { - string versionTemp = Path.Combine(Path.GetTempPath(), "version_chunk.bin"); - - var psi = new ProcessStartInfo - { - FileName = AppPaths.EsptoolPath, - Arguments = $"--chip esp32 --port {portName} read_flash 0x10100 0x1000 \"{versionTemp}\"", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - process.Start(); - - await process.WaitForExitAsync(); - - if (!File.Exists(versionTemp)) - return "Unknown"; - - byte[] firmwareBytes = await File.ReadAllBytesAsync(versionTemp); - string content = Encoding.ASCII.GetString(firmwareBytes); - - var match = Regex.Match(content, @"v?\d+\.\d+\.\d+"); - return match.Success ? match.Value : "Unknown"; - } - } -} diff --git a/Services/GitHubReleaseService.cs b/Services/GitHubReleaseService.cs deleted file mode 100644 index 545c989..0000000 --- a/Services/GitHubReleaseService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using LumenLabInstaller.Configuration; -using LumenLabInstaller.Models; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace LumenLabInstaller.Services -{ - - public class GitHubReleaseService - { - private readonly HttpClient _httpClient; - - public GitHubReleaseService() - { - _httpClient = new HttpClient(); - _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LumenLabInstaller"); - _httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json"); - } - - public async Task> GetReleasesAsync() - { - using var response = await _httpClient.GetAsync(AppConstants.GithubReleasesUrl); - response.EnsureSuccessStatusCode(); - - var json = await response.Content.ReadAsStringAsync(); - - var apiReleases = JsonSerializer.Deserialize>(json); - - if (apiReleases == null) return new List(); - - var requiredAssets = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "lumenlab-firmware.zip" // can be expanded to include PS4 controllers - }; - - var filtered = apiReleases.Select(r => new GitHubRelease - { - TagName = r.TagName, - PublishedAt = r.PublishedAt, - Name = r.Name, - Assets = r.Assets?.Select(a => new GitHubAsset - { - Name = a.Name, - BrowserDownloadUrl = a.BrowserDownloadUrl - }).ToList() ?? [] - }) - .Where(release => - { - if (release.Assets.Count != 1) return false; - - var assetNames = release.Assets - .Select(a => a.Name) - .ToHashSet(StringComparer.OrdinalIgnoreCase); - - return requiredAssets.All(required => assetNames.Contains(required)); - }) - .ToList(); - - return filtered; - } - } -} diff --git a/Services/NetworkService.cs b/Services/NetworkService.cs new file mode 100644 index 0000000..fd5406f --- /dev/null +++ b/Services/NetworkService.cs @@ -0,0 +1,89 @@ +using LumenLabInstaller.Configuration; +using LumenLabInstaller.Models; +using System.Text.Json; + +namespace LumenLabInstaller.Services +{ + public class NetworkService + { + private readonly HttpClient _httpClient; + + public NetworkService(HttpClient httpClient) + { + _httpClient = httpClient; + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LumenLabInstaller"); + _httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json"); + } + + public async Task DownloadAsync(Uri uri, string destinationPath, CancellationToken cancellationToken = default) + { + using var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + + response.EnsureSuccessStatusCode(); + + var totalBytes = response.Content.Headers.ContentLength; + + await using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken); + await using var fileStream = new FileStream(destinationPath, + FileMode.Create, + FileAccess.Write, + FileShare.None, + bufferSize: 8192, + useAsync: true); + + var buffer = new byte[8192]; + long totalRead = 0; + int bytesRead; + + while ((bytesRead = await contentStream.ReadAsync(buffer, cancellationToken)) > 0) + { + await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); + totalRead += bytesRead; + } + + return new DownloadResult(destinationPath, totalRead); + } + + public async Task> GetReleasesAsync() + { + using var response = await _httpClient.GetAsync(AppConstants.GithubReleasesUrl); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + + var apiReleases = JsonSerializer.Deserialize>(json); + + if (apiReleases == null) return new List(); + + var requiredAssets = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "lumenlab-firmware.zip" // can be expanded to include PS4 controllers + }; + + var filtered = apiReleases.Select(r => new GitHubRelease + { + TagName = r.TagName, + PublishedAt = r.PublishedAt, + Name = r.Name, + Assets = r.Assets?.Select(a => new GitHubAsset + { + Name = a.Name, + BrowserDownloadUrl = a.BrowserDownloadUrl + }).ToList() ?? [] + }) + .Where(release => + { + if (release.Assets.Count != 1) return false; + + var assetNames = release.Assets + .Select(a => a.Name) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + return requiredAssets.All(required => assetNames.Contains(required)); + }) + .ToList(); + + return filtered; + } + } +} \ No newline at end of file diff --git a/Services/ToolManager.cs b/Services/ToolManager.cs deleted file mode 100644 index 5a792d4..0000000 --- a/Services/ToolManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Diagnostics; -using System.Reflection; -using LumenLabInstaller.Infrastructure; - -namespace LumenLabInstaller.Services -{ - internal static class ToolManager - { - public static void EnsureEsptoolExists() - { - if (File.Exists(AppPaths.EsptoolPath)) - return; - - Directory.CreateDirectory(AppPaths.ToolsFolder); - - var assembly = Assembly.GetExecutingAssembly(); - var resourceName = "LumenLabInstaller.Tools.esptool.exe"; - using var resourceStream = assembly.GetManifestResourceStream(resourceName) ?? throw new InvalidOperationException("Embedded esptool not found."); - - using var fileStream = new FileStream(AppPaths.EsptoolPath, FileMode.Create, FileAccess.Write); - - resourceStream.CopyTo(fileStream); - } - - public static async Task RunEsptoolAsync(string arguments, Action onOutput, CancellationToken cancellationToken) - { - var psi = new ProcessStartInfo - { - FileName = AppPaths.EsptoolPath, - Arguments = arguments, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using var process = new Process { StartInfo = psi }; - - process.OutputDataReceived += (s, e) => - { - if (!string.IsNullOrEmpty(e.Data)) - onOutput(e.Data); - }; - - process.ErrorDataReceived += (s, e) => - { - if (!string.IsNullOrEmpty(e.Data)) - onOutput(e.Data); - }; - - process.Start(); - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - await process.WaitForExitAsync(cancellationToken); - - return process.ExitCode; - } - } -} - diff --git a/assets/main-screenshot.png b/assets/main-screenshot.png new file mode 100644 index 0000000..54ed402 Binary files /dev/null and b/assets/main-screenshot.png differ diff --git a/lumenlab-installer.csproj b/lumenlab-installer.csproj index 9b653f4..6cdec59 100644 --- a/lumenlab-installer.csproj +++ b/lumenlab-installer.csproj @@ -13,7 +13,7 @@ false true true - 1.0.0 + 99.99.99 $(Version) $(Version)