Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dfu support and demo page for dfu #125

Merged
merged 1 commit into from
Jun 21, 2024
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
2 changes: 0 additions & 2 deletions eslint-local-rules.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ const _ = require('lodash')
const eslintPluginTsdoc = require('eslint-plugin-tsdoc')
const fs = require('fs')

fs.writeFileSync('./debug.txt', 'test\r\n', { flag: 'as' })

function eslintPluginTsdocPatch () {
const origRule = eslintPluginTsdoc.rules.syntax
return {
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@taichunmin/buffer": "^0.13.6",
"debug": "^4.3.4",
"jszip": "^3.10.1",
"lodash": "^4.17.21",
"serialport": "^12.0.0",
"web-serial-polyfill": "^1.0.15",
Expand Down Expand Up @@ -99,6 +100,15 @@
"require": "./dist/Crypto1.js",
"script": "./dist/Crypto1.global.js"
},
"./plugin/DfuZip": {
"types": {
"import": "./dist/plugin/DfuZip.d.mts",
"require": "./dist/plugin/DfuZip.d.ts"
},
"import": "./dist/plugin/DfuZip.mjs",
"require": "./dist/plugin/DfuZip.js",
"script": "./dist/plugin/DfuZip.global.js"
},
"./plugin/SerialPortAdapter": {
"types": {
"import": "./dist/plugin/SerialPortAdapter.d.mts",
Expand Down
30 changes: 25 additions & 5 deletions pages/demos.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
- [Demos](#demos)
- [device-settings.html](#device-settingshtml)
- [Features](#features)
- [mfkey32.html](#mfkey32html)
- [dfu.html](#dfuhtml)
- [Features](#features-1)
- [Related links](#related-links)
- [mifare1k.html](#mifare1khtml)
- [mfkey32.html](#mfkey32html)
- [Features](#features-2)
- [hf14a-scanner.html](#hf14a-scannerhtml)
- [Related links](#related-links-1)
- [mifare1k.html](#mifare1khtml)
- [Features](#features-3)
- [mifare-xiaomi.html](#mifare-xiaomihtml)
- [hf14a-scanner.html](#hf14a-scannerhtml)
- [Features](#features-4)
- [mifare-value.html](#mifare-valuehtml)
- [mifare-xiaomi.html](#mifare-xiaomihtml)
- [Features](#features-5)
- [mifare-value.html](#mifare-valuehtml)
- [Features](#features-6)

## [device-settings.html](https://taichunmin.idv.tw/chameleon-ultra.js/device-settings.html)

Expand All @@ -33,6 +36,23 @@ A ChameleonUltra tool to management the device info and settings.

- - -

## [dfu.html](https://taichunmin.idv.tw/chameleon-ultra.js/dfu.html)

A tool to upload firmware to ChameleonUltra.

### Features

- Select tag to upload firmware

### Related links

- [Uploading the code in DFU mode](https://github.com/RfidResearchGroup/ChameleonUltra/blob/main/docs/development.md#uploading-the-code-in-dfu-mode)
- [nRF5 SDK: DFU protocol](https://docs.nordicsemi.com/bundle/sdk_nrf5_v17.1.0/page/lib_dfu_transport.html)
- [GitHub: GameTec-live/ChameleonUltraGUI](https://github.com/GameTec-live/ChameleonUltraGUI/blob/main/chameleonultragui/lib/bridge/dfu.dart)
- [GitHub: thegecko/web-bluetooth-dfu](https://github.com/thegecko/web-bluetooth-dfu)

- - -

## [mfkey32.html](https://taichunmin.idv.tw/chameleon-ultra.js/mfkey32.html)

A ChameleonUltra tool to detect the mifare key that reader is authenticating (a.k.a. MFKey32).
Expand Down
1 change: 1 addition & 0 deletions pug/include/bootstrapV4.pug
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ html(lang="zh-Hant")
script(crossorigin="anonymous", src=`${baseurl}index.global.js`)
script(crossorigin="anonymous", src=`${baseurl}Crypto1.global.js`)
script(crossorigin="anonymous", src=`${baseurl}plugin/Debug.global.js`)
script(crossorigin="anonymous", src=`${baseurl}plugin/DfuZip.global.js`)
script(crossorigin="anonymous", src=`${baseurl}plugin/WebbleAdapter.global.js`)
script(crossorigin="anonymous", src=`${baseurl}plugin/WebserialAdapter.global.js`)
block script
8 changes: 4 additions & 4 deletions pug/src/device-settings.pug
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ block content
block script
script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js")
script.
const { AnimationMode, ButtonAction, ButtonType, ChameleonDebug, ChameleonUltra, DeviceMode, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const { AnimationMode, ButtonAction, ButtonType, ChameleonUltra, Debug, DeviceMode, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new ChameleonDebug())
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new ChameleonDebug())
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())

const { joi: Joi } = window
Expand Down Expand Up @@ -231,7 +231,7 @@ block script
},
async btnEnterBootloader () {
if (!await this.confirm('Enter bootloader?', 'Yes', 'Cancel')) return
await this.ultra.cmdEnterBootloader().catch(err => { this.ultra.emitter.emit('error', err) })
await this.ultra.cmdDfuEnter().catch(err => { this.ultra.emitter.emit('error', err) })
},
async btnResetSettings () {
if (!await this.confirm('Reset to default settings?', 'Yes', 'Cancel')) return
Expand Down
193 changes: 193 additions & 0 deletions pug/src/dfu.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
extends /include/bootstrapV4

block beforehtml
- const title = 'Upload Firmware'

block style
meta(property="og:description", content="A tool to upload firmware to ChameleonUltra.")
meta(property="og:locale", content="zh_TW")
meta(property="og:title", content=title)
meta(property="og:type", content="website")
meta(property="og:url", content=`${baseurl}dfu.html`)
style
:sass
[v-cloak]
display: none
body, .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6
font-family: 'Noto Sans TC', sans-serif
.input-group-prepend > .input-group-text
width: 80px
.letter-spacing-n1px
&, .btn, textarea, select, input
letter-spacing: -1px
.text-sm
font-size: 0.875rem

block content
#app.my-3.container.text-monospace(v-cloak)
h4.mb-3.text-center.letter-spacing-n1px #[.bgicon.bgicon-chameleon-ultra.mr-1] #{title}
.form-group.letter-spacing-n1px
label Connect method:
.input-group.input-group-sm.mb-3
select.form-control(v-model="ls.adapter")
option(value="ble") BLE (PC & Android)
option(value="usb") USB Serial (PC only)
.input-group-append: button.btn.btn-outline-secondary(@click="btnAdapterTips") #[i.fa.fa-fw.fa-question]
.form-group.letter-spacing-n1px.mb-3
label Tag Name:
select.form-control.form-control-sm(v-model="ss.tagName")
option(v-for="[k, v] of tagNames", :value="k") {{ v }}
button.btn.btn-block.btn-outline-primary.letter-spacing-n1px.mb-2(@click="btnUploadFirmware") #[i.fa.mr-1.fa-download] Upload Firmware

block script
script.
const { Buffer, ChameleonUltra, Debug, DfuZip, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())

window.vm = new Vue({
el: '#app',
data: {
ls: {
adapter: 'ble',
},
ss: {
tagName: '',
},
manifest: {},
},
async mounted () {
// 自動儲存功能
for (const [storage, key] of [[localStorage, 'ls'], [sessionStorage, 'ss']]) {
try {
const saved = JSON5.parse(storage.getItem(location.pathname))
if (saved) this.$set(this, key, _.merge(this[key], saved))
} catch (err) {}
this.$watch(key, () => {
storage.setItem(location.pathname, JSON5.stringify(this[key]))
}, { deep: true })
}
await this.fetchManifest()
},
computed: {
ultra () {
return this.ls.adapter === 'usb' ? ultraUsb : ultraBle
},
tagNames () {
return _.map(_.orderBy(this?.manifest?.releases, ['createdAt'], ['desc']), release => {
if (_.isNil(release.gitVersion)) return [release.tagName, release.tagName]
return [release.tagName, `${release.tagName} (${release.gitVersion})`]
})
},
releases () {
const isAsset = model => asset => asset.name.indexOf(model) >= 0 && asset.name.indexOf('app') >= 0
const isUltraAsset = isAsset('ultra')
const isLiteAsset = isAsset('lite')
return _.fromPairs(_.map(this?.manifest?.releases, release => [release.tagName, {
..._.pick(release, ['commit', 'gitVersion', 'prerelease', 'tagName']),
createdAt: new Date(release.createdAt),
lite: _.find(release.assets, isLiteAsset),
ultra: _.find(release.assets, isUltraAsset),
}]))
},
},
methods: {
async btnAdapterTips () {
await Swal.fire({
title: 'Browser & OS',
html: '<strong class="text-success">BLE</strong> is available in ChromeOS, Chrome for Android 6.0, Mac (Chrome 56) and Windows 10 (Chrome 70), <a class="btn-link" target="_blank" href="https://apps.apple.com/app/bluefy-web-ble-browser/id1492822055">Bluefy</a> for iPhone and iPad.<hr><strong class="text-success">USB</strong> is available on all desktop platforms (ChromeOS, Linux, macOS, and Windows) in Chrome 89.',
})
},
async fetchManifest () {
this.showLoading({ text: 'Loading firmwares...' })
const url = `https://taichunmin.idv.tw/ChameleonUltra-releases/manifest.json?t=${Math.trunc(Date.now() / 6e5)}`
this.$set(this, 'manifest', (await axios.get(url))?.data ?? {})
this.ss.tagName = _.first(this.tagNames)[0]
Swal.close()
},
async btnUploadFirmware () {
const { ultra } = this
const numFormater = new Intl.NumberFormat('en', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
const showProgress = ({ func, offset, size, type }) => {
if (func !== 'dfuUploadObject' || type !== 2) return
const width = _.round(size > 0 ? offset / size * 100 : 0, 1)
const html = `<div class="d-flex flex-column"><div class="progress mb-2"><div class="progress-bar progress-bar-striped" role="progressbar" style="width: ${width}%"></div></div><div class="d-flex justify-content-between"><span>Uploading:</span><span>${numFormater.format(width)} %</span></div></div>`
this.showLoading({ html })
}
try {
this.showLoading({ text: 'Download firmware...' })
const release = this.releases[this.ss.tagName]
if (_.isNil(release)) throw new Error('Invalid tagName')
const images = await Promise.all(_.map(['ultra', 'lite'], async model => {
const dfuZipUrl = `${release[model].url}?t=${release.createdAt.getTime()}` // 避免快取,加上時間戳
ultra.emitter.emit('debug', 'web', `model = ${model}, url = ${dfuZipUrl}`)
const dfuZip = new DfuZip(new Buffer((await axios.get(dfuZipUrl, { responseType: 'arraybuffer' }))?.data))
return await dfuZip.getAppImage()
}))
this.showLoading({ text: 'Connect device...' })
await ultra.connect()
if (!ultra.isDfu()) {
const gitVersion = await ultra.cmdGetGitVersion()
if (!_.isNil(release.gitVersion) && release.gitVersion === gitVersion) {
const msg1 = `gitVersion(${gitVersion}) is the same, do you want to upload again?`
if (!await this.confirm(msg1, 'Yes', 'Cancel')) return
}
await ultra.cmdDfuEnter()
}
this.showLoading({ text: 'Uploading Firmware...' })
ultra.emitter.on('progress', showProgress)
let isUploadSuccess = false
for (const image of images) {
try {
await ultra.dfuUploadImage(image)
isUploadSuccess = true
break
} catch (err) {
ultra.emitter.emit('error', _.set(new Error(err.message), 'originalError', err))
}
}
if (!isUploadSuccess) throw new Error('Upload failed')
await Swal.fire({ icon: 'success', title: 'Upload Success' })
} catch (err) {
ultra.emitter.emit('error', err)
await Swal.fire({ icon: 'error', title: 'Upload Failed', text: err.message })
}
ultra.emitter.removeListener('progress', showProgress)
},
async sleep (t) {
await new Promise(resolve => setTimeout(resolve, t))
},
async confirm (text, confirmButtonText, cancelButtonText) {
return await new Promise((resolve, reject) => {
let isConfirmed = false
const args = {
cancelButtonColor: '#3085d6',
cancelButtonText,
confirmButtonColor: '#d33',
confirmButtonText,
didDestroy: () => { resolve(isConfirmed) },
focusCancel: true,
icon: 'warning',
reverseButtons: true,
showCancelButton: true,
text,
}
Swal.fire(args).then(res => { isConfirmed = res.isConfirmed })
})
},
showLoading (opts = {}) {
opts = {
allowOutsideClick: false,
showConfirmButton: false,
...opts,
}
if (Swal.isVisible()) return Swal.update(_.omit(opts, ['progressStepsDistance']))
Swal.fire({ ...opts, didRender: () => { Swal.showLoading() } })
},
},
})

6 changes: 3 additions & 3 deletions pug/src/hf14a-scanner.pug
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ block content

block script
script.
const { ChameleonDebug, ChameleonUltra, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const { ChameleonUltra, Debug, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new ChameleonDebug())
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new ChameleonDebug())
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())
const toHex = buf => _.toUpper(buf.toString('hex'))

Expand Down
6 changes: 3 additions & 3 deletions pug/src/mfkey32.pug
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,12 @@ block content

block script
script.
const { Buffer, ChameleonDebug, ChameleonUltra, DeviceMode, FreqType, Mf1KeyType, TagType, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const { Buffer, ChameleonUltra, Debug, DeviceMode, FreqType, Mf1KeyType, TagType, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new ChameleonDebug())
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new ChameleonDebug())
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())

window.vm = new Vue({
Expand Down
6 changes: 3 additions & 3 deletions pug/src/mifare-value.pug
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ block content
block script
script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js")
script.
const { Buffer, ChameleonDebug, ChameleonUltra, Mf1KeyType, Mf1VblockOperator, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const { Buffer, ChameleonUltra, Debug, Mf1KeyType, Mf1VblockOperator, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new ChameleonDebug())
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new ChameleonDebug())
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())

window.vm = new Vue({
Expand Down
6 changes: 3 additions & 3 deletions pug/src/mifare-xiaomi.pug
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ block content

block script
script.
const { Buffer, ChameleonDebug, ChameleonUltra, DeviceMode, FreqType, TagType, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const { Buffer, ChameleonUltra, Debug, DeviceMode, FreqType, TagType, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new ChameleonDebug())
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new ChameleonDebug())
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())

const toHex = buf => _.toUpper(buf.toString('hex'))
Expand Down
6 changes: 3 additions & 3 deletions pug/src/mifare1k.pug
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,12 @@ block content
block script
script(crossorigin="anonymous", src="https://cdn.jsdelivr.net/npm/joi@17/dist/joi-browser.min.js")
script.
const { Buffer, ChameleonDebug, ChameleonUltra, DeviceMode, FreqType, TagType, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const { Buffer, ChameleonUltra, Debug, DeviceMode, FreqType, TagType, WebbleAdapter, WebserialAdapter } = window.ChameleonUltraJS
const ultraUsb = new ChameleonUltra()
ultraUsb.use(new ChameleonDebug())
ultraUsb.use(new Debug())
ultraUsb.use(new WebserialAdapter())
const ultraBle = new ChameleonUltra()
ultraBle.use(new ChameleonDebug())
ultraBle.use(new Debug())
ultraBle.use(new WebbleAdapter())

const toHex = buf => _.toUpper(buf.toString('hex'))
Expand Down
Loading