Skip to content

Commit

Permalink
DFU: support choose zip file to upload (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
taichunmin authored Jun 21, 2024
2 parents da173c8 + 26d65e0 commit 931d798
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 18 deletions.
90 changes: 79 additions & 11 deletions pug/src/dfu.pug
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,23 @@ block content
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
.card.shadow-sm.mb-2
h6.card-header #[i.fa.fa-fw.fa-github] From GitHub
.card-body.px-3.pt-3.pb-2.letter-spacing-n1px
.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="btnUploadRelease") #[i.fa.mr-1.fa-puzzle-piece] Upload Firmware
.card.shadow-sm.mb-2
h6.card-header #[i.fa.fa-fw.fa-file-archive-o] From Zip File
.card-body.px-3.pt-3.pb-2.letter-spacing-n1px
.form-group.letter-spacing-n1px.mb-3
label Choose firmware:
.custom-file.form-control-sm.letter-spacing-n1px
input.custom-file-input#imageSelected(type="file", v-model="imageSelected", ref="imageSelected", accept=".zip")
label.custom-file-label(for="imageSelected") {{ getFilename(imageSelected) || 'Choose firmware...' }}
button.btn.btn-block.btn-outline-primary.letter-spacing-n1px.mb-2(@click="btnUploadSelected") #[i.fa.mr-1.fa-puzzle-piece] Upload Firmware

block script
script.
Expand All @@ -58,6 +70,7 @@ block script
ss: {
tagName: '',
},
imageSelected: '',
manifest: {},
},
async mounted () {
Expand Down Expand Up @@ -109,7 +122,7 @@ block script
this.ss.tagName = _.first(this.tagNames)[0]
Swal.close()
},
async btnUploadFirmware () {
async btnUploadRelease () {
const { ultra } = this
const numFormater = new Intl.NumberFormat('en', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
const showProgress = ({ func, offset, size, type }) => {
Expand All @@ -129,14 +142,22 @@ block script
return await dfuZip.getAppImage()
}))
this.showLoading({ text: 'Connect device...' })
await ultra.connect()
if (!ultra.isConnected()) 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
try {
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
}
} catch (err) {
// 有可能是因為不支援 cmdGetGitVersion
ultra.emitter.emit('error', _.set(new Error(err.message), 'originalError', err))
}
await ultra.cmdDfuEnter()
await Swal.fire({ icon: 'info', text: 'Please reconnect your device!' })
if (!ultra.isConnected()) await ultra.connect()
if (!ultra.isDfu()) throw new Error('Device is not in DFU mode')
}
this.showLoading({ text: 'Uploading Firmware...' })
ultra.emitter.on('progress', showProgress)
Expand All @@ -158,6 +179,50 @@ block script
}
ultra.emitter.removeListener('progress', showProgress)
},
async btnUploadSelected () {
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: 'Unzip firmware...' })
const dfuFile = this.$refs.imageSelected.files[0]
if (_.isNil(dfuFile)) throw new Error('Please browse a firmware file.')
const dfuZip = new DfuZip(new Buffer(await dfuFile.arrayBuffer()))
const image = await dfuZip.getAppImage()
const imageGitVersion = await dfuZip.getGitVersion()
this.showLoading({ text: 'Connect device...' })
if (!ultra.isConnected()) await ultra.connect()
if (!ultra.isDfu()) {
try {
const gitVersion = await ultra.cmdGetGitVersion()
if (!_.isNil(imageGitVersion) && imageGitVersion === gitVersion) {
const msg1 = `gitVersion(${gitVersion}) is the same, do you want to upload again?`
if (!await this.confirm(msg1, 'Yes', 'Cancel')) return
}
} catch (err) {
// 有可能是因為不支援 cmdGetGitVersion
ultra.emitter.emit('error', _.set(new Error(err.message), 'originalError', err))
}
await ultra.cmdDfuEnter()
await Swal.fire({ icon: 'info', text: 'Please reconnect your device!' })
if (!ultra.isConnected()) await ultra.connect()
if (!ultra.isDfu()) throw new Error('Device is not in DFU mode')
}
this.showLoading({ text: 'Uploading Firmware...' })
ultra.emitter.on('progress', showProgress)
await ultra.dfuUploadImage(image)
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))
},
Expand All @@ -179,6 +244,9 @@ block script
Swal.fire(args).then(res => { isConfirmed = res.isConfirmed })
})
},
getFilename (str) {
return str.replaceAll(/.*[/\\]/g, '')
},
showLoading (opts = {}) {
opts = {
allowOutsideClick: false,
Expand Down
4 changes: 2 additions & 2 deletions pug/src/mfkey32.pug
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ block content
option(value="usb") USB Serial (PC only)
.input-group-append: button.btn.btn-outline-secondary(@click="btnAdapterTips") #[i.fa.fa-fw.fa-question]
.card.mb-3
.card-header.letter-spacing-n1px #[i.fa.fa-fw.fa-id-card] Scan UID / Emulate Tag
h6.card-header #[i.fa.fa-fw.fa-id-card] Scan UID / Emulate Tag
.card-body
.input-group.input-group-sm.mb-2
.input-group-prepend: span.input-group-text.justify-content-center Slot
Expand All @@ -58,7 +58,7 @@ block content
.col.px-1: button.btn.btn-block.btn-success.letter-spacing-n1px(@click="btnScanTag") #[i.fa.fa-fw.fa-sign-out] Scan UID
.col.px-1: button.btn.btn-block.btn-primary.letter-spacing-n1px(@click="btnEmulateTag") #[i.fa.fa-fw.fa-sign-in] Emulate
.card.mb-3
.card-header.letter-spacing-n1px #[i.fa.fa-fw.fa-lock] Key recover / Check key
h6.card-header #[i.fa.fa-fw.fa-lock] Key recover / Check key
.card-body
.input-group.input-group-sm.mb-2.was-validated
.input-group-prepend: span.input-group-text.justify-content-center Block
Expand Down
6 changes: 3 additions & 3 deletions src/ChameleonUltra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export class ChameleonUltra {

/**
* Read a response from device.
* @param timeout - The timeout in milliseconds.
* @param args.timeout - The timeout in milliseconds.
* @internal
* @group Internal
*/
Expand Down Expand Up @@ -1759,8 +1759,8 @@ export class ChameleonUltra {
* @param opts.data - The data to be send. If `appendCrc` is `true`, the maximum length of data is `62`, otherwise is `64`.
* @param opts.dataBitLength - Number of bits to send. Useful for send partial byte. `dataBitLength` is incompatible with `appendCrc`.
* @param opts.keepRfField - Set `true` to keep the RF field active after sending.
* @param opts.readResponse - Default value is `true`. Set `false` to skip reading tag response.
* @param opts.timeout - Default value is `1000 ms`. Maximum timeout for reading tag response in ms while `readResponse` is `true`.
* @param opts.waitResponse - Default value is `true`. Set `false` to skip reading tag response.
* @param opts.timeout - Default value is `1000 ms`. Maximum timeout for reading tag response in ms while `waitResponse` is `true`.
* @returns The response from tag.
* @group Reader Related
*/
Expand Down
12 changes: 11 additions & 1 deletion src/plugin/Debug.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import _ from 'lodash'
import { type Buffer } from '@taichunmin/buffer'
import { type ChameleonPlugin, type PluginInstallContext as ChameleonCtx } from '../ChameleonUltra'
import createDebugger, { type Debugger } from 'debug'

let Buffer1: typeof Buffer

export default class Debug implements ChameleonPlugin {
filter?: DebugFilter
debugers = new Map<string, Debugger>()
filter?: DebugFilter
name = 'debug'

async install (context: ChameleonCtx): Promise<this> {
const { ultra } = context
if (_.isNil(Buffer1)) Buffer1 = context.Buffer
ultra.emitter.on('error', (err: Error) => {
const errJson = errToJson(err)
ultra.emitter.emit('debug', 'error', jsonStringify(errJson))
Expand Down Expand Up @@ -68,6 +72,7 @@ export function stringifyClone (obj: any): any {
if (preventCircular.has(val1)) return '[Circular]'
preventCircular.add(val1)
}
if (Buffer1?.isBuffer(val1)) return { type: 'Buffer', hex: val1.toString('hex') }
if (typeof val1 === 'bigint') return val1.toString()
if (val1 instanceof Error) return errToJson(val1)
if (val1 instanceof Map) return _.fromPairs([...val1.entries()])
Expand All @@ -90,3 +95,8 @@ export function stringifyReplacer (this: any, key: any, val: any): any {
export function jsonStringify (obj: object, space?: number): string {
return JSON.stringify(stringifyClone(obj), stringifyReplacer, space)
}

export function arrayBufferViewToHex (view: ArrayBufferView): string {
const u8 = new Uint8Array(view.buffer, view.byteOffset, view.byteLength)
return _.map(u8, b => `0${b.toString(16)}`.slice(-2)).join('')
}
7 changes: 7 additions & 0 deletions src/plugin/DfuZip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export default class DfuZip {
async getAppImage (): Promise<DfuImage | null> {
return await this.getImage(['application'])
}

async getGitVersion (): Promise<string | null> {
const image = await this.getAppImage()
if (_.isNil(image)) return null
// eslint-disable-next-line no-control-regex
return image.body.toString('utf8').match(/\x00(v\d+(?:\.\d+)*[\w-]*)\x00/)?.[1] ?? null
}
}

;((globalThis as any ?? {}).ChameleonUltraJS ?? {}).DfuZip = DfuZip // eslint-disable-line @typescript-eslint/prefer-optional-chain
Expand Down
1 change: 0 additions & 1 deletion tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export default defineConfig((options): Options[] => [
minify: !options.watch,
entry: [
'src/Crypto1.ts',
'src/plugin/BufferMockAdapter.ts',
'src/plugin/Debug.ts',
'src/plugin/DfuZip.ts',
'src/plugin/SerialPortAdapter.ts',
Expand Down

0 comments on commit 931d798

Please sign in to comment.