- Under Device in the menu bar, select Create New Device.
-
-
-
- Fill in three fields. The first field is just a description and you can fill in anything. The next two
- fields are very important. Fill them in with {VENDOR_ID} and {PRODUCT_ID}
- respectively. Press "Install Driver" and give it a few minutes to install.
-
-
-
-
No additional software is required for macOS, Linux or Android.
- >)}
-
-
-
-
-
Flashing
-
Follow these steps to put your device into QDL mode:
-
-
Unplug the device and wait for the LED to switch off.
-
First, connect the device to your computer using the lowerUSB-C port (port 1).
-
Second, connect power to the upperOBD-C port (port 2).
-
-
-
Your device's screen will remain blank for the entire flashing process. This is normal.
- {isLinux && (<>
- Note for Linux users
-
- On Linux systems, devices in QDL mode are automatically bound to the kernel's qcserial driver, and
- need to be unbound before we can access the device. Copy the script below into your terminal and run it
- after plugging in your device.
-
- {DETACH_SCRIPT}
- >)}
-
- Next, click the button to start flashing. From the prompt select the device which starts with
- “QUSB_BULK”.
-
-
- The process can take 30+ minutes depending on your internet connection and system performance. Do not
- unplug the device until all steps are complete.
-
-
-
-
-
-
Troubleshooting
-
Lost connection
-
- Try using high quality USB 3 cables. You should also try different USB ports on the front or back of your
- computer. If you're using a USB hub, try connecting directly to your computer instead.
-
-
My device's screen is blank
-
- This is normal in QDL mode. You can verify that the “QUSB_BULK” device shows up when you press
- the Flash button to know that it is working correctly.
-
-
My device says “fastboot mode”
-
- You may have followed outdated instructions for flashing. Please read the instructions above for putting
- your device into QDL mode.
-
-
General Tips
-
-
Try another computer or OS
-
Try different USB ports on your computer
-
Try different USB-C cables; low quality cables are often the source of problems. Note that the included OBD-C cable will not work.
-
-
Other questions
-
- If you need help, join our Discord server and go to
- the #hw-three-3x channel.
-
diff --git a/src/app/Flash.jsx b/src/components/flash.jsx
similarity index 90%
rename from src/app/Flash.jsx
rename to src/components/flash.jsx
index 7b3ad764..08248c56 100644
--- a/src/app/Flash.jsx
+++ b/src/components/flash.jsx
@@ -1,66 +1,57 @@
-import { useEffect, useRef, useState } from 'react'
+import { useEffect, useRef, useState } from 'preact/hooks'
import { FlashManager, StepCode, ErrorCode } from '../utils/manager'
import { useImageManager } from '../utils/image'
import { isLinux } from '../utils/platform'
import config from '../config'
-import bolt from '../assets/bolt.svg'
-import cable from '../assets/cable.svg'
-import deviceExclamation from '../assets/device_exclamation_c3.svg'
-import deviceQuestion from '../assets/device_question_c3.svg'
-import done from '../assets/done.svg'
-import exclamation from '../assets/exclamation.svg'
-import systemUpdate from '../assets/system_update_c3.svg'
-
-
const steps = {
[StepCode.INITIALIZING]: {
status: 'Initializing...',
bgColor: 'bg-gray-400 dark:bg-gray-700',
- icon: bolt,
+ icon: 'bolt',
},
[StepCode.READY]: {
status: 'Tap to start',
bgColor: 'bg-[#51ff00]',
- icon: bolt,
+ icon: 'bolt',
iconStyle: '',
},
[StepCode.CONNECTING]: {
status: 'Waiting for connection',
description: 'Follow the instructions to connect your device to your computer',
bgColor: 'bg-yellow-500',
- icon: cable,
+ icon: 'cable',
},
[StepCode.REPAIR_PARTITION_TABLES]: {
status: 'Repairing partition tables...',
description: 'Do not unplug your device until the process is complete',
bgColor: 'bg-lime-400',
- icon: systemUpdate,
+ icon: 'systemUpdate',
},
[StepCode.ERASE_DEVICE]: {
status: 'Erasing device...',
description: 'Do not unplug your device until the process is complete',
bgColor: 'bg-lime-400',
- icon: systemUpdate,
+ icon: 'systemUpdate',
},
[StepCode.FLASH_SYSTEM]: {
status: 'Flashing device...',
description: 'Do not unplug your device until the process is complete',
bgColor: 'bg-lime-400',
- icon: systemUpdate,
+ icon: 'systemUpdate',
},
[StepCode.FINALIZING]: {
status: 'Finalizing...',
description: 'Do not unplug your device until the process is complete',
bgColor: 'bg-lime-400',
- icon: systemUpdate,
+ icon: 'systemUpdate',
},
[StepCode.DONE]: {
status: 'Done',
description: 'Your device was flashed successfully. It should now boot into the openpilot setup.',
bgColor: 'bg-green-500',
- icon: done,
+ icon: 'done',
},
}
@@ -69,7 +60,7 @@ const errors = {
status: 'Unknown error',
description: 'An unknown error has occurred. Unplug your device, restart your browser and try again.',
bgColor: 'bg-red-500',
- icon: exclamation,
+ icon: 'exclamation',
},
[ErrorCode.REQUIREMENTS_NOT_MET]: {
status: 'Requirements not met',
@@ -85,30 +76,30 @@ const errors = {
description: 'The device connected to your computer is not supported. Try using a different cable, USB port, or ' +
'computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
bgColor: 'bg-yellow-500',
- icon: deviceQuestion,
+ icon: 'deviceQuestion',
},
[ErrorCode.LOST_CONNECTION]: {
status: 'Lost connection',
description: 'The connection to your device was lost. Unplug your device and try again.',
- icon: cable,
+ icon: 'cable',
},
[ErrorCode.REPAIR_PARTITION_TABLES_FAILED]: {
status: 'Repairing partition tables failed',
description: 'Your device\'s partition tables could not be repaired. Try using a different cable, USB port, or ' +
'computer. If the problem persists, join the #hw-three-3x channel on Discord for help.',
- icon: deviceExclamation,
+ icon: 'deviceExclamation',
},
[ErrorCode.ERASE_FAILED]: {
status: 'Erase failed',
description: 'The device could not be erased. Try using a different cable, USB port, or computer. If the problem ' +
'persists, join the #hw-three-3x channel on Discord for help.',
- icon: deviceExclamation,
+ icon: 'deviceExclamation',
},
[ErrorCode.FLASH_SYSTEM_FAILED]: {
status: 'Flash failed',
description: 'AGNOS could not be flashed to your device. Try using a different cable, USB port, or computer. If ' +
'the problem persists, join the #hw-three-3x channel on Discord for help.',
- icon: deviceExclamation,
+ icon: 'deviceExclamation',
},
}
@@ -171,7 +162,7 @@ function beforeUnloadListener(event) {
}
-export default function Flash() {
+export default function Flash () {
const [step, setStep] = useState(StepCode.INITIALIZING)
const [message, setMessage] = useState('')
const [progress, setProgress] = useState(-1)
@@ -183,7 +174,7 @@ export default function Flash() {
const imageManager = useImageManager()
useEffect(() => {
- if (!imageManager.current) return
+ if (!imageManager.current || imageManager.current.root) return
fetch(config.loader.url)
.then((res) => res.arrayBuffer())
@@ -246,13 +237,12 @@ export default function Flash() {
style={{ cursor: canStart ? 'pointer' : 'default' }}
onClick={canStart ? handleStart : null}
>
-
+ className={`${iconStyle} ${!error && step !== StepCode.DONE ? 'animate-pulse' : ''}`}>
+
+
diff --git a/src/components/platform-gate.tsx b/src/components/platform-gate.tsx
new file mode 100644
index 00000000..705848ce
--- /dev/null
+++ b/src/components/platform-gate.tsx
@@ -0,0 +1,15 @@
+import { platform as currentPlatform } from "../utils/platform";
+
+type PlatformGateProps = {
+ platform: string;
+ children: preact.ComponentChildren;
+};
+
+const PlatformGate = ({ platform: targetPlatform, children }: PlatformGateProps) => {
+ if ((currentPlatform as string).toLowerCase() === targetPlatform.toLowerCase()) {
+ return <>{children}>;
+ }
+ return null;
+};
+
+export default PlatformGate;
\ No newline at end of file
diff --git a/index.html b/src/layouts/base-layout.astro
similarity index 58%
rename from index.html
rename to src/layouts/base-layout.astro
index 33a000a3..51a019cc 100644
--- a/index.html
+++ b/src/layouts/base-layout.astro
@@ -1,22 +1,28 @@
+---
+import "../index.css";
+
+import { Font } from "astro:assets";
+---
+
-
+
flash.comma.ai
+
+
-
-
+
+ src="https://plausible.io/js/script.outbound-links.js">
diff --git a/src/main.jsx b/src/main.jsx
deleted file mode 100644
index 40fea3ef..00000000
--- a/src/main.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-
-import '@fontsource-variable/inter'
-import '@fontsource-variable/jetbrains-mono'
-
-import './index.css'
-import App from './app'
-
-ReactDOM.createRoot(document.getElementById('root')).render(
-
-
- ,
-)
diff --git a/src/pages/index.astro b/src/pages/index.astro
new file mode 100644
index 00000000..cd1ee29e
--- /dev/null
+++ b/src/pages/index.astro
@@ -0,0 +1,227 @@
+---
+import BaseLayout from "../layouts/base-layout.astro";
+import CommaLogo from "../assets/comma.svg";
+import QDLPorts from "../assets/qdl-ports.svg";
+import Bolt from "../assets/bolt.svg";
+import Cable from "../assets/cable.svg";
+import DeviceExclamation from "../assets/device_exclamation_c3.svg";
+import DeviceQuestion from "../assets/device_question_c3.svg";
+import Done from "../assets/done.svg";
+import Exclamation from "../assets/exclamation.svg";
+import SystemUpdate from "../assets/system_update_c3.svg";
+
+import zadigCreateNewDevice from "../assets/zadig_create_new_device.png";
+import zadigForm from "../assets/zadig_form.png";
+
+import PlatformGate from "../components/platform-gate";
+import Flash from "../components/flash";
+import CopyText from "../components/copy-text.astro";
+
+const version = import.meta.env.ASTRO_PUBLIC_GIT_SHA || "dev";
+const VENDOR_ID = "0x2b3c";
+const PRODUCT_ID = "0x0001";
+const DETACH_SCRIPT =
+ 'for d in /sys/bus/usb/drivers/qcserial/*-*; do [ -e "$d" ] && echo -n "$(basename $d)" | sudo tee /sys/bus/usb/drivers/qcserial/unbind > /dev/null; done';
+---
+
+
+
+
+
+
+
flash.comma.ai
+
+ This tool allows you to flash AGNOS onto your comma device. AGNOS is
+ the Ubuntu-based operating system for your comma 3/3X.
+
+
+
+
+
+
Requirements
+
+
+ A web browser which supports WebUSB
+ {" "}(such as Google Chrome, Microsoft Edge, Opera), running on
+ Windows, macOS, Linux, or Android.
+
+
+ A good quality USB-C cable to connect the device to your computer. USB 3
+ {" "}is recommended for faster flashing speed.
+
+
+ Another USB-C cable and a charger, to power the device outside your
+ car.
+
+
+
+ <>
+
USB Driver
+
+ You need additional driver software for Windows before you connect
+ your device.
+
+ Under Device in the menu bar, select{" "}
+ Create New Device.
+
+
+
+ Fill in three fields. The first field is just a description and
+ you can fill in anything. The next two fields are very
+ important. Fill them in with {VENDOR_ID} and{" "}
+ {PRODUCT_ID}
+ respectively. Press "Install Driver" and give it a few
+ minutes to install.
+
+
+
+
+ No additional software is required for macOS, Linux or Android.
+
+ >
+
+
+
+
+
+
Flashing
+
Follow these steps to put your device into QDL mode:
+
+
Unplug the device and wait for the LED to switch off.
+
+ First, connect the device to your computer using the lower
+ USB-C port (port 1).
+
+
+ Second, connect power to the upper
+ OBD-C port (port 2).
+
+
+
+
+ Your device's screen will remain blank for the entire flashing
+ process. This is normal.
+
+
+ <>
+ Note for Linux users
+
+ On Linux systems, devices in QDL mode are automatically bound to
+ the kernel's qcserial driver, and need to be unbound before
+ we can access the device. Copy the script below into your terminal
+ and run it after plugging in your device.
+
+
+ >
+
+
+ Next, click the button to start flashing. From the prompt select the
+ device which starts with “QUSB_BULK”.
+
+
+ The process can take 30+ minutes depending on your internet connection
+ and system performance. Do not unplug the device until all steps are
+ complete.
+
+
+
+
+
+
Troubleshooting
+
Lost connection
+
+ Try using high quality USB 3 cables. You should also try different USB
+ ports on the front or back of your computer. If you're using a
+ USB hub, try connecting directly to your computer instead.
+
+
My device's screen is blank
+
+ This is normal in QDL mode. You can verify that the
+ “QUSB_BULK” device shows up when you press the Flash
+ button to know that it is working correctly.
+
+
My device says “fastboot mode”
+
+ You may have followed outdated instructions for flashing. Please read
+ the instructions above for putting your device into QDL mode.
+
+
General Tips
+
+
Try another computer or OS
+
Try different USB ports on your computer
+
+ Try different USB-C cables; low quality cables are often the source
+ of problems. Note that the included OBD-C cable will not work.
+
+
+
Other questions
+
+ If you need help, join our Discord server and go to the #hw-three-3x channel.
+
+
+
+
+
+ flash.comma.ai version: {version}
+
+
+
+
+
+
+
+
+ flash.comma.ai version: {version}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/index.test.ts b/src/test/index.test.ts
new file mode 100644
index 00000000..ee2534e3
--- /dev/null
+++ b/src/test/index.test.ts
@@ -0,0 +1,19 @@
+import { experimental_AstroContainer as AstroContainer } from "astro/container";
+import { expect, test } from "vitest";
+import preactRenderer from "@astrojs/preact/server.js";
+import HomePage from "../pages/index.astro";
+
+test("renders without crashing", async () => {
+ const container = await AstroContainer.create();
+ container.addServerRenderer({
+ name: "@astrojs/preact",
+ renderer: preactRenderer,
+ });
+ container.addClientRenderer({
+ name: "@astrojs/preact",
+ entrypoint: "@astrojs/preact/client.js",
+ });
+ const result = await container.renderToString(HomePage);
+
+ expect(result).toContain("flash.comma.ai");
+});
diff --git a/src/test/setup.js b/src/test/setup.js
deleted file mode 100644
index c44951a6..00000000
--- a/src/test/setup.js
+++ /dev/null
@@ -1 +0,0 @@
-import '@testing-library/jest-dom'
diff --git a/src/test/setup.ts b/src/test/setup.ts
new file mode 100644
index 00000000..0d51b9ce
--- /dev/null
+++ b/src/test/setup.ts
@@ -0,0 +1,25 @@
+// JSDom + Vitest don't play well with each other. Long story short - default
+// TextEncoder produces Uint8Array objects that are _different_ from the global
+// Uint8Array objects, so some functions that compare their types explode.
+// https://github.com/vitest-dev/vitest/issues/4043#issuecomment-1905172846
+class ESBuildAndJSDOMCompatibleTextEncoder extends TextEncoder {
+ constructor() {
+ super();
+ }
+
+ encode(input: string) {
+ if (typeof input !== "string") {
+ throw new TypeError("`input` must be a string");
+ }
+
+ const decodedURI = decodeURIComponent(encodeURIComponent(input));
+ const arr = new Uint8Array(decodedURI.length);
+ const chars = decodedURI.split("");
+ for (let i = 0; i < chars.length; i++) {
+ arr[i] = decodedURI[i].charCodeAt(0);
+ }
+ return arr;
+ }
+}
+
+global.TextEncoder = ESBuildAndJSDOMCompatibleTextEncoder;
diff --git a/src/utils/image.js b/src/utils/image.js
index e1977f97..d7d88c82 100644
--- a/src/utils/image.js
+++ b/src/utils/image.js
@@ -1,4 +1,4 @@
-import { useEffect, useRef } from 'react'
+import { useEffect, useRef } from 'preact/hooks'
import { XzReadableStream } from 'xz-decompress'
import { fetchStream } from './stream'
diff --git a/src/utils/platform.js b/src/utils/platform.js
index f022d8da..2addc60e 100644
--- a/src/utils/platform.js
+++ b/src/utils/platform.js
@@ -1,12 +1,17 @@
-const platform = (() => {
- if ('userAgentData' in navigator && 'platform' in navigator.userAgentData && navigator.userAgentData.platform) {
- return navigator.userAgentData.platform
+export const platform = (() => {
+ if (
+ "userAgentData" in navigator &&
+ "platform" in navigator.userAgentData &&
+ navigator.userAgentData.platform
+ ) {
+ return navigator.userAgentData.platform;
}
- const userAgent = navigator.userAgent.toLowerCase()
- if (userAgent.includes('linux')) return 'Linux' // includes Android
- if (userAgent.includes('win32') || userAgent.includes('windows')) return 'Windows'
- return null
-})()
+ const userAgent = navigator.userAgent.toLowerCase();
+ if (userAgent.includes("linux")) return "Linux"; // includes Android
+ if (userAgent.includes("win32") || userAgent.includes("windows"))
+ return "Windows";
+ return null;
+})();
-export const isWindows = !platform || platform === 'Windows'
-export const isLinux = platform === 'Linux'
+export const isWindows = !platform || platform === "Windows";
+export const isLinux = platform === "Linux";
diff --git a/tailwind.config.js b/tailwind.config.js
index f1f20e20..defcf3c3 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,8 +1,7 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
- './index.html',
- './src/**/*.{js,jsx}',
+ './src/**/*.{astro,js,jsx,ts,tsx}',
],
theme: {
extend: {
@@ -12,8 +11,7 @@ export default {
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
fontFamily: {
- sans: ['Inter Variable', 'sans-serif'],
- monospace: ['JetBrains Mono Variable', 'monospace'],
+ sans: ['var(--font-inter)'],
},
},
},
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 00000000..3832a3d4
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "astro/tsconfigs/strict",
+ "include": [
+ ".astro/types.d.ts",
+ "**/*"
+ ],
+ "exclude": [
+ "dist"
+ ],
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "preact"
+ }
+}
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
deleted file mode 100644
index 17ac32a0..00000000
--- a/vite.config.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-
-// https://vitejs.dev/config/
-export default defineConfig({
- plugins: [react()],
- test: {
- globals: true,
- environment: 'jsdom',
- setupFiles: './src/test/setup.js',
- },
-})
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 00000000..17097e0f
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,9 @@
+///
+import { getViteConfig } from "astro/config";
+
+export default getViteConfig({
+ test: {
+ environment: "jsdom",
+ setupFiles: "./src/test/setup.ts",
+ },
+});