From 5762bcb549d71f1c591708dbdeb00ae7dc8b605d Mon Sep 17 00:00:00 2001 From: chopster44 Date: Sat, 29 Nov 2025 22:54:40 +0000 Subject: [PATCH] possible file transfer --- dist/index.cjs.js | 85 +++++++++++++++++++++++++-- dist/index.d.ts | 10 +++- dist/index.es.js | 85 +++++++++++++++++++++++++-- src/index.ts | 142 ++++++++++++++++++++++++++++++++++++---------- 4 files changed, 284 insertions(+), 38 deletions(-) diff --git a/dist/index.cjs.js b/dist/index.cjs.js index 971f0be..a962253 100644 --- a/dist/index.cjs.js +++ b/dist/index.cjs.js @@ -8,15 +8,21 @@ class Updater { feedType; bleObject; bleDeviceId; - _updaterServiceUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e56193b"; - _updaterVersionCharactersiticUUID = "1978a3df-c009-4837-b295-57ef429dde8c"; + _updaterServiceUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561900"; + _updaterVersionCharactersiticUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561910"; + _updaterCommandCharacterisitcUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561920"; + _updateFileCharacteristicUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561930"; file; - constructor(archiveURL = "/", feedType = "atom", bleObject) { + _fileSize; + _fileProgress = 0; + _packetSize; + constructor(archiveURL = "/", feedType = "atom", bleObject, packetSize = 512) { this.archiveURL = archiveURL; this.feedType = feedType; if (bleObject) { this.bleObject = bleObject; } + this._packetSize = packetSize; } /* FEEDS @@ -157,6 +163,7 @@ class Updater { const res = await fetch(`http://cors.emaker.limited/?url=${this.archiveURL}/download/${version.title}/firmware.bin`); let buf = await res.arrayBuffer(); this.file = new Int8Array(buf); + this._fileSize = this.file.byteLength; return true; } catch { @@ -164,7 +171,77 @@ class Updater { } } getFileSize() { - return this.file.byteLength; + return this._fileSize; + } + async sendNextPacket() { + let packet = this.file.slice(this._fileProgress, this._fileProgress + this._packetSize); + return new Promise((resolve, reject) => { + this.bleObject.write(this.bleDeviceId, this._updaterServiceUUID, this._updateFileCharacteristicUUID, packet.buffer, () => { + resolve(true); + }, (error) => { + reject(error); + }); + }); + } + async sendEndCmd(agree) { + const buffer = new ArrayBuffer(1); + let view = new Int8Array(buffer); + view[0] = agree ? 3 : 4; + await this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer); + return; + } + // start and autoconnect before you run this function, so that you can have an "uninterrupted" connection when the board reboots + async flashFirmware(progressCallback) { + // write filesize to board + // await notify from cmd - ready + // send a packet + // check for error + // write file length + return new Promise(async (resolve, reject) => { + const buffer = new ArrayBuffer(4); + let view = new Int32Array(buffer); + view[0] = this._fileSize; + await this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer); + // start notify + this.bleObject.startNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, async (rawData) => { + let dataView = new Int8Array(rawData); + if (dataView[0] == 1) { + // send file + await this.sendNextPacket(); + progressCallback(`Sending (${Math.floor((this._fileProgress * 100) / this._fileSize)})`); + } + else if (dataView[0] == 2) { + // done logic + if (this._fileProgress >= this._fileSize) { + // send agree + await this.sendEndCmd(true); + progressCallback(`Complete!`); + this.bleObject.stopNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, () => { + // success + resolve(true); + }, (error) => { + reject("Error: Failed to stop notify"); + }); + } + else { + // send disagree + await this.sendEndCmd(false); + progressCallback(`Error, starting over`); + this._fileProgress = 0; + } + } + else if (dataView[0] == 15) { + // error cmd + progressCallback(`Error on remote`); + reject("Error on remote"); + } + else { + // no command + progressCallback(`Error on remote`); + reject("Error: command does not exist"); + } + }, () => { reject("Error: Failed to start notify"); }); + }); } } diff --git a/dist/index.d.ts b/dist/index.d.ts index 15e10c8..5845c1a 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -11,8 +11,13 @@ export default class Updater { protected bleDeviceId: string; private readonly _updaterServiceUUID; private readonly _updaterVersionCharactersiticUUID; + private readonly _updaterCommandCharacterisitcUUID; + private readonly _updateFileCharacteristicUUID; file: Int8Array; - constructor(archiveURL?: string, feedType?: string, bleObject?: BLECentralPlugin.BLECentralPluginStatic); + private _fileSize; + private _fileProgress; + private _packetSize; + constructor(archiveURL?: string, feedType?: string, bleObject?: BLECentralPlugin.BLECentralPluginStatic, packetSize?: number); private getRawArchive; private atomGetVersionDetails; private atomGetArchive; @@ -27,4 +32,7 @@ export default class Updater { getBoardVersion(): Promise; getFirmware(version: versionNotes): Promise; getFileSize(): number; + private sendNextPacket; + private sendEndCmd; + flashFirmware(progressCallback: (message: string) => void): Promise; } diff --git a/dist/index.es.js b/dist/index.es.js index ff1ca2e..76514cb 100644 --- a/dist/index.es.js +++ b/dist/index.es.js @@ -6,15 +6,21 @@ class Updater { feedType; bleObject; bleDeviceId; - _updaterServiceUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e56193b"; - _updaterVersionCharactersiticUUID = "1978a3df-c009-4837-b295-57ef429dde8c"; + _updaterServiceUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561900"; + _updaterVersionCharactersiticUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561910"; + _updaterCommandCharacterisitcUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561920"; + _updateFileCharacteristicUUID = "71a4438e-fd52-4b15-b3d2-ec0e3e561930"; file; - constructor(archiveURL = "/", feedType = "atom", bleObject) { + _fileSize; + _fileProgress = 0; + _packetSize; + constructor(archiveURL = "/", feedType = "atom", bleObject, packetSize = 512) { this.archiveURL = archiveURL; this.feedType = feedType; if (bleObject) { this.bleObject = bleObject; } + this._packetSize = packetSize; } /* FEEDS @@ -155,6 +161,7 @@ class Updater { const res = await fetch(`http://cors.emaker.limited/?url=${this.archiveURL}/download/${version.title}/firmware.bin`); let buf = await res.arrayBuffer(); this.file = new Int8Array(buf); + this._fileSize = this.file.byteLength; return true; } catch { @@ -162,7 +169,77 @@ class Updater { } } getFileSize() { - return this.file.byteLength; + return this._fileSize; + } + async sendNextPacket() { + let packet = this.file.slice(this._fileProgress, this._fileProgress + this._packetSize); + return new Promise((resolve, reject) => { + this.bleObject.write(this.bleDeviceId, this._updaterServiceUUID, this._updateFileCharacteristicUUID, packet.buffer, () => { + resolve(true); + }, (error) => { + reject(error); + }); + }); + } + async sendEndCmd(agree) { + const buffer = new ArrayBuffer(1); + let view = new Int8Array(buffer); + view[0] = agree ? 3 : 4; + await this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer); + return; + } + // start and autoconnect before you run this function, so that you can have an "uninterrupted" connection when the board reboots + async flashFirmware(progressCallback) { + // write filesize to board + // await notify from cmd - ready + // send a packet + // check for error + // write file length + return new Promise(async (resolve, reject) => { + const buffer = new ArrayBuffer(4); + let view = new Int32Array(buffer); + view[0] = this._fileSize; + await this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer); + // start notify + this.bleObject.startNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, async (rawData) => { + let dataView = new Int8Array(rawData); + if (dataView[0] == 1) { + // send file + await this.sendNextPacket(); + progressCallback(`Sending (${Math.floor((this._fileProgress * 100) / this._fileSize)})`); + } + else if (dataView[0] == 2) { + // done logic + if (this._fileProgress >= this._fileSize) { + // send agree + await this.sendEndCmd(true); + progressCallback(`Complete!`); + this.bleObject.stopNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, () => { + // success + resolve(true); + }, (error) => { + reject("Error: Failed to stop notify"); + }); + } + else { + // send disagree + await this.sendEndCmd(false); + progressCallback(`Error, starting over`); + this._fileProgress = 0; + } + } + else if (dataView[0] == 15) { + // error cmd + progressCallback(`Error on remote`); + reject("Error on remote"); + } + else { + // no command + progressCallback(`Error on remote`); + reject("Error: command does not exist"); + } + }, () => { reject("Error: Failed to start notify"); }); + }); } } diff --git a/src/index.ts b/src/index.ts index 0033799..82d5529 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,10 +2,10 @@ import { parseXml, XmlDocument, XmlElement, XmlText } from "@rgrove/parse-xml"; export type versionNotes = { - title: string; - date: Date | string; - link: string; - html: string; + title: string; + date: Date | string; + link: string; + html: string; } export default class Updater { @@ -17,17 +17,24 @@ export default class Updater { private readonly _updaterServiceUUID: string = "71a4438e-fd52-4b15-b3d2-ec0e3e561900"; private readonly _updaterVersionCharactersiticUUID: string = "71a4438e-fd52-4b15-b3d2-ec0e3e561910"; + private readonly _updaterCommandCharacterisitcUUID: string = "71a4438e-fd52-4b15-b3d2-ec0e3e561920"; + private readonly _updateFileCharacteristicUUID: string = "71a4438e-fd52-4b15-b3d2-ec0e3e561930"; public file: Int8Array; + private _fileSize: number; + private _fileProgress: number = 0; + + private _packetSize: number; - constructor(archiveURL: string = "/", feedType: string = "atom", bleObject?: BLECentralPlugin.BLECentralPluginStatic) { + constructor(archiveURL: string = "/", feedType: string = "atom", bleObject?: BLECentralPlugin.BLECentralPluginStatic, packetSize: number = 512) { this.archiveURL = archiveURL; this.feedType = feedType; if (bleObject) { this.bleObject = bleObject; } + this._packetSize = packetSize; } - + /* FEEDS */ @@ -42,7 +49,7 @@ export default class Updater { // atom feeds private atomGetVersionDetails(entry: XmlElement): versionNotes { - let outEntry: versionNotes = {title: "", date: new Date, link: "", html: ""}; + let outEntry: versionNotes = { title: "", date: new Date, link: "", html: "" }; entry.children.forEach((elm) => { let element = elm as XmlElement; if (element.name == "title") { @@ -54,7 +61,7 @@ export default class Updater { else if (element.name == "link") { outEntry.link = element.attributes["href"]; } else if (element.name == "content") { - outEntry.html = (element.children[0] as XmlText).text; + outEntry.html = (element.children[0] as XmlText).text; } }) @@ -80,7 +87,7 @@ export default class Updater { // rss feeds private rssGetVersionDetails(entry: XmlElement): versionNotes { - let outEntry: versionNotes = {title: "", date: new Date, link: "", html: ""}; + let outEntry: versionNotes = { title: "", date: new Date, link: "", html: "" }; entry.children.forEach((elm) => { let element = elm as XmlElement; if (element.name == "title") { @@ -92,7 +99,7 @@ export default class Updater { else if (element.name == "link") { outEntry.link = (element.children[0] as XmlText).text; } else if (element.name == "content") { - outEntry.html = (element.children[0] as XmlText).text; + outEntry.html = (element.children[0] as XmlText).text; } }) @@ -114,11 +121,11 @@ export default class Updater { }) return output; } - + public async getArchive(): Promise { if (this.feedType == "atom") { return this.atomGetArchive() - } else if (this.feedType == "rss"){ + } else if (this.feedType == "rss") { return this.rssGetArchive() } } @@ -138,16 +145,17 @@ export default class Updater { private async readVersionNumber(): Promise { return new Promise((resolve, reject) => { this.bleObject.read( - this.bleDeviceId, - this._updaterServiceUUID, - this._updaterVersionCharactersiticUUID, - (rawData: ArrayBuffer) => { - resolve(this.bytesToString(rawData)); - }, - (error: string) => { - reject(`Error: ${error}`); - } - )}); + this.bleDeviceId, + this._updaterServiceUUID, + this._updaterVersionCharactersiticUUID, + (rawData: ArrayBuffer) => { + resolve(this.bytesToString(rawData)); + }, + (error: string) => { + reject(`Error: ${error}`); + } + ) + }); } private async getLatestVersion(): Promise { @@ -169,7 +177,7 @@ export default class Updater { // compare with latest version const latestVersion = await this.getLatestVersion(); if (deviceVersion != latestVersion) { - return true; + return true; } // update return false; @@ -178,7 +186,7 @@ export default class Updater { public async getBoardVersion(): Promise { return await this.readVersionNumber(); } - + /* FILE FLASHING */ @@ -188,6 +196,7 @@ export default class Updater { const res = await fetch(`http://cors.emaker.limited/?url=${this.archiveURL}/download/${version.title}/firmware.bin`); let buf = await res.arrayBuffer(); this.file = new Int8Array(buf); + this._fileSize = this.file.byteLength; return true; } catch { return false; @@ -195,11 +204,86 @@ export default class Updater { } public getFileSize(): number { - return this.file.byteLength; + return this._fileSize; } - // public async flashFirmware(): Promise { - - // } + private async sendNextPacket(): Promise { + let packet = this.file.slice(this._fileProgress, this._fileProgress+this._packetSize); + return new Promise((resolve, reject) => { + this.bleObject.write(this.bleDeviceId, this._updaterServiceUUID, this._updateFileCharacteristicUUID, packet.buffer, + () => { + resolve(true); + }, + (error) => { + reject(error); + } + ) + }); + } + + private async sendEndCmd(agree: boolean): Promise { + const buffer = new ArrayBuffer(1); + let view = new Int8Array(buffer); + view[0] = agree ? 3 : 4; + await this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer); + return; + } + + // start and autoconnect before you run this function, so that you can have an "uninterrupted" connection when the board reboots + public async flashFirmware(progressCallback: (message: string) => void): Promise { + // write filesize to board + // await notify from cmd - ready + // send a packet + // check for error + + // write file length + return new Promise(async (resolve, reject) => { + const buffer = new ArrayBuffer(4) + let view = new Int32Array(buffer); + view[0] = this._fileSize; + await this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer); + + // start notify + this.bleObject.startNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, + async (rawData: ArrayBuffer): Promise => { + let dataView = new Int8Array(rawData); + if (dataView[0] == 1) { + // send file + await this.sendNextPacket(); + progressCallback(`Sending (${Math.floor((this._fileProgress *100)/this._fileSize)})`); + } else if (dataView[0] == 2) { + // done logic + if (this._fileProgress >= this._fileSize) { + // send agree + await this.sendEndCmd(true); + progressCallback(`Complete!`); + this.bleObject.stopNotification(this.bleDeviceId, + this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, + () => { + // success + resolve(true); + }, + (error) => { + reject("Error: Failed to stop notify"); + } + ); + } else { + // send disagree + await this.sendEndCmd(false); + progressCallback(`Error, starting over`); + this._fileProgress = 0; + } + } else if (dataView[0] == 15) { + // error cmd + progressCallback(`Error on remote`); + reject("Error on remote"); + } else { + // no command + progressCallback(`Error on remote`); + reject("Error: command does not exist"); + } + }, () => { reject("Error: Failed to start notify"); }); + }); + } } - +