import { pond } from "protobuf-ts/pond"; import { or } from "utils/types"; import { cloneDeep } from "lodash"; import { MarkerData } from "maps/mapMarkers/Markers"; interface FeatureVersionByPlatform { photon: string; electron: string; v2Wifi: string; v2Cell: string; v2WifiS3: string; v2CellS3: string; v2CellBlack: string; v2CellGreen: string; v2WifiBlue: string; v2CellBlue: string; v2EthBlue: string; } const featureVersions: Map = new Map([ [ "individualCalibrations", { photon: "1.19.64", electron: "1.19.64", v2Cell: "2.0.44", v2Wifi: "2.0.44", v2WifiS3: "2.0.44", v2CellS3: "2.0.44", v2CellBlack: "2.0.44", v2CellGreen: "2.0.44", v2WifiBlue: "2.0.44", v2CellBlue: "2.0.44", v2EthBlue: "2.0.44" } ],[ "reverseNodes", { photon: "N/A", electron: "N/A", v2Wifi: "N/A", v2Cell: "N/A", v2WifiS3: "N/A", v2CellS3: "N/A", v2CellBlack: "2.1.8", v2CellGreen: "N/A", v2WifiBlue: "2.1.8", v2CellBlue: "2.1.8", v2EthBlue: "2.1.8" } ], [ "detectI2C", { photon: "N/A", electron: "N/A", v2Wifi: "N/A", v2Cell: "N/A", v2WifiS3: "N/A", v2CellS3: "N/A", v2CellBlack: "2.1.7", v2CellGreen: "N/A", v2WifiBlue: "2.1.7", v2CellBlue: "2.1.7", v2EthBlue: "2.1.7" } ],[ "detectOneWire", { photon: "N/A", electron: "N/A", v2Wifi: "N/A", v2Cell: "N/A", v2WifiS3: "N/A", v2CellS3: "N/A", v2CellBlack: "2.1.10", v2CellGreen: "N/A", v2WifiBlue: "2.1.10", v2CellBlue: "2.1.10", v2EthBlue: "2.1.10" } ] ]); export class Device { public settings: pond.DeviceSettings = pond.DeviceSettings.create(); public status: pond.DeviceStatus = pond.DeviceStatus.create(); public static create(pb?: pond.Device): Device { let my = new Device(); if (pb) { my.settings = pond.DeviceSettings.fromObject(or(pb.settings, {})); my.status = pond.DeviceStatus.fromObject(or(pb.status, {})); } return my; } public static any(data: any): Device { if (data.device) return Device.create(pond.Device.fromObject(cloneDeep(data.device))); return Device.create(pond.Device.fromObject(cloneDeep(data))); } public static clone(other: Device): Device { let my = new Device(); my.settings = pond.DeviceSettings.fromObject(cloneDeep(other.settings)); my.status = pond.DeviceStatus.fromObject(cloneDeep(other.status)); return my; } public id(): number { return Number(this.settings.deviceId); } public name(): string { return this.settings.name !== "" ? this.settings.name : this.id() > 0 ? "Device " + this.id() : ""; } public location(): pond.Location { let loc = pond.Location.create(); loc.longitude = this.settings.longitude; loc.latitude = this.settings.latitude; return loc; } public getMarkerData( clickFunc?: (event: React.PointerEvent, index: number, isMobile: boolean) => void, updateFunc?: (location: pond.Location) => void ): MarkerData { let m: MarkerData = { longitude: this.location()?.longitude ?? 0, latitude: this.location()?.latitude ?? 0, title: this.name(), colour: this.settings.theme?.color ?? "blue", clickFunc: clickFunc, updateFunc: updateFunc }; return m; } public platformName(): string { switch (this.settings.platform) { case pond.DevicePlatform.DEVICE_PLATFORM_PHOTON: return "Photon" case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON: return "Electron" case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR: return "V2 Cellular" case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI: return "V2 Wifi" case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3: return "V2 Wifi S3" case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_S3: return "V2 Cellular S3" case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK: return "V2 Cellular Black" case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN: return "V2 Callular Green" case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE: return "V2 Wifi Blue" case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE: return "V2 Cellular Blue" case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE: return "V2 Ethernet Blue" default: return ""; } } public maxConditions(): number { let max = 2 switch (this.settings.platform) { case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK: case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE: case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE: case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE: max = this.versionComparison(this.status.firmwareVersion, "2.1.9") ? 4 : 2; } return max } private versionComparison(deviceVersion: string, requiredVersion: string): boolean { // Feature explicitly not supported on this platform if (!requiredVersion || requiredVersion === "N/A") { return false; } // Device firmware missing / unset if (!deviceVersion || deviceVersion.trim() === "") { return false; } //compare the versions const parse = (v: string) => { const [core, pre] = v.split("-"); const parts = core.split(".").map(n => parseInt(n, 10)); let preLabel = ""; let preNum = 0; if (pre) { const match = pre.match(/^([a-zA-Z]+)(\d*)$/); if (match) { preLabel = match[1].toLowerCase(); preNum = match[2] ? parseInt(match[2], 10) : 0; } } return { parts, preLabel, preNum }; }; const rank = (label: string): number => { switch (label) { case "alpha": return 1; case "beta": return 2; case "rc": return 3; default: return 0; // release } }; const a = parse(deviceVersion); const b = parse(requiredVersion); // Compare major/minor/patch const len = Math.max(a.parts.length, b.parts.length); for (let i = 0; i < len; i++) { const av = a.parts[i] ?? 0; const bv = b.parts[i] ?? 0; if (av > bv) return true; if (av < bv) return false; } // Handle prerelease vs release if (!a.preLabel && b.preLabel) return true; // release > prerelease if (a.preLabel && !b.preLabel) return false; // Both prereleases → compare rank if (a.preLabel !== b.preLabel) { return rank(a.preLabel) >= rank(b.preLabel); } // Same prerelease label → compare numeric suffix return a.preNum >= b.preNum; } public featureSupported(feature: string): boolean { let versions = featureVersions.get(feature); if (!versions) { return false; } switch (this.settings.platform) { case pond.DevicePlatform.DEVICE_PLATFORM_PHOTON: return this.versionComparison(this.status.firmwareVersion, versions.photon); case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON: return this.versionComparison(this.status.firmwareVersion, versions.electron); case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR: return this.versionComparison(this.status.firmwareVersion, versions.v2Cell); case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI: return this.versionComparison(this.status.firmwareVersion, versions.v2Wifi); case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3: return this.versionComparison(this.status.firmwareVersion, versions.v2WifiS3); case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_S3: return this.versionComparison(this.status.firmwareVersion, versions.v2CellS3); case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK: return this.versionComparison(this.status.firmwareVersion, versions.v2CellBlack); case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN: return this.versionComparison(this.status.firmwareVersion, versions.v2CellGreen); case pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE: return this.versionComparison(this.status.firmwareVersion, versions.v2WifiBlue); case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE: return this.versionComparison(this.status.firmwareVersion, versions.v2CellBlue); case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE: return this.versionComparison(this.status.firmwareVersion, versions.v2EthBlue); default: return false; } } }