Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ bun dev
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.

You can start editing the page by modifying `src/app/index.jsx`. The page auto-updates as you edit the file.

**Helpful for debugging**
* [chrome://usb-internals/](chrome://usb-internals/)
* [chrome://device-log/](chrome://device-log/)
* Add `?fast=1` to the URL to skip flashing the system partition (the slowest). Useful for testing the full flow quickly.
* Add `?windows=1` to the URL to force Windows mode (shows Zadig driver instructions). Useful for testing the Windows flow on other platforms.
10 changes: 10 additions & 0 deletions deploy-preview.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash
set -e

BRANCH="${1:-$(git branch --show-current)}"

bun run build
bunx wrangler pages deploy dist --project-name=connect --branch="$BRANCH"

echo ""
echo "Preview URL: https://${BRANCH}.connect-d5y.pages.dev"
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Update your comma device to the latest software"
content="Restore your comma device to a fresh factory state"
/>
<title>flash.comma.ai</title>
</head>
Expand Down
616 changes: 544 additions & 72 deletions src/app/Flash.jsx

Large diffs are not rendered by default.

204 changes: 37 additions & 167 deletions src/app/index.jsx
Original file line number Diff line number Diff line change
@@ -1,180 +1,50 @@
import { Suspense, lazy } from 'react'
import Flash from './Flash'

import comma from '../assets/comma.svg'
import qdlPortsFour from '../assets/qdl-ports-four.svg'
import qdlPortsThree from '../assets/qdl-ports-three.svg'
import zadigCreateNewDevice from '../assets/zadig_create_new_device.png'
import zadigForm from '../assets/zadig_form.png'

import { isLinux, isWindows } from '../utils/platform'

const Flash = lazy(() => import('./Flash'))
function DiscordIcon({ className }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
</svg>
)
}

const VENDOR_ID = '05C6'
const PRODUCT_ID = '9008'
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';
function GitHubIcon({ className }) {
return (
<svg className={className} viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z" />
</svg>
)
}

function CopyText({ children: text }) {
return <div className="relative text-sm">
<pre className="font-mono pt-12">{text}</pre>
<button
className="absolute top-2 right-2 bg-blue-600 hover:bg-blue-500 active:bg-blue-300 transition-colors text-white p-1 rounded-md"
onClick={() => navigator.clipboard.writeText(text)}
>
Copy
</button>
</div>;
function CommaIcon({ className }) {
return (
<svg className={className} viewBox="2 3 42 42" fill="currentColor">
<path fillRule="evenodd" clipRule="evenodd" d="M16.6964 40C16.6964 39.2596 16.6385 38.6393 16.7236 38.0415C16.7599 37.7865 17.0575 37.5135 17.3001 37.3595C18.4832 36.6087 19.7684 36.0092 20.8699 35.1481C24.4378 32.3587 26.5526 28.6866 26.6682 23.9166C26.7009 22.5622 26.203 22.2238 25.0654 22.7514C21.7817 24.2746 18.2505 23.3815 16.3659 20.5509C14.3107 17.4636 14.6001 13.3531 17.0626 10.6562C20.2079 7.21156 25.3833 7.10849 28.9522 10.3982C31.09 12.3688 32.1058 14.9132 32.3591 17.8074C33.2084 27.5032 28.3453 35.495 19.4941 39.0057C18.6181 39.353 17.7198 39.6382 16.6964 40Z" />
</svg>
)
}

export default function App() {
const version = import.meta.env.VITE_PUBLIC_GIT_SHA || 'dev'
console.info(`flash.comma.ai version: ${version}`)
return (
<div className="flex flex-col lg:flex-row flex-wrap">
<main className="p-12 md:p-16 lg:p-20 xl:p-24 w-screen max-w-none lg:max-w-prose lg:w-auto h-auto lg:h-screen lg:overflow-y-auto prose dark:prose-invert prose-green bg-white dark:bg-gray-900">
<section>
<img src={comma} alt="comma" width={128} height={128} className="dark:invert" />
<h1>flash.comma.ai</h1>
<p>
This tool allows you to flash AGNOS onto your comma device. AGNOS is the Ubuntu-based operating system for
your <a href="https://comma.ai/shop/comma-four" target="_blank">comma 3/3X/4</a>.
</p>
</section>
<hr />

<section>
<h2>Requirements</h2>
<ul>
<li>
A web browser which supports <a href="https://caniuse.com/webusb" target="_blank">WebUSB</a>
{" "}(such as Google Chrome, Microsoft Edge, Opera), running on Windows, macOS, Linux, or Android.
</li>
<li>
A good quality USB-C cable to connect the device to your computer. <span title="SuperSpeed">USB 3</span>
{" "}is recommended for faster flashing speed.
</li>
<li>
Another USB-C cable and a charger, to power the device outside your car.
</li>
</ul>
{isWindows && (<>
<h3>USB Driver</h3>
<p>You need additional driver software for Windows before you connect your device.</p>
<ol>
<li>
Download and run <a href="https://zadig.akeo.ie/" target="_blank">Zadig</a>.
</li>
<li>
Under <code>Device</code> in the menu bar, select <code>Create New Device</code>.
<img
src={zadigCreateNewDevice}
alt="Zadig Create New Device"
width={575}
height={254}
/>
</li>
<li>
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 <code>{VENDOR_ID}</code> and <code>{PRODUCT_ID}</code>
respectively. Press &quot;Install Driver&quot; and give it a few minutes to install.
<img
src={zadigForm}
alt="Zadig Form"
width={575}
height={254}
/>
</li>
</ol>
<p>No additional software is required for macOS, Linux or Android.</p>
</>)}
</section>
<hr />

<section>
<h2>Flashing</h2>
<p>Follow these steps to put your device into QDL mode:</p>
<ol>
<li>Unplug the device and wait for the LED to switch off.</li>
<li>First, connect the device to your computer using the <strong>lower</strong> <span className="whitespace-nowrap">USB-C</span> port <strong>(port 1)</strong>.</li>
<li>Second, connect power to the <strong>upper</strong> <span className="whitespace-nowrap">OBD-C</span> port <strong>(port 2)</strong>.</li>
</ol>
<img
src={qdlPortsFour}
alt="image showing comma four and two ports. the right port is labeled 1. the left port is labeled 2."
width={225}
height={150}
/>
<img
src={qdlPortsThree}
alt="image showing comma three and two ports. the lower port is labeled 1. the upper port is labeled 2."
width={450}
height={300}
/>
<p>Your device&apos;s screen will remain blank for the entire flashing process. This is normal.</p>
{isLinux && (<>
<strong>Note for Linux users</strong>
<p>
On Linux systems, devices in QDL mode are automatically bound to the kernel&apos;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.
</p>
<CopyText>{DETACH_SCRIPT}</CopyText>
</>)}
<p>
Next, click the button to start flashing. From the prompt select the device which starts with
&ldquo;QUSB_BULK&rdquo;.
</p>
<p>
The process can take 30+ minutes depending on your internet connection and system performance. Do not
unplug the device until all steps are complete.
</p>
</section>
<hr />

<section>
<h2>Troubleshooting</h2>
<h3>Lost connection</h3>
<p>
Try using high quality USB 3 cables. You should also try different USB ports on the front or back of your
computer. If you&apos;re using a USB hub, try connecting directly to your computer instead.
</p>
<h3>My device&apos;s screen is blank</h3>
<p>
This is normal in QDL mode. You can verify that the &ldquo;QUSB_BULK&rdquo; device shows up when you press
the Flash button to know that it is working correctly.
</p>
<h3>My device says &ldquo;fastboot mode&rdquo;</h3>
<p>
You may have followed outdated instructions for flashing. Please read the instructions above for putting
your device into QDL mode.
</p>
<h3>General Tips</h3>
<ul>
<li>Try another computer or OS</li>
<li>Try different USB ports on your computer</li>
<li>Try different USB-C cables; low quality cables are often the source of problems. Note that the included OBD-C cable will not work.</li>
</ul>
<h3>Other questions</h3>
<p>
If you need help, join our <a href="https://discord.comma.ai" target="_blank">Discord server</a> and go to
the <strong>#hw-three-3x</strong> channel.
</p>
</section>

<div className="hidden lg:block">
<hr />
flash.comma.ai version: <a href={`https://github.com/commaai/flash/tree/${version}`} target="_blank"><code>{version}</code></a>
</div>
</main>

<div className="lg:flex-1 h-[700px] lg:h-screen bg-gray-100 dark:bg-gray-800">
<Suspense fallback={<p className="text-black dark:text-white">Loading...</p>}>
<Flash />
</Suspense>
<div className="h-screen w-screen bg-gray-100">
<Flash />
<div className="absolute top-4 right-4 flex items-center gap-6">
<a href="https://comma.ai" target="_blank" rel="noopener noreferrer" title="comma.ai" className="text-gray-400 hover:text-[#51ff00] transition-colors">
<CommaIcon className="w-12 h-12" />
</a>
<a href="https://discord.comma.ai" target="_blank" rel="noopener noreferrer" title="Discord" className="text-gray-400 hover:text-[#51ff00] transition-colors">
<DiscordIcon className="w-12 h-12" />
</a>
<a href="https://github.com/commaai/flash" target="_blank" rel="noopener noreferrer" title="GitHub" className="text-gray-400 hover:text-[#51ff00] transition-colors">
<GitHubIcon className="w-12 h-12" />
</a>
</div>

<div className="w-screen max-w-none p-12 md:p-16 prose dark:prose-invert bg-white dark:bg-gray-900 lg:hidden">
flash.comma.ai version: <a href={`https://github.com/commaai/flash/tree/${version}`} target="_blank"><code>{version}</code></a>
<div className="absolute bottom-4 left-4 text-sm text-gray-500">
<a href={`https://github.com/commaai/flash/tree/${version}`} target="_blank" className="hover:underline">
{version}
</a>
</div>
</div>
)
Expand Down
Binary file added src/assets/comma3X.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/four_screen_on.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/qdl-ports-four.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/qdl-ports-three.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const config = {
release_mici: 'https://raw.githubusercontent.com/commaai/openpilot/ccf7361798a2b7ff36f5065dffb602eb40c22302/system/hardware/tici/all-partitions.json',
release_tizi: 'https://raw.githubusercontent.com/commaai/openpilot/927548621be1be0c2c9063868b93d1f5020904de/system/hardware/tici/all-partitions.json',
release_tici: 'https://raw.githubusercontent.com/commaai/openpilot/927548621be1be0c2c9063868b93d1f5020904de/system/hardware/tici/all-partitions.json',
master: 'https://raw.githubusercontent.com/commaai/openpilot/master/system/hardware/tici/all-partitions.json',
},
loader: {
url: 'https://raw.githubusercontent.com/commaai/flash/master/src/QDL/programmer.bin',
Expand Down
6 changes: 6 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

::selection {
background-color: #51ff00;
color: black;
}

20 changes: 15 additions & 5 deletions src/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,18 @@ import '@fontsource-variable/jetbrains-mono'
import './index.css'
import App from './app'

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
// Explicitly load fonts before rendering to prevent FOUT
async function loadFonts() {
await Promise.all([
document.fonts.load('16px "Inter Variable"'),
document.fonts.load('16px "JetBrains Mono Variable"'),
])
}

loadFonts().then(() => {
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
})
18 changes: 13 additions & 5 deletions src/utils/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { fetchStream } from './stream'
* @returns {void}
*/

const MIN_QUOTA_MB = 5250
const MIN_QUOTA_GB = 5.25

export class ImageManager {
/** @type {FileSystemDirectoryHandle} */
Expand All @@ -20,14 +20,22 @@ export class ImageManager {
async init() {
if (!this.root) {
this.root = await navigator.storage.getDirectory()
await this.root.remove({ recursive: true })
// Clean up any leftover files from previous sessions
try {
await this.root.remove({ recursive: true })
} catch (e) {
// Ignore errors - directory might not exist or be empty
console.debug('[ImageManager] Could not remove old directory:', e)
}
// Re-get the directory after removal
this.root = await navigator.storage.getDirectory()
console.info('[ImageManager] Initialized')
}

const estimate = await navigator.storage.estimate()
const quotaMB = (estimate.quota || 0) / (1024 ** 2)
if (quotaMB < MIN_QUOTA_MB) {
throw new Error(`Not enough storage: ${quotaMB.toFixed(0)}MB free, need ${MIN_QUOTA_MB.toFixed(0)}MB`)
const quotaGB = (estimate.quota || 0) / (1024 ** 3)
if (quotaGB < MIN_QUOTA_GB) {
throw new Error(`Not enough storage: ${quotaGB.toFixed(1)}GB free, need ${MIN_QUOTA_GB.toFixed(1)}GB`)
}
}

Expand Down
Loading