-
-
Notifications
You must be signed in to change notification settings - Fork 508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: sds firmware update #3142
Draft
Julusian
wants to merge
6
commits into
main
Choose a base branch
from
feat/sds-firmware-update
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
9912001
feat: check for and indicate sds firmware updates against hardcoded v…
Julusian 153a536
chore: update streamdeck lib
Julusian a52ba44
feat: online sds version update check
Julusian 0ac92ac
chore: update lib
Julusian a914b88
fix
Julusian abf81de
Update companion/lib/Surface/FirmwareUpdateCheck.ts
Julusian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import { isEqual } from 'lodash-es' | ||
import type { SurfaceHandler } from './Handler.js' | ||
import LogController from '../Log/Controller.js' | ||
|
||
const FIRMWARE_UPDATE_POLL_INTERVAL = 1000 * 60 * 60 * 24 // 24 hours | ||
const FIRMWARE_PAYLOAD_CACHE_TTL = 1000 * 60 * 60 * 4 // 4 hours | ||
const FIRMWARE_PAYLOAD_CACHE_MAX_TTL = 1000 * 60 * 60 * 24 // 24 hours | ||
|
||
interface PayloadCacheEntry { | ||
timestamp: number | ||
payload: Record<string, string> | ||
} | ||
|
||
export class SurfaceFirmwareUpdateCheck { | ||
readonly #logger = LogController.createLogger('Surface/FirmwareUpdateCheck') | ||
|
||
readonly #payloadCache = new Map<string, PayloadCacheEntry>() | ||
|
||
readonly #payloadUpdating = new Map<string, Promise<Record<string, string> | null>>() | ||
|
||
/** | ||
* All the opened and active surfaces | ||
*/ | ||
readonly #surfaceHandlers: Map<string, SurfaceHandler | null> | ||
|
||
readonly #updateDevicesList: () => void | ||
|
||
constructor(surfaceHandlers: Map<string, SurfaceHandler | null>, updateDevicesList: () => void) { | ||
this.#surfaceHandlers = surfaceHandlers | ||
this.#updateDevicesList = updateDevicesList | ||
|
||
setInterval(() => this.#checkAllSurfacesForUpdates(), FIRMWARE_UPDATE_POLL_INTERVAL) | ||
setTimeout(() => this.#checkAllSurfacesForUpdates(), 5000) | ||
} | ||
|
||
#checkAllSurfacesForUpdates() { | ||
// Compile a list of all urls to check, and the surfaces that use them | ||
const allUpdateUrls = new Map<string, string[]>() | ||
for (const [surfaceId, handler] of this.#surfaceHandlers) { | ||
if (!handler) continue | ||
const updateUrl = handler.panel.info.firmwareUpdateVersionsUrl | ||
if (!updateUrl) continue | ||
|
||
const currentList = allUpdateUrls.get(updateUrl) | ||
if (currentList) { | ||
currentList.push(handler.surfaceId) | ||
} else { | ||
allUpdateUrls.set(updateUrl, [surfaceId]) | ||
} | ||
} | ||
|
||
// No updates to check | ||
if (allUpdateUrls.size === 0) return | ||
|
||
this.#logger.debug(`Checking for firmware updates from ${allUpdateUrls.size} urls`) | ||
|
||
Promise.resolve() | ||
.then(async () => { | ||
await Promise.allSettled( | ||
Array.from(allUpdateUrls).map(async ([url, surfaceIds]) => { | ||
// Scrape the api for an updated payload | ||
const versionsInfo = await this.#fetchPayloadForUrl(url, true) | ||
|
||
// Perform the update for each surface | ||
await Promise.allSettled( | ||
surfaceIds.map((surfaceId) => { | ||
const handler = this.#surfaceHandlers.get(surfaceId) | ||
if (!handler) return | ||
|
||
return this.#performForSurface(handler, versionsInfo) | ||
}) | ||
) | ||
}) | ||
) | ||
|
||
// Inform the ui, even though there may be no changes | ||
this.#updateDevicesList() | ||
}) | ||
.catch((e) => { | ||
this.#logger.warn(`Failed to check for firmware updates: ${e}`) | ||
}) | ||
} | ||
|
||
/** | ||
* Fetch the payload for a specific url, either from cache or from the server | ||
* @param url The url to fetch the payload from | ||
* @param skipCache Whether to skip the cache and always fetch a new payload | ||
* @returns The payload, or null if it could not be fetched | ||
*/ | ||
async #fetchPayloadForUrl(url: string, skipCache?: boolean): Promise<Record<string, string> | null> { | ||
let cacheEntry = this.#payloadCache.get(url) | ||
|
||
// Check if the cache is too old to be usable | ||
if (cacheEntry && cacheEntry.timestamp < Date.now() - FIRMWARE_PAYLOAD_CACHE_MAX_TTL) { | ||
cacheEntry = undefined | ||
this.#payloadCache.delete(url) | ||
} | ||
|
||
// Check if cache is new enough to return directly | ||
if (!skipCache && cacheEntry && cacheEntry.timestamp < Date.now() - FIRMWARE_PAYLOAD_CACHE_TTL) { | ||
return cacheEntry.payload | ||
} | ||
|
||
// If one is in flight, return that | ||
const currentInFlight = this.#payloadUpdating.get(url) | ||
if (currentInFlight) return currentInFlight | ||
|
||
// @ts-expect-error | ||
const { promise: pendingPromise, resolve } = Promise.withResolvers<Record<string, string> | null>() | ||
this.#payloadUpdating.set(url, pendingPromise) | ||
|
||
// Fetch new data | ||
fetch(url) | ||
.then((res) => res.json() as Promise<Record<string, string>>) | ||
.catch((e) => { | ||
this.#logger.warn(`Failed to fetch firmware update payload from "${url}": ${e}`) | ||
return null | ||
}) | ||
.then((newPayload) => { | ||
// Update cache with the new value | ||
if (newPayload) { | ||
this.#payloadCache.set(url, { timestamp: Date.now(), payload: newPayload }) | ||
} | ||
|
||
// No longer in flight | ||
this.#payloadUpdating.delete(url) | ||
|
||
// Return the new value | ||
resolve(newPayload || cacheEntry?.payload || null) | ||
}) | ||
|
||
return pendingPromise | ||
} | ||
|
||
/** | ||
* Trigger a check for updates for a specific surface | ||
* @param surface Surface to check for updates | ||
*/ | ||
triggerCheckSurfaceForUpdates(surface: SurfaceHandler): void { | ||
setTimeout(() => { | ||
Promise.resolve() | ||
.then(async () => { | ||
// fetch latest versions info | ||
const versionsInfo = surface.panel.info.firmwareUpdateVersionsUrl | ||
? await this.#fetchPayloadForUrl(surface.panel.info.firmwareUpdateVersionsUrl) | ||
: null | ||
|
||
const changed = await this.#performForSurface(surface, versionsInfo) | ||
|
||
// Inform ui of the updates | ||
if (changed) this.#updateDevicesList() | ||
}) | ||
|
||
.catch((e) => { | ||
this.#logger.warn(`Failed to check for firmware updates for surface "${surface.surfaceId}": ${e}`) | ||
}) | ||
}, 0) | ||
} | ||
|
||
async #performForSurface(surface: SurfaceHandler, versionsInfo: Record<string, string> | null): Promise<boolean> { | ||
// Check if panel has updates | ||
const firmwareUpdatesBefore = surface.panel.info.hasFirmwareUpdates | ||
await surface.panel.checkForFirmwareUpdates?.(versionsInfo ?? undefined) | ||
if (isEqual(firmwareUpdatesBefore, surface.panel.info.hasFirmwareUpdates)) return false | ||
|
||
this.#logger.info(`Firmware updates change for surface "${surface.surfaceId}"`) | ||
|
||
return true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make more sense to serve this from GitHub raw like so?:
https://raw.githubusercontent.com/bitfocus/companion/refs/heads/main/nodejs-versions.json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both of these urls are placeholders currently, waiting on the update tool to be available