mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-04-19 12:48:55 +00:00
feat: factory kit
This commit is contained in:
121
src/lib/assets/factory-flash.ps1
Normal file
121
src/lib/assets/factory-flash.ps1
Normal file
@@ -0,0 +1,121 @@
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
|
||||
# Function to create a form for COM port selection
|
||||
function Show-ComPortSelectionForm {
|
||||
# Create the form
|
||||
$form = New-Object System.Windows.Forms.Form
|
||||
$form.Text = "Select a COM Port"
|
||||
$form.Size = New-Object System.Drawing.Size(400, 400) # Set the size of the form
|
||||
$form.StartPosition = "CenterScreen"
|
||||
|
||||
# Create a label to display a message
|
||||
$infoLabel = New-Object System.Windows.Forms.Label
|
||||
$infoLabel.Location = New-Object System.Drawing.Point(20, 20)
|
||||
$infoLabel.Size = New-Object System.Drawing.Size(340, 30)
|
||||
$infoLabel.Text = "Select a COM port."
|
||||
|
||||
# Create a ListBox to hold the COM port items
|
||||
$comPortListBox = New-Object System.Windows.Forms.ListBox
|
||||
$comPortListBox.Location = New-Object System.Drawing.Point(20, 60)
|
||||
$comPortListBox.Size = New-Object System.Drawing.Size(340, 200)
|
||||
|
||||
# Create a label for status output
|
||||
$statusLabel = New-Object System.Windows.Forms.Label
|
||||
$statusLabel.Location = New-Object System.Drawing.Point(20, 280)
|
||||
$statusLabel.Size = New-Object System.Drawing.Size(340, 60)
|
||||
$statusLabel.Text = "Status: Ready"
|
||||
$statusLabel.ForeColor = [System.Drawing.Color]::Black
|
||||
|
||||
# Function to populate COM ports
|
||||
function PopulateComPorts {
|
||||
# Clear existing items
|
||||
$comPortListBox.Items.Clear()
|
||||
|
||||
# Get all COM ports with "OK" status
|
||||
$comPorts = Get-PnpDevice | Where-Object { $_.Class -eq 'Ports' -and $_.Status -eq 'OK' }
|
||||
|
||||
# Add full friendly name to the ListBox
|
||||
foreach ($comPort in $comPorts) {
|
||||
$comPortListBox.Items.Add($comPort.FriendlyName)
|
||||
}
|
||||
}
|
||||
|
||||
# Populate COM ports initially
|
||||
PopulateComPorts
|
||||
|
||||
# Event handler for mouse click
|
||||
$comPortListBox.Add_MouseClick({
|
||||
ProcessSelection
|
||||
})
|
||||
|
||||
# Event handler for "Enter" key press
|
||||
$comPortListBox.Add_KeyDown({
|
||||
param($sender, $e)
|
||||
if ($e.KeyCode -eq 'Enter') {
|
||||
ProcessSelection
|
||||
}
|
||||
})
|
||||
|
||||
# Function to process selection
|
||||
function ProcessSelection {
|
||||
if ($comPortListBox.SelectedItem) {
|
||||
# Get the selected full friendly name
|
||||
$selectedFriendlyName = $comPortListBox.SelectedItem
|
||||
|
||||
# Extract the COMx part from the friendly name
|
||||
$selectedFriendlyName -match '(COM\d+)' | Out-Null
|
||||
$selectedComPort = $Matches[1]
|
||||
|
||||
if ($selectedComPort) {
|
||||
# Clear the status label
|
||||
$statusLabel.Text = "Status: Executing..."
|
||||
$statusLabel.ForeColor = [System.Drawing.Color]::Black
|
||||
|
||||
# Run the batch file with the extracted COM port (e.g., COM3)
|
||||
$process = Start-Process cmd.exe -ArgumentList "/c .\flash.bat $selectedComPort" -NoNewWindow -PassThru -Wait
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
# Successful execution
|
||||
$statusLabel.Text = "Status: Execution completed successfully."
|
||||
$statusLabel.ForeColor = [System.Drawing.Color]::Green
|
||||
} else {
|
||||
# Error occurred
|
||||
$statusLabel.Text = "Status: Error occurred during execution."
|
||||
$statusLabel.ForeColor = [System.Drawing.Color]::Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a timer for automatic updates
|
||||
$timer = New-Object System.Windows.Forms.Timer
|
||||
$timer.Interval = 1000 # Set interval to 1 second
|
||||
|
||||
# Timer Tick event to refresh the COM port items
|
||||
$timer.Add_Tick({
|
||||
PopulateComPorts # Refresh the COM port items
|
||||
})
|
||||
|
||||
# Start the timer when the form is shown
|
||||
$form.Add_Shown({
|
||||
$timer.Start()
|
||||
})
|
||||
|
||||
# Stop the timer when the form is closed
|
||||
$form.Add_FormClosing({
|
||||
$timer.Stop()
|
||||
})
|
||||
|
||||
# Add controls to the form
|
||||
$form.Controls.Add($infoLabel)
|
||||
$form.Controls.Add($comPortListBox) # Add the ListBox to the form
|
||||
$form.Controls.Add($statusLabel) # Add the status label
|
||||
|
||||
# Show the form
|
||||
$form.ShowDialog()
|
||||
}
|
||||
|
||||
# Show the COM port selection dialog
|
||||
Show-ComPortSelectionForm
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { lt as semverLt } from "semver";
|
||||
import type { LoaderOptions, ESPLoader } from "esptool-js";
|
||||
import ProgressButton from "$lib/ProgressButton.svelte";
|
||||
import FactoryKit from "./FactoryKit.svelte";
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
@@ -354,6 +355,10 @@
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if data.meta.update.esptool}
|
||||
<FactoryKit meta={data.meta as any} />
|
||||
{/if}
|
||||
|
||||
{#if false && data.meta.update.esptool}
|
||||
<section>
|
||||
<h3>Factory Flash (WIP)</h3>
|
||||
|
||||
64
src/routes/(app)/ccos/[device]/[version]/FactoryKit.svelte
Normal file
64
src/routes/(app)/ccos/[device]/[version]/FactoryKit.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import ProgressButton from "$lib/ProgressButton.svelte";
|
||||
import {
|
||||
assembleFactoryKit,
|
||||
type VersionMetaWithEsptool,
|
||||
} from "./factory-kit";
|
||||
|
||||
let { meta }: { meta: VersionMetaWithEsptool } = $props();
|
||||
|
||||
const osChoices = ["Windows", "Linux", "MacOS"] as const;
|
||||
let os: (typeof osChoices)[number] = $state(osChoices[0]);
|
||||
|
||||
let working = $state(false);
|
||||
let progress = $state(0);
|
||||
let error: string | undefined = $state(undefined);
|
||||
|
||||
async function download() {
|
||||
progress = 0;
|
||||
error = undefined;
|
||||
working = true;
|
||||
try {
|
||||
const result = await assembleFactoryKit(meta, os, (value) => {
|
||||
progress = value;
|
||||
});
|
||||
const url = URL.createObjectURL(result);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `factory-kit-${meta.device}-${meta.version}-${os}.zip`;
|
||||
a.click();
|
||||
} catch (err) {
|
||||
error = (err as Error).message;
|
||||
} finally {
|
||||
working = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<h3><span class="icon">factory</span> Factory Kit</h3>
|
||||
<div class="os-selection">
|
||||
{#each osChoices as value}
|
||||
<label
|
||||
><input type="radio" name="os" {value} bind:group={os} />{value}</label
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
<ProgressButton onclick={download} {progress} {working} {error}
|
||||
><span class="icon">download</span> Download</ProgressButton
|
||||
>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
.os-selection {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
142
src/routes/(app)/ccos/[device]/[version]/factory-kit.ts
Normal file
142
src/routes/(app)/ccos/[device]/[version]/factory-kit.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import type { VersionMeta } from "$lib/meta/types/meta";
|
||||
|
||||
async function progressFetch(
|
||||
url: string,
|
||||
onProgress: (progress: number) => void,
|
||||
) {
|
||||
const request = new XMLHttpRequest();
|
||||
request.open("GET", url, true);
|
||||
request.responseType = "arraybuffer";
|
||||
request.onprogress = (event) => {
|
||||
if (event.total > 0) {
|
||||
onProgress(event.loaded / event.total);
|
||||
}
|
||||
};
|
||||
const result = new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
request.onload = () => {
|
||||
onProgress(1);
|
||||
if (request.status >= 200 && request.status < 300) {
|
||||
resolve(request.response);
|
||||
} else {
|
||||
reject(new Error(`Failed to fetch ${url}: ${request.statusText}`));
|
||||
}
|
||||
};
|
||||
});
|
||||
request.send();
|
||||
return result;
|
||||
}
|
||||
export type VersionMetaWithEsptool = VersionMeta & {
|
||||
update: { esptool: Exclude<VersionMeta["update"]["esptool"], undefined> };
|
||||
};
|
||||
|
||||
export async function assembleFactoryKit(
|
||||
meta: VersionMetaWithEsptool,
|
||||
os: "Windows" | "Linux" | "MacOS",
|
||||
onProgress: (progress: number) => void,
|
||||
): Promise<Blob> {
|
||||
let otherProgress: number[] = new Array(
|
||||
Object.keys(meta.update.esptool.files).length,
|
||||
).fill(0);
|
||||
let esptoolProgress = 0;
|
||||
let compressProgress = 0;
|
||||
function reportProgress() {
|
||||
const total =
|
||||
0.1 * (otherProgress.reduce((a, b) => a + b) / otherProgress.length) +
|
||||
0.5 * esptoolProgress +
|
||||
0.4 * compressProgress;
|
||||
onProgress(total);
|
||||
}
|
||||
const esptool =
|
||||
os === "Windows"
|
||||
? progressFetch(`/esptool-v5.2.0-windows-amd64.zip`, (progress) => {
|
||||
esptoolProgress = progress;
|
||||
reportProgress();
|
||||
})
|
||||
: undefined;
|
||||
const files = Object.values(meta.update.esptool.files).map(
|
||||
(file, i) =>
|
||||
[
|
||||
file,
|
||||
progressFetch(`${meta.path}/${file}`, (progress) => {
|
||||
otherProgress[i] = progress;
|
||||
reportProgress();
|
||||
}),
|
||||
] as const,
|
||||
);
|
||||
console.log(files);
|
||||
const JSZip = await import("jszip").then((m) => m.default);
|
||||
const zip = new JSZip();
|
||||
const esptoolZipPromise = esptool
|
||||
? esptool.then((it) => JSZip.loadAsync(it))
|
||||
: undefined;
|
||||
for (const [file, dataPromise] of files) {
|
||||
zip.file(file, dataPromise);
|
||||
}
|
||||
|
||||
const esptoolScript = ".\\esptool\\esptool.exe";
|
||||
const port = "%1";
|
||||
const ext = os === "Windows" ? "bat" : "sh";
|
||||
zip.file(
|
||||
`flash.${ext}`,
|
||||
[
|
||||
esptoolScript,
|
||||
"--chip",
|
||||
meta.update.esptool.chip,
|
||||
"--port",
|
||||
port,
|
||||
"--baud",
|
||||
meta.update.esptool.baud,
|
||||
"--before",
|
||||
meta.update.esptool.before,
|
||||
"--after",
|
||||
meta.update.esptool.after,
|
||||
"write_flash",
|
||||
"-z",
|
||||
"--flash_mode",
|
||||
meta.update.esptool.flash_mode,
|
||||
"--flash_freq",
|
||||
meta.update.esptool.flash_freq,
|
||||
"--flash_size",
|
||||
meta.update.esptool.flash_size,
|
||||
...Object.entries(meta.update.esptool.files).flatMap(
|
||||
([address, file]) => [address, file],
|
||||
),
|
||||
].join(" "),
|
||||
{ unixPermissions: "755" },
|
||||
);
|
||||
zip.file(`${meta.device}-${meta.version}`, "");
|
||||
if (os === "Windows") {
|
||||
zip.file(
|
||||
"factory-flash.ps1",
|
||||
import("$lib/assets/factory-flash.ps1?raw").then((m) => m.default),
|
||||
);
|
||||
}
|
||||
zip.file(
|
||||
"README.txt",
|
||||
[
|
||||
`Factory Kit for ${meta.device} ${meta.version}`,
|
||||
"",
|
||||
os !== "Windows"
|
||||
? "Requires esptool, please download from https://github.com/espressif/esptool/releases"
|
||||
: "",
|
||||
os === "Windows"
|
||||
? 'Right click factory-flash.ps1 and select "Run with PowerShell'
|
||||
: `Run flash.sh with \`./flash.sh <port>\``,
|
||||
].join("\n"),
|
||||
);
|
||||
if (esptoolZipPromise) {
|
||||
const esptoolZip = await esptoolZipPromise;
|
||||
for (const [path, file] of Object.entries(esptoolZip.files)) {
|
||||
if (!file.dir) {
|
||||
zip.file(
|
||||
path.replace(/^esptool[^\/]*\//, "esptool/"),
|
||||
file.async("arraybuffer"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return zip.generateAsync({ type: "blob" }, ({ percent }) => {
|
||||
compressProgress = percent / 100;
|
||||
reportProgress();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user