diff --git a/pug/src/dfu.pug b/pug/src/dfu.pug index 8122d40..478f1b2 100644 --- a/pug/src/dfu.pug +++ b/pug/src/dfu.pug @@ -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. @@ -58,6 +70,7 @@ block script ss: { tagName: '', }, + imageSelected: '', manifest: {}, }, async mounted () { @@ -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 }) => { @@ -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) @@ -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 = `
Uploading:${numFormater.format(width)} %
` + 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)) }, @@ -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, diff --git a/pug/src/mfkey32.pug b/pug/src/mfkey32.pug index 830ea3e..17ebd56 100644 --- a/pug/src/mfkey32.pug +++ b/pug/src/mfkey32.pug @@ -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 @@ -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 diff --git a/src/ChameleonUltra.ts b/src/ChameleonUltra.ts index bce52d1..0b66dc7 100644 --- a/src/ChameleonUltra.ts +++ b/src/ChameleonUltra.ts @@ -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 */ @@ -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 */ diff --git a/src/plugin/Debug.ts b/src/plugin/Debug.ts index a034ea7..f460295 100644 --- a/src/plugin/Debug.ts +++ b/src/plugin/Debug.ts @@ -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() + filter?: DebugFilter name = 'debug' async install (context: ChameleonCtx): Promise { 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)) @@ -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()]) @@ -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('') +} diff --git a/src/plugin/DfuZip.ts b/src/plugin/DfuZip.ts index f91f8e9..1ff6136 100644 --- a/src/plugin/DfuZip.ts +++ b/src/plugin/DfuZip.ts @@ -43,6 +43,13 @@ export default class DfuZip { async getAppImage (): Promise { return await this.getImage(['application']) } + + async getGitVersion (): Promise { + 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 diff --git a/tsup.config.ts b/tsup.config.ts index b9a8bd6..eb6a54d 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -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',