Compare commits

...

64 Commits

Author SHA1 Message Date
f85c89e2c4 (1.1.0) successful file transfer 2025-12-01 21:29:11 +00:00
6447692d4c (1.0.37) bump 2025-12-01 21:21:05 +00:00
855eb07d89 set file progress rather than adding 2025-12-01 21:20:55 +00:00
8e840c0966 (1.0.36) bump 2025-12-01 21:07:05 +00:00
5ab862d703 loading the file progress correctly 2025-12-01 21:06:56 +00:00
7c6f0e8a3e (1.0.35) bump 2025-12-01 20:59:49 +00:00
bda9dddd4f more logging 2025-12-01 20:59:41 +00:00
95709e0285 (1.0.34) bump 2025-12-01 20:56:24 +00:00
4c4b896978 more logging 2025-12-01 20:56:15 +00:00
c20c0b02cf (1.0.33) bump 2025-12-01 20:47:55 +00:00
46bd6f7161 track recieved data size to avoid false branching 2025-12-01 20:47:48 +00:00
eeb90f630d (1.0.32) bump 2025-12-01 20:45:31 +00:00
bccbd61ad5 log recieved file progress 2025-12-01 20:44:27 +00:00
2017847b9e (1.0.31) bump 2025-12-01 20:30:50 +00:00
b471d0e20f trying file progress from remote, due to packet loss 2025-12-01 20:30:37 +00:00
a7d5880057 (1.0.30) bump 2025-12-01 18:40:16 +00:00
e5ce96adcf only add to progress the number of bytes written 2025-12-01 18:40:08 +00:00
27403e0aed (1.0.29) bump 2025-12-01 18:33:57 +00:00
faf4e377d7 trying a different way to confirm mtu size 2025-12-01 18:33:48 +00:00
ed4890818f (1.0.28) bump 2025-12-01 18:24:51 +00:00
0f61fd26c5 mtu logging 2025-12-01 18:24:40 +00:00
d1927bc24e (1.0.27) bump 2025-12-01 18:08:02 +00:00
1b7719ac3d wait for mtu to be agreed 2025-12-01 18:07:50 +00:00
ebcfb38e41 (1.0.26) bump 2025-12-01 17:49:29 +00:00
ad7402f04b without responses again 2025-12-01 17:49:19 +00:00
e8e9547a20 (1.0.25) bump 2025-12-01 17:27:08 +00:00
852fcf67f4 going back to writing with response due to transmission issues 2025-12-01 17:26:43 +00:00
e798f997d3 (1.0.24) bump 2025-11-30 21:58:24 +00:00
d2f9809f52 (1.0.23) bump 2025-11-30 21:47:57 +00:00
3e7c26e3ba file progress in more places 2025-11-30 21:47:47 +00:00
e5912cd94e (1.0.22) bump 2025-11-30 21:43:00 +00:00
20c203ffa4 fix packet size issue 2025-11-30 21:42:47 +00:00
ad464e1c0d (1.0.21) bump 2025-11-30 21:20:50 +00:00
5009b0c8d1 Reducing number of awaits for speed 2025-11-30 21:20:33 +00:00
b13787b704 (1.0.20) bump 2025-11-30 21:11:31 +00:00
b155baed03 do file progress counter preemtively 2025-11-30 21:11:05 +00:00
41a2e30aca (1.0.19) bump 2025-11-30 17:42:03 +00:00
266044215d dbg 2025-11-30 17:41:52 +00:00
418df318ee (1.0.18) bump 2025-11-30 16:51:58 +00:00
896cccd7ff More debug 2025-11-30 16:51:45 +00:00
66cbb31dde (1.0.17) bump 2025-11-30 12:02:38 +00:00
90b8e08946 actually count up file progress 2025-11-30 12:02:21 +00:00
4900cf6acd (1.0.16) dbg 2025-11-30 11:47:25 +00:00
3e07911cac Some debug messages 2025-11-30 11:46:58 +00:00
8c3dd4c2e3 (1.0.15) bump 2025-11-29 23:15:37 +00:00
3bae392285 communicate error 2025-11-29 23:15:17 +00:00
b978ffa313 (1.0.14) file transfer go 1 2025-11-29 22:55:11 +00:00
5762bcb549 possible file transfer 2025-11-29 22:54:40 +00:00
836e0f70aa Update characteristic UUIDs 2025-11-28 15:31:17 +00:00
b0813ed082 (1.0.13) bug fix 2025-11-26 22:19:44 +00:00
6ffc5c1e8a needed cors proxy 2025-11-26 22:19:21 +00:00
fdcf232f8e (1.0.12) downloading firmware file 2025-11-26 22:02:52 +00:00
c65ec8dcd2 public file for testing 2025-11-26 22:02:05 +00:00
3e7c9f358e get the firmware 2025-11-26 22:01:04 +00:00
6f77c15088 (1.0.11) reading board version to the program 2025-11-18 21:28:09 +00:00
403f01d52f return board version 2025-11-18 21:27:29 +00:00
0125bae4e5 (1.0.10) attempt add bluetooth 2025-11-15 19:08:43 +00:00
0a37ee00bf basic bluetooth code 2025-11-15 18:58:27 +00:00
03038eaa00 version bump script 2025-11-15 18:58:12 +00:00
76a6d725f5 (1.0.9) spelling mistake which broke rss feeds 2025-10-31 20:40:41 +00:00
1452d6eaad forgot to build last update 2025-10-31 13:26:19 +00:00
b800542afa (1.0.7) functional feed retrieval 2025-10-31 13:16:01 +00:00
678d7e89d2 the date retrieved is now correct 2025-10-31 13:14:24 +00:00
387f75a4d3 use cors proxy correctly 2025-10-31 12:20:30 +00:00
6 changed files with 628 additions and 52 deletions

180
dist/index.cjs.js vendored
View File

@@ -2,16 +2,34 @@
var parseXml = require('@rgrove/parse-xml');
/// <reference types="cordova-plugin-ble-central" />
class Updater {
archiveURL;
feedType;
constructor(archiveURL = "/", feedType = "atom") {
bleObject;
bleDeviceId;
_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;
_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
*/
async getRawArchive() {
const res = await fetch(`https://cors.emaker.limited/?url=${this.archiveURL}.${this.feedType}`, {
"mode": "cors"
const res = await fetch(`http://cors.emaker.limited/?url=${this.archiveURL}.${this.feedType}`, {
// "mode": "cors"
});
const text = await res.text();
return text;
@@ -24,7 +42,7 @@ class Updater {
if (element.name == "title") {
outEntry.title = element.children[0].text;
}
else if (element.name == "date") {
else if (element.name == "updated") {
outEntry.date = new Date(element.children[0].text);
}
else if (element.name == "link") {
@@ -79,7 +97,7 @@ class Updater {
if (elm.type == "element") {
const element = elm;
if (element.name == "item") {
output.push(this.atomGetVersionDetails(element));
output.push(this.rssGetVersionDetails(element));
}
}
});
@@ -93,6 +111,158 @@ class Updater {
return this.rssGetArchive();
}
}
/*
BLUETOOTH
*/
setDeviceId(id) {
this.bleDeviceId = id;
}
bytesToString(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
async readVersionNumber() {
return new Promise((resolve, reject) => {
this.bleObject.read(this.bleDeviceId, this._updaterServiceUUID, this._updaterVersionCharactersiticUUID, (rawData) => {
resolve(this.bytesToString(rawData));
}, (error) => {
reject(`Error: ${error}`);
});
});
}
async getLatestVersion() {
let feed = await this.getArchive();
let newestDate = feed[0].date;
let i = 0;
feed.forEach((item, index) => {
if (item.date > newestDate) {
newestDate = item.date;
i = index;
}
});
return feed[i].title;
}
async checkForUpdate() {
// read device value
const deviceVersion = await this.readVersionNumber();
// compare with latest version
const latestVersion = await this.getLatestVersion();
if (deviceVersion != latestVersion) {
return true;
}
// update
return false;
}
async getBoardVersion() {
return await this.readVersionNumber();
}
/*
FILE FLASHING
*/
async getFirmware(version) {
try {
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;
}
}
getFileSize() {
return this._fileSize;
}
async sendNextPacket() {
let packet = this.file.slice(this._fileProgress, this._fileProgress + this._packetSize);
// this._fileProgress += this._packetSize;
return new Promise((resolve, reject) => {
this.bleObject.writeWithoutResponse(this.bleDeviceId, this._updaterServiceUUID, this._updateFileCharacteristicUUID, packet.buffer, () => {
console.log(`Sent: ${packet}, progress: ${this._fileProgress}`);
resolve(true);
}, (error) => {
this._fileProgress -= this._packetSize;
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) => {
// set mtu
this._packetSize = await this.bleObject.withPromises.requestMtu(this.bleDeviceId, this._packetSize);
// start notify
this.bleObject.startNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, (rawData) => {
let dataView = new Uint8Array(rawData);
console.log(dataView);
if (dataView[0] == 1 && dataView.length == 1) {
// send file
this.sendNextPacket();
progressCallback(`MTU: ${this._packetSize}; Sending (${Math.floor((this._fileProgress * 100) / this._fileSize)}%), ${this._fileProgress} / ${this._fileSize}`);
}
else if (dataView[0] == 2 && dataView.length == 1) {
// done logic
console.log(`progress >= filesize: ${this._fileProgress} vs ${this._fileSize}`);
if (this._fileProgress >= (this._fileSize - this._packetSize)) {
console.log("true");
// send agree
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 {
console.log("False");
// send disagree
this.sendEndCmd(false);
progressCallback(`Error, starting over: ${this._fileProgress} / ${this._fileSize}`);
this._fileProgress = 0;
}
}
else if (dataView[0] == 15 && dataView.length == 1) {
// error cmd
progressCallback(`Error on remote: ${this._fileProgress} / ${this._fileSize}`);
reject("Error on remote");
}
else if (dataView[0] == 0 && dataView.length == 1) {
// ignore no command
progressCallback(`Board is on`);
}
else {
// should be the file progress
let fileProgressView = new Uint32Array(rawData);
this._fileProgress = (dataView[3] << 24) |
(dataView[2] << 16) |
(dataView[1] << 8) | dataView[0];
this.sendNextPacket();
progressCallback(`Recieved progress ${fileProgressView[0]}; Sending (${Math.floor((this._fileProgress * 100) / this._fileSize)}%), ${this._fileProgress} / ${this._fileSize}`);
}
}, (error) => {
reject("Error: Failed to start notify");
console.error(error);
});
const buffer = new ArrayBuffer(4);
let view = new Int32Array(buffer);
view[0] = this._fileSize;
this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer);
});
}
}
module.exports = Updater;

23
dist/index.d.ts vendored
View File

@@ -7,11 +7,32 @@ export type versionNotes = {
export default class Updater {
archiveURL: string;
feedType: string;
constructor(archiveURL?: string, feedType?: string);
bleObject: BLECentralPlugin.BLECentralPluginStatic;
protected bleDeviceId: string;
private readonly _updaterServiceUUID;
private readonly _updaterVersionCharactersiticUUID;
private readonly _updaterCommandCharacterisitcUUID;
private readonly _updateFileCharacteristicUUID;
file: Int8Array;
private _fileSize;
private _fileProgress;
private _packetSize;
constructor(archiveURL?: string, feedType?: string, bleObject?: BLECentralPlugin.BLECentralPluginStatic, packetSize?: number);
private getRawArchive;
private atomGetVersionDetails;
private atomGetArchive;
private rssGetVersionDetails;
private rssGetArchive;
getArchive(): Promise<versionNotes[]>;
setDeviceId(id: string): void;
private bytesToString;
private readVersionNumber;
private getLatestVersion;
checkForUpdate(): Promise<boolean>;
getBoardVersion(): Promise<string>;
getFirmware(version: versionNotes): Promise<boolean>;
getFileSize(): number;
private sendNextPacket;
private sendEndCmd;
flashFirmware(progressCallback: (message: string) => void): Promise<boolean>;
}

180
dist/index.es.js vendored
View File

@@ -1,15 +1,33 @@
import { parseXml } from '@rgrove/parse-xml';
/// <reference types="cordova-plugin-ble-central" />
class Updater {
archiveURL;
feedType;
constructor(archiveURL = "/", feedType = "atom") {
bleObject;
bleDeviceId;
_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;
_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
*/
async getRawArchive() {
const res = await fetch(`https://cors.emaker.limited/?url=${this.archiveURL}.${this.feedType}`, {
"mode": "cors"
const res = await fetch(`http://cors.emaker.limited/?url=${this.archiveURL}.${this.feedType}`, {
// "mode": "cors"
});
const text = await res.text();
return text;
@@ -22,7 +40,7 @@ class Updater {
if (element.name == "title") {
outEntry.title = element.children[0].text;
}
else if (element.name == "date") {
else if (element.name == "updated") {
outEntry.date = new Date(element.children[0].text);
}
else if (element.name == "link") {
@@ -77,7 +95,7 @@ class Updater {
if (elm.type == "element") {
const element = elm;
if (element.name == "item") {
output.push(this.atomGetVersionDetails(element));
output.push(this.rssGetVersionDetails(element));
}
}
});
@@ -91,6 +109,158 @@ class Updater {
return this.rssGetArchive();
}
}
/*
BLUETOOTH
*/
setDeviceId(id) {
this.bleDeviceId = id;
}
bytesToString(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
async readVersionNumber() {
return new Promise((resolve, reject) => {
this.bleObject.read(this.bleDeviceId, this._updaterServiceUUID, this._updaterVersionCharactersiticUUID, (rawData) => {
resolve(this.bytesToString(rawData));
}, (error) => {
reject(`Error: ${error}`);
});
});
}
async getLatestVersion() {
let feed = await this.getArchive();
let newestDate = feed[0].date;
let i = 0;
feed.forEach((item, index) => {
if (item.date > newestDate) {
newestDate = item.date;
i = index;
}
});
return feed[i].title;
}
async checkForUpdate() {
// read device value
const deviceVersion = await this.readVersionNumber();
// compare with latest version
const latestVersion = await this.getLatestVersion();
if (deviceVersion != latestVersion) {
return true;
}
// update
return false;
}
async getBoardVersion() {
return await this.readVersionNumber();
}
/*
FILE FLASHING
*/
async getFirmware(version) {
try {
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;
}
}
getFileSize() {
return this._fileSize;
}
async sendNextPacket() {
let packet = this.file.slice(this._fileProgress, this._fileProgress + this._packetSize);
// this._fileProgress += this._packetSize;
return new Promise((resolve, reject) => {
this.bleObject.writeWithoutResponse(this.bleDeviceId, this._updaterServiceUUID, this._updateFileCharacteristicUUID, packet.buffer, () => {
console.log(`Sent: ${packet}, progress: ${this._fileProgress}`);
resolve(true);
}, (error) => {
this._fileProgress -= this._packetSize;
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) => {
// set mtu
this._packetSize = await this.bleObject.withPromises.requestMtu(this.bleDeviceId, this._packetSize);
// start notify
this.bleObject.startNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, (rawData) => {
let dataView = new Uint8Array(rawData);
console.log(dataView);
if (dataView[0] == 1 && dataView.length == 1) {
// send file
this.sendNextPacket();
progressCallback(`MTU: ${this._packetSize}; Sending (${Math.floor((this._fileProgress * 100) / this._fileSize)}%), ${this._fileProgress} / ${this._fileSize}`);
}
else if (dataView[0] == 2 && dataView.length == 1) {
// done logic
console.log(`progress >= filesize: ${this._fileProgress} vs ${this._fileSize}`);
if (this._fileProgress >= (this._fileSize - this._packetSize)) {
console.log("true");
// send agree
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 {
console.log("False");
// send disagree
this.sendEndCmd(false);
progressCallback(`Error, starting over: ${this._fileProgress} / ${this._fileSize}`);
this._fileProgress = 0;
}
}
else if (dataView[0] == 15 && dataView.length == 1) {
// error cmd
progressCallback(`Error on remote: ${this._fileProgress} / ${this._fileSize}`);
reject("Error on remote");
}
else if (dataView[0] == 0 && dataView.length == 1) {
// ignore no command
progressCallback(`Board is on`);
}
else {
// should be the file progress
let fileProgressView = new Uint32Array(rawData);
this._fileProgress = (dataView[3] << 24) |
(dataView[2] << 16) |
(dataView[1] << 8) | dataView[0];
this.sendNextPacket();
progressCallback(`Recieved progress ${fileProgressView[0]}; Sending (${Math.floor((this._fileProgress * 100) / this._fileSize)}%), ${this._fileProgress} / ${this._fileSize}`);
}
}, (error) => {
reject("Error: Failed to start notify");
console.error(error);
});
const buffer = new ArrayBuffer(4);
let view = new Int32Array(buffer);
view[0] = this._fileSize;
this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer);
});
}
}
export { Updater as default };

View File

@@ -1,6 +1,6 @@
{
"name": "updaterweblibrary",
"version": "1.0.6",
"version": "1.1.0",
"description": "OTA Updater App frontend library",
"repository": {
"type": "git",
@@ -24,7 +24,8 @@
"scripts": {
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
"build": "rm -rf ./dist && npm run build:types && rollup -c",
"test": "vitest"
"test": "vitest",
"version": "npm run build && git add -A ./dist"
},
"devDependencies": {
"@rollup/plugin-typescript": "^12.1.4",
@@ -34,6 +35,7 @@
"vitest": "^3.2.4"
},
"dependencies": {
"@rgrove/parse-xml": "^4.2.0"
"@rgrove/parse-xml": "^4.2.0",
"cordova-plugin-ble-central": "^2.0.0"
}
}

View File

@@ -1,23 +1,47 @@
/// <reference types="cordova-plugin-ble-central" />
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 {
public archiveURL: string;
public feedType: string;
constructor(archiveURL: string = "/", feedType: string = "atom") {
public bleObject: BLECentralPlugin.BLECentralPluginStatic;
protected bleDeviceId: string;
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, packetSize: number = 512) {
this.archiveURL = archiveURL;
this.feedType = feedType;
if (bleObject) {
this.bleObject = bleObject;
}
this._packetSize = packetSize;
}
/*
FEEDS
*/
private async getRawArchive(): Promise<string> {
const res = await fetch(`https://cors.emaker.limited/?url=${this.archiveURL}.${this.feedType}`, {
"mode": "cors"
const res = await fetch(`http://cors.emaker.limited/?url=${this.archiveURL}.${this.feedType}`, {
// "mode": "cors"
});
const text = await res.text();
return text;
@@ -25,19 +49,19 @@ 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") {
outEntry.title = (element.children[0] as XmlText).text;
}
else if (element.name == "date") {
else if (element.name == "updated") {
outEntry.date = new Date((element.children[0] as XmlText).text);
}
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;
}
})
@@ -63,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") {
@@ -75,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;
}
})
@@ -91,19 +115,194 @@ export default class Updater {
if (elm.type == "element") {
const element = elm as XmlElement;
if (element.name == "item") {
output.push(this.atomGetVersionDetails(element))
output.push(this.rssGetVersionDetails(element))
}
}
})
return output;
}
public async getArchive(): Promise<versionNotes[]> {
if (this.feedType == "atom") {
return this.atomGetArchive()
} else if (this.feedType == "rss"){
} else if (this.feedType == "rss") {
return this.rssGetArchive()
}
}
/*
BLUETOOTH
*/
public setDeviceId(id: string): void {
this.bleDeviceId = id;
}
private bytesToString(buffer: ArrayBuffer): string {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
private async readVersionNumber(): Promise<string> {
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}`);
}
)
});
}
private async getLatestVersion(): Promise<string> {
let feed: versionNotes[] = await this.getArchive();
let newestDate: Date = feed[0].date as Date;
let i: number = 0;
feed.forEach((item: versionNotes, index: number) => {
if (item.date > newestDate) {
newestDate = item.date as Date;
i = index;
}
});
return feed[i].title;
}
public async checkForUpdate(): Promise<boolean> {
// read device value
const deviceVersion = await this.readVersionNumber();
// compare with latest version
const latestVersion = await this.getLatestVersion();
if (deviceVersion != latestVersion) {
return true;
}
// update
return false;
}
public async getBoardVersion(): Promise<string> {
return await this.readVersionNumber();
}
/*
FILE FLASHING
*/
public async getFirmware(version: versionNotes): Promise<boolean> {
try {
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;
}
}
public getFileSize(): number {
return this._fileSize;
}
private async sendNextPacket(): Promise<boolean> {
let packet = this.file.slice(this._fileProgress, this._fileProgress+this._packetSize);
// this._fileProgress += this._packetSize;
return new Promise((resolve, reject) => {
this.bleObject.writeWithoutResponse(this.bleDeviceId, this._updaterServiceUUID, this._updateFileCharacteristicUUID, packet.buffer,
() => {
console.log(`Sent: ${packet}, progress: ${this._fileProgress}`);
resolve(true);
},
(error) => {
this._fileProgress -= this._packetSize;
reject(error);
}
)
});
}
private async sendEndCmd(agree: boolean): Promise<void> {
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<boolean> {
// write filesize to board
// await notify from cmd - ready
// send a packet
// check for error
// write file length
return new Promise(async (resolve, reject) => {
// set mtu
this._packetSize = await this.bleObject.withPromises.requestMtu(this.bleDeviceId, this._packetSize);
// start notify
this.bleObject.startNotification(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID,
(rawData: ArrayBuffer): void => {
let dataView = new Uint8Array(rawData);
console.log(dataView);
if (dataView[0] == 1 && dataView.length == 1) {
// send file
this.sendNextPacket();
progressCallback(`MTU: ${this._packetSize}; Sending (${Math.floor((this._fileProgress *100)/this._fileSize)}%), ${this._fileProgress} / ${this._fileSize}`);
} else if (dataView[0] == 2 && dataView.length == 1) {
// done logic
console.log(`progress >= filesize: ${this._fileProgress} vs ${this._fileSize}`);
if (this._fileProgress >= (this._fileSize - this._packetSize)) {
console.log("true");
// send agree
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 {
console.log("False");
// send disagree
this.sendEndCmd(false);
progressCallback(`Error, starting over: ${this._fileProgress} / ${this._fileSize}`);
this._fileProgress = 0;
}
} else if (dataView[0] == 15 && dataView.length == 1) {
// error cmd
progressCallback(`Error on remote: ${this._fileProgress} / ${this._fileSize}`);
reject("Error on remote");
} else if (dataView[0] == 0 && dataView.length == 1) {
// ignore no command
progressCallback(`Board is on`);
}
else {
// should be the file progress
let fileProgressView = new Uint32Array(rawData);
this._fileProgress = (dataView[3] << 24) |
(dataView[2] << 16) |
(dataView[1] << 8) | dataView[0];
this.sendNextPacket();
progressCallback(`Recieved progress ${fileProgressView[0]}; Sending (${Math.floor((this._fileProgress *100)/this._fileSize)}%), ${this._fileProgress} / ${this._fileSize}`);
}
}, (error) => { reject("Error: Failed to start notify");
console.error(error);
}
);
const buffer = new ArrayBuffer(4)
let view = new Int32Array(buffer);
view[0] = this._fileSize;
this.bleObject.withPromises.write(this.bleDeviceId, this._updaterServiceUUID, this._updaterCommandCharacterisitcUUID, buffer);
});
}
}

View File

@@ -1,23 +1,37 @@
import { expect, expectTypeOf, test } from "vitest";
import Updater from "../src/index.ts";
import { describe, expect, test } from "vitest";
import Updater, { versionNotes } from "../src/index.ts";
test("get an atom feed from gitea", async (): Promise<void> => {
describe("retrieving data", () => {
test("get an atom feed from gitea", async (): Promise<void> => {
const updater = new Updater("https://git.emaker.limited/MicrocontrollerCD/SoftwareRelease/releases", "atom");
const res = await updater.getArchive();
expect(res).not.toBe(null);
expect(res.length).toBeGreaterThan(0);
});
test("get an rss feed from gitea", async (): Promise<void> => {
const updater = new Updater("https://git.emaker.limited/MicrocontrollerCD/SoftwareRelease/releases", "rss");
const res = await updater.getArchive();
expect(res).not.toBe(null);
expect(res.length).toBeGreaterThan(0);
});
test("get an atom feed from github", async (): Promise<void> => {
const updater = new Updater("https://github.com/chopster44/Phaser_3_pong/releases", "atom");
const res = await updater.getArchive();
expect(res).not.toBe(null);
expect(res.length).toBeGreaterThan(0);
});
});
describe("data validation", () => {
const updater = new Updater("https://git.emaker.limited/MicrocontrollerCD/SoftwareRelease/releases", "atom");
const res = await updater.getArchive();
expect(res).not.toBe(null);
expect(res.length).toBeGreaterThan(0);
});
test("get an rss feed from gitea", async(): Promise<void> => {
const updater = new Updater("https://git.emaker.limited/MicrocontrollerCD/SoftwareRelease/releases", "rss");
const res = await updater.getArchive();
expect(res).not.toBe(null);
expect(res.length).toBeGreaterThan(0);
});
test("get an atom feed from github", async (): Promise<void> => {
const updater = new Updater("https://github.com/chopster44/Phaser_3_pong/releases", "atom");
const res = await updater.getArchive();
expect(res).not.toBe(null);
expect(res.length).toBeGreaterThan(0);
});
test("date checked is not current date", async () => {
const res: versionNotes[] = await updater.getArchive();
const date: Date = res[0].date as Date;
const today: Date = new Date;
console.log(date);
console.log(today)
expect(date.getUTCSeconds()).not.toBe(today.getUTCSeconds())
})
})