import {z} from 'zod' export type DeviceInstallRequest = {label?: string | undefined, locationId: unknown, roomId?: string | undefined, app: {profileId: string, installedAppId: unknown, externalId?: string | undefined}}; export type CreateApiDevicesArgs = {body: DeviceInstallRequest}; export type CreateApiDevicesOptions = {baseUrl?: string | undefined, Bearer?: string | undefined, fetch?: typeof fetch | undefined}; export const deviceId = z.string(); export const capabilityConfigurationValue = z.object({key: z.string(), range: z.array(z.number()).optional(), enabledValues: z.array(z.string()).optional(), step: z.number().optional()}); export const capabilityConfiguration = z.object({values: z.array(capabilityConfigurationValue)}); export const restriction = z.object({tier: z.number().int(), historyRetentionTTLDays: z.number().int().optional(), visibleWhenRestricted: z.boolean().optional()}); export const attributeState = z.object({value: z.object({}).optional(), unit: z.string().optional(), data: z.record(z.string(), z.object({})).optional(), timestamp: z.string().optional()}); export const capabilityStatus = z.record(z.string(), attributeState); export const deviceCapabilityReference = z.object({id: z.string(), version: z.number().int().optional(), optional: z.boolean().optional(), config: capabilityConfiguration.optional(), restrictions: restriction.optional(), ephemeral: z.boolean().optional(), status: capabilityStatus.optional()}); export const deviceCategory = z.object({name: z.string(), categoryType: z.enum(["manufacturer", "user"])}); export const deviceComponent = z.object({id: z.string(), label: z.string().optional(), capabilities: z.array(deviceCapabilityReference), categories: z.array(deviceCategory), restrictions: restriction.optional(), optional: z.boolean().optional()}); export const idLessHealthState = z.object({state: z.enum(["ONLINE", "OFFLINE"]).optional(), lastUpdatedDate: z.string().optional()}); export const deviceProfileReference = z.object({id: z.string().optional()}); export const appDeviceDetails = z.object({installedAppId: z.string().optional(), externalId: z.string().max(64).optional(), profile: deviceProfileReference.optional()}); export const bleDeviceDetails = z.object({}); export const bleD2DDeviceDetails = z.object({encryptionKey: z.string().optional(), cipher: z.string().optional(), gattCipher: z.string().optional(), advertisingId: z.string().optional(), identifier: z.string().optional(), configurationVersion: z.string().optional(), configurationUrl: z.string().optional(), bleDeviceType: z.string().optional(), metadata: z.object({}).optional()}); export const deviceNetworkType = z.string(); export const executingLocally = z.boolean(); export const hubId = z.string(); export const deviceNetworkId = z.string(); export const deviceNetworkSecurityLevel = z.enum(["UNKNOWN", "ZWAVE_LEGACY_NON_SECURE", "ZWAVE_S0_LEGACY", "ZWAVE_S0_FALLBACK", "ZWAVE_S2_UNAUTHENTICATED", "ZWAVE_S2_AUTHENTICATED", "ZWAVE_S2_ACCESS_CONTROL", "ZWAVE_S2_FAILED", "ZWAVE_S0_FAILED", "ZWAVE_S2_DOWNGRADE", "ZWAVE_S0_DOWNGRADE"]); export const fingerprintType = z.enum(["DTH", "ZIGBEE_GENERIC", "ZIGBEE_MANUFACTURER", "ZWAVE_MANUFACTURER", "ZWAVE_GENERIC"]); export const fingerprintId = z.string(); export const dthDeviceDetails = z.object({completedSetup: z.boolean(), deviceNetworkType: deviceNetworkType.optional(), deviceTypeId: z.string(), deviceTypeName: z.string(), executingLocally: executingLocally.optional(), hubId: hubId.optional(), installedGroovyAppId: z.string().optional(), networkId: deviceNetworkId.optional(), networkSecurityLevel: deviceNetworkSecurityLevel.optional(), fingerprintType: fingerprintType.optional(), fingerprintId: fingerprintId.optional()}); export const driverId = z.string(); export const provisioningState = z.enum(["PROVISIONED", "TYPED", "DRIVER_SWITCH", "NONFUNCTIONAL"]); export const lanDeviceDetails = z.object({networkId: deviceNetworkId.optional(), driverId: driverId.optional(), executingLocally: executingLocally.optional(), hubId: hubId.optional(), provisioningState: provisioningState.optional(), fingerprintType: fingerprintType.optional(), fingerprintId: fingerprintId.optional()}); export const deviceEui = z.string(); export const zigbeeDeviceDetails = z.object({eui: deviceEui.optional(), networkId: deviceNetworkId.optional(), driverId: driverId.optional(), executingLocally: executingLocally.optional(), hubId: hubId.optional(), provisioningState: provisioningState.optional(), fingerprintType: fingerprintType.optional(), fingerprintId: fingerprintId.optional()}); export const zWaveManufacturerId = z.number().int().gte(0).lte(65535); export const zWaveProductType = z.number().int().gte(0).lte(65535); export const zWaveProductId = z.number().int().gte(0).lte(65535); export const zwaveDeviceDetails = z.object({networkId: deviceNetworkId.optional(), driverId: driverId.optional(), executingLocally: executingLocally.optional(), hubId: hubId.optional(), networkSecurityLevel: deviceNetworkSecurityLevel.optional(), provisioningState: provisioningState.optional(), manufacturerId: zWaveManufacturerId.optional(), productType: zWaveProductType.optional(), productId: zWaveProductId.optional(), fingerprintType: fingerprintType.optional(), fingerprintId: fingerprintId.optional()}); export const matterListeningType = z.enum(["ALWAYS", "SLEEPY"]); export const matterNetworkInterface = z.enum(["THREAD", "WIFI", "ETHERNET"]); export const matterNetworkInterfaces = z.array(matterNetworkInterface); export const matterVersion = z.object({hardware: z.number().int().gte(0).lte(65535).optional(), hardwareLabel: z.string().optional(), software: z.number().gte(0).lte(4294967295).optional(), softwareLabel: z.string().optional()}); export const matterEndpointDeviceType = z.object({deviceTypeId: z.number().gte(0).lte(4294967295).optional()}); export const matterEndpoint = z.object({endpointId: z.number().int().gte(0).lte(65535).optional(), deviceTypes: z.array(matterEndpointDeviceType).optional()}); export const matterEndpoints = z.array(matterEndpoint); export const syncDrivers = z.boolean(); export const matterDeviceDetails = z.object({driverId: driverId.optional(), hubId: hubId.optional(), provisioningState: provisioningState.optional(), networkId: deviceNetworkId.optional(), executingLocally: executingLocally.optional(), uniqueId: z.string().optional(), vendorId: z.number().int().optional(), productId: z.number().int().optional(), serialNumber: z.string().optional(), listeningType: matterListeningType.optional(), supportedNetworkInterfaces: matterNetworkInterfaces.optional(), version: matterVersion.optional(), endpoints: matterEndpoints.optional(), syncDrivers: syncDrivers.optional(), fingerprintType: fingerprintType.optional(), fingerprintId: fingerprintId.optional()}); export const hubDriver = z.object({driverVersion: z.string().optional(), driverId: z.string(), channelId: z.string().optional()}); export const zigbeeOta = z.enum(["DISABLED", "ENABLED", "ENABLED_WITH_LIGHTS"]); export const availabilityCharacteristic = z.enum(["Available", "Unavailable", "Unsupported"]); export const edgeDriverSupportedEndpointApps = z.object({apps: z.array(z.object({appName: z.string(), version: z.string()}))}); export const repeaterReachabilityCharacteristic = z.enum(["Unknown", "Unreachable", "Reachable"]); export const matterRendezvousHedgeSupported = z.boolean(); export const matterCommissionerSupport = z.object({version: z.number().int(), supportedModes: z.array(z.string())}); export const matterSoftwareComponentVersion = z.string(); export const hubDeviceDetails = z.object({hubEui: z.string(), firmwareVersion: z.string(), hubDrivers: z.array(hubDriver), hubData: z.object({serialNumber: z.string().optional(), zwaveStaticDsk: z.string().optional(), zwaveS2: z.boolean(), hardwareType: z.string(), hardwareId: z.string().optional(), zigbeeFirmware: z.string().optional(), zigbee3: z.boolean(), zigbeeOta: zigbeeOta.optional(), otaEnable: z.string().optional(), zigbeeUnsecureRejoin: z.boolean(), zigbeeRequiresExternalHardware: z.boolean().optional(), threadRequiresExternalHardware: z.boolean().optional(), failoverAvailability: availabilityCharacteristic.optional(), primarySupportAvailability: availabilityCharacteristic.optional(), secondarySupportAvailability: availabilityCharacteristic.optional(), zigbeeAvailability: availabilityCharacteristic.optional(), zwaveAvailability: availabilityCharacteristic.optional(), threadAvailability: availabilityCharacteristic.optional(), lanAvailability: availabilityCharacteristic.optional(), matterAvailability: availabilityCharacteristic.optional(), localVirtualDeviceAvailability: availabilityCharacteristic.optional(), childDeviceAvailability: availabilityCharacteristic.optional(), edgeDriversAvailability: availabilityCharacteristic.optional(), hubReplaceAvailability: availabilityCharacteristic.optional(), hubLocalApiAvailability: availabilityCharacteristic.optional(), hubLocalApiJwkOfflineRetentionSeconds: z.number().int().optional(), edgeDriverSupportedEndpointApps: edgeDriverSupportedEndpointApps.optional(), repeaterReachability: repeaterReachabilityCharacteristic.optional(), zigbeeManualFirmwareUpdateSupported: z.boolean().optional(), matterRendezvousHedgeSupported: matterRendezvousHedgeSupported.optional(), matterCommissionerSupport: matterCommissionerSupport.optional(), matterSoftwareComponentVersion: matterSoftwareComponentVersion.optional(), matterDeviceDiagnosticsAvailability: availabilityCharacteristic.optional(), zigbeeDeviceDiagnosticsAvailability: availabilityCharacteristic.optional(), hedgeTlsCertificate: z.string().optional(), hub2hubSupportMatrix: z.object({capabilities: z.array(z.object({name: z.string(), version: z.number().int().gte(0)}))}).optional(), primaryHubDeviceId: z.string().optional(), zigbeeChannel: z.string().optional(), zigbeePanId: z.string().optional(), zigbeeEui: z.string().optional(), zigbeeNodeID: z.string().optional(), zwaveNodeID: z.string().optional(), zwaveHomeID: z.string().optional(), zwaveSucID: z.string().optional(), zwaveVersion: z.string().optional(), zwaveRegion: z.string().optional(), macAddress: z.string().optional(), localIP: z.string().optional(), wifiSsid: z.string().optional(), zigbeeRadioFunctional: z.boolean().optional(), zwaveRadioFunctional: z.boolean().optional(), zigbeeRadioEnabled: z.boolean().optional(), zwaveRadioEnabled: z.boolean().optional(), zigbeeRadioDetected: z.boolean().optional(), zwaveRadioDetected: z.boolean().optional(), enrollmentChannel: z.enum(["PUBLIC", "BETA"]).optional()}), driverId: z.string()}); export const parentAssignedChildKey = z.string(); export const edgeChildDeviceDetails = z.object({driverId: driverId.optional(), hubId: hubId.optional(), provisioningState: provisioningState.optional(), networkId: deviceNetworkId.optional(), executingLocally: executingLocally.optional(), parentAssignedChildKey: parentAssignedChildKey.optional(), fingerprintType: fingerprintType.optional(), fingerprintId: fingerprintId.optional()}); export const irDeviceDetails: z.ZodType = z.object({parentDeviceId: z.string().optional(), profileId: z.string().optional(), ocfDeviceType: z.string().optional(), irCode: z.string().optional(), functionCodes: z.object({default: z.string().optional()}).and(z.record(z.string(), z.unknown())).optional(), childDevices: z.array(z.lazy(() => irDeviceDetails)).optional(), metadata: z.record(z.string(), z.unknown()).optional()}); export const ocfDeviceDetails = z.object({ocfDeviceType: z.string().optional(), name: z.string().optional(), specVersion: z.string().optional(), verticalDomainSpecVersion: z.string().optional(), manufacturerName: z.string().optional(), modelNumber: z.string().optional(), platformVersion: z.string().optional(), platformOS: z.string().optional(), hwVersion: z.string().optional(), firmwareVersion: z.string().optional(), vendorId: z.string().optional(), vendorResourceClientServerVersion: z.string().optional(), locale: z.string().optional(), lastSignupTime: z.string().optional(), transferCandidate: z.boolean().optional(), additionalAuthCodeRequired: z.boolean().optional(), modelCode: z.string().optional()}); export const viperDeviceDetails = z.object({uniqueIdentifier: z.string().optional(), manufacturerName: z.string().optional(), modelName: z.string().optional(), swVersion: z.string().optional(), hwVersion: z.string().optional(), endpointAppId: z.string().optional()}); export const deviceGroupType = z.enum(["UNDETERMINED", "GENERIC", "VIDEO_CAMERA", "LIGHTING"]); export const groupDeviceDetails = z.object({groupName: z.string().optional(), groupType: deviceGroupType.optional(), devices: z.array(z.object({deviceId: z.string(), components: z.array(z.object({id: z.string().optional()}).and(z.record(z.string(), z.unknown()))).optional()})).optional()}); export const attributeValue = z.object({attribute: z.string().optional(), inputType: z.enum(["STATIC", "ARGUMENT"]).optional(), staticValue: z.object({}).optional()}); export const commandMapping = z.object({capabilityId: z.string(), version: z.number().int(), command: z.string(), eventValues: z.array(attributeValue)}); export const commandMappings = z.object({commands: z.array(commandMapping).optional()}); export const virtualDeviceDetails = z.object({name: z.string().optional(), hubId: hubId.optional(), driverId: driverId.optional(), executingLocally: executingLocally.optional(), commandMappings: commandMappings.optional()}); export const mqttDeviceDetails = z.object({hubId: z.string().optional(), executingLocally: executingLocally.optional(), transferCandidate: z.boolean().optional(), authCodeRequired: z.boolean().optional()}); export const deviceIntegrationType = z.enum(["BLE", "BLE_D2D", "DTH", "ENDPOINT_APP", "GROUP", "HUB", "IR", "IR_OCF", "LAN", "MATTER", "MOBILE", "MQTT", "OCF", "SAFER", "STPRO", "PENGYOU", "SHP", "VIDEO", "VIPER", "VIRTUAL", "WATCH", "ZIGBEE", "ZWAVE", "EDGE_CHILD"]); export const indoorMap = z.object({coordinates: z.array(z.number()).optional(), rotation: z.array(z.number()).optional(), visible: z.boolean().optional(), data: z.record(z.string(), z.unknown()).optional()}); export const executionContext = z.enum(["LOCAL", "CLOUD"]); export const relationshipTypeEnum = z.enum(["COMBINATION", "CHILD"]); export const onDeleteEnum = z.enum(["CASCADE", "NONE"]); export const onLocationMoveEnum = z.literal("INCLUDE"); export const onOwnershipTransferEnum = z.literal("INCLUDE"); export const deviceRelationship = z.object({deviceId: z.string().optional(), relationshipType: relationshipTypeEnum.optional(), onDelete: onDeleteEnum.optional(), onLocationMove: onLocationMoveEnum.optional(), onOwnershipTransfer: onOwnershipTransferEnum.optional()}); export const device: z.ZodType = z.object({deviceId: deviceId, name: z.string().optional(), label: z.string().optional(), manufacturerName: z.string(), presentationId: z.string(), deviceManufacturerCode: z.string().optional(), locationId: z.string().optional(), ownerId: z.string().optional(), roomId: z.string().optional(), deviceTypeId: z.string().optional(), deviceTypeName: z.string().optional(), deviceNetworkType: z.string().optional(), productId: z.string().optional(), brandId: z.string().optional(), components: z.array(deviceComponent).optional(), createTime: z.string().optional(), healthState: idLessHealthState.optional(), parentDeviceId: z.string().optional(), childDevices: z.array(z.lazy(() => device)).optional(), profile: deviceProfileReference.optional(), app: appDeviceDetails.optional(), ble: bleDeviceDetails.optional(), bleD2D: bleD2DDeviceDetails.optional(), dth: dthDeviceDetails.optional(), lan: lanDeviceDetails.optional(), zigbee: zigbeeDeviceDetails.optional(), zwave: zwaveDeviceDetails.optional(), matter: matterDeviceDetails.optional(), hub: hubDeviceDetails.optional(), edgeChild: edgeChildDeviceDetails.optional(), ir: irDeviceDetails.optional(), irOcf: irDeviceDetails.optional(), ocf: ocfDeviceDetails.optional(), viper: viperDeviceDetails.optional(), group: groupDeviceDetails.optional(), virtual: virtualDeviceDetails.optional(), mqtt: mqttDeviceDetails.optional(), type: deviceIntegrationType, restrictionTier: z.number().int(), allowed: z.array(z.string()).optional(), indoorMap: indoorMap.optional(), executionContext: executionContext.optional(), relationships: z.array(deviceRelationship).optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Install a Device * * POST /devices */ export const createApiDevices = async (args: CreateApiDevicesArgs, options?: CreateApiDevicesOptions) => { const {baseUrl = "https://api.smartthings.com/v1", fetch: fetchFn = fetch, Bearer} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (Bearer !== undefined) headers.set('authorization', `Bearer ${Bearer}`) const url = `${baseUrl.replace(/\/+$/, '')}/devices${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(device, await res.json()) };