Skip to content

Commit 122e684

Browse files
committed
feat: implement OTA upload hook
- refactor GH and download firmwares into separate hooks/files - fix some bugs in websocket hook
1 parent d5f70a6 commit 122e684

File tree

6 files changed

+154
-128
lines changed

6 files changed

+154
-128
lines changed

GUI/ETVR/src/pages/appSettings/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { EraseButton } from '@components/Button/EraseButton'
22
import WebSerial from '@components/WebSerial'
3-
import { useGHRelease } from '@hooks/api/useGHReleases'
3+
import { useDownloadFirmware } from '@hooks/api/useDownloadFirmware'
44
import { handleSound } from '@hooks/app'
55

66
const AppSettings = () => {
7-
const downloadAsset = useGHRelease()
7+
const downloadAsset = useDownloadFirmware()
88
return (
99
<div class="flex justify-center items-center content-center flex-col pt-[100px] text-white">
1010
Coming Soon
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { readTextFile, BaseDirectory, writeTextFile } from '@tauri-apps/api/fs'
2+
import { appConfigDir, join } from '@tauri-apps/api/path'
3+
import { invoke, convertFileSrc } from '@tauri-apps/api/tauri'
4+
import { download } from 'tauri-plugin-upload-api'
5+
import { addNotification, ENotificationType, ENotificationAction } from '@hooks/notifications'
6+
import { firmwareAssets, firmwareVersion } from '@store/api/selectors'
7+
8+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
9+
const getRelease = async (firmware: string) => {
10+
const appConfigDirPath = await appConfigDir()
11+
if (firmware === '') {
12+
// TODO: notify user to select a firmware
13+
console.log('[Github Release]: No firmware selected')
14+
return
15+
}
16+
17+
console.log('[Github Release]: App Config Dir: ', appConfigDirPath)
18+
19+
// check if the firmware chosen matches the one names in the firmwareAssets array of objects
20+
const firmwareAsset = firmwareAssets().find((asset) => asset.name === firmware)
21+
22+
console.log('[Github Release]: Firmware Asset: ', firmwareAsset)
23+
24+
if (firmwareAsset) {
25+
console.log('[Github Release]: Downloading firmware: ', firmware)
26+
console.log('[Github Release]: Firmware URL: ', firmwareAsset)
27+
28+
// parse out the file name from the firmwareAsset.url and append it to the appConfigDirPath
29+
const fileName =
30+
firmwareAsset.browser_download_url.split('/')[
31+
firmwareAsset.browser_download_url.split('/').length - 1
32+
]
33+
//console.log('[Github Release]: File Name: ', fileName)
34+
// ${appConfigDirPath}${fileName}
35+
const path = await join(appConfigDirPath, fileName)
36+
console.log('[Github Release]: Path: ', path)
37+
// get the latest release
38+
const response = await download(
39+
firmwareAsset.browser_download_url,
40+
path,
41+
(progress, total) => {
42+
console.log(`[Github Release]: Downloaded ${progress} of ${total} bytes`)
43+
},
44+
)
45+
console.log('[Github Release]: Download Response: ', response)
46+
47+
addNotification(
48+
{
49+
title: 'ETVR Firmware Downloaded',
50+
message: `Downloaded Firmware ${firmware}`,
51+
type: ENotificationType.INFO,
52+
},
53+
ENotificationAction.APP,
54+
)
55+
56+
const res = await invoke('unzip_archive', {
57+
archivePath: path,
58+
targetDir: appConfigDirPath,
59+
})
60+
61+
console.log('[Github Release]: Unzip Response: ', res)
62+
63+
const manifest = await readTextFile('manifest.json', { dir: BaseDirectory.AppConfig })
64+
65+
const config_json = JSON.parse(manifest)
66+
67+
if (manifest !== '') {
68+
// modify the version property
69+
config_json['version'] = firmwareVersion()
70+
// loop through the builds array and the parts array and update the path property
71+
for (let i = 0; i < config_json['builds'].length; i++) {
72+
for (let j = 0; j < config_json['builds'][i]['parts'].length; j++) {
73+
const firmwarePath = await join(
74+
appConfigDirPath,
75+
config_json['builds'][i]['parts'][j]['path'],
76+
)
77+
console.log('[Github Release]: Firmware Path: ', firmwarePath)
78+
const firmwareSrc = convertFileSrc(firmwarePath)
79+
console.log('[Github Release]: Firmware Src: ', firmwareSrc)
80+
config_json['builds'][i]['parts'][j]['path'] = firmwareSrc
81+
}
82+
}
83+
84+
// write the config file
85+
writeTextFile('manifest.json', JSON.stringify(config_json), {
86+
dir: BaseDirectory.AppConfig,
87+
})
88+
.then(() => {
89+
console.log('[Manifest Updated]: Manifest Updated Successfully')
90+
})
91+
.finally(() => {
92+
console.log('[Manifest Updated]: Finished')
93+
})
94+
.catch((err) => {
95+
console.error('[Manifest Update Error]: ', err)
96+
})
97+
98+
console.log('[Github Release]: Manifest: ', config_json)
99+
return
100+
}
101+
}
102+
}
103+
104+
/**
105+
* @description A hook that will return the data from the github release endpoint and a function that will download the asset from the github release endpoint
106+
* @returns {object} data - The data returned from the github release endpoint
107+
* @returns {function} downloadAsset - The function that will download the asset from the github release endpoint
108+
*/
109+
export const useDownloadFirmware = () => {
110+
const downloadAsset = async (firmware: string) => {
111+
const response = await getRelease(firmware)
112+
console.log('[Github Release]: Download Response: ', response)
113+
}
114+
return downloadAsset
115+
}

GUI/ETVR/src/utils/hooks/api/useGHReleases.ts

+2-112
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { readTextFile, BaseDirectory, writeTextFile } from '@tauri-apps/api/fs'
22
import { getClient, ResponseType } from '@tauri-apps/api/http'
3-
import { appConfigDir, join } from '@tauri-apps/api/path'
4-
import { invoke, convertFileSrc } from '@tauri-apps/api/tauri'
5-
import { download } from 'tauri-plugin-upload-api'
63
import { addNotification, ENotificationType, ENotificationAction } from '@hooks/notifications'
74
import { setFirmwareAssets, setGHRestStatus, setFirmwareVersion } from '@store/api/ghAPI'
85
import { RESTStatus } from '@store/api/restAPI'
@@ -11,6 +8,8 @@ import { ghRESTEndpoint, firmwareAssets, firmwareVersion } from '@store/api/sele
118
// TODO: Implement a way to readif the merged-firmware.bin file and manifest.json file exists in the app config directory and if it does, then use that instead of downloading the firmware from github
129
// Note: If both files are present, we should early return and set a UI store value that will be used to display a message to the user that they can use the firmware that is already downloadedand show an optional button to erase the firmware
1310

11+
//TODO: Add notifications to all the console.log statements
12+
1413
interface IGHRelease {
1514
data: object
1615
headers: object
@@ -20,115 +19,6 @@ interface IGHRelease {
2019
url: string
2120
}
2221

23-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
24-
const getRelease = async (firmware: string) => {
25-
const appConfigDirPath = await appConfigDir()
26-
if (firmware === '') {
27-
// TODO: notify user to select a firmware
28-
console.log('[Github Release]: No firmware selected')
29-
return
30-
}
31-
32-
console.log('[Github Release]: App Config Dir: ', appConfigDirPath)
33-
34-
// check if the firmware chosen matches the one names in the firmwareAssets array of objects
35-
const firmwareAsset = firmwareAssets().find((asset) => asset.name === firmware)
36-
37-
console.log('[Github Release]: Firmware Asset: ', firmwareAsset)
38-
39-
if (firmwareAsset) {
40-
console.log('[Github Release]: Downloading firmware: ', firmware)
41-
console.log('[Github Release]: Firmware URL: ', firmwareAsset)
42-
43-
// parse out the file name from the firmwareAsset.url and append it to the appConfigDirPath
44-
const fileName =
45-
firmwareAsset.browser_download_url.split('/')[
46-
firmwareAsset.browser_download_url.split('/').length - 1
47-
]
48-
//console.log('[Github Release]: File Name: ', fileName)
49-
// ${appConfigDirPath}${fileName}
50-
const path = await join(appConfigDirPath, fileName)
51-
console.log('[Github Release]: Path: ', path)
52-
// get the latest release
53-
const response = await download(
54-
firmwareAsset.browser_download_url,
55-
path,
56-
(progress, total) => {
57-
console.log(`[Github Release]: Downloaded ${progress} of ${total} bytes`)
58-
},
59-
)
60-
console.log('[Github Release]: Download Response: ', response)
61-
62-
addNotification(
63-
{
64-
title: 'ETVR Firmware Downloaded',
65-
message: `Downloaded Firmware ${firmware}`,
66-
type: ENotificationType.INFO,
67-
},
68-
ENotificationAction.APP,
69-
)
70-
71-
const res = await invoke('unzip_archive', {
72-
archivePath: path,
73-
targetDir: appConfigDirPath,
74-
})
75-
76-
console.log('[Github Release]: Unzip Response: ', res)
77-
78-
const manifest = await readTextFile('manifest.json', { dir: BaseDirectory.AppConfig })
79-
80-
const config_json = JSON.parse(manifest)
81-
82-
if (manifest !== '') {
83-
// modify the version property
84-
config_json['version'] = firmwareVersion()
85-
// loop through the builds array and the parts array and update the path property
86-
for (let i = 0; i < config_json['builds'].length; i++) {
87-
for (let j = 0; j < config_json['builds'][i]['parts'].length; j++) {
88-
const firmwarePath = await join(
89-
appConfigDirPath,
90-
config_json['builds'][i]['parts'][j]['path'],
91-
)
92-
console.log('[Github Release]: Firmware Path: ', firmwarePath)
93-
const firmwareSrc = convertFileSrc(firmwarePath)
94-
console.log('[Github Release]: Firmware Src: ', firmwareSrc)
95-
config_json['builds'][i]['parts'][j]['path'] = firmwareSrc
96-
}
97-
}
98-
99-
// write the config file
100-
writeTextFile('manifest.json', JSON.stringify(config_json), {
101-
dir: BaseDirectory.AppConfig,
102-
})
103-
.then(() => {
104-
console.log('[Manifest Updated]: Manifest Updated Successfully')
105-
})
106-
.finally(() => {
107-
console.log('[Manifest Updated]: Finished')
108-
})
109-
.catch((err) => {
110-
console.error('[Manifest Update Error]: ', err)
111-
})
112-
113-
console.log('[Github Release]: Manifest: ', config_json)
114-
return
115-
}
116-
}
117-
}
118-
119-
/**
120-
* @description A hook that will return the data from the github release endpoint and a function that will download the asset from the github release endpoint
121-
* @returns {object} data - The data returned from the github release endpoint
122-
* @returns {function} downloadAsset - The function that will download the asset from the github release endpoint
123-
*/
124-
export const useGHRelease = () => {
125-
const downloadAsset = async (firmware: string) => {
126-
const response = await getRelease(firmware)
127-
console.log('[Github Release]: Download Response: ', response)
128-
}
129-
return downloadAsset
130-
}
131-
13222
const setGHData = (data: IGHRelease, update: boolean) => {
13323
setFirmwareVersion(data['name'])
13424
const assets = data['assets']
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { appConfigDir, join } from '@tauri-apps/api/path'
2+
import { upload } from 'tauri-plugin-upload-api'
3+
import { endpoints } from '@src/store/api/selectors'
4+
import { cameras } from '@src/store/camera/selectors'
5+
6+
/**
7+
* @description Uploads a firmware to a device
8+
* @returns doOTA - function that takes a firmware name and a device address and uploads the firmware to the device
9+
*/
10+
export const useOTA = async () => {
11+
const _endpoints = endpoints()
12+
const doOTA = async (firmwareName: string, device: string) => {
13+
const ota: string = _endpoints.get('ota')?.url ?? ''
14+
const camera = cameras().find((camera) => camera.address === device)
15+
if (!camera) {
16+
console.log('No camera found at that address')
17+
return
18+
}
19+
const server = camera.address + ota
20+
const path = await join(await appConfigDir(), firmwareName + '.bin')
21+
await upload(server, path)
22+
}
23+
return { doOTA }
24+
}

GUI/ETVR/src/utils/hooks/app/index.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { invoke } from '@tauri-apps/api/tauri'
22
import { appWindow } from '@tauri-apps/api/window'
3-
import { setWebsocketClients } from '@store/api/websocket'
43
import { doGHRequest } from '@utils/hooks/api/useGHReleases'
54
import { generateWebsocketClients } from '@utils/hooks/websocket'
65

@@ -32,8 +31,7 @@ export const handleAppBoot = () => {
3231

3332
// TODO: call these after the MDNS service is up and running and discoveres the camera's
3433
// TODO: Implement a websocket client that can be used to connect to the camera'susing the `tauri-plugin-websocket` rust crate
35-
//const clients = generateWebsocketClients()
36-
//setWebsocketClients(clients)
34+
generateWebsocketClients()
3735
doGHRequest()
3836
}
3937

GUI/ETVR/src/utils/hooks/websocket/index.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface IWebRTCMessage {
1111
msg: string | object | null | undefined
1212
}
1313

14-
const sendToRTCServer = (msg: IWebRTCMessage) => {
14+
export const sendToRTCServer = (msg: IWebRTCMessage) => {
1515
if (!msg) {
1616
console.error('[sendToRTCServer]: Message is null or undefined')
1717
return
@@ -22,8 +22,7 @@ const sendToRTCServer = (msg: IWebRTCMessage) => {
2222
}
2323

2424
export const check = () => {
25-
const ws = cameras()
26-
ws.forEach((camera) => {
25+
cameras().forEach((camera) => {
2726
if (!camera.ws || camera.ws.readyState == WebSocket.CLOSED) {
2827
//check if websocket instance is closed, if so call `init` function.
2928
setRTCStatus(RTCState.DISCONNECTED)
@@ -32,12 +31,14 @@ export const check = () => {
3231
})
3332
}
3433

35-
const generateWebsocketClients = () => {
36-
const clients = cameras().map((_, i) => {
37-
return new WebSocket(`${LOCAL_HOST}:${PORT}/camera_${i + 1}`)
34+
export const generateWebsocketClients = () => {
35+
const clients = cameras().map((camera, i) => {
36+
if (!camera.ws) {
37+
camera.ws = new WebSocket(`${LOCAL_HOST}:${PORT + i}`)
38+
return camera.ws
39+
}
3840
})
39-
console.log('[WebSocket Handler]: websocket clients', clients)
40-
return clients
41+
//console.log('[WebSocket Handler]: Websocket Clients - ', clients)
4142
}
4243

4344
/********************************* connect *************************************/
@@ -46,7 +47,7 @@ const generateWebsocketClients = () => {
4647
* we use websocket heartbeat to the server
4748
* every 10 seconds
4849
*/
49-
const initWebSocket = () => {
50+
export const initWebSocket = () => {
5051
setRTCStatus(RTCState.CONNECTING)
5152
cameras().forEach((camera) => {
5253
if (camera.ws) {
@@ -105,5 +106,3 @@ const initWebSocket = () => {
105106
}
106107
})
107108
}
108-
109-
export { sendToRTCServer, initWebSocket, generateWebsocketClients }

0 commit comments

Comments
 (0)