diff --git a/.github/scripts/compute-semver.sh b/.github/scripts/compute-semver.sh new file mode 100644 index 0000000..7368cd7 --- /dev/null +++ b/.github/scripts/compute-semver.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -euo pipefail + +echo "Determining next SemVer..." + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN not set" + exit 1 +fi + +OWNER="${GITHUB_REPOSITORY%%/*}" +REPO="${GITHUB_REPOSITORY##*/}" +SHA="${GITHUB_SHA}" + +git fetch --tags +LATEST_TAG=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1 || true) + +if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="v0.0.0" +fi + +echo "Latest tag: $LATEST_TAG" + +VERSION="${LATEST_TAG#v}" +IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" + +PRS_JSON=$(curl -s \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/pulls") + +echo "$PRS_JSON" | jq . + +PR_COUNT=$(echo "$PRS_JSON" | jq length) + +if [ "$PR_COUNT" -eq 0 ]; then + echo "No PR associated with this commit." + exit 1 +fi + +LABELS=$(echo "$PRS_JSON" | jq -r '.[0].labels[].name' | tr '[:upper:]' '[:lower:]') + +BUMP="patch" + +COUNT=0 +for LABEL in major minor patch; do + if echo "$LABELS" | grep -qx "$LABEL"; then + BUMP="$LABEL" + COUNT=$((COUNT+1)) + fi +done + +if [ "$COUNT" -gt 1 ]; then + echo "Multiple SemVer labels applied. Use only one of: major, minor, patch." + exit 1 +fi + +echo "Version bump type: $BUMP" + +case "$BUMP" in + major) + MAJOR=$((MAJOR+1)) + MINOR=0 + PATCH=0 + ;; + minor) + MINOR=$((MINOR+1)) + PATCH=0 + ;; + patch) + PATCH=$((PATCH+1)) + ;; +esac + +NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}" + +echo "New tag: $NEW_TAG" + +echo "tag=$NEW_TAG" >> "$GITHUB_OUTPUT" diff --git a/.github/scripts/get-pr-details.sh b/.github/scripts/get-pr-details.sh new file mode 100644 index 0000000..339ac25 --- /dev/null +++ b/.github/scripts/get-pr-details.sh @@ -0,0 +1,48 @@ + +#!/usr/bin/env bash + +set -euo pipefail + +echo "Retrieving PR information..." + +if [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GITHUB_TOKEN not set" + exit 1 +fi + +OWNER="${GITHUB_REPOSITORY%%/*}" +REPO="${GITHUB_REPOSITORY##*/}" +SHA="${GITHUB_SHA}" + +PRS_JSON=$(curl -s \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/pulls") + +PR_COUNT=$(echo "$PRS_JSON" | jq length) + +if [ "$PR_COUNT" -eq 0 ]; then + echo "No PR associated with this commit." + echo "title=" >> "$GITHUB_OUTPUT" + echo "body=" >> "$GITHUB_OUTPUT" + exit 0 +fi + +TITLE=$(echo "$PRS_JSON" | jq -r '.[0].title // ""') +BODY=$(echo "$PRS_JSON" | jq -r '.[0].body // ""') + +{ + echo "title<> "$GITHUB_OUTPUT" + +{ + echo "body<> "$GITHUB_OUTPUT" + +echo "PR details captured." +echo "- Title = $TITLE." +echo "- Description = $BODY." \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..19ef314 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,29 @@ +name: CI - Build Installer + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore + run: dotnet restore + + - name: Build (Release) + run: dotnet build -c Release --no-restore \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..85861c0 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,67 @@ +name: CD - Deploy, Label, and Create Release + +on: + push: + branches: + - main + +permissions: + contents: write + +concurrency: + group: release-main + cancel-in-progress: false + +jobs: + release: + runs-on: windows-latest + + steps: + - name: Checkout (full history) + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Determine next SemVer from PR labels + id: semver + run: bash .github/scripts/compute-semver.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get release description notes from PR + id: pr_details + run: bash .github/scripts/get-pr-details.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Restore + run: dotnet restore + + - name: Publish single-file installer + run: | + dotnet publish -c Release \ + -r win-x64 \ + --self-contained true \ + -p:PublishSingleFile=true \ + -p:IncludeNativeLibrariesForSelfExtract=true \ + -p:Version=${{ steps.semver.outputs.tag }} \ + -o publish + + - name: Rename output for clean artifact name + run: | + ren publish\*.exe LumenLabInstaller-${{ steps.semver.outputs.tag }}.exe + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.semver.outputs.tag }} + name: ${{ steps.semver.outputs.tag }} - ${{ steps.pr_details.outputs.title }} + body: ${{ steps.pr_details.outputs.body }} + files: publish/LumenLabInstaller-${{ steps.semver.outputs.tag }}.exe + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9491a2f..81cdbb1 100644 --- a/.gitignore +++ b/.gitignore @@ -360,4 +360,9 @@ MigrationBackup/ .ionide/ # Fody - auto-generated XML schema -FodyWeavers.xsd \ No newline at end of file +FodyWeavers.xsd + +Firmware/ConfigurationSetup/.gitignore +Firmware/ConfigurationSetup/.pio +Firmware/ConfigurationSetup/.vscode/* +!Firmware/ConfigurationSetup/.vscode/extensions.json diff --git a/Configuration/AppConstants.cs b/Configuration/AppConstants.cs new file mode 100644 index 0000000..442c187 --- /dev/null +++ b/Configuration/AppConstants.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LumenLabInstaller.Configuration +{ + internal static class AppConstants + { + public static string GithubReleasesUrl = "https://api.github.com/repos/ericmcdaniel/lumenlab/releases"; + } +} diff --git a/Firmware/ConfigurationSetup/.vscode/extensions.json b/Firmware/ConfigurationSetup/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/Firmware/ConfigurationSetup/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/Firmware/ConfigurationSetup/platformio.ini b/Firmware/ConfigurationSetup/platformio.ini new file mode 100644 index 0000000..06b52f7 --- /dev/null +++ b/Firmware/ConfigurationSetup/platformio.ini @@ -0,0 +1,22 @@ +[platformio] +name = LumenLab Installation Tool +description = A Windows-only installation tool to easily write NVS memory preferences on demand for global reference and config for the LumenLab. + +[env] +upload_speed = 115200 +monitor_speed = 115200 + + +[env:debug] +platform = espressif32 +board = upesy_wroom +framework = arduino +build_unflags = -std=gnu++11 +build_flags = -Os -DDEBUG -std=gnu++2a + +[env:release] +platform = espressif32 +board = upesy_wroom +framework = arduino +build_unflags = -std=gnu++11 +build_flags = -Os -DRELEASE -std=gnu++2a diff --git a/Firmware/ConfigurationSetup/src/main.cpp b/Firmware/ConfigurationSetup/src/main.cpp new file mode 100644 index 0000000..77486df --- /dev/null +++ b/Firmware/ConfigurationSetup/src/main.cpp @@ -0,0 +1,221 @@ +#include +#include + +Preferences preferences; + +static const uint32_t SERIAL_BAUD = 115200; +#ifdef RELEASE +static const char *NVS_NAMESPACE = "lumenlab"; +#else +static const char *NVS_NAMESPACE = "lumenlab-dev"; +#endif + +String inputBuffer; + +void processCommand(const String &cmd); +void writeKeyValue(const String &key, const String &value); +void getKeyValue(const String &key); +void dumpAll(); +void clearAll(); +void sendOK(); +void sendError(const String &msg); +void printMenu(); + +void setup() +{ + Serial.begin(SERIAL_BAUD); + preferences.begin(NVS_NAMESPACE, false); + while (!Serial) + { + delay(10); + } + printMenu(); +} + +void loop() +{ + while (Serial.available()) + { + char c = Serial.read(); + + if (c == '\n') + { + inputBuffer.trim(); + if (inputBuffer.length() > 0) + { + processCommand(inputBuffer); + } + inputBuffer = ""; + } + else + { + inputBuffer += c; + } + } +} + +void processCommand(const String &cmd) +{ + if (cmd.startsWith("SET ")) + { + int equalsIndex = cmd.indexOf('='); + if (equalsIndex < 0) + { + sendError("INVALID_FORMAT"); + return; + } + + String key = cmd.substring(4, equalsIndex); + String value = cmd.substring(equalsIndex + 1); + + key.trim(); + value.trim(); + + writeKeyValue(key, value); + } + else if (cmd.startsWith("GET ")) + { + String key = cmd.substring(4); + key.trim(); + getKeyValue(key); + } + else if (cmd == "GETALL") + { + dumpAll(); + } + else if (cmd == "CLEAR") + { + clearAll(); + } + else if (cmd == "REBOOT") + { + sendOK(); + delay(100); + ESP.restart(); + } + else if (cmd == "HELP") + { + printMenu(); + } + else + { + sendError("UNKNOWN_COMMAND"); + } +} + +void writeKeyValue(const String &key, const String &value) +{ + if (!preferences.begin(NVS_NAMESPACE, false)) + { + sendError("NVS_BEGIN_FAILED"); + return; + } + + size_t written = preferences.putString(key.c_str(), value); + + preferences.end(); + + if (written > 0) + { + Serial.print("Wrote to NVS: "); + Serial.print(key); + Serial.print("="); + Serial.println(value); + sendOK(); + } + else + sendError("WRITE_FAILED"); +} + +void getKeyValue(const String &key) +{ + preferences.begin(NVS_NAMESPACE, true); + String value = preferences.getString(key.c_str(), "__NOT_FOUND__"); + preferences.end(); + + if (value == "__NOT_FOUND__") + { + sendError("NOT_FOUND"); + return; + } + + Serial.print(key); + Serial.print("="); + Serial.println(value); + sendOK(); +} + +void dumpAll() +{ + preferences.begin(NVS_NAMESPACE, true); + + size_t count = preferences.freeEntries(); + Serial.println("BEGIN_DUMP"); + + const char *knownKeys[] = { + "macAddress", + "numLeds", + "serialBaud", + "boundary_1", + "boundary_2", + "boundary_3"}; + + for (auto key : knownKeys) + { + if (!preferences.isKey(key)) + { + Serial.print(key); + Serial.println(" is not in memory."); + continue; + } + + String value = preferences.getString(key, ""); + if (value.length() > 0) + { + Serial.print(key); + Serial.print("="); + Serial.println(value); + } + } + + preferences.end(); + + Serial.println("END_DUMP"); + sendOK(); +} + +void clearAll() +{ + preferences.begin(NVS_NAMESPACE, false); + bool result = preferences.clear(); + preferences.end(); + + if (result) + sendOK(); + else + sendError("CLEAR_FAILED"); +} + +void sendOK() +{ + Serial.println("OK"); +} + +void sendError(const String &msg) +{ + Serial.print("ERROR:"); + Serial.println(msg); +} + +void printMenu() +{ + Serial.println(); + Serial.println("================================="); + Serial.println("LUMENLAB PROVISIONING MODE"); + char strBuff[64]; + snprintf(strBuff, sizeof(strBuff), "Namespace: %s", NVS_NAMESPACE); + Serial.println(strBuff); + Serial.println("Commands: SET, GET, GETALL, CLEAR, REBOOT, HELP"); + Serial.println("================================="); + Serial.println("READY"); +} \ No newline at end of file diff --git a/Firmware/LumenLabInstaller.code-workspace b/Firmware/LumenLabInstaller.code-workspace new file mode 100644 index 0000000..bc0d57f --- /dev/null +++ b/Firmware/LumenLabInstaller.code-workspace @@ -0,0 +1,76 @@ +{ + "folders": [ + { + "path": "ConfigurationSetup" + } + ], + "settings": { + "platformio-ide.toolbar": [ + { + "text": "$(home)", + "tooltip": "PlatformIO: Home", + "commands": [ + { + "id": "platformio-ide.showHome" + }, + ] + }, + { + "text": "$(check)", + "tooltip": "PlatformIO: Build", + "commands": [ + { + "id": "platformio-ide.build" + }, + ] + }, + { + "text": "$(terminal)", + "tooltip": "PlatformIO: Upload (Debug)", + "type": "shell", + "commands": [ + { + "id": "workbench.action.tasks.runTask", + "args": [ + "Upload and Start Debug" + ] + } + ] + }, + { + "text": "$(arrow-right)", + "tooltip": "PlatformIO: Upload (Release)", + "type": "shell", + "commands": [ + { + "id": "workbench.action.tasks.runTask", + "args": [ + "LumenLab Installer - Upload (release)" + ] + }, + ] + }, + { + "text": "$(trashcan)", + "tooltip": "PlatformIO: Clean", + "commands": [ + { + "id": "workbench.action.tasks.runTask", + "args": [ + "LumenLab Installer - Clean", + ], + }, + ] + }, + { + "text": "$(plug)", + "tooltip": "PlatformIO: Serial Monitor", + "commands": [ + { + "id": "platformio-ide.serialMonitor" + }, + ] + } + ], + } +} \ No newline at end of file diff --git a/Firmware/esptool.exe b/Firmware/esptool.exe new file mode 100644 index 0000000..b069e63 Binary files /dev/null and b/Firmware/esptool.exe differ diff --git a/Infrastructure/AppPaths.cs b/Infrastructure/AppPaths.cs new file mode 100644 index 0000000..ffc3646 --- /dev/null +++ b/Infrastructure/AppPaths.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LumenLabInstaller.Infrastructure +{ + public static class AppPaths + { + // Should go to %LocalAppData% \ LumenLab + public static string BasePath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LumenLab"); + + public static string BinariesPath => Path.Combine(BasePath, "bin"); + + public static string ToolsFolder => Path.Combine(BasePath, "tools"); + + public static string EsptoolPath => Path.Combine(ToolsFolder, "esptool.exe"); + + public static string ConfigFilePath = Path.Combine(BasePath, "config.json"); + + static AppPaths() + { + Directory.CreateDirectory(BasePath); + Directory.CreateDirectory(BinariesPath); + } + } +} diff --git a/LumenLabInstallerForm.Designer.cs b/LumenLabInstallerForm.Designer.cs new file mode 100644 index 0000000..3e09d6b --- /dev/null +++ b/LumenLabInstallerForm.Designer.cs @@ -0,0 +1,468 @@ +namespace LumenLabInstaller +{ + partial class LumenLabInstallerForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LumenLabInstallerForm)); + BtnFlashFirmware = new Button(); + pictureBox1 = new PictureBox(); + outputBox = new TextBox(); + DgvGitHubReleases = new DataGridView(); + BtnSync = new Button(); + menuStrip1 = new MenuStrip(); + fileToolStripMenuItem = new ToolStripMenuItem(); + loadConfigToolStripMenuItem = new ToolStripMenuItem(); + toolStripSeparator1 = new ToolStripSeparator(); + exitToolStripMenuItem = new ToolStripMenuItem(); + viewToolStripMenuItem = new ToolStripMenuItem(); + toggleDebugWindowToolStripMenuItem = new ToolStripMenuItem(); + helpToolStripMenuItem = new ToolStripMenuItem(); + aboutToolStripMenuItem = new ToolStripMenuItem(); + ChkCustomizeConfiguration = new CheckBox(); + LblTotalLeds = new Label(); + LblMacAddress = new Label(); + TxtMacAddress = new TextBox(); + LblSerialBaud = new Label(); + LblBoundaries = new Label(); + TxtBound1 = new NumericUpDown(); + TxtBound2 = new NumericUpDown(); + TxtBound3 = new NumericUpDown(); + TxtSerialBaud = new NumericUpDown(); + TxtNumLeds = new NumericUpDown(); + contextMenuStrip1 = new ContextMenuStrip(components); + groupBox1 = new GroupBox(); + LblInstalledVersion = new RichTextBox(); + LblLatestRelease = new RichTextBox(); + label1 = new Label(); + label2 = new Label(); + ((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit(); + ((System.ComponentModel.ISupportInitialize)DgvGitHubReleases).BeginInit(); + menuStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)TxtBound1).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TxtBound2).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TxtBound3).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TxtSerialBaud).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TxtNumLeds).BeginInit(); + groupBox1.SuspendLayout(); + SuspendLayout(); + // + // BtnFlashFirmware + // + BtnFlashFirmware.Cursor = Cursors.Hand; + BtnFlashFirmware.Enabled = false; + BtnFlashFirmware.FlatStyle = FlatStyle.System; + BtnFlashFirmware.ForeColor = SystemColors.ActiveCaptionText; + BtnFlashFirmware.Location = new Point(691, 303); + BtnFlashFirmware.Name = "BtnFlashFirmware"; + BtnFlashFirmware.Size = new Size(207, 26); + BtnFlashFirmware.TabIndex = 0; + BtnFlashFirmware.Text = "Install Latest Release"; + BtnFlashFirmware.UseVisualStyleBackColor = true; + BtnFlashFirmware.Click += BtnFlashFirmware_Click; + // + // pictureBox1 + // + pictureBox1.BackColor = Color.Transparent; + pictureBox1.BackgroundImageLayout = ImageLayout.Stretch; + pictureBox1.Image = (Image)resources.GetObject("pictureBox1.Image"); + pictureBox1.Location = new Point(72, 51); + pictureBox1.Name = "pictureBox1"; + pictureBox1.Size = new Size(277, 75); + pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; + pictureBox1.TabIndex = 1; + pictureBox1.TabStop = false; + // + // outputBox + // + outputBox.BackColor = SystemColors.ControlDarkDark; + outputBox.BorderStyle = BorderStyle.None; + outputBox.Cursor = Cursors.IBeam; + outputBox.Dock = DockStyle.Bottom; + outputBox.Font = new Font("Courier New", 9F, FontStyle.Regular, GraphicsUnit.Point, 0); + outputBox.ForeColor = SystemColors.Window; + outputBox.Location = new Point(0, 366); + outputBox.Multiline = true; + outputBox.Name = "outputBox"; + outputBox.ReadOnly = true; + outputBox.ScrollBars = ScrollBars.Both; + outputBox.Size = new Size(944, 200); + outputBox.TabIndex = 2; + outputBox.WordWrap = false; + // + // DgvGitHubReleases + // + DgvGitHubReleases.BackgroundColor = SystemColors.ControlDarkDark; + DgvGitHubReleases.BorderStyle = BorderStyle.None; + DgvGitHubReleases.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + DgvGitHubReleases.Location = new Point(466, 137); + DgvGitHubReleases.Name = "DgvGitHubReleases"; + DgvGitHubReleases.Size = new Size(432, 150); + DgvGitHubReleases.TabIndex = 3; + // + // BtnSync + // + BtnSync.Enabled = false; + BtnSync.Location = new Point(767, 78); + BtnSync.Name = "BtnSync"; + BtnSync.Size = new Size(131, 48); + BtnSync.TabIndex = 4; + BtnSync.Text = "Refresh Device List"; + BtnSync.UseVisualStyleBackColor = true; + BtnSync.Click += BtnSync_Click; + // + // menuStrip1 + // + menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, viewToolStripMenuItem, helpToolStripMenuItem }); + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(944, 24); + menuStrip1.TabIndex = 7; + menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { loadConfigToolStripMenuItem, toolStripSeparator1, exitToolStripMenuItem }); + fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + fileToolStripMenuItem.Size = new Size(37, 20); + fileToolStripMenuItem.Text = "File"; + // + // loadConfigToolStripMenuItem + // + loadConfigToolStripMenuItem.Name = "loadConfigToolStripMenuItem"; + loadConfigToolStripMenuItem.Size = new Size(139, 22); + loadConfigToolStripMenuItem.Text = "Load Config"; + // + // toolStripSeparator1 + // + toolStripSeparator1.Name = "toolStripSeparator1"; + toolStripSeparator1.Size = new Size(136, 6); + // + // exitToolStripMenuItem + // + exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + exitToolStripMenuItem.Size = new Size(139, 22); + exitToolStripMenuItem.Text = "Exit"; + exitToolStripMenuItem.Click += exitToolStripMenuItem_Click; + // + // viewToolStripMenuItem + // + viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { toggleDebugWindowToolStripMenuItem }); + viewToolStripMenuItem.Name = "viewToolStripMenuItem"; + viewToolStripMenuItem.Size = new Size(44, 20); + viewToolStripMenuItem.Text = "View"; + // + // toggleDebugWindowToolStripMenuItem + // + toggleDebugWindowToolStripMenuItem.Checked = true; + toggleDebugWindowToolStripMenuItem.CheckState = CheckState.Checked; + toggleDebugWindowToolStripMenuItem.Name = "toggleDebugWindowToolStripMenuItem"; + toggleDebugWindowToolStripMenuItem.Size = new Size(143, 22); + toggleDebugWindowToolStripMenuItem.Text = "Verbose Logs"; + toggleDebugWindowToolStripMenuItem.Click += toggleDebugWindowToolStripMenuItem_Click; + // + // helpToolStripMenuItem + // + helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { aboutToolStripMenuItem }); + helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + helpToolStripMenuItem.Size = new Size(44, 20); + helpToolStripMenuItem.Text = "Help"; + // + // aboutToolStripMenuItem + // + aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + aboutToolStripMenuItem.Size = new Size(107, 22); + aboutToolStripMenuItem.Text = "About"; + // + // ChkCustomizeConfiguration + // + ChkCustomizeConfiguration.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + ChkCustomizeConfiguration.AutoSize = true; + ChkCustomizeConfiguration.Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point, 0); + ChkCustomizeConfiguration.Location = new Point(97, -11); + ChkCustomizeConfiguration.Margin = new Padding(10); + ChkCustomizeConfiguration.Name = "ChkCustomizeConfiguration"; + ChkCustomizeConfiguration.Padding = new Padding(10); + ChkCustomizeConfiguration.Size = new Size(190, 41); + ChkCustomizeConfiguration.TabIndex = 9; + ChkCustomizeConfiguration.Text = "Customize Configuration"; + ChkCustomizeConfiguration.TextAlign = ContentAlignment.MiddleCenter; + ChkCustomizeConfiguration.UseVisualStyleBackColor = true; + ChkCustomizeConfiguration.CheckedChanged += ChkCustomizeConfiguration_CheckedChanged; + // + // LblTotalLeds + // + LblTotalLeds.AutoSize = true; + LblTotalLeds.Enabled = false; + LblTotalLeds.Location = new Point(40, 40); + LblTotalLeds.Name = "LblTotalLeds"; + LblTotalLeds.Size = new Size(64, 15); + LblTotalLeds.TabIndex = 11; + LblTotalLeds.Text = "Total LEDs:"; + // + // LblMacAddress + // + LblMacAddress.AutoSize = true; + LblMacAddress.Enabled = false; + LblMacAddress.Location = new Point(40, 71); + LblMacAddress.Name = "LblMacAddress"; + LblMacAddress.Size = new Size(158, 15); + LblMacAddress.TabIndex = 12; + LblMacAddress.Text = "PS3 controller MAC Address:"; + // + // TxtMacAddress + // + TxtMacAddress.Enabled = false; + TxtMacAddress.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + TxtMacAddress.Location = new Point(201, 67); + TxtMacAddress.Name = "TxtMacAddress"; + TxtMacAddress.PlaceholderText = "00:00:00:00:00"; + TxtMacAddress.Size = new Size(130, 25); + TxtMacAddress.TabIndex = 13; + TxtMacAddress.Text = "00:00:00:00:00"; + // + // LblSerialBaud + // + LblSerialBaud.AutoSize = true; + LblSerialBaud.Enabled = false; + LblSerialBaud.Location = new Point(40, 102); + LblSerialBaud.Name = "LblSerialBaud"; + LblSerialBaud.Size = new Size(68, 15); + LblSerialBaud.TabIndex = 14; + LblSerialBaud.Text = "Serial Baud:"; + // + // LblBoundaries + // + LblBoundaries.AutoSize = true; + LblBoundaries.Enabled = false; + LblBoundaries.Location = new Point(40, 132); + LblBoundaries.Name = "LblBoundaries"; + LblBoundaries.Size = new Size(69, 15); + LblBoundaries.TabIndex = 16; + LblBoundaries.Text = "Boundaries:"; + // + // TxtBound1 + // + TxtBound1.Enabled = false; + TxtBound1.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + TxtBound1.Location = new Point(125, 128); + TxtBound1.Maximum = new decimal(new int[] { 300, 0, 0, 0 }); + TxtBound1.Name = "TxtBound1"; + TxtBound1.Size = new Size(65, 25); + TxtBound1.TabIndex = 17; + TxtBound1.Value = new decimal(new int[] { 75, 0, 0, 0 }); + // + // TxtBound2 + // + TxtBound2.Enabled = false; + TxtBound2.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + TxtBound2.Location = new Point(195, 128); + TxtBound2.Maximum = new decimal(new int[] { 300, 0, 0, 0 }); + TxtBound2.Name = "TxtBound2"; + TxtBound2.Size = new Size(65, 25); + TxtBound2.TabIndex = 18; + TxtBound2.Value = new decimal(new int[] { 150, 0, 0, 0 }); + // + // TxtBound3 + // + TxtBound3.Enabled = false; + TxtBound3.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + TxtBound3.Location = new Point(266, 128); + TxtBound3.Maximum = new decimal(new int[] { 300, 0, 0, 0 }); + TxtBound3.Name = "TxtBound3"; + TxtBound3.Size = new Size(65, 25); + TxtBound3.TabIndex = 19; + TxtBound3.Value = new decimal(new int[] { 225, 0, 0, 0 }); + // + // TxtSerialBaud + // + TxtSerialBaud.Enabled = false; + TxtSerialBaud.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + TxtSerialBaud.Increment = new decimal(new int[] { 100, 0, 0, 0 }); + TxtSerialBaud.Location = new Point(201, 97); + TxtSerialBaud.Maximum = new decimal(new int[] { 1000000, 0, 0, 0 }); + TxtSerialBaud.Name = "TxtSerialBaud"; + TxtSerialBaud.Size = new Size(130, 25); + TxtSerialBaud.TabIndex = 20; + TxtSerialBaud.Value = new decimal(new int[] { 921600, 0, 0, 0 }); + // + // TxtNumLeds + // + TxtNumLeds.Enabled = false; + TxtNumLeds.Font = new Font("Consolas", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + TxtNumLeds.Location = new Point(201, 36); + TxtNumLeds.Maximum = new decimal(new int[] { 3000, 0, 0, 0 }); + TxtNumLeds.Name = "TxtNumLeds"; + TxtNumLeds.Size = new Size(130, 25); + TxtNumLeds.TabIndex = 21; + TxtNumLeds.Value = new decimal(new int[] { 300, 0, 0, 0 }); + // + // contextMenuStrip1 + // + contextMenuStrip1.Name = "contextMenuStrip1"; + contextMenuStrip1.Size = new Size(61, 4); + // + // groupBox1 + // + groupBox1.Controls.Add(ChkCustomizeConfiguration); + groupBox1.Controls.Add(LblTotalLeds); + groupBox1.Controls.Add(TxtNumLeds); + groupBox1.Controls.Add(LblMacAddress); + groupBox1.Controls.Add(TxtSerialBaud); + groupBox1.Controls.Add(TxtMacAddress); + groupBox1.Controls.Add(TxtBound3); + groupBox1.Controls.Add(LblSerialBaud); + groupBox1.Controls.Add(TxtBound2); + groupBox1.Controls.Add(LblBoundaries); + groupBox1.Controls.Add(TxtBound1); + groupBox1.Location = new Point(29, 150); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new Size(368, 181); + groupBox1.TabIndex = 23; + groupBox1.TabStop = false; + // + // LblInstalledVersion + // + LblInstalledVersion.BackColor = SystemColors.ControlDark; + LblInstalledVersion.BorderStyle = BorderStyle.None; + LblInstalledVersion.Font = new Font("Segoe UI", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + LblInstalledVersion.Location = new Point(600, 81); + LblInstalledVersion.Name = "LblInstalledVersion"; + LblInstalledVersion.Size = new Size(142, 21); + LblInstalledVersion.TabIndex = 25; + LblInstalledVersion.Text = "N/A"; + // + // LblLatestRelease + // + LblLatestRelease.BackColor = SystemColors.ControlDark; + LblLatestRelease.BorderStyle = BorderStyle.None; + LblLatestRelease.Font = new Font("Segoe UI", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + LblLatestRelease.Location = new Point(600, 105); + LblLatestRelease.Name = "LblLatestRelease"; + LblLatestRelease.Size = new Size(142, 21); + LblLatestRelease.TabIndex = 26; + LblLatestRelease.Text = "N/A"; + // + // label1 + // + label1.AutoSize = true; + label1.Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point, 0); + label1.Location = new Point(492, 83); + label1.Margin = new Padding(0); + label1.Name = "label1"; + label1.Size = new Size(106, 17); + label1.TabIndex = 27; + label1.Text = "Current Installed:"; + // + // label2 + // + label2.AutoSize = true; + label2.Font = new Font("Segoe UI", 9.75F, FontStyle.Regular, GraphicsUnit.Point, 0); + label2.Location = new Point(497, 107); + label2.Margin = new Padding(0); + label2.Name = "label2"; + label2.Size = new Size(101, 17); + label2.TabIndex = 28; + label2.Text = "Latest Available:"; + // + // LumenLabInstallerForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + BackColor = SystemColors.ControlDark; + ClientSize = new Size(944, 566); + Controls.Add(label2); + Controls.Add(label1); + Controls.Add(LblLatestRelease); + Controls.Add(LblInstalledVersion); + Controls.Add(groupBox1); + Controls.Add(BtnSync); + Controls.Add(DgvGitHubReleases); + Controls.Add(outputBox); + Controls.Add(pictureBox1); + Controls.Add(BtnFlashFirmware); + Controls.Add(menuStrip1); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = (Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = menuStrip1; + MaximizeBox = false; + Name = "LumenLabInstallerForm"; + StartPosition = FormStartPosition.CenterScreen; + Text = "LumenLab Installer"; + Load += LumenLabInstallerForm_Shown; + ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit(); + ((System.ComponentModel.ISupportInitialize)DgvGitHubReleases).EndInit(); + menuStrip1.ResumeLayout(false); + menuStrip1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)TxtBound1).EndInit(); + ((System.ComponentModel.ISupportInitialize)TxtBound2).EndInit(); + ((System.ComponentModel.ISupportInitialize)TxtBound3).EndInit(); + ((System.ComponentModel.ISupportInitialize)TxtSerialBaud).EndInit(); + ((System.ComponentModel.ISupportInitialize)TxtNumLeds).EndInit(); + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private Button BtnFlashFirmware; + private PictureBox pictureBox1; + private TextBox outputBox; + private DataGridView DgvGitHubReleases; + private Button BtnSync; + private MenuStrip menuStrip1; + private ToolStripMenuItem fileToolStripMenuItem; + private ToolStripMenuItem exitToolStripMenuItem; + private ToolStripMenuItem helpToolStripMenuItem; + private ToolStripMenuItem aboutToolStripMenuItem; + private Label LblCustomizeConfig; + private CheckBox ChkCustomizeConfiguration; + private Label LblTotalLeds; + private Label LblMacAddress; + private TextBox TxtMacAddress; + private Label LblSerialBaud; + private Label LblBoundaries; + private NumericUpDown TxtBound1; + private NumericUpDown TxtBound2; + private NumericUpDown TxtBound3; + private NumericUpDown TxtSerialBaud; + private NumericUpDown TxtNumLeds; + private ContextMenuStrip contextMenuStrip1; + private GroupBox groupBox1; + private ToolStripMenuItem viewToolStripMenuItem; + private ToolStripMenuItem toggleDebugWindowToolStripMenuItem; + private RichTextBox LblInstalledVersion; + private RichTextBox LblLatestRelease; + private ToolStripSeparator toolStripSeparator1; + private ToolStripMenuItem loadConfigToolStripMenuItem; + private Label label1; + private Label label2; + } +} diff --git a/LumenLabInstallerForm.cs b/LumenLabInstallerForm.cs new file mode 100644 index 0000000..1fc83d7 --- /dev/null +++ b/LumenLabInstallerForm.cs @@ -0,0 +1,273 @@ +using System; +using System.IO.Ports; +using System.Management; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using Humanizer; +using LumenLabInstaller.Infrastructure; +using LumenLabInstaller.Services; +using LumenLabInstaller.Models; +using System.Reflection; + + +namespace LumenLabInstaller +{ + public partial class LumenLabInstallerForm : Form + { + private readonly InstallerContext _context; + private readonly DeviceDiscoveryService _deviceService; + private readonly FirmwareService _firmwareService; + private readonly BinaryDownloadService _downloadService; + + private List _availableReleases = new(); + private BindingSource _releaseBindingSource = new(); + + + public LumenLabInstallerForm(BinaryDownloadService downloadService, + InstallerContext context, + FirmwareService firmwareService, + DeviceDiscoveryService deviceService) + { + _downloadService = downloadService; + _context = context; + _firmwareService = firmwareService; + _deviceService = deviceService; + InitializeComponent(); + ConfigureReleaseGrid(); + } + + 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(); + + DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn + { + DataPropertyName = "TagName", + HeaderText = "Version", + Width = 60 + }); + + DgvGitHubReleases.Columns.Add(new DataGridViewTextBoxColumn + { + DataPropertyName = "Name", + HeaderText = "Release Name", + Width = 230 + }); + + 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(); + await ReadDevice(); + } + catch (Exception ex) + { + MessageBox.Show($"Error retrieving releases:{Environment.NewLine}{ex.Message}", + "GitHub API Error", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + } + + private async Task QueryGitHub() + { + var releaseService = new GitHubReleaseService(); + var releases = await releaseService.GetReleasesAsync(); + + _availableReleases = releases; + + var gridRows = releases + .OrderByDescending(r => r.PublishedAt) + .Select(r => new ReleaseGridRow + { + TagName = r.TagName, + Name = r.Name.Substring(r.Name.IndexOf("-") + 2), + PublishedAt = r.PublishedAt, + Release = r + }) + .ToList(); + + _releaseBindingSource.DataSource = gridRows; + + LblLatestRelease.Text = releases[0].TagName; + + foreach (var release in releases) + { + outputBox.AppendText($"{release.TagName} - {release.PublishedAt}{Environment.NewLine}"); + foreach (var asset in release.Assets) + { + outputBox.AppendText($" Asset: {asset.Name} -> {asset.BrowserDownloadUrl}{Environment.NewLine}"); + } + } + + // PopulateReleaseTable(releases); + } + + private async Task ReadDevice() + { + BtnSync.Enabled = false; + this.UseWaitCursor = true; + BtnFlashFirmware.Enabled = false; + LblInstalledVersion.Text = "N/A"; + + outputBox.AppendText($"Scanning USB ports to find LumenLab.{Environment.NewLine}"); + + string? port = await _deviceService.DetectLumenLabPortAsync(); + _context.PortName = port; + + if (port == null) + { + outputBox.AppendText("LumenLab not detected." + Environment.NewLine); + } + else + { + outputBox.AppendText($"LumenLab detected on {port}" + Environment.NewLine); + + string version = await _firmwareService.ReadFirmwareVersionAsync(port); + _context.FirmwareVersion = new Version(version.Substring(1)); + + LblInstalledVersion.Text = $"v{_context.FirmwareVersion.ToString()}"; + + outputBox.AppendText($"{version}{Environment.NewLine}"); + BtnFlashFirmware.Enabled = true; + } + + outputBox.AppendText($"Scan complete.{Environment.NewLine}"); + BtnSync.Enabled = true; + this.UseWaitCursor = false; + } + + private void AppendLog(string text) + { + if (InvokeRequired) + { + Invoke(new Action(AppendLog), text); + return; + } + + outputBox.AppendText(text + Environment.NewLine); + } + + private async void BtnFlashFirmware_Click(object sender, EventArgs e) + { + BtnFlashFirmware.Enabled = false; + var selectedRelease = GetSelectedRelease(); + + if (selectedRelease == null) + { + MessageBox.Show("Please select a version to install."); + return; + } + + var selectedReleaseVersionId = new Version(selectedRelease.TagName.Substring(1)); + + if (selectedReleaseVersionId < _context.FirmwareVersion) + { + var result = 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); + MessageBox.Show($"You selected {result}"); + } + + MessageBox.Show(selectedRelease.TagName); + MessageBox.Show(selectedRelease.Assets[0].Name); + + BtnFlashFirmware.Enabled = true; + + //btnDownload.Enabled = false; + + //try + //{ + // var cts = new CancellationTokenSource(); + // outputBox.AppendText("Downloading LumenLab v0.3.0." + Environment.NewLine); + // 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") + // ); + + // outputBox.AppendText("Download complete." + Environment.NewLine); + // outputBox.AppendText($"Downloaded {result.BytesWritten.Bytes()}." + Environment.NewLine); + // 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) + { + await ReadDevice(); + } + + private GitHubRelease? GetSelectedRelease() + { + if (_releaseBindingSource.Current is ReleaseGridRow row) + return row.Release; + + return null; + } + + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + Application.Exit(); + } + + private void ChkCustomizeConfiguration_CheckedChanged(object sender, EventArgs e) + { + TxtNumLeds.Enabled = !TxtNumLeds.Enabled; + LblTotalLeds.Enabled = !LblTotalLeds.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) + { + this.Height += outputBox.Height; + } + else + { + this.Height -= outputBox.Height; + } + } + } +} diff --git a/LumenLabInstallerForm.resx b/LumenLabInstallerForm.resx new file mode 100644 index 0000000..9915450 --- /dev/null +++ b/LumenLabInstallerForm.resx @@ -0,0 +1,791 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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 + + + + 17, 17 + + + 132, 17 + + + 30 + + + + AAABAAMAEBAAAAEAIABoBAAANgAAACAgAAABACAAKBEAAJ4EAAAwMAAAAQAgAGgmAADGFQAAKAAAABAA + AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZGRmGIyMj7jU1Nf81NTX/NTU1/zU1 + Nf81NTX/NTU1/zU1Nf81NTX/NTU1/zU1Nf8kJCTwGhoaigAAAAUZGRmEbGxs//T09P/19fX/29vb//// + /////////////////////////v7+/9bW1v/29vb/9fX1/3Fxcf8aGhqKISEh6vPz8//4+Pj/PDw8/xoa + Gv90dHT/9/f3//j4+P/4+Pj/9vb2/29vb/8aGhr/TExM//7+/v/19fX/JCQk8C8vL///////39/f/xoa + Gv8aGhr/Kysr/xwcHP8cHBz/HBwc/xwcHP8aGhr/Ghoa/xoaGv/o6Oj//////zU1Nf8vLy////////f3 + 9/8gICD/U1NT/76+vv8+Pj7/Ghoa/xoaGv8zMzP/s7Oz/ywsLP8qKir/+/v7//////81NTX/Ly8v//// + ////////WFhY/yYmJv+BgYH/ISEh/xoaGv8aGhr/JiYm/319ff8iIiL/bW1t////////////NTU1/y8v + L////////////8LCwv8jIyP/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/KCgo/9PT0////////////zU1 + Nf8vLy//////////////////+Pj4//Hx8f/u7u7/kpKS/4+Pj//x8fH/8fHx//n5+f////////////// + //81NTX/Ly8v//////////////////////+kpKT/T1lb/zdER/82Q0b/UVla/8PDw/////////////// + ////////NTU1/y8vL///////////////////////q6ur/wagx/8AzP//AMz//wqBn//T09P///////// + /////////////zU1Nf8vLy///////////////////////6ysrP8GoMf/AMz//wDM//8KgZ//09PT//// + //////////////////81NTX/Ly8v//////////////////////+1tbX/OaG7/0bY/f8AzP//DHqV/9zc + 3P//////////////////////NTU1/y8vL///////////////////////7u7u/ylda/9o2PT/Ar3s/z9f + Z//9/f3//////////////////////zU1Nf8gICDp8fHx///////////////////////T09P/aHN2/3J6 + e//l5eX///////////////////////T09P8jIyPuGhoagGhoaP/x8fH///////////////////////// + //////////////////////////////Pz8/9sbGz/GRkZhgAAAAMaGhqAICAg6S8vL/8vLy//Ly8v/y8v + L/8vLy//Ly8v/y8vL/8vLy//Ly8v/y8vL/8hISHqGRkZhAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAgAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxkZGVwaGhrDGhoa+BoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa+hoaGscaGhpjAAAABQAAAAAAAAAAAAAAABEREQ8ZGRm5Ghoa/xwcHP88PDz/UFBQ/1BQ + UP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQ + UP9QUFD/UFBQ/1BQUP89PT3/HR0d/xoaGv8ZGRnAGxsbEwAAAAAAAAADGRkZthoaGv9RUVH/1tbW//7+ + /v////////////////////////////////////////////////////////////////////////////// + //////////////////////////////7+/v/a2tr/WFhY/xoaGv8ZGRnAAAAABRoaGlcaGhr/UFBQ//f3 + 9//////////////////Z2dn/np6e/8/Pz//+/v7///////////////////////////////////////// + //////////////39/f++vr7/nZ2d/9vb2//////////////////5+fn/WFhY/xoaGv8aGhpjGhoauhsb + G//Q0ND/////////////////mJiY/x4eHv8aGhr/Gxsb/2dnZ//19fX///////////////////////// + ///////////////////y8vL/X19f/xoaGv8aGhr/IiIi/729vf/////////////////a2tr/HR0d/xoa + GscaGhrwMzMz//39/f///////////+Xl5f8gICD/Ghoa/xoaGv8aGhr/Ghoa/1tbW//t7e3/8fHx//Hx + 8f/x8fH/8fHx//Hx8f/x8fH/6urq/1NTU/8aGhr/Ghoa/xoaGv8aGhr/Nzc3//r6+v////////////7+ + /v89PT3/Ghoa+hoaGv9ERET/////////////////vLy8/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/x0d + Hf8eHh7/Hh4e/x4eHv8eHh7/Hh4e/x4eHv8dHR3/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/1dXV//// + /////////////1BQUP8aGhr/Ghoa/0RERP/////////////////BwcH/Ghoa/xoaGv8aGhr/Ghoa/0VF + Rf8zMzP/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv/Ozs7/////////////////UFBQ/xoaGv8aGhr/RERE/////////////////+Hh4f8bGxv/Ghoa/xsb + G/8wMDD/vr6+/4eHh/8oKCj/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/KCgo/5KSkv+JiYn/ISEh/xoa + Gv8aGhr/ISEh//Hx8f////////////////9QUFD/Ghoa/xoaGv9ERET//////////////////Pz8/zMz + M/8aGhr/KSkp/9jY2P/Z2dn/2dnZ/5ubm/8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv9ycnL/2dnZ/9nZ + 2f9bW1v/Ghoa/xoaGv9UVFT//////////////////////1BQUP8aGhr/Ghoa/0RERP////////////// + ////////cnJy/xoaGv8dHR3/R0dH/8LCwv+Tk5P/ODg4/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/0pK + Sv/T09P/zc3N/zk5Of8aGhr/Ghoa/5ubm///////////////////////UFBQ/xoaGv8aGhr/RERE//// + //////////////////+6urr/Ghoa/xoaGv8aGhr/ZmZm/0hISP8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/ywsLP8oKCj/Ghoa/xoaGv8dHR3/4eHh//////////////////////9QUFD/Ghoa/xoa + Gv9ERET///////////////////////n5+f88PDz/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/2FhYf///////////////////////////1BQ + UP8aGhr/Ghoa/0RERP///////////////////////////9bW1v89PT3/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xsbG/9TU1P/7Ozs//////////////////// + ////////UFBQ/xoaGv8aGhr/RERE//////////////////////////////////v7+//m5ub/4+Pj/+Pj + 4//j4+P/3d3d/zU1Nf/h4eH/jo6O/4GBgf/j4+P/4+Pj/+Pj4//j4+P/6urq//7+/v////////////// + //////////////////9QUFD/Ghoa/xoaGv9ERET///////////////////////////////////////// + ///////////////////4+Pj/ODg4//z8/P+enp7/j4+P//////////////////////////////////// + /////////////////////////////1BQUP8aGhr/Ghoa/0RERP////////////////////////////// + //////////////T09P+RkZH/ioqK/4aGhv8pKSn/iIiI/1tbW/9TU1P/ioqK/4uLi/+wsLD///////// + ////////////////////////////////////////UFBQ/xoaGv8aGhr/RERE//////////////////// + ////////////////////////5ubm/yUlJf8YJSj/FjA2/xYwNv8WMDb/FjA2/xYwNv8WMDb/GR4f/15e + Xv////////////////////////////////////////////////9QUFD/Ghoa/xoaGv9ERET///////// + ////////////////////////////////////////WFhY/wx1j/8AzP//AMz//wDM//8AzP//AMz//wDL + /v8VOEH/p6en/////////////////////////////////////////////////1BQUP8aGhr/Ghoa/0RE + RP////////////////////////////////////////////////9YWFj/DHWP/wDM//8AzP//AMz//wDM + //8AzP//AMv+/xU4Qf+np6f/////////////////////////////////////////////////UFBQ/xoa + Gv8aGhr/RERE/////////////////////////////////////////////////1hYWP8MdY//AMz//wDM + //8AzP//AMz//wDM//8Ay/7/FThB/6enp/////////////////////////////////////////////// + //9QUFD/Ghoa/xoaGv9ERET/////////////////////////////////////////////////WVlZ/wx1 + j/8AzP//AMz//wDM//8AzP//AMz//wDL/v8VOEH/p6en//////////////////////////////////// + /////////////1BQUP8aGhr/Ghoa/0RERP////////////////////////////////////////////// + //9dXV3/DXOM/zPV/f8s1P7/AMz//wDM//8AzP//AMv+/xY2Pv+srKz///////////////////////// + ////////////////////////UFBQ/xoaGv8aGhr/RERE//////////////////////////////////// + /////////////3h4eP8RWGr/lOb7/9vz+f8Sz/7/AMz//wDM//8BxPX/GSMl/8fHx/////////////// + //////////////////////////////////9QUFD/Ghoa/xoaGv9ERET///////////////////////// + ////////////////////////vr6+/xkjJf8bu+P/4vX5/3vi/P8AzP//AMz//wuCoP8wMDD/9/f3//// + /////////////////////////////////////////////1BQUP8aGhr/Ghoa/kRERP////////////// + ///////////////////////////////////9/f3/XFxc/xU8R/8qu9//GdD+/wDL/f8Ik7b/GSIl/6io + qP//////////////////////////////////////////////////////UFBQ/xoaGv8aGhruMTEx//39 + /f/////////////////////////////////////////////////x8fH/YGBg/xofIP8VPEb/FjI5/yMk + JP+ZmZn//v7+//////////////////////////////////////////////////7+/v88PDz/Ghoa+BkZ + GbcbGxv/y8vL///////////////////////////////////////////////////////+/v7/0NDQ/6Gh + of+pqan/6Ojo////////////////////////////////////////////////////////////1tbW/xwc + HP8aGhrDGRkZURoaGv5JSUn/9fX1//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////f3 + 9/9RUVH/Ghoa/xkZGVwAAAACGRkZrRoaGv9JSUn/y8vL//39/f////////////////////////////// + //////////////////////////////////////////////////////////////////////////////39 + /f/Q0ND/UFBQ/xoaGv8ZGRm5AAAAAwAAAAAVFRUMGRkZrRoaGv4bGxv/MTEx/0RERP9ERET/RERE/0RE + RP9ERET/RERE/0RERP9ERET/RERE/0RERP9ERET/RERE/0RERP9ERET/RERE/0RERP9ERET/RERE/0RE + RP9ERET/MzMz/xsbG/8aGhr/GRkZthEREQ8AAAAAAAAAAAAAAAAAAAACGRkZURkZGbcaGhruGhoa/hoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhrwGhoauhoaGlcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAADAA + AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXFxcLGhoaMRoa + GogZGRnTGhoa+xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGvwaGhrYGRkZjhgYGDYUFBQNAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsb + GyYaGhqUGhoa6RoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGu4aGhqcHR0dLAAAAAAAAAAAAAAAAAAA + AAAAAAAAGBgYKhoaGtEZGRn7Ghoa/xsbG/8wMDD/Xl5e/3Nzc/91dXX/dXV1/3V1df91dXX/dXV1/3V1 + df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1 + df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dHR0/2BgYP80NDT/HBwc/xoaGv8aGhr8Ghoa1x0d + HTQAAAAAAAAAAAAAAAAVFRUkGhoazxoaGv8dHR3/Ojo6/6Ghof/w8PD///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////y8vL/qamp/0BA + QP8eHh7/Ghoa/xoaGtcdHR0sAAAAARoaGgoZGRmPGhoa+h0dHf9cXFz/19fX//////////////////// + //////////////39/f/+/v7///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////9/f3//f39//////////////////// + /////////////93d3f9iYmL/Hh4e/xoaGvwaGhqcFBQUDRgYGCsaGhrmGhoa/zk5Of/V1dX//v7+//// + ///////////////////z8/P/pqam/2FhYf90dHT/ycnJ//39/f////////////////////////////// + ////////////////////////////////////////////////////////+/v7/62trf9kZGT/YWFh/6io + qP/29vb///////////////////////7+/v/d3d3/QEBA/xoaGv8aGhruGBgYNhkZGXkaGhr+Gxsb/5iY + mP///////////////////////////97e3v9ZWVn/HBwc/xoaGv8aGhr/HR0d/2hoaP/g4OD//v7+//// + //////////////////////////////////////////////////////////////7+/v/b29v/X19f/xsb + G/8aGhr/Ghoa/x4eHv92dnb/8PDw////////////////////////////qamp/xwcHP8aGhr/GRkZjhkZ + GcMaGhr/KSkp/+np6f//////////////////////9/f3/19fX/8bGxv/Ghoa/xoaGv8aGhr/Ghoa/x0d + Hf9aWlr/3d3d/////////////////////////////////////////////////////////////////9bW + 1v9SUlL/HBwc/xoaGv8aGhr/Ghoa/xoaGv8iIiL/mpqa////////////////////////////8vLy/zQ0 + NP8aGhr/Ghoa2BoaGu8aGhr/Tk5O//7+/v//////////////////////wsLC/x0dHf8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8dHR3/T09P/93d3f/p6en/6enp/+np6f/p6en/6enp/+np6f/p6en/6enp/+np + 6f/p6en/1dXV/0VFRf8cHBz/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/NTU1//Pz8/////////////// + /////////////2BgYP8aGhr/Ghoa/BoaGv4aGhr/YGBg////////////////////////////lJSU/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/x8fH/8hISH/ISEh/yEhIf8hISH/ISEh/yEh + If8hISH/ISEh/yEhIf8hISH/Hx8f/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/8TE + xP///////////////////////////3R0dP8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// + ////////jIyM/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/6enp////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// + ////////////////////////nZ2d/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/1ZWVv9ubm7/Kioq/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/7S0tP///////////////////////////3V1df8aGhr/Ghoa/xoa + Gv8aGhr/YWFh////////////////////////////wsLC/xoaGv8aGhr/Ghoa/xoaGv8eHh7/IiIi/6io + qP/X19f/SEhI/yEhIf8bGxv/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/IiIi/1hY + WP96enr/TExM/x4eHv8aGhr/Ghoa/xoaGv8aGhr/Hx8f/+Dg4P///////////////////////////3V1 + df8aGhr/Ghoa/xoaGv8aGhr/YWFh////////////////////////////6urq/yYmJv8aGhr/Ghoa/x0d + Hf9cXFz/kJCQ/8bGxv/Y2Nj/n5+f/4KCgv8tLS3/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8bGxv/c3Nz/9TU1P/Z2dn/zs7O/1VVVf8aGhr/Ghoa/xoaGv8aGhr/R0dH//r6+v////////////// + /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh/////////////////////////////f39/1JS + Uv8aGhr/Ghoa/x8fH/+IiIj/2dnZ/9nZ2f/Z2dn/2dnZ/8TExP88PDz/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8fHx//r6+v/9nZ2f/Z2dn/2dnZ/46Ojv8aGhr/Ghoa/xoaGv8aGhr/j4+P//// + /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// + /////////////5iYmP8bGxv/Ghoa/xwcHP9ERET/ZmZm/7u7u//X19f/f39//15eXv8mJib/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8bGxv/h4eH/9bW1v/Z2dn/09PT/2ZmZv8aGhr/Ghoa/xoa + Gv8mJib/zc3N/////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// + /////////////////////////////9LS0v8qKir/Ghoa/xoaGv8aGhr/Ghoa/5+fn//Nzc3/QEBA/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/LCws/3t7e/+Tk5P/bm5u/yEh + If8aGhr/Ghoa/xoaGv9NTU3/6urq/////////////////////////////////3V1df8aGhr/Ghoa/xoa + Gv8aGhr/YWFh/////////////////////////////////+zs7P9TU1P/Ghoa/xoaGv8aGhr/Ghoa/zQ0 + NP89PT3/ISEh/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/x4e + Hv8jIyP/HBwc/xoaGv8aGhr/Ghoa/xwcHP99fX3/+Pj4/////////////////////////////////3V1 + df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////z8/P+Tk5P/ICAg/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/ywsLP+7u7v///////////////////////// + /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// + ///i4uL/WFhY/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Hh4e/4WFhf/29vb///////// + /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// + ///////////////////+/v7/5OTk/25ubv8kJCT/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xsbG/8wMDD/mJiY//T0 + 9P///////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// + //////////////////////////////////////////////39/f/m5ub/09PT/9HR0f/R0dH/0dHR/9HR + 0f/R0dH/yMjI/y0tLf+EhIT/0dHR/7S0tP8yMjL/rKys/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9bW + 1v/w8PD//v7+/////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa + Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// + ////////////////////////8/Pz/zIyMv+dnZ3//v7+/9vb2/84ODj/0NDQ//////////////////// + /////////////////////////////////////////////////////////////////////////////3V1 + df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////////////////////// + ////////////////////////////////////////8/Pz/zIyMv+dnZ3//v7+/9vb2/84ODj/0NDQ//// + //////////////////////////////////////////////////////////////////////////////// + /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// + //////////////////////////////7+/v/n5+f/0tLS/8/Pz//Pz8//xsbG/y0tLf+CgoL/zs7O/7Oz + s/8yMjL/qqqq/8/Pz//Pz8//0NDQ/9fX1//29vb///////////////////////////////////////// + /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// + //////////////////////////////////////////////X19f92dnb/IiIi/x4eHv8eHh7/Hh4e/xoa + Gv8cHBz/Hh4e/x0dHf8aGhr/HR0d/x4eHv8eHh7/Hx8f/zU1Nf/Gxsb///////////////////////// + /////////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// + //////////////////////////////////////////////////////////////n5+f+NjY3/ICAg/xkh + JP8UPkn/FD9J/xQ/Sf8UP0n/FD9J/xQ/Sf8UP0n/FD9J/xQ/Sf8WMzr/Ghwd/z4+Pv/Y2Nj///////// + /////////////////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa + Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// + ///Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/25u + bv/+/v7//////////////////////////////////////////////////////////////////////3V1 + df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////////////////////// + ///////////////////Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM + //8IkbP/GCQn/25ubv/+/v7///////////////////////////////////////////////////////// + /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// + ///////////////////////////////////Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM + //8AzP//AMz//wDM//8IkbP/GCQn/25ubv/+/v7///////////////////////////////////////// + /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// + ///////////////////////////////////////////////////Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM + //8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/25ubv/+/v7///////////////////////// + /////////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// + ///////////////////////////////////////////////////////////////////Ly8v/Ly8v/xQ/ + Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/25ubv/+/v7///////// + /////////////////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa + Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// + ///Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/29v + b//+/v7//////////////////////////////////////////////////////////////////////3V1 + df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////////////////////// + ///////////////////Ozs7/MDAw/xQ/Sf8Dyfr/GtH+/xfQ/v8BzP//AMz//wDM//8AzP//AMz//wDM + //8Jj7H/GSQn/3Jycv/+/v7///////////////////////////////////////////////////////// + /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// + ///////////////////////////////////Z2dn/NDQ0/xU5Qv8exvD/u+76/7ft+v8n0/7/AMz//wDM + //8AzP//AMz//wDM//8KhqX/GSEj/4WFhf////////////////////////////////////////////// + /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// + ///////////////////////////////////////////////////v7+//QUFB/xctMv8ardL/xe/6//X4 + +f+O5fv/Cs7//wDM//8AzP//AMz//wHI+v8ObIT/Ghwc/6+vr/////////////////////////////// + /////////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// + ///////////////////////////////////////////////////////////////////+/v7/eXl5/xkf + IP8MeJP/QtX6/+r2+f/o9vn/MNX+/wDM//8AzP//AMz//wO14v8VPkj/Kysr/+rq6v////////////// + /////////////////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa + Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// + ////////2tra/zAwMP8WNDz/CKDH/23f/P+07Pr/KtP+/wDM//8AzP//AcP0/w9kef8dICH/ioqK//7+ + /v///////////////////////////////////////////////////////////////////////////3V1 + df8aGhr/Ghoa/xoaGv4aGhr/YGBg//////////////////////////////////////////////////// + /////////////////////////v7+/6mpqf8iIiP/FzE3/w+Ssv8Uy/j/A83//wDL/v8DtOD/EVhr/xof + IP9VVVX/5ubm//////////////////////////////////////////////////////////////////// + /////////////3Nzc/8aGhr/Ghoa/xoaGu0aGhr/S0tL//7+/v////////////////////////////// + //////////////////////////////////////////////f39/+Ojo7/LCws/xodHf8VOUL/EFls/xJN + W/8YJyv/Hx8g/0pKSv/R0dH///////////////////////////////////////////////////////// + /////////////////////////////15eXv8aGhr/Ghoa+xoaGr4aGhr/Jycn/+bm5v////////////// + ///////////////////////////////////////////////////////////////////19fX/uLi4/2xs + bP8+Pj7/Li4u/zQ0NP9SUlL/kJCQ/+Dg4P/+/v7///////////////////////////////////////// + ////////////////////////////////////////8PDw/zAwMP8aGhr/GRkZ0xgYGHMaGhr+Ghoa/46O + jv/+/v7///////////////////////////////////////////////////////////////////////// + /////////v7+//T09P/j4+P/29vb/97e3v/r6+v/+/v7//////////////////////////////////// + ////////////////////////////////////////////////////////oaGh/xsbG/8aGhr/GhoaiBoa + GicaGhrhGhoa/zQ0NP/Nzc3//v7+//////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////7+/v/X19f/Ojo6/xoa + Gv8aGhrpGhoaMRwcHAkYGBiIGhoa+RwcHP9VVVX/zc3N//7+/v////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////9XV + 1f9cXFz/HR0d/xkZGfsaGhqUFxcXCwAAAAAaGhodGhoaxxoaGv8cHBz/NDQ0/46Ojv/m5ub//v7+//// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////7+ + /v/p6en/mJiY/zk5Of8dHR3/Ghoa/xoaGtEbGxsmAAAAAAAAAAAAAAAAFxcXIRoaGscaGhr5Ghoa/xoa + Gv8nJyf/S0tL/2BgYP9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2Fh + Yf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2Fh + Yf9hYWH/YGBg/05OTv8pKSn/Gxsb/xoaGv8aGhr6GhoazxgYGCoAAAAAAAAAAAAAAAAAAAAAAAAAABoa + Gh0YGBiIGhoa4RoaGv4aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/hoaGuYZGRmPFRUVJAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAcHBwJGhoaJxgYGHMaGhq+Ghoa7RoaGv4aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa + Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/hoaGu8ZGRnDGRkZeRgYGCsaGhoKAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== + + + \ No newline at end of file diff --git a/Models/DownloadResult.cs b/Models/DownloadResult.cs new file mode 100644 index 0000000..1e94d53 --- /dev/null +++ b/Models/DownloadResult.cs @@ -0,0 +1,14 @@ +namespace LumenLabInstaller.Models +{ + public class DownloadResult + { + public string FilePath { get; } + public long BytesWritten { get; } + + public DownloadResult(string filePath, long bytesWritten) + { + FilePath = filePath; + BytesWritten = bytesWritten; + } + } +} \ No newline at end of file diff --git a/Models/GitHubRelease.cs b/Models/GitHubRelease.cs new file mode 100644 index 0000000..676f43f --- /dev/null +++ b/Models/GitHubRelease.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace LumenLabInstaller.Models +{ + public class GitHubRelease + { + public string TagName { get; set; } + public DateTime PublishedAt { get; set; } + public string Name { get; set; } + public List Assets { get; set; } = new(); + } + + public class GitHubAsset + { + public string Name { get; set; } + public string BrowserDownloadUrl { get; set; } + } + + public class GitHubReleaseApiModel + { + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + + [JsonPropertyName("published_at")] + public DateTime PublishedAt { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("assets")] + public List Assets { get; set; } + } + + public class GitHubAssetApiModel + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("browser_download_url")] + public string BrowserDownloadUrl { get; set; } + } +} diff --git a/Models/InstallerContextcs.cs b/Models/InstallerContextcs.cs new file mode 100644 index 0000000..9b554b2 --- /dev/null +++ b/Models/InstallerContextcs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LumenLabInstaller.Models +{ + public sealed class InstallerContext + { + public string? PortName { get; set; } + + public Version? FirmwareVersion { get; set; } + + public bool DeviceDetected => !string.IsNullOrWhiteSpace(PortName); + } +} diff --git a/Models/ReleaseGridRow.cs b/Models/ReleaseGridRow.cs new file mode 100644 index 0000000..570129f --- /dev/null +++ b/Models/ReleaseGridRow.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LumenLabInstaller.Models +{ + public class ReleaseGridRow + { + public string TagName { get; set; } + public string Name { get; set; } + public DateTime PublishedAt { get; set; } + public GitHubRelease Release { get; set; } + } +} diff --git a/Program.cs b/Program.cs index 6f8acd3..5040a7f 100644 --- a/Program.cs +++ b/Program.cs @@ -1,17 +1,22 @@ -namespace lumenlab_installer -{ - internal static class Program +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http; +using Microsoft.Extensions.Hosting; +using LumenLabInstaller; +using LumenLabInstaller.Services; +using LumenLabInstaller.Models; + +ApplicationConfiguration.Initialize(); + +var host = Host.CreateDefaultBuilder() + .ConfigureServices((context, services) => { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - // To customize application configuration such as set high DPI settings or default font, - // see https://aka.ms/applicationconfiguration. - ApplicationConfiguration.Initialize(); - Application.Run(new LumenLabInstaller()); - } - } -} \ No newline at end of file + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddHttpClient(); + services.AddTransient(); + }) + .Build(); + +var form = host.Services.GetRequiredService(); +Application.Run(form); \ No newline at end of file diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs new file mode 100644 index 0000000..f3ff672 --- /dev/null +++ b/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LumenLabInstaller.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LumenLabInstaller.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Properties/Resources.resx b/Properties/Resources.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Services/BinaryDownloadService.cs b/Services/BinaryDownloadService.cs new file mode 100644 index 0000000..861678b --- /dev/null +++ b/Services/BinaryDownloadService.cs @@ -0,0 +1,45 @@ +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/ConfigManager.cs b/Services/ConfigManager.cs new file mode 100644 index 0000000..9d31f46 --- /dev/null +++ b/Services/ConfigManager.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Threading.Tasks; +using LumenLabInstaller.Infrastructure; + +namespace LumenLabInstaller.Services +{ + public class Config + { + [JsonPropertyName("version")] + public string Version { get; set; } = "v0.0.0"; + } + + public static class ConfigManager + { + public static Config LoadConfig() + { + try + { + + if (!File.Exists(AppPaths.ConfigFilePath)) + { + var defaultConfig = new Config(); + SaveConfig(defaultConfig); + return defaultConfig; + } + + var json = File.ReadAllText(AppPaths.ConfigFilePath); + var config = JsonSerializer.Deserialize(json); + + if (config == null || string.IsNullOrWhiteSpace(config.Version)) + { + throw new Exception("Invalid config file content."); + } + + return config; + } + catch (UnauthorizedAccessException) + { + Console.Error.WriteLine("Access to the config file is denied."); + return new Config(); + } + catch (JsonException) + { + Console.Error.WriteLine("Config file is corrupted. Creating a new default config."); + var defaultConfig = new Config(); + SaveConfig(defaultConfig); + return defaultConfig; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Unexpected error reading config: {ex.Message}"); + return new Config(); + } + } + + public static void SaveConfig(Config config) + { + try + { + var json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(AppPaths.ConfigFilePath, json); + } + catch (UnauthorizedAccessException) + { + Console.Error.WriteLine("Cannot write config file, access denied."); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Unexpected error saving config: {ex.Message}"); + } + } + } +} diff --git a/Services/DeviceDiscoveryService.cs b/Services/DeviceDiscoveryService.cs new file mode 100644 index 0000000..4aef5c0 --- /dev/null +++ b/Services/DeviceDiscoveryService.cs @@ -0,0 +1,39 @@ +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/FirmwareService.cs b/Services/FirmwareService.cs new file mode 100644 index 0000000..acadb22 --- /dev/null +++ b/Services/FirmwareService.cs @@ -0,0 +1,43 @@ +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 new file mode 100644 index 0000000..6b93207 --- /dev/null +++ b/Services/GitHubReleaseService.cs @@ -0,0 +1,65 @@ +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) + { + "firmware.bin", + "bootloader.bin", + "partitions.bin" + }; + + 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() ?? new List() + }) + .Where(release => + { + if (release.Assets.Count < 3) 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/ToolManager.cs b/Services/ToolManager.cs new file mode 100644 index 0000000..5a792d4 --- /dev/null +++ b/Services/ToolManager.cs @@ -0,0 +1,62 @@ +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/lumenlab-icon.png b/assets/lumenlab-icon.png new file mode 100644 index 0000000..9d1150a Binary files /dev/null and b/assets/lumenlab-icon.png differ diff --git a/assets/lumenlab-logo-inkscape.svg b/assets/lumenlab-logo-inkscape.svg new file mode 100644 index 0000000..c17585c --- /dev/null +++ b/assets/lumenlab-logo-inkscape.svg @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + LumenLab + Installation Tool + + + + + + + + diff --git a/assets/lumenlab-logo-transparent.png b/assets/lumenlab-logo-transparent.png new file mode 100644 index 0000000..0c591c4 Binary files /dev/null and b/assets/lumenlab-logo-transparent.png differ diff --git a/lumenlab-install-form.Designer.cs b/lumenlab-install-form.Designer.cs deleted file mode 100644 index 92663e9..0000000 --- a/lumenlab-install-form.Designer.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace lumenlab_installer -{ - partial class LumenLabInstaller - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LumenLabInstaller)); - SuspendLayout(); - // - // LumenLabInstaller - // - AutoScaleDimensions = new SizeF(7F, 15F); - AutoScaleMode = AutoScaleMode.Font; - BackColor = Color.FromArgb(10, 10, 10); - ClientSize = new Size(800, 450); - FormBorderStyle = FormBorderStyle.FixedSingle; - Icon = (Icon)resources.GetObject("$this.Icon"); - MaximizeBox = false; - Name = "LumenLabInstaller"; - StartPosition = FormStartPosition.CenterScreen; - Text = "LumenLab Installer"; - Load += LumenLabInstaller_Load; - ResumeLayout(false); - } - - #endregion - - private Button button1; - } -} diff --git a/lumenlab-install-form.cs b/lumenlab-install-form.cs deleted file mode 100644 index 8fb9039..0000000 --- a/lumenlab-install-form.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace lumenlab_installer -{ - public partial class LumenLabInstaller : Form - { - public LumenLabInstaller() - { - InitializeComponent(); - } - - private void LumenLabInstaller_Load(object sender, EventArgs e) - { - - } - } -} diff --git a/lumenlab-install-form.resx b/lumenlab-install-form.resx deleted file mode 100644 index 730e019..0000000 --- a/lumenlab-install-form.resx +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAMAEBAAAAEAIABoBAAANgAAACAgAAABACAAKBEAAJ4EAAAwMAAAAQAgAGgmAADGFQAAKAAAABAA - AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQZGRmGIyMj7jU1Nf81NTX/NTU1/zU1 - Nf81NTX/NTU1/zU1Nf81NTX/NTU1/zU1Nf8kJCTwGhoaigAAAAUZGRmEbGxs//T09P/19fX/29vb//// - /////////////////////////v7+/9bW1v/29vb/9fX1/3Fxcf8aGhqKISEh6vPz8//4+Pj/PDw8/xoa - Gv90dHT/9/f3//j4+P/4+Pj/9vb2/29vb/8aGhr/TExM//7+/v/19fX/JCQk8C8vL///////39/f/xoa - Gv8aGhr/Kysr/xwcHP8cHBz/HBwc/xwcHP8aGhr/Ghoa/xoaGv/o6Oj//////zU1Nf8vLy////////f3 - 9/8gICD/U1NT/76+vv8+Pj7/Ghoa/xoaGv8zMzP/s7Oz/ywsLP8qKir/+/v7//////81NTX/Ly8v//// - ////////WFhY/yYmJv+BgYH/ISEh/xoaGv8aGhr/JiYm/319ff8iIiL/bW1t////////////NTU1/y8v - L////////////8LCwv8jIyP/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/KCgo/9PT0////////////zU1 - Nf8vLy//////////////////+Pj4//Hx8f/u7u7/kpKS/4+Pj//x8fH/8fHx//n5+f////////////// - //81NTX/Ly8v//////////////////////+kpKT/T1lb/zdER/82Q0b/UVla/8PDw/////////////// - ////////NTU1/y8vL///////////////////////q6ur/wagx/8AzP//AMz//wqBn//T09P///////// - /////////////zU1Nf8vLy///////////////////////6ysrP8GoMf/AMz//wDM//8KgZ//09PT//// - //////////////////81NTX/Ly8v//////////////////////+1tbX/OaG7/0bY/f8AzP//DHqV/9zc - 3P//////////////////////NTU1/y8vL///////////////////////7u7u/ylda/9o2PT/Ar3s/z9f - Z//9/f3//////////////////////zU1Nf8gICDp8fHx///////////////////////T09P/aHN2/3J6 - e//l5eX///////////////////////T09P8jIyPuGhoagGhoaP/x8fH///////////////////////// - //////////////////////////////Pz8/9sbGz/GRkZhgAAAAMaGhqAICAg6S8vL/8vLy//Ly8v/y8v - L/8vLy//Ly8v/y8vL/8vLy//Ly8v/y8vL/8hISHqGRkZhAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAACAAAABAAAAAAQAgAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxkZGVwaGhrDGhoa+BoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa+hoaGscaGhpjAAAABQAAAAAAAAAAAAAAABEREQ8ZGRm5Ghoa/xwcHP88PDz/UFBQ/1BQ - UP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQUP9QUFD/UFBQ/1BQ - UP9QUFD/UFBQ/1BQUP89PT3/HR0d/xoaGv8ZGRnAGxsbEwAAAAAAAAADGRkZthoaGv9RUVH/1tbW//7+ - /v////////////////////////////////////////////////////////////////////////////// - //////////////////////////////7+/v/a2tr/WFhY/xoaGv8ZGRnAAAAABRoaGlcaGhr/UFBQ//f3 - 9//////////////////Z2dn/np6e/8/Pz//+/v7///////////////////////////////////////// - //////////////39/f++vr7/nZ2d/9vb2//////////////////5+fn/WFhY/xoaGv8aGhpjGhoauhsb - G//Q0ND/////////////////mJiY/x4eHv8aGhr/Gxsb/2dnZ//19fX///////////////////////// - ///////////////////y8vL/X19f/xoaGv8aGhr/IiIi/729vf/////////////////a2tr/HR0d/xoa - GscaGhrwMzMz//39/f///////////+Xl5f8gICD/Ghoa/xoaGv8aGhr/Ghoa/1tbW//t7e3/8fHx//Hx - 8f/x8fH/8fHx//Hx8f/x8fH/6urq/1NTU/8aGhr/Ghoa/xoaGv8aGhr/Nzc3//r6+v////////////7+ - /v89PT3/Ghoa+hoaGv9ERET/////////////////vLy8/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/x0d - Hf8eHh7/Hh4e/x4eHv8eHh7/Hh4e/x4eHv8dHR3/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/1dXV//// - /////////////1BQUP8aGhr/Ghoa/0RERP/////////////////BwcH/Ghoa/xoaGv8aGhr/Ghoa/0VF - Rf8zMzP/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv/Ozs7/////////////////UFBQ/xoaGv8aGhr/RERE/////////////////+Hh4f8bGxv/Ghoa/xsb - G/8wMDD/vr6+/4eHh/8oKCj/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/KCgo/5KSkv+JiYn/ISEh/xoa - Gv8aGhr/ISEh//Hx8f////////////////9QUFD/Ghoa/xoaGv9ERET//////////////////Pz8/zMz - M/8aGhr/KSkp/9jY2P/Z2dn/2dnZ/5ubm/8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv9ycnL/2dnZ/9nZ - 2f9bW1v/Ghoa/xoaGv9UVFT//////////////////////1BQUP8aGhr/Ghoa/0RERP////////////// - ////////cnJy/xoaGv8dHR3/R0dH/8LCwv+Tk5P/ODg4/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/0pK - Sv/T09P/zc3N/zk5Of8aGhr/Ghoa/5ubm///////////////////////UFBQ/xoaGv8aGhr/RERE//// - //////////////////+6urr/Ghoa/xoaGv8aGhr/ZmZm/0hISP8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/ywsLP8oKCj/Ghoa/xoaGv8dHR3/4eHh//////////////////////9QUFD/Ghoa/xoa - Gv9ERET///////////////////////n5+f88PDz/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/2FhYf///////////////////////////1BQ - UP8aGhr/Ghoa/0RERP///////////////////////////9bW1v89PT3/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xsbG/9TU1P/7Ozs//////////////////// - ////////UFBQ/xoaGv8aGhr/RERE//////////////////////////////////v7+//m5ub/4+Pj/+Pj - 4//j4+P/3d3d/zU1Nf/h4eH/jo6O/4GBgf/j4+P/4+Pj/+Pj4//j4+P/6urq//7+/v////////////// - //////////////////9QUFD/Ghoa/xoaGv9ERET///////////////////////////////////////// - ///////////////////4+Pj/ODg4//z8/P+enp7/j4+P//////////////////////////////////// - /////////////////////////////1BQUP8aGhr/Ghoa/0RERP////////////////////////////// - //////////////T09P+RkZH/ioqK/4aGhv8pKSn/iIiI/1tbW/9TU1P/ioqK/4uLi/+wsLD///////// - ////////////////////////////////////////UFBQ/xoaGv8aGhr/RERE//////////////////// - ////////////////////////5ubm/yUlJf8YJSj/FjA2/xYwNv8WMDb/FjA2/xYwNv8WMDb/GR4f/15e - Xv////////////////////////////////////////////////9QUFD/Ghoa/xoaGv9ERET///////// - ////////////////////////////////////////WFhY/wx1j/8AzP//AMz//wDM//8AzP//AMz//wDL - /v8VOEH/p6en/////////////////////////////////////////////////1BQUP8aGhr/Ghoa/0RE - RP////////////////////////////////////////////////9YWFj/DHWP/wDM//8AzP//AMz//wDM - //8AzP//AMv+/xU4Qf+np6f/////////////////////////////////////////////////UFBQ/xoa - Gv8aGhr/RERE/////////////////////////////////////////////////1hYWP8MdY//AMz//wDM - //8AzP//AMz//wDM//8Ay/7/FThB/6enp/////////////////////////////////////////////// - //9QUFD/Ghoa/xoaGv9ERET/////////////////////////////////////////////////WVlZ/wx1 - j/8AzP//AMz//wDM//8AzP//AMz//wDL/v8VOEH/p6en//////////////////////////////////// - /////////////1BQUP8aGhr/Ghoa/0RERP////////////////////////////////////////////// - //9dXV3/DXOM/zPV/f8s1P7/AMz//wDM//8AzP//AMv+/xY2Pv+srKz///////////////////////// - ////////////////////////UFBQ/xoaGv8aGhr/RERE//////////////////////////////////// - /////////////3h4eP8RWGr/lOb7/9vz+f8Sz/7/AMz//wDM//8BxPX/GSMl/8fHx/////////////// - //////////////////////////////////9QUFD/Ghoa/xoaGv9ERET///////////////////////// - ////////////////////////vr6+/xkjJf8bu+P/4vX5/3vi/P8AzP//AMz//wuCoP8wMDD/9/f3//// - /////////////////////////////////////////////1BQUP8aGhr/Ghoa/kRERP////////////// - ///////////////////////////////////9/f3/XFxc/xU8R/8qu9//GdD+/wDL/f8Ik7b/GSIl/6io - qP//////////////////////////////////////////////////////UFBQ/xoaGv8aGhruMTEx//39 - /f/////////////////////////////////////////////////x8fH/YGBg/xofIP8VPEb/FjI5/yMk - JP+ZmZn//v7+//////////////////////////////////////////////////7+/v88PDz/Ghoa+BkZ - GbcbGxv/y8vL///////////////////////////////////////////////////////+/v7/0NDQ/6Gh - of+pqan/6Ojo////////////////////////////////////////////////////////////1tbW/xwc - HP8aGhrDGRkZURoaGv5JSUn/9fX1//////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////f3 - 9/9RUVH/Ghoa/xkZGVwAAAACGRkZrRoaGv9JSUn/y8vL//39/f////////////////////////////// - //////////////////////////////////////////////////////////////////////////////39 - /f/Q0ND/UFBQ/xoaGv8ZGRm5AAAAAwAAAAAVFRUMGRkZrRoaGv4bGxv/MTEx/0RERP9ERET/RERE/0RE - RP9ERET/RERE/0RERP9ERET/RERE/0RERP9ERET/RERE/0RERP9ERET/RERE/0RERP9ERET/RERE/0RE - RP9ERET/MzMz/xsbG/8aGhr/GRkZthEREQ8AAAAAAAAAAAAAAAAAAAACGRkZURkZGbcaGhruGhoa/hoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhrwGhoauhoaGlcAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAAADAA - AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAXFxcLGhoaMRoa - GogZGRnTGhoa+xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGvwaGhrYGRkZjhgYGDYUFBQNAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsb - GyYaGhqUGhoa6RoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGu4aGhqcHR0dLAAAAAAAAAAAAAAAAAAA - AAAAAAAAGBgYKhoaGtEZGRn7Ghoa/xsbG/8wMDD/Xl5e/3Nzc/91dXX/dXV1/3V1df91dXX/dXV1/3V1 - df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dXV1/3V1 - df91dXX/dXV1/3V1df91dXX/dXV1/3V1df91dXX/dHR0/2BgYP80NDT/HBwc/xoaGv8aGhr8Ghoa1x0d - HTQAAAAAAAAAAAAAAAAVFRUkGhoazxoaGv8dHR3/Ojo6/6Ghof/w8PD///////////////////////// - //////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////y8vL/qamp/0BA - QP8eHh7/Ghoa/xoaGtcdHR0sAAAAARoaGgoZGRmPGhoa+h0dHf9cXFz/19fX//////////////////// - //////////////39/f/+/v7///////////////////////////////////////////////////////// - ///////////////////////////////////////////////////9/f3//f39//////////////////// - /////////////93d3f9iYmL/Hh4e/xoaGvwaGhqcFBQUDRgYGCsaGhrmGhoa/zk5Of/V1dX//v7+//// - ///////////////////z8/P/pqam/2FhYf90dHT/ycnJ//39/f////////////////////////////// - ////////////////////////////////////////////////////////+/v7/62trf9kZGT/YWFh/6io - qP/29vb///////////////////////7+/v/d3d3/QEBA/xoaGv8aGhruGBgYNhkZGXkaGhr+Gxsb/5iY - mP///////////////////////////97e3v9ZWVn/HBwc/xoaGv8aGhr/HR0d/2hoaP/g4OD//v7+//// - //////////////////////////////////////////////////////////////7+/v/b29v/X19f/xsb - G/8aGhr/Ghoa/x4eHv92dnb/8PDw////////////////////////////qamp/xwcHP8aGhr/GRkZjhkZ - GcMaGhr/KSkp/+np6f//////////////////////9/f3/19fX/8bGxv/Ghoa/xoaGv8aGhr/Ghoa/x0d - Hf9aWlr/3d3d/////////////////////////////////////////////////////////////////9bW - 1v9SUlL/HBwc/xoaGv8aGhr/Ghoa/xoaGv8iIiL/mpqa////////////////////////////8vLy/zQ0 - NP8aGhr/Ghoa2BoaGu8aGhr/Tk5O//7+/v//////////////////////wsLC/x0dHf8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8dHR3/T09P/93d3f/p6en/6enp/+np6f/p6en/6enp/+np6f/p6en/6enp/+np - 6f/p6en/1dXV/0VFRf8cHBz/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/NTU1//Pz8/////////////// - /////////////2BgYP8aGhr/Ghoa/BoaGv4aGhr/YGBg////////////////////////////lJSU/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/x8fH/8hISH/ISEh/yEhIf8hISH/ISEh/yEh - If8hISH/ISEh/yEhIf8hISH/Hx8f/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/8TE - xP///////////////////////////3R0dP8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// - ////////jIyM/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/6enp////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// - ////////////////////////nZ2d/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/1ZWVv9ubm7/Kioq/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/7S0tP///////////////////////////3V1df8aGhr/Ghoa/xoa - Gv8aGhr/YWFh////////////////////////////wsLC/xoaGv8aGhr/Ghoa/xoaGv8eHh7/IiIi/6io - qP/X19f/SEhI/yEhIf8bGxv/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/IiIi/1hY - WP96enr/TExM/x4eHv8aGhr/Ghoa/xoaGv8aGhr/Hx8f/+Dg4P///////////////////////////3V1 - df8aGhr/Ghoa/xoaGv8aGhr/YWFh////////////////////////////6urq/yYmJv8aGhr/Ghoa/x0d - Hf9cXFz/kJCQ/8bGxv/Y2Nj/n5+f/4KCgv8tLS3/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8bGxv/c3Nz/9TU1P/Z2dn/zs7O/1VVVf8aGhr/Ghoa/xoaGv8aGhr/R0dH//r6+v////////////// - /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh/////////////////////////////f39/1JS - Uv8aGhr/Ghoa/x8fH/+IiIj/2dnZ/9nZ2f/Z2dn/2dnZ/8TExP88PDz/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8fHx//r6+v/9nZ2f/Z2dn/2dnZ/46Ojv8aGhr/Ghoa/xoaGv8aGhr/j4+P//// - /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// - /////////////5iYmP8bGxv/Ghoa/xwcHP9ERET/ZmZm/7u7u//X19f/f39//15eXv8mJib/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8bGxv/h4eH/9bW1v/Z2dn/09PT/2ZmZv8aGhr/Ghoa/xoa - Gv8mJib/zc3N/////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// - /////////////////////////////9LS0v8qKir/Ghoa/xoaGv8aGhr/Ghoa/5+fn//Nzc3/QEBA/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/LCws/3t7e/+Tk5P/bm5u/yEh - If8aGhr/Ghoa/xoaGv9NTU3/6urq/////////////////////////////////3V1df8aGhr/Ghoa/xoa - Gv8aGhr/YWFh/////////////////////////////////+zs7P9TU1P/Ghoa/xoaGv8aGhr/Ghoa/zQ0 - NP89PT3/ISEh/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/x4e - Hv8jIyP/HBwc/xoaGv8aGhr/Ghoa/xwcHP99fX3/+Pj4/////////////////////////////////3V1 - df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////z8/P+Tk5P/ICAg/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/ywsLP+7u7v///////////////////////// - /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// - ///i4uL/WFhY/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Hh4e/4WFhf/29vb///////// - /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// - ///////////////////+/v7/5OTk/25ubv8kJCT/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xsbG/8wMDD/mJiY//T0 - 9P///////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// - //////////////////////////////////////////////39/f/m5ub/09PT/9HR0f/R0dH/0dHR/9HR - 0f/R0dH/yMjI/y0tLf+EhIT/0dHR/7S0tP8yMjL/rKys/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9bW - 1v/w8PD//v7+/////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa - Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// - ////////////////////////8/Pz/zIyMv+dnZ3//v7+/9vb2/84ODj/0NDQ//////////////////// - /////////////////////////////////////////////////////////////////////////////3V1 - df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////////////////////// - ////////////////////////////////////////8/Pz/zIyMv+dnZ3//v7+/9vb2/84ODj/0NDQ//// - //////////////////////////////////////////////////////////////////////////////// - /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// - //////////////////////////////7+/v/n5+f/0tLS/8/Pz//Pz8//xsbG/y0tLf+CgoL/zs7O/7Oz - s/8yMjL/qqqq/8/Pz//Pz8//0NDQ/9fX1//29vb///////////////////////////////////////// - /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// - //////////////////////////////////////////////X19f92dnb/IiIi/x4eHv8eHh7/Hh4e/xoa - Gv8cHBz/Hh4e/x0dHf8aGhr/HR0d/x4eHv8eHh7/Hx8f/zU1Nf/Gxsb///////////////////////// - /////////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// - //////////////////////////////////////////////////////////////n5+f+NjY3/ICAg/xkh - JP8UPkn/FD9J/xQ/Sf8UP0n/FD9J/xQ/Sf8UP0n/FD9J/xQ/Sf8WMzr/Ghwd/z4+Pv/Y2Nj///////// - /////////////////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa - Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// - ///Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/25u - bv/+/v7//////////////////////////////////////////////////////////////////////3V1 - df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////////////////////// - ///////////////////Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM - //8IkbP/GCQn/25ubv/+/v7///////////////////////////////////////////////////////// - /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// - ///////////////////////////////////Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM - //8AzP//AMz//wDM//8IkbP/GCQn/25ubv/+/v7///////////////////////////////////////// - /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// - ///////////////////////////////////////////////////Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM - //8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/25ubv/+/v7///////////////////////// - /////////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// - ///////////////////////////////////////////////////////////////////Ly8v/Ly8v/xQ/ - Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/25ubv/+/v7///////// - /////////////////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa - Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// - ///Ly8v/Ly8v/xQ/Sv8Ayfv/AMz//wDM//8AzP//AMz//wDM//8AzP//AMz//wDM//8IkbP/GCQn/29v - b//+/v7//////////////////////////////////////////////////////////////////////3V1 - df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////////////////////// - ///////////////////Ozs7/MDAw/xQ/Sf8Dyfr/GtH+/xfQ/v8BzP//AMz//wDM//8AzP//AMz//wDM - //8Jj7H/GSQn/3Jycv/+/v7///////////////////////////////////////////////////////// - /////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////////////////////// - ///////////////////////////////////Z2dn/NDQ0/xU5Qv8exvD/u+76/7ft+v8n0/7/AMz//wDM - //8AzP//AMz//wDM//8KhqX/GSEj/4WFhf////////////////////////////////////////////// - /////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//////////////////// - ///////////////////////////////////////////////////v7+//QUFB/xctMv8ardL/xe/6//X4 - +f+O5fv/Cs7//wDM//8AzP//AMz//wHI+v8ObIT/Ghwc/6+vr/////////////////////////////// - /////////////////////////////////////////////3V1df8aGhr/Ghoa/xoaGv8aGhr/YWFh//// - ///////////////////////////////////////////////////////////////////+/v7/eXl5/xkf - IP8MeJP/QtX6/+r2+f/o9vn/MNX+/wDM//8AzP//AMz//wO14v8VPkj/Kysr/+rq6v////////////// - /////////////////////////////////////////////////////////////3V1df8aGhr/Ghoa/xoa - Gv8aGhr/YWFh//////////////////////////////////////////////////////////////////// - ////////2tra/zAwMP8WNDz/CKDH/23f/P+07Pr/KtP+/wDM//8AzP//AcP0/w9kef8dICH/ioqK//7+ - /v///////////////////////////////////////////////////////////////////////////3V1 - df8aGhr/Ghoa/xoaGv4aGhr/YGBg//////////////////////////////////////////////////// - /////////////////////////v7+/6mpqf8iIiP/FzE3/w+Ssv8Uy/j/A83//wDL/v8DtOD/EVhr/xof - IP9VVVX/5ubm//////////////////////////////////////////////////////////////////// - /////////////3Nzc/8aGhr/Ghoa/xoaGu0aGhr/S0tL//7+/v////////////////////////////// - //////////////////////////////////////////////f39/+Ojo7/LCws/xodHf8VOUL/EFls/xJN - W/8YJyv/Hx8g/0pKSv/R0dH///////////////////////////////////////////////////////// - /////////////////////////////15eXv8aGhr/Ghoa+xoaGr4aGhr/Jycn/+bm5v////////////// - ///////////////////////////////////////////////////////////////////19fX/uLi4/2xs - bP8+Pj7/Li4u/zQ0NP9SUlL/kJCQ/+Dg4P/+/v7///////////////////////////////////////// - ////////////////////////////////////////8PDw/zAwMP8aGhr/GRkZ0xgYGHMaGhr+Ghoa/46O - jv/+/v7///////////////////////////////////////////////////////////////////////// - /////////v7+//T09P/j4+P/29vb/97e3v/r6+v/+/v7//////////////////////////////////// - ////////////////////////////////////////////////////////oaGh/xsbG/8aGhr/GhoaiBoa - GicaGhrhGhoa/zQ0NP/Nzc3//v7+//////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////7+/v/X19f/Ojo6/xoa - Gv8aGhrpGhoaMRwcHAkYGBiIGhoa+RwcHP9VVVX/zc3N//7+/v////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////9XV - 1f9cXFz/HR0d/xkZGfsaGhqUFxcXCwAAAAAaGhodGhoaxxoaGv8cHBz/NDQ0/46Ojv/m5ub//v7+//// - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////7+ - /v/p6en/mJiY/zk5Of8dHR3/Ghoa/xoaGtEbGxsmAAAAAAAAAAAAAAAAFxcXIRoaGscaGhr5Ghoa/xoa - Gv8nJyf/S0tL/2BgYP9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2Fh - Yf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2FhYf9hYWH/YWFh/2Fh - Yf9hYWH/YGBg/05OTv8pKSn/Gxsb/xoaGv8aGhr6GhoazxgYGCoAAAAAAAAAAAAAAAAAAAAAAAAAABoa - Gh0YGBiIGhoa4RoaGv4aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/hoaGuYZGRmPFRUVJAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAcHBwJGhoaJxgYGHMaGhq+Ghoa7RoaGv4aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoa - Gv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/xoaGv8aGhr/Ghoa/hoaGu8ZGRnDGRkZeRgYGCsaGhoKAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== - - - \ No newline at end of file diff --git a/lumenlab-installer.csproj b/lumenlab-installer.csproj index e3b55c4..a1cfa04 100644 --- a/lumenlab-installer.csproj +++ b/lumenlab-installer.csproj @@ -3,10 +3,50 @@ WinExe net8.0-windows - lumenlab_installer + LumenLabInstaller enable true enable + true + true + win-x64 + false + true + true + 1.0.0 + $(Version) + $(Version) + + + + + + + Firmware.firmware.bin + + + Firmware.bootloader.bin + + + Firmware.partitions.bin + + + + + + + + + + + + + + + + + + \ No newline at end of file