Skip to content
Closed
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ 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.
You can start editing the page by modifying `src/app/index.js`. The page auto-updates as you edit the file.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.jsx"></script>
<script type="module" src="./src/main.js"></script>
<script
defer
data-domain="flash.comma.ai"
Expand Down
7 changes: 0 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,10 @@
"@commaai/qdl": "git+https://github.com/commaai/qdl.js.git#52021f0b1ace58673ebca1fae740f6900ebff707",
"@fontsource-variable/inter": "^5.2.5",
"@fontsource-variable/jetbrains-mono": "^5.2.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"xz-decompress": "^0.2.2"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.16",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@types/react": "^18.3.20",
"@types/react-dom": "^18.3.6",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "10.4.21",
"jsdom": "^26.0.0",
"postcss": "^8.5.3",
Expand Down
10 changes: 0 additions & 10 deletions src/app/App.test.jsx

This file was deleted.

227 changes: 227 additions & 0 deletions src/app/Flash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { FlashManager, StepCode, ErrorCode } from '../utils/manager'
import { createImageManager } 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,
},
[StepCode.READY]: {
status: 'Tap to start',
bgColor: 'bg-[#51ff00]',
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,
},
[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,
},
[StepCode.ERASE_DEVICE]: {
status: 'Erasing device...',
description: 'Do not unplug your device until the process is complete',
bgColor: 'bg-lime-400',
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,
},
[StepCode.FINALIZING]: {
status: 'Finalizing...',
description: 'Do not unplug your device until the process is complete',
bgColor: 'bg-lime-400',
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,
},
}

const errors = {
[ErrorCode.UNKNOWN]: {
status: 'Unknown error',
description: 'An unknown error has occurred. Unplug your device, restart your browser and try again.',
bgColor: 'bg-red-500',
icon: exclamation,
},
[ErrorCode.REQUIREMENTS_NOT_MET]: {
status: 'Requirements not met',
description: 'Your system does not meet the requirements to flash your device. Make sure to use a browser which supports WebUSB and is up to date.',
},
[ErrorCode.STORAGE_SPACE]: {
description: 'Your system does not have enough space available to download AGNOS. Your browser may be restricting the available space if you are in a private, incognito or guest session.',
},
[ErrorCode.UNRECOGNIZED_DEVICE]: {
status: 'Unrecognized device',
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,
},
[ErrorCode.LOST_CONNECTION]: {
status: 'Lost connection',
description: 'The connection to your device was lost. Unplug your device and try again.',
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,
},
[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,
},
[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,
},
}

if (isLinux) {
errors[ErrorCode.LOST_CONNECTION].description += ' Did you forget to unbind the device from qcserial?'
}

function beforeUnloadListener(event) {
event.preventDefault()
return (event.returnValue = "Flash in progress. Are you sure you want to leave?")
}

class FlashView extends HTMLElement {
constructor() {
super()
this.step = StepCode.INITIALIZING
this.message = ''
this.progress = -1
this.error = ErrorCode.NONE
this.connected = false
this.serial = null

this.qdlManager = null
this.imageManager = createImageManager()
}

connectedCallback() {
this.render()
fetch(config.loader.url)
.then(res => res.arrayBuffer())
.then(programmer => {
this.qdlManager = new FlashManager(config.manifests.release, programmer, {
onStepChange: s => { this.step = s; this.render() },
onMessageChange: m => { this.message = m; this.render() },
onProgressChange: p => { this.progress = p; this.render() },
onErrorChange: e => { this.error = e; this.render() },
onConnectionChange: c => { this.connected = c; this.render() },
onSerialChange: s => { this.serial = s; this.render() },
})
return this.qdlManager.initialize(this.imageManager)
})
.catch(err => {
console.error('Error initializing Flash manager:', err)
this.error = ErrorCode.UNKNOWN
this.render()
})

this.addEventListener('click', this.handleClick)
}

disconnectedCallback() {
this.removeEventListener('click', this.handleClick)
window.removeEventListener('beforeunload', beforeUnloadListener, { capture: true })
}

handleClick = (e) => {
const { id } = e.target
if (id === 'flash-start' && this.canStart()) {
this.qdlManager?.start()
} else if (id === 'flash-retry') {
window.location.reload()
}
}

canStart() {
return this.step === StepCode.READY && !this.error
}

render() {
const uiState = { ...steps[this.step] }
if (this.error) {
Object.assign(uiState, errors[ErrorCode.UNKNOWN], errors[this.error])
}
const { status, description, bgColor, icon, iconStyle = 'invert' } = uiState

let title
if (this.message && !this.error) {
title = this.message + '...'
if (this.progress >= 0) {
title += ` (${(this.progress * 100).toFixed(0)}%)`
}
} else if (this.error === ErrorCode.STORAGE_SPACE) {
title = this.message
} else {
title = status
}

if (this.step >= StepCode.REPAIR_PARTITION_TABLES && this.step <= StepCode.FINALIZING) {
window.addEventListener('beforeunload', beforeUnloadListener, { capture: true })
} else {
window.removeEventListener('beforeunload', beforeUnloadListener, { capture: true })
}

this.innerHTML = `
<div class="relative flex flex-col gap-8 justify-center items-center h-full">
<div id="flash-start" class="p-8 rounded-full ${bgColor}" style="cursor: ${this.canStart() ? 'pointer' : 'default'}">
<img src="${icon}" alt="cable" width="128" height="128" class="${iconStyle} ${!this.error && this.step !== StepCode.DONE ? 'animate-pulse' : ''}" />
</div>
<div class="w-full max-w-3xl px-8 transition-opacity duration-300" style="opacity: ${this.progress === -1 ? 0 : 1}">
<div class="relative w-full h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="absolute top-0 bottom-0 left-0 w-full transition-all ${bgColor}" style="transform: translateX(${(this.progress * 100) - 100}%)"></div>
</div>
</div>
<span class="text-3xl dark:text-white font-mono font-light">${title}</span>
<span class="text-xl dark:text-white px-8 max-w-xl">${description || ''}</span>
${this.error ? `<button id="flash-retry" class="px-4 py-2 rounded-md bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200 transition-colors">Retry</button>` : ''}
${this.connected ? `
<div class="absolute bottom-0 m-0 lg:m-4 p-4 w-full sm:w-auto sm:min-w-[350px] sm:border sm:border-gray-200 dark:sm:border-gray-600 bg-white dark:bg-gray-700 text-black dark:text-white rounded-md flex flex-row gap-2" style="left:50%; transform:translate(-50%, -50%);">
<div class="flex flex-row gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 96 960 960" class="text-green-500 dark:text-[#51ff00]" height="24" width="24"><path fill="currentColor" d="M480 976q-32 0-52-20t-20-52q0-22 11-40t31-29V724H302q-24 0-42-18t-18-42V555q-20-9-31-26.609-11-17.608-11-40.108Q200 456 220 436t52-20q32 0 52 20t20 52.411Q344 511 333 528.5T302 555v109h148V324h-80l110-149 110 149h-80v340h148V560h-42V416h144v144h-42v104q0 24-18 42t-42 18H510v111q19.95 10.652 30.975 29.826Q552 884 552 904q0 32-20 52t-52 20Z"/></svg>
Device connected
</div>
<span class="text-gray-400">|</span>
<div class="flex flex-row gap-2">
<span>Serial:<span class="ml-2 font-mono">${this.serial || 'unknown'}</span></span>
</div>
</div>` : ''}
</div>
`
}
}

customElements.define('flash-view', FlashView)
export default FlashView

Loading
Loading