Skip to content
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: Add autodl for Hue (partial) #663

Merged
merged 3 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,18 @@ Any field not in this list will be ignored. Any field not matching the required

###### To place restrictions

- "force": boolean _(ignore `fileVersion` and always present as 'available')_
- "hardwareVersionMax": number
- "hardwareVersionMin": number
- "manufacturerName": array of strings _(target only devices with one of these manufacturer names)_
- "maxFileVersion": number _(target only devices with this version or below)_
- "minFileVersion": number _(target only devices with this version or above)_
- "modelId": string _(target only devices with this model ID)_
- "force": boolean _(ignore `fileVersion` and always present as 'available')_
- "hardwareVersionMax": number
- "hardwareVersionMin": number
- "manufacturerName": array of strings _(target only devices with one of these manufacturer names)_
- "maxFileVersion": number _(target only devices with this version or below)_
- "minFileVersion": number _(target only devices with this version or above)_
- "modelId": string _(target only devices with this model ID)_

###### For record purpose

- "originalUrl": string
- "releaseNotes": string
- "originalUrl": string
- "releaseNotes": string

If the pull request contains multiple files, the metadata is added for all files. If some files require different metadata, add the matching `fileName` to the JSON using an encompassing array instead. It will be used to assign metadata as directed.

Expand All @@ -114,8 +114,8 @@ Example:

### Notes for maintainers & developers

- `images` and `index.json` contain added (PR or auto download) "upgrade" images.
- `images1` and `index1.json` contain automatically archived "downgrade" images (automatically moved from `images`/`index.json` after a merged PR introduced a newer version, or during auto download).
- `images` and `index.json` contain added (PR or auto download) "upgrade" images.
- `images1` and `index1.json` contain automatically archived "downgrade" images (automatically moved from `images`/`index.json` after a merged PR introduced a newer version, or during auto download).

If a manual modification of the manifests is necessary, it should be done in a PR that does not trigger the `update_ota_pr` workflow (no changes in `images/**` directory). As a last resort, the label `ignore-ota-workflow` can be added to prevent the workflow from running.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zigbee-ota",
"version": "1.1.1",
"version": "1.2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/Koenkk/zigbee-OTA.git"
Expand Down
105 changes: 105 additions & 0 deletions src/autodl/hue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {getJson, getLatestImage, readCacheJson, writeCacheJson} from '../common.js';
import {processFirmwareImage} from '../process_firmware_image.js';

type ImageJson = {
createdAt: string;
updatedAt: string;
fileSize: number;
md5: string;
binaryUrl: string;
version: number;
versionName: string;
releaseNotes: string;
};
type PageJson = {updates: ImageJson[]};

const NAME = 'Hue';
const BASE_URL = 'https://firmware.meethue.com/v1/checkupdate?version=0&deviceTypeId=';
Copy link
Contributor

@TheJulianJES TheJulianJES Feb 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always using version=0 here will give you old updates if intermediary updates are required, since you will always get a compatible image for version=0 per cid/type, which is always an old image.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script is comparing actual versions in the list once fetched (sorting by version and picking latest).
Currently, there are no device with anything other than 1 update though, so I'm not sure how the intermediary updates work for Hue. I assume version limit if they followed spec? If using that scheme, it should be handled, since the update logic won't let an OTA file with a limitation be overridden, it will always keep both, which, in turn, should allow Z2M to update in the appropriate order.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other scripts might handle this properly, but by always sending version=0, you won't fetch the newest firmware in the first place. As an example, say you send version=0 and get a firmware with version 1.
If you then send version=1, you might get another image with version 2 (which you did not get by sending ver=0).
This is why the parameter exists in the first place. The Hue mobile app always calls that with the currently installed version on the lamp.

So, the script would have to be updated to not only send 0, but also the latest known version number for that model, until no more updates are returned.

However, it seems like no intermediary updates are returned by the mobile API at the moment. I'm getting different results via the proper Hue Bridge AP, which is a bit weird...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I saw with the Hue bridge device ID, by sending version 0, you get all versions returned at once in the array. But yea, that's the only one that currently returns more than 1 firmware, so, it's more of an assumption.

Copy link
Contributor

@TheJulianJES TheJulianJES Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, only versions that can be installed from the current version are returned. For the OTA API used by the Hue Bridge, with older TI or ATmel-baed lights, you only get older firmware images until you increase version to the next image.

And the mobile API isn't used for updating the Bridge (anymore?) IIRC.
Using the Hue Bridge OTA API for querying images for BSB002 with version=0 returns the latest update and not the two older ones, so it seems like there are no required intermediary updates for the Bridge.
So, the two year old results that are included in BSB002 for the mobile API seem to be unintentional.

Btw., see your Discord DMs if you have some time.

const DEVICE_TYPE_IDS: string[] = [
'100b-111',
'100b-112',
// '100b-113',
'100b-114',
'100b-115',
// '100b-116',
'100b-117',
'100b-118',
// '100b-119',
'100b-11a',
// '100b-11b',
// '100b-11c',
'100b-11d',
'100b-11e',
'100b-11f',
'100b-120',
// '100b-121',
// '100b-122',
'100b-123',
// '100b-124',
'100b-125',
// '100b-126',
'100b-127',
// '100b-128',
'100b-129',
// '100b-12a',
// '100b-12b',
// '100b-12c',
// '100b-12d',
// '100b-12e',
// '100b-12f',
];

function sortByVersion(a: ImageJson, b: ImageJson): number {
return a.version < b.version ? -1 : a.version > b.version ? 1 : 0;
}

function isDifferent(newData: PageJson, cachedData?: PageJson): boolean {
return (
Boolean(process.env.IGNORE_CACHE) ||
!cachedData?.updates.length ||
getLatestImage(cachedData.updates, sortByVersion)?.version !== getLatestImage(newData.updates, sortByVersion)?.version
);
}

export async function writeCache(): Promise<void> {
for (const deviceTypeId of DEVICE_TYPE_IDS) {
const url = `${BASE_URL}${deviceTypeId}`;
const page = await getJson<PageJson>(NAME, url);

if (page?.updates.length) {
writeCacheJson(`${NAME}_${deviceTypeId}`, page);
}
}
}

export async function download(): Promise<void> {
for (const deviceTypeId of DEVICE_TYPE_IDS) {
const logPrefix = `[${NAME}:${deviceTypeId}]`;
const url = `${BASE_URL}${deviceTypeId}`;
const page = await getJson<PageJson>(NAME, url);

if (!page?.updates.length) {
console.error(`${logPrefix} No image data.`);
continue;
}

const cacheFileName = `${NAME}_${deviceTypeId}`;

if (!isDifferent(page, readCacheJson(cacheFileName))) {
console.log(`${logPrefix} No change from last run.`);
continue;
}

writeCacheJson(cacheFileName, page);

const image = getLatestImage(page.updates, sortByVersion);

if (!image) {
continue;
}

const firmwareFileName = image.binaryUrl.split('/').pop()!;

await processFirmwareImage(NAME, firmwareFileName, image.binaryUrl, {releaseNotes: image.releaseNotes || undefined});
}
}
1 change: 1 addition & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const PR_ARTIFACT_NUMBER_FILEPATH = path.join(PR_ARTIFACT_DIR, PR_NUMBER_
*/
export const ALL_AUTODL_MANUFACTURERS = [
'gammatroniques',
'hue',
'ikea_new',
'ikea',
'inovelli',
Expand Down
2 changes: 2 additions & 0 deletions tests/ghw_reprocess_all_images.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ describe('Github Workflow: Re-Process All Images', () => {
ok: fetchReturnedStatus.ok,
status: fetchReturnedStatus.status,
body: fetchReturnedStatus.body,
// @ts-expect-error Buffer <> ArrayBuffer (props not used)
arrayBuffer: (): ArrayBuffer => readFileSync(getImageOriginalDirPath((input as string).split('/').pop()!)),
};
},
Expand Down Expand Up @@ -762,6 +763,7 @@ describe('Github Workflow: Re-Process All Images', () => {
ok: fetchReturnedStatus.ok,
status: fetchReturnedStatus.status,
body: fetchReturnedStatus.body,
// @ts-expect-error Buffer <> ArrayBuffer (props not used)
arrayBuffer: (): ArrayBuffer => readFileSync(getImageOriginalDirPath(fileName)),
};
},
Expand Down
1 change: 1 addition & 0 deletions tests/process_firmware_image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ describe('Process Firmware Image', () => {
ok: fetchReturnedStatus.ok,
status: fetchReturnedStatus.status,
body: fetchReturnedStatus.body,
// @ts-expect-error Buffer <> ArrayBuffer (props not used)
arrayBuffer: (): ArrayBuffer => readFileSync(getImageOriginalDirPath(input as string)),
};
},
Expand Down