diff --git a/ioBroker/.iobroker/types/javascript.d.ts b/ioBroker/.iobroker/types/javascript.d.ts new file mode 100644 index 00000000..e51690f8 --- /dev/null +++ b/ioBroker/.iobroker/types/javascript.d.ts @@ -0,0 +1,1732 @@ +// import all modules that are available in the sandbox +// this has a nice side effect that we may augment the global scope +import * as child_process from "child_process"; +import * as os from "os"; + +type EmptyCallback = () => void | Promise; +type ErrorCallback = (err?: string) => void | Promise; +type GenericCallback = (err?: string | null, result?: T) => void | Promise; +type SimpleCallback = (result?: T) => void | Promise; +type LogCallback = (msg: any) => void | Promise; + +type SecondParameterOf any> = T extends ( + arg0: any, + arg1: infer R, + ...args: any[] +) => any + ? R + : never; +/** Infers the return type from a callback-style API and strips out null and undefined */ +type NonNullCallbackReturnTypeOf any> = Exclude< + SecondParameterOf, + null | undefined +>; +/** Infers the return type from a callback-style API and leaves null and undefined in */ +type CallbackReturnTypeOf any> = SecondParameterOf; + +/** Returns a type that requires at least one of the properties from the given type */ +type AtLeastOne = { [K in keyof U]: { [P in K]: U[P] } }[keyof U]; + +/** Returns all possible keys of a union of objects */ +type AllKeys = T extends any ? keyof T : never; +/** Simplifies mapped types to their basic forms */ +type Simplify = U extends infer O ? { [K in keyof O]: O[K] } : never; + +/** Takes an object type and adds all missing properties from the Keys union with the type `never` */ +type AddMissingNever = { + [K in Keys]: K extends keyof T ? T[K] : never; +}; + +/** + * Takes a union of objects and returns an object type + * which has all properties that exist on at least one of the objects. + * + * E.g. CombineObjectUnion<{a: 1} | {b: 2}> = {a: 1; b: 2}; + */ +type CombineObjectUnion< + T, + Keys extends string | number | symbol = AllKeys, + O = T extends any ? AddMissingNever : never + > = Simplify<{ [K in Keys]: K extends keyof O ? O[K] : never }> + +/** + * Takes a union of ioBroker Object types and returns a combined object type + * which has all properties that could exist on at least one of the objects. + * + * Note: This is not entirely sound to work with, but better for JS and working with read objects + */ +type AnyOf< + T, + Keys extends string | number | symbol = AllKeys, + O = T extends any ? AddMissingNever : never + > = Simplify<{ + [K in Keys]: K extends keyof O ? ( + O[K] extends any[] ? O[K] + : O[K] extends Record ? CombineObjectUnion + : O[K] + ) : never; + }>; + +// tslint:disable:no-namespace +declare global { + + namespace iobJS { + + enum StateQuality { + good = 0x00, // or undefined or null + bad = 0x01, + general_problem = 0x01, + general_device_problem = 0x41, + general_sensor_problem = 0x81, + device_not_connected = 0x42, + sensor_not_connected = 0x82, + device_reports_error = 0x44, + sensor_reports_error = 0x84, + } + + type PrimitiveTypeStateValue = string | number | boolean; + type StateValue = null | PrimitiveTypeStateValue | PrimitiveTypeStateValue[] | Record; + + interface State { + /** The value of the state. */ + val: T; + + /** Direction flag: false for desired value and true for actual value. Default: false. */ + ack: boolean; + + /** Unix timestamp. Default: current time */ + ts: number; + + /** Unix timestamp of the last time the value changed */ + lc: number; + + /** Name of the adapter instance which set the value, e.g. "system.adapter.web.0" */ + from: string; + + /** Optional time in seconds after which the state is reset to null */ + expire?: number; + + /** Optional quality of the state value */ + q?: StateQuality; + + /** Optional comment */ + c?: string; + + /** Discriminant property to switch between AbsentState and State */ + notExist: undefined; + } + + type SettableState = AtLeastOne; + + interface AbsentState { + val: null; + notExist: true; + + ack: undefined; + ts: undefined; + lc: undefined; + from: undefined; + expire: undefined; + q: undefined; + c: undefined; + } + + type Languages = 'en' | 'de' | 'ru' | 'pt' | 'nl' | 'fr' | 'it' | 'es' | 'pl' | 'zh-cn'; + type StringOrTranslated = string | { [lang in Languages]?: string; }; + type CommonType = "number" | "string" | "boolean" | "array" | "object" | "mixed" | "file"; + + /** Defines access rights for a single object type */ + interface ObjectOperationPermissions { + /** Whether a user may enumerate objects of this type */ + list: boolean; + /** Whether a user may read objects of this type */ + read: boolean; + /** Whether a user may write objects of this type */ + write: boolean; + /** Whether a user may create objects of this type */ + create: boolean; + /** Whether a user may delete objects of this type */ + delete: boolean; + } + + /** Defines the rights a user or group has to change objects */ + interface ObjectPermissions { + /** The access rights for files */ + file: ObjectOperationPermissions; + /** The access rights for objects */ + object: ObjectOperationPermissions; + /** The access rights for users/groups */ + users: ObjectOperationPermissions; + /** The access rights for states */ + state?: ObjectOperationPermissions; + } + /** Defined the complete set of access rights a user has */ + interface PermissionSet extends ObjectPermissions { + /** The name of the user this ACL is for */ + user: string; + /** The name of the groups this ACL was merged from */ + groups: string[]; + /** The access rights for certain commands */ + other: { + execute: boolean; + http: boolean; + sendto: boolean; + }; + } + + interface ObjectACL { + /** Full name of the user who owns this object, e.g. "system.user.admin" */ + owner: string; + /** Full name of the group who owns this object, e.g. "system.group.administrator" */ + ownerGroup: string; + /** Linux-type permissions defining access to this object */ + object: number; + } + /** Defines access rights for a single state object */ + interface StateACL extends ObjectACL { + /** Linux-type permissions defining access to this state */ + state: number; + } + + /** Defines the existing object types in ioBroker */ + type ObjectType = + | 'state' + | 'channel' + | 'device' + | 'folder' + | 'enum' + | 'adapter' + | 'config' + | 'group' + | 'host' + | 'instance' + | 'meta' + | 'script' + | 'user' + | 'chart'; + + // Define the naming schemes for objects, so we can provide more specific types for get/setObject + namespace ObjectIDs { + // Guaranteed meta objects + type Meta = + | `${string}.${number}` + | `${string}.${"meta" | "admin"}` + | `${string}.meta.${string}` + | `${string}.${number}.meta.${string}`; + + // Unsure, can be folder, device, channel or state + // --> We need this match to avoid matching the more specific types below + type Misc = + | `system.host.${string}.${string}` + | `0_userdata.0.${string}`; + + // Guaranteed channel objects + type Channel = + | `script.js.${"common" | "global"}` + | `${string}.${number}.info`; + // Either script or channel object + type ScriptOrChannel = `script.js.${string}`; + // Guaranteed state objects + type State = + | `system.adapter.${string}.${number}.${string}`; + // Guaranteed enum objects + type Enum = `enum.${string}`; + // Guaranteed instance objects + type Instance = `system.adapter.${string}.${number}`; + // Guaranteed adapter objects + type Adapter = `system.adapter.${string}`; + // Guaranteed group objects + type Group = `system.group.${string}`; + // Guaranteed user objects + type User = `system.user.${string}`; + // Guaranteed host objects + type Host = `system.host.${string}`; + // Guaranteed config objects + type Config = `system.${"certificates" | "config" | "repositories"}`; + + // Unsure, can be folder, device, channel or state (or whatever an adapter does) + type AdapterScoped = + | `${string}.${number}.${string}`; + + /** All possible typed object IDs */ + type Any = + | Meta + | Misc + | Channel + | ScriptOrChannel + | State + | Enum + | Instance + | Adapter + | Group + | User + | Host + | Config + | AdapterScoped; + } + + type ObjectIdToObjectType< + T extends string, + Read extends "read" | "write" = "read", + O = + // State must come before Adapter or system.adapter.admin.0.foobar will resolve to AdapterObject + T extends ObjectIDs.State ? StateObject : + // Instance and Adapter must come before meta or `system.adapter.admin` will resolve to MetaObject + T extends ObjectIDs.Instance ? InstanceObject : + T extends ObjectIDs.Adapter ? AdapterObject : + T extends ObjectIDs.Channel ? ChannelObject : + T extends ObjectIDs.Meta ? MetaObject : + T extends ObjectIDs.Misc ? AdapterScopedObject : + T extends ObjectIDs.ScriptOrChannel ? (ScriptObject | ChannelObject) : + T extends ObjectIDs.Enum ? EnumObject : + T extends ObjectIDs.Group ? GroupObject : + T extends ObjectIDs.User ? UserObject : + T extends ObjectIDs.Host ? HostObject : + T extends ObjectIDs.Config ? OtherObject & { type: "config" } : + T extends ObjectIDs.AdapterScoped ? AdapterScopedObject : + iobJS.AnyObject + // When reading objects, we should be less strict, so working with the return type is less of a pain to work with + > = Read extends "read" ? AnyOf : O; + + interface ObjectCommon { + /** The name of this object as a simple string or an object with translations */ + name: StringOrTranslated; + + /** When set to true, this object may not be deleted */ + dontDelete?: true; + + /** When set to true, this object is only visible when expert mode is turned on in admin */ + expert?: true; + + // Icon and role aren't defined in SCHEMA.md, + // but they are being used by some adapters + /** Icon for this object */ + icon?: string; + /** role of the object */ + role?: string; + } + + interface StateCommonAlias { + /** The target state id or two target states used for reading and writing values */ + id: string | { read: string; write: string }; + /** An optional conversion function when reading, e.g. `"(val − 32) * 5/9"` */ + read?: string; + /** An optional conversion function when reading, e.g. `"(val * 9/5) + 32"` */ + write?: string; + } + + interface StateCommon extends ObjectCommon { + /** Type of this state. See https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#state-commonrole for a detailed description */ + type?: CommonType; + /** minimum value */ + min?: number; + /** maximum value */ + max?: number; + /** the allowed interval for numeric values */ + step?: number; + /** unit of the value */ + unit?: string; + /** description of this state */ + desc?: StringOrTranslated; + + /** if this state is readable */ + read: boolean; + /** if this state is writable */ + write: boolean; + /** role of the state (used in user interfaces to indicate which widget to choose) */ + role: string; + + /** the default value */ + def?: any; + /** the default status of the ack flag */ + defAck?: boolean; + + /** Configures this state as an alias for another state */ + alias?: StateCommonAlias; + + /** + * Dictionary of possible values for this state in the form + *
+			 * {
+			 *     "internal value 1": "displayed value 1",
+			 *     "internal value 2": "displayed value 2",
+			 *     ...
+			 * }
+			 * 
+ * In old ioBroker versions, this could also be a string of the form + * "val1:text1;val2:text2" (now deprecated) + */ + states?: Record | string; + + /** ID of a helper state indicating if the handler of this state is working */ + workingID?: string; + + /** attached history information */ + history?: any; + + /** Custom settings for this state */ + custom?: Record; + + /** + * Settings for IOT adapters and how the state should be named in e.g., Alexa. + * The string "ignore" is a special case, causing the state to be ignored. + */ + smartName?: string | ({ [lang in Languages]?: string; } & { + /** Which kind of device this is */ + smartType?: string | null; + /** Which value to set when the ON command is issued */ + byOn?: string | null; + }); + } + interface ChannelCommon extends ObjectCommon { + /** description of this channel */ + desc?: string; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + interface DeviceCommon extends ObjectCommon { + // TODO: any other definition for device? + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + interface EnumCommon extends ObjectCommon { + /** The IDs of the enum members */ + members?: string[]; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + interface MetaCommon extends ObjectCommon { + // Meta-objects have to additional CommonTypes + type: CommonType | "meta.user" | "meta.folder"; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + type InstanceMode = 'none' | 'daemon' | 'subscribe' | 'schedule' | 'once' | 'extension'; + interface InstanceCommon extends ObjectCommon { + /** The name of the host where this instance is running */ + host: string; + enabled: boolean; + /** How and when this instance should be started */ + mode: InstanceMode; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + interface HostCommon extends ObjectCommon { + /** The display name of this host */ + name: string; + title: string; + installedVersion: string; // e.g. 1.2.3 (following semver) + /** The command line of the executable */ + cmd: string; + hostname: string; + /** An array of IP addresses this host exposes */ + address: string[]; // IPv4 or IPv6 + + type: 'js-controller'; + platform: 'Javascript/Node.js'; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + interface HostNative { + process: { + title: string; + versions: NodeJS.ProcessVersions; + env: Record; + }; + os: { + hostname: string, + type: ReturnType; + platform: ReturnType; + arch: ReturnType; + release: ReturnType; + endianness: ReturnType; + tmpdir: ReturnType; + }; + hardware: { + cpus: ReturnType; + totalmem: ReturnType; + networkInterfaces: ReturnType; + }; + } + + interface UserCommon extends ObjectCommon { + /** The username */ + name: string; + /** The hashed password */ + password: string; + /** Whether this user is enabled */ + enabled: boolean; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + interface GroupCommon extends ObjectCommon { + /** The name of this group */ + name: string; + /** The users of this group */ + members: string[]; // system.user.name, ... + /** The default permissions of this group */ + acl: Omit; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + interface ScriptCommon extends ObjectCommon { + name: string; + /** Defines the type of the script, e.g. TypeScript/ts, JavaScript/js or Blockly */ + engineType: string; + /** The instance id of the instance which executes this script */ + engine: string; + /** The source code of this script */ + source: string; + debug: boolean; + verbose: boolean; + /** Whether this script should be executed */ + enabled: boolean; + /** Is used to determine whether a script has changed and needs to be recompiled */ + sourceHash?: string; + /** If the script uses a compiled language like TypeScript, this contains the compilation output */ + compiled?: string; + /** If the script uses a compiled language like TypeScript, this contains the generated declarations (global scripts only) */ + declarations?: string; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + type WelcomeScreenEntry = string | { + link: string; + name: string; + img: string; + color: string; + }; + + interface AdapterCommon extends ObjectCommon { + /** Custom attributes to be shown in admin in the object browser */ + adminColumns?: any[]; + /** Settings for custom Admin Tabs */ + adminTab?: { + name?: string; + /** Icon name for FontAwesome */ + "fa-icon"?: string; + /** If true, the Tab is not reloaded when the configuration changes */ + ignoreConfigUpdate?: boolean; + /** Which URL should be loaded in the tab. Supports placeholders like http://%ip%:%port% */ + link?: string; + /** If true, only one instance of this tab will be created for all instances */ + singleton?: boolean; + }; + allowInit?: boolean; + /** Possible values for the instance mode (if more than one is possible) */ + availableModes?: InstanceMode[]; + /** Whether this adapter includes custom blocks for Blockly. If true, `admin/blockly.js` must exist. */ + blockly?: boolean; + /** Where the adapter will get its data from. Set this together with @see dataSource */ + connectionType?: "local" | "cloud"; + /** If true, this adapter can be started in compact mode (in the same process as other adpaters) */ + compact?: boolean; + /** The directory relative to iobroker-data where the adapter stores the data. Supports the placeholder `%INSTANCE%`. This folder will be backed up and restored automatically. */ + dataFolder?: string; + /** How the adapter will mainly receive its data. Set this together with @see connectionType */ + dataSource?: "poll" | "push" | "assumption"; + /** A record of ioBroker adapters (including "js-controller") and version ranges which are required for this adapter. */ + dependencies?: Array>; + /** Which files outside of the README.md have documentation for the adapter */ + docs?: Partial>; + /** Whether new instances should be enabled by default. *Should* be `false`! */ + enabled: boolean; + /** If true, all previous data in the target directory (web) should be deleted before uploading */ + eraseOnUpload?: boolean; + /** URL of an external icon that is shown for adapters that are not installed */ + extIcon?: string; + /** Whether this adapter responds to `getHistory` messages */ + getHistory?: boolean; + /** Filename of the local icon which is shown for installed adapters. Should be located in the `admin` directory */ + icon?: string; + /** Which version of this adapter is installed */ + installedVersion: string; + keywords?: string[]; + /** A dictionary of links to web services this adapter provides */ + localLinks?: Record; + /** @deprecated Use @see localLinks */ + localLink?: string; + logLevel?: LogLevel; + /** Whether this adapter receives logs from other hosts and adapters (e.g., to strore them somewhere) */ + logTransporter?: boolean; + /** Path to the start file of the adapter. Should be the same as in `package.json` */ + main?: string; + /** Whether the admin tab is written in materialize style. Required for Admin 3+ */ + materializeTab: boolean; + /** Whether the admin configuration dialog is written in materialize style. Required for Admin 3+ */ + materialize: boolean; + /** If `true`, the object `system.adapter...messagebox will be created to send messages to the adapter (used for email, pushover, etc...) */ + messagebox?: true; + mode: InstanceMode; + /** Name of the adapter (without leading `ioBroker.`) */ + name: string; + /** If `true`, no configuration dialog will be shown */ + noConfig?: true; + /** If `true`, this adapter's instances will not be shown in the admin overview screen. Useful for icon sets and widgets... */ + noIntro?: true; + /** Set to `true` if the adapter is not available in the official ioBroker repositories. */ + noRepository?: true; + /** If `true`, manual installation from GitHub is not possible */ + nogit?: true; + /** If `true`, this adapter cannot be deleted or updated manually. */ + nondeletable?: true; + /** If `true`, this "adapter" only contains HTML files and no main executable */ + onlyWWW?: boolean; + /** Used to configure native (OS) dependencies of this adapter that need to be installed with system package manager before installing the adapter */ + osDependencies?: { + /** For OSX */ + darwin: string[]; + /** For Linux */ + linux: string[]; + /** For Windows */ + win32: string[]; + }; + /** Which OSes this adapter supports */ + os?: "linux" | "darwin" | "win32" | Array<("linux" | "darwin" | "win32")>; + platform: "Javascript/Node.js"; + /** The keys of common attributes (e.g. `history`) which are not deleted in a `setObject` call even if they are not present. Deletion must be done explicitly by setting them to `null`. */ + preserveSettings?: string | string[]; + /** Which adapters must be restarted after installing or updating this adapter. */ + restartAdapters?: string[]; + /** If the adapter runs in `schedule` mode, this contains the CRON */ + schedule?: string; + serviceStates?: boolean | string; + /** Whether this adapter may only be installed once per host */ + singletonHost?: boolean; + /** Whether this adapter may only be installed once in the whole system */ + singleton?: boolean; + /** Whether the adapter must be stopped before an update */ + stopBeforeUpdate?: boolean; + /** Overrides the default timeout that ioBroker will wait before force-stopping the adapter */ + stopTimeout?: number; + subscribable?: boolean; + subscribe?: any; // ? + /** If `true`, this adapter provides custom per-state settings. Requires a `custom_m.html` file in the `admin` directory. */ + supportCustoms?: boolean; + /** Whether the adapter supports the signal stopInstance via messagebox */ + supportStopInstance?: boolean; + /** The translated names of this adapter to be shown in the admin UI */ + titleLang?: Record; + /** @deprecated The name of this adapter to be shown in the admin UI. Use @see titleLang instead. */ + title?: string; + /** The type of this adapter */ + type?: string; + /** If `true`, the `npm` package must be installed with the `--unsafe-perm` flag */ + unsafePerm?: true; + /** The available version in the ioBroker repo. */ + version: string; + /** If `true`, the adapter will be started if any value is written into `system.adapter...wakeup. Normally, the adapter should stop after processing the event. */ + wakeup?: boolean; + /** Include the adapter version in the URL of the web adapter, e.g. `http://ip:port/1.2.3/material` instead of `http://ip:port/material` */ + webByVersion?: boolean; + /** Whether the web server in this adapter can be extended with plugin/extensions */ + webExtendable?: boolean; + /** Relative path to a module that contains an extension for the web adapter. Use together with @see native.webInstance to configure which instances this affects */ + webExtension?: string; + webPreSettings?: any; // ? + webservers?: any; // ? + /** A list of pages that should be shown on the "web" index page */ + welcomeScreen?: WelcomeScreenEntry[]; + /** A list of pages that should be shown on the ioBroker cloud index page */ + welcomeScreenPro?: WelcomeScreenEntry[]; + wwwDontUpload?: boolean; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + interface OtherCommon extends ObjectCommon { + [propName: string]: any; + + // Make it possible to narrow the object type using the custom property + custom?: undefined; + } + + /* Base type for Objects. Should not be used directly */ + interface BaseObject { + /** The ID of this object */ + _id: string; + type: ObjectType; // specified in the derived interfaces + // Ideally we would limit this to JSON-serializable objects, but TypeScript doesn't allow this + // without bugging users to change their code --> https://github.com/microsoft/TypeScript/issues/15300 + native: Record; + common: Record; + enums?: Record; + acl?: ObjectACL; + from?: string; + /** The user who created or updated this object */ + user?: string; + ts?: number; + } + + interface StateObject extends BaseObject { + type: 'state'; + common: StateCommon; + acl?: StateACL; + } + interface PartialStateObject extends Partial> { + common?: Partial; + acl?: Partial; + } + + interface ChannelObject extends BaseObject { + type: 'channel'; + common: ChannelCommon; + } + interface PartialChannelObject + extends Partial> { + common?: Partial; + } + + interface DeviceObject extends BaseObject { + type: 'device'; + common: DeviceCommon; + } + interface PartialDeviceObject extends Partial> { + common?: Partial; + } + + interface FolderObject extends BaseObject { + type: 'folder'; + // Nothing is set in stone here, so start with allowing every property + common: OtherCommon; + } + interface PartialFolderObject extends Partial> { + common?: Partial; + } + + interface EnumObject extends BaseObject { + type: 'enum'; + common: EnumCommon; + } + interface PartialEnumObject extends Partial> { + common?: Partial; + } + + interface MetaObject extends BaseObject { + type: 'meta'; + common: MetaCommon; + } + interface PartialMetaObject extends Partial> { + common?: Partial; + } + + interface InstanceObject extends BaseObject { + type: 'instance'; + common: InstanceCommon; + } + interface PartialInstanceObject extends Partial> { + common?: Partial; + } + + interface AdapterObject extends BaseObject { + type: 'adapter'; + common: AdapterCommon; + /** An array of `native` properties which cannot be accessed from outside the defining adapter */ + protectedNative?: string[]; + /** Like protectedNative, but the properties are also encrypted and decrypted automatically */ + encryptedNative?: string[]; + } + interface PartialAdapterObject extends Partial> { + common?: Partial; + } + + interface HostObject extends BaseObject { + type: 'host'; + common: HostCommon; + native: HostNative; + } + interface PartialHostObject extends Partial> { + common?: Partial; + native?: Partial; + } + + interface UserObject extends BaseObject { + type: 'user'; + common: UserCommon; + } + interface PartialUserObject extends Partial> { + common?: Partial; + } + + interface GroupObject extends BaseObject { + type: 'group'; + common: GroupCommon; + } + interface PartialGroupObject extends Partial> { + common?: Partial; + } + + interface ScriptObject extends BaseObject { + type: 'script'; + common: ScriptCommon; + } + interface PartialScriptObject extends Partial> { + common?: Partial; + } + + interface OtherObject extends BaseObject { + type: 'config' | 'chart'; + common: OtherCommon; + } + interface PartialOtherObject extends Partial> { + common?: Partial; + } + + type AnyObject = + | StateObject + | ChannelObject + | DeviceObject + | FolderObject + | EnumObject + | MetaObject + | HostObject + | AdapterObject + | InstanceObject + | UserObject + | GroupObject + | ScriptObject + | OtherObject; + + type AnyPartialObject = + | PartialStateObject + | PartialChannelObject + | PartialDeviceObject + | PartialFolderObject + | PartialEnumObject + | PartialMetaObject + | PartialHostObject + | PartialAdapterObject + | PartialInstanceObject + | PartialUserObject + | PartialGroupObject + | PartialScriptObject + | PartialOtherObject; + + /** All objects that usually appear in an adapter scope */ + type AdapterScopedObject = FolderObject | DeviceObject | ChannelObject | StateObject; + + // For all objects that are exposed to the user we need to tone the strictness down. + // Otherwise, every operation on objects becomes a pain to work with + type Object = AnyObject; + + // In set[Foreign]Object[NotExists] methods, the ID and acl of the object is optional + type SettableObjectWorker = T extends AnyObject ? Omit & { + _id?: T['_id']; + acl?: T['acl']; + } : never; + // in extend[Foreign]Object, most properties are optional + type PartialObjectWorker = T extends AnyObject ? AnyPartialObject & { type?: T["type"] } : never; + + type PartialObject = PartialObjectWorker; + + // Convenient definitions for manually specifying settable object types + type SettableObject = SettableObjectWorker; + type SettableStateObject = SettableObject; + type SettableChannelObject = SettableObject; + type SettableDeviceObject = SettableObject; + type SettableFolderObject = SettableObject; + type SettableEnumObject = SettableObject; + type SettableMetaObject = SettableObject; + type SettableHostObject = SettableObject; + type SettableAdapterObject = SettableObject; + type SettableInstanceObject = SettableObject; + type SettableUserObject = SettableObject; + type SettableGroupObject = SettableObject; + type SettableScriptObject = SettableObject; + type SettableOtherObject = SettableObject; + + /** Represents the change of a state */ + interface ChangedStateObject extends StateObject { + common: StateCommon; + native: Record; + id?: string; + name?: string; + channelId?: string; + channelName?: string; + deviceId?: string; + deviceName?: string; + /** The IDs of enums this state is assigned to. For example ["enum.functions.Licht","enum.rooms.Garten"] */ + enumIds?: string[]; + /** The names of enums this state is assigned to. For example ["Licht","Garten"] */ + enumNames?: string[]; + /** new state */ + state: State; + /** @deprecated Use state instead **/ + newState: State; + /** previous state */ + oldState: State; + /** Name of the adapter instance which set the value, e.g. "system.adapter.web.0" */ + from?: string; + /** Unix timestamp. Default: current time */ + ts?: number; + /** Unix timestamp of the last time the value changed */ + lc?: number; + /** Direction flag: false for desired value and true for actual value. Default: false. */ + ack?: boolean; + } + + type GetStateCallback = (err?: string | null, state?: State | AbsentState) => void | Promise; + type ExistsStateCallback = (err?: string | null, exists?: Boolean) => void | Promise; + + type GetBinaryStateCallback = (err?: string | null, state?: Buffer) => void | Promise; + type GetBinaryStatePromise = Promise>; + + type SetStateCallback = (err?: string | null, id?: string) => void | Promise; + type SetStatePromise = Promise>; + + type StateChangeHandler = (obj: ChangedStateObject) => void | Promise; + + type FileChangeHandler = + // Variant 1: WithFile is false, data/mimeType is definitely not there + [WithFile] extends [false] ? (id: string, fileName: string, size: number, data?: undefined, mimeType?: undefined) => void | Promise + // Variant 2: WithFile is true, data (and mimeType?) is definitely there + : [WithFile] extends [true] ? (id: string, fileName: string, size: number, data: Buffer | string, mimeType?: string) => void | Promise + // Variant 3: WithFile is not known, data/mimeType might be there + : (id: string, fileName: string, size: number, data?: Buffer | string, mimeType?: string) => void | Promise; + + type SetObjectCallback = (err?: string | null, obj?: { id: string }) => void | Promise; + type SetObjectPromise = Promise>; + + type GetObjectCallback = (err?: string | null, obj?: ObjectIdToObjectType | null) => void; + type GetObjectPromise = Promise>>; + + type LogLevel = "silly" | "debug" | "info" | "warn" | "error" | "force"; + + type ReadFileCallback = (err?: string | null, file?: Buffer | string, mimeType?: string) => void | Promise; + type ReadFilePromise = Promise>; + + /** Callback information for a passed message */ + interface MessageCallbackInfo { + /** The original message payload */ + message: string | object; + /** ID of this callback */ + id: number; + // ??? + ack: boolean; + /** Timestamp of this message */ + time: number; + } + type MessageCallback = (result?: any) => void | Promise; + + interface Subscription { + name: string; + pattern: string | RegExp | string[] | iobJS.SubscribeOptions | iobJS.SubscribeTime | iobJS.AstroSchedule; + } + + interface SubscribeOptions { + /** "and" or "or" logic to combine the conditions (default: "and") */ + logic?: "and" | "or"; + /** name is equal or matches to given one or name marches to any item in given list */ + id?: string | string[] | SubscribeOptions[] | RegExp | RegExp[]; + /** name is equal or matches to given one */ + name?: string | string[] | RegExp; + /** type of change */ + change?: "eq" | "ne" | "gt" | "ge" | "lt" | "le" | "any"; + val?: StateValue; + /** New value must not be equal to given one */ + valNe?: StateValue; + /** New value must be greater than given one */ + valGt?: number; + /** New value must be greater or equal to given one */ + valGe?: number; + /** New value must be smaller than given one */ + valLt?: number; + /** New value must be smaller or equal to given one */ + valLe?: number; + /** Acknowledged state of new value is equal to given one */ + ack?: boolean; + /** Previous value must be equal to given one */ + oldVal?: StateValue; + /** Previous value must be not equal to given one */ + oldValNe?: StateValue; + /** Previous value must be greater than given one */ + oldValGt?: number; + /** Previous value must be greater or equal given one */ + oldValGe?: number; + /** Previous value must be smaller than given one */ + oldValLt?: number; + /** Previous value must be smaller or equal to given one */ + oldValLe?: number; + /** Acknowledged state of previous value is equal to given one */ + oldAck?: boolean; + /** New value time stamp must be equal to given one (state.ts == ts) */ + ts?: number; + /** New value time stamp must be not equal to the given one (state.ts != ts) */ + tsGt?: number; + /** New value time stamp must be greater than given value (state.ts > ts) */ + tsGe?: number; + /** New value time stamp must be greater or equal to given one (state.ts >= ts) */ + tsLt?: number; + /** New value time stamp must be smaller than given one (state.ts < ts) */ + tsLe?: number; + /** Previous time stamp must be equal to given one (oldState.ts == ts) */ + oldTs?: number; + /** Previous time stamp must be not equal to the given one (oldState.ts != ts) */ + oldTsGt?: number; + /** Previous time stamp must be greater than the given value (oldState.ts > ts) */ + oldTsGe?: number; + /** Previous time stamp must be greater or equal to given one (oldState.ts >= ts) */ + oldTsLt?: number; + /** Previous time stamp must be smaller than given one (oldState.ts < ts) */ + oldTsLe?: number; + /** Last change time stamp must be equal to given one (state.lc == lc) */ + lc?: number; + /** Last change time stamp must be not equal to the given one (state.lc != lc) */ + lcGt?: number; + /** Last change time stamp must be greater than the given value (state.lc > lc) */ + lcGe?: number; + /** Last change time stamp must be greater or equal to given one (state.lc >= lc) */ + lcLt?: number; + /** Last change time stamp must be smaller than given one (state.lc < lc) */ + lcLe?: number; + /** Previous last change time stamp must be equal to given one (oldState.lc == lc) */ + oldLc?: number; + /** Previous last change time stamp must be not equal to the given one (oldState.lc != lc) */ + oldLcGt?: number; + /** Previous last change time stamp must be greater than the given value (oldState.lc > lc) */ + oldLcGe?: number; + /** Previous last change time stamp must be greater or equal to given one (oldState.lc >= lc) */ + oldLcLt?: number; + /** Previous last change time stamp must be smaller than given one (oldState.lc < lc) */ + oldLcLe?: number; + /** Channel ID must be equal or match to given one */ + channelId?: string | string[] | RegExp; + /** Channel name must be equal or match to given one */ + channelName?: string | string[] | RegExp; + /** Device ID must be equal or match to given one */ + deviceId?: string | string[] | RegExp; + /** Device name must be equal or match to given one */ + deviceName?: string | string[] | RegExp; + /** State belongs to given enum or one enum ID of state satisfy the given regular expression */ + enumId?: string | string[] | RegExp; + /** State belongs to given enum or one enum name of state satisfy the given regular expression */ + enumName?: string | string[] | RegExp; + /** New value is from defined adapter */ + from?: string | string[] | RegExp; + /** New value is not from defined adapter */ + fromNe?: string | string[] | RegExp; + /** Old value is from defined adapter */ + oldFrom?: string | string[] | RegExp; + /** Old value is not from defined adapter */ + oldFromNe?: string | string[] | RegExp; + } + + interface QueryResult extends Iterable { + /** State-ID */ + [index: number]: string; + /** Number of matched states */ + length: number; + /** Contains the error if one happened */ + error?: string; + + /** + * Executes a function for each state id in the result array + * The execution is canceled if a callback returns false + */ + each(callback?: (id: string, index: number) => boolean | void | Promise): this; + + /** + * Returns the first state found by this query. + * If the adapter is configured to subscribe to all states on start, + * this can be called synchronously and immediately returns the state. + * Otherwise, you need to provide a callback. + */ + getState(callback: GetStateCallback): void; + getState(): State | null | undefined; + getStateAsync(): Promise | null | undefined>; + + /** + * Returns the first state found by this query. + * If the adapter is configured to subscribe to all states on start, + * this can be called synchronously and immediately returns the state. + * Otherwise, you need to provide a callback. + */ + getBinaryState(callback: GetBinaryStateCallback): void; + getBinaryState(): Buffer | null | undefined; + getBinaryStateAsync(): Promise; + + /** + * Sets all queried states to the given value. + */ + setState(state: State | StateValue | SettableState, ack?: boolean, callback?: SetStateCallback): this; + setStateAsync(state: State | StateValue | SettableState, ack?: boolean): Promise; + setStateDelayed(state: any, isAck?: boolean, delay?: number, clearRunning?: boolean, callback?: SetStateCallback): this; + + /** + * Sets all queried binary states to the given value. + */ + setBinaryState(state: Buffer, ack?: boolean, callback?: SetStateCallback): this; + setBinaryStateAsync(state: Buffer, ack?: boolean): Promise; + + /** + * Subscribes the given callback to changes of the matched states. + */ + on(callback: StateChangeHandler): this; + } + + /** + * * "sunrise": sunrise (top edge of the sun appears on the horizon) + * * "sunriseEnd": sunrise ends (bottom edge of the sun touches the horizon) + * * "goldenHourEnd": morning golden hour (soft light, best time for photography) ends + * * "solarNoon": solar noon (sun is in the highest position) + * * "goldenHour": evening golden hour starts + * * "sunsetStart": sunset starts (bottom edge of the sun touches the horizon) + * * "sunset": sunset (sun disappears below the horizon, evening civil twilight starts) + * * "dusk": dusk (evening nautical twilight starts) + * * "nauticalDusk": nautical dusk (evening astronomical twilight starts) + * * "night": night starts (dark enough for astronomical observations) + * * "nightEnd": night ends (morning astronomical twilight starts) + * * "nauticalDawn": nautical dawn (morning nautical twilight starts) + * * "dawn": dawn (morning nautical twilight ends, morning civil twilight starts) + * * "nadir": nadir (darkest moment of the night, sun is in the lowest position) + */ + type AstroPattern = "sunrise" | "sunriseEnd" | "goldenHourEnd" | "solarNoon" | "goldenHour" | "sunsetStart" | "sunset" | "dusk" | "nauticalDusk" | "night" | "nightEnd" | "nauticalDawn" | "dawn" | "nadir"; + + interface AstroSchedule { + astro: AstroPattern; + /** + * Shift to the astro schedule. + */ + shift?: number; + } + + interface AstroDate { + astro: AstroPattern; + /** Offset to the astro event in minutes */ + offset?: number; + /** Date for which the astro time is wanted */ + date?: Date; + } + + /** + * from https://github.com/node-schedule/node-schedule + */ + interface ScheduleRule { + /** + * Day of the month. + */ + date?: number | number[] | string | string[]; + + /** + * Day of the week. + */ + dayOfWeek?: number | number[] | string | string[]; + + /** + * Hour. + */ + hour?: number | number[] | string | string[]; + + /** + * Minute. + */ + minute?: number | number[] | string | string[]; + + /** + * Month. + */ + month?: number | number[] | string | string[]; + + /** + * Second. + */ + second?: number | number[] | string | string[]; + + /** + * Year. + */ + year?: number | number[] | string | string[]; + /** + * timezone which should be used + * https://github.com/moment/moment-timezone + */ + tz?: string; + } + + /** + * from https://github.com/node-schedule/node-schedule + */ + interface ScheduleRuleConditional { + /** + * set a start time for schedule + * a Data object or a dateString resp a number in milliseconds which can create a Date object + */ + start?: Date | string | number; + /** + * set an end time for schedule + * a Data object or a dateString resp a number in milliseconds which can create a Date object + */ + end?: Date | string | number; + /** + * timezone which should be used + * https://github.com/moment/moment-timezone + */ + tz?: string; + /** + * scheduling rule + * schedule rule, a Data object or a dateString resp a number in milliseconds which can create a Date object + */ + rule: ScheduleRule | Date | string | number; + } + + interface LogMessage { + severity: LogLevel; // severity + ts: number; // timestamp as Date.now() + message: string; // message + from: string; // origin of the message + } + + type SchedulePattern = ScheduleRule | ScheduleRuleConditional | Date | string | number; + + interface SubscribeTime { + time: SchedulePattern; + } + + interface StateTimer { + id: number; + left: number; + delay: number; + val: any; + ack: boolean; + } + + type MessageSubscribeID = number; + interface MessageTarget { + /** Javascript Instance */ + instance?: string; + /** Script name */ + script?: string; + /** Message name */ + message: string; + } + } // end namespace iobJS + + // ======================================================= + // available functions in the sandbox + // ======================================================= + + // The already preloaded request module + const request: typeof import("request"); + + /** + * The instance number of the JavaScript adapter this script runs in + */ + const instance: number; + /** + * The name of the current script + */ + // @ts-ignore We need this variable, although it conflicts with lib.es6 + const name: string; + /** + * The name of the current script + */ + const scriptName: string; + + /** + * Queries all states with the given selector + * @param selector See @link{https://github.com/ioBroker/ioBroker.javascript#---selector} for a description + */ + // @ts-ignore We need this function, although it conflicts with vue + function $(selector: string): iobJS.QueryResult; + + /** + * Prints a message in the ioBroker log + * @param message The message to print + * @param severity (optional) severity of the message. default = "info" + */ + function log(message: string, severity?: iobJS.LogLevel): void; + + // console functions + // @ts-ignore We need this variable, although it conflicts with the node typings + namespace console { + /** log a message with debug level */ + function debug(message: string): void; + /** log a message with info level (default output level for all adapters) */ + function info(message: string): void; + /** log a message with warning severity */ + function warn(message: string): void; + /** log a message with error severity */ + function error(message: string): void; + } + + /** + * Executes a system command + */ + const exec: typeof import("child_process").exec; + + /** + * Sends an email using the email adapter. + * See the adapter documentation for a description of the msg parameter. + */ + function email(msg: any): void; + + /** + * Sends a pushover message using the pushover adapter. + * See the adapter documentation for a description of the msg parameter. + */ + function pushover(msg: any): void; + + /** + * Subscribe to the changes of the matched states. + */ + function on(pattern: string | RegExp | string[], handler: iobJS.StateChangeHandler): any; + function on( + astroOrScheduleOrOptions: iobJS.AstroSchedule | iobJS.SubscribeTime | iobJS.SubscribeOptions, + handler: iobJS.StateChangeHandler + ): any; + /** + * Subscribe to the changes of the matched states. + */ + function subscribe(pattern: string | RegExp | string[], handler: iobJS.StateChangeHandler): any; + function subscribe( + astroOrScheduleOrOptions: iobJS.AstroSchedule | iobJS.SubscribeTime | iobJS.SubscribeOptions, + handler: iobJS.StateChangeHandler + ): any; + + /** + * Subscribe to the changes of the matched files. + * The return value can be used for offFile later + * @param id ID of meta-object, like `vis.0` + * @param filePattern File name or file pattern, like `main/*` + * @param withFile If the content of the file must be returned in callback (high usage of memory) + * @param handler Callback: function (id, fileName, size, data, mimeType) {} + */ + function onFile(id: string, filePattern: string | string[], withFile: WithFile, handler: iobJS.FileChangeHandler): any; + function onFile(id: string, filePattern: string | string[], handler: iobJS.FileChangeHandler): any; + + /** + * Un-subscribe from the changes of the matched files. + * @param id ID of meta-object, like `vis.0`. You can provide here can be a returned object from onFile. In this case, no filePattern required. + * @param filePattern File name or file pattern, like `main/*` + */ + function offFile(id: string | any, filePattern?: string | string[]): boolean; + + /** + * Registers a one-time subscription which automatically unsubscribes after the first invocation + */ + function once( + pattern: string | RegExp | string[] | iobJS.AstroSchedule | iobJS.SubscribeTime | iobJS.SubscribeOptions, + handler: iobJS.StateChangeHandler + ): any; + function once( + pattern: string | RegExp | string[] | iobJS.AstroSchedule | iobJS.SubscribeTime | iobJS.SubscribeOptions + ): Promise; + + /** + * Causes all changes of the state with id1 to the state with id2. + * The return value can be used to unsubscribe later + */ + function on(id1: string, id2: string): any; + /** + * Watches the state with id1 for changes and overwrites the state with id2 with value2 when any occur. + * @param id1 The state to watch for changes + * @param id2 The state to update when changes occur + * @param value2 The value to write into state `id2` when `id1` gets changed + */ + function on(id1: string, id2: string, value2: any): any; + + /** + * Causes all changes of the state with id1 to the state with id2 + */ + function subscribe(id1: string, id2: string): any; + /** + * Watches the state with id1 for changes and overwrites the state with id2 with value2 when any occur. + * @param id1 The state to watch for changes + * @param id2 The state to update when changes occur + * @param value2 The value to write into state `id2` when `id1` gets changed + */ + function subscribe(id1: string, id2: string, value2: any): any; + + /** + * Returns the list of all currently active subscriptions + */ + function getSubscriptions(): { [id: string]: iobJS.Subscription[] }; + + /** + * Unsubscribe from changes of the given object ID(s) or handler(s) + */ + function unsubscribe(id: string | string[]): boolean; + function unsubscribe(handler: any | any[]): boolean; + + function adapterSubscribe(id: string): void; + function adapterUnsubscribe(id: string): void; + + /** + * Schedules a function to be executed on a defined schedule. + * The return value can be used to clear the schedule later. + */ + function schedule(pattern: string | iobJS.SchedulePattern, callback: EmptyCallback): any; + function schedule(date: Date, callback: EmptyCallback): any; + function schedule(astro: iobJS.AstroSchedule, callback: EmptyCallback): any; + /** + * Clears a schedule. Returns true if it was successful. + */ + function clearSchedule(schedule: any): boolean; + + /** + * Calculates the astro time which corresponds to the given pattern. + * For valid patterns, see @link{https://github.com/ioBroker/ioBroker.javascript/blob/master/docs/en/javascript.md#astro-function} + * @param pattern One of predefined patterns, like: sunrise, sunriseEnd, ... + * @param date (optional) The date for which the astro time should be calculated. Default = today + * @param offsetMinutes (optional) The number of minutes to be added to the return value. + */ + function getAstroDate(pattern: string, date?: Date | number, offsetMinutes?: number): Date; + + /** + * Determines if now is between sunrise and sunset. + */ + function isAstroDay(): boolean; + + /** + * Sets a state to the given value + * @param id The ID of the state to be set + */ + function setState(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, callback?: iobJS.SetStateCallback): void; + function setState(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, ack: boolean, callback?: iobJS.SetStateCallback): void; + + function setStateAsync(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, ack?: boolean): iobJS.SetStatePromise; + + /** + * Sets a state to the given value after a timeout has passed. + * Returns the timer, so it can be manually cleared with clearStateDelayed + * @param id The ID of the state to be set + * @param delay The delay in milliseconds + * @param clearRunning (optional) Whether an existing timeout for this state should be cleared + * @returns If a delayed setState was scheduled, this returns the timer id, otherwise null. + */ + function setStateDelayed(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, delay: number, clearRunning: boolean, callback?: iobJS.SetStateCallback): number | null; + function setStateDelayed(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, ack: boolean, clearRunning: boolean, callback?: iobJS.SetStateCallback): number | null; + function setStateDelayed(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, ack: boolean, delay: number, callback?: iobJS.SetStateCallback): number | null; + function setStateDelayed(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, delay: number, callback?: iobJS.SetStateCallback): number | null; + function setStateDelayed(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, callback?: iobJS.SetStateCallback): number | null; + function setStateDelayed(id: string, state: iobJS.State | iobJS.StateValue | iobJS.SettableState, ack: boolean, delay: number, clearRunning: boolean, callback?: iobJS.SetStateCallback): number | null; + + /** + * Clears a timer created by setStateDelayed + * @param id The state id for which the timer should be cleared + * @param timerID (optional) ID of the specific timer to clear. If none is given, all timers are cleared. + */ + function clearStateDelayed(id: string, timerID?: number): boolean; + + /** + * Returns information about a specific timer created with `setStateDelayed`. + * @param timerId The timer id that was returned by `setStateDelayed`. + */ + function getStateDelayed(timerId: number): iobJS.StateTimer | null; + /** + * Returns a list of all timers created with `setStateDelayed`. Can be limited to a specific state id. + * @param id The state id for which the timers should be. + */ + function getStateDelayed(id?: string): iobJS.StateTimer[]; + + /** + * Sets a binary state to the given value + * @param id The ID of the state to be set + * @param state binary data as buffer + * @param callback called when the operation finished + */ + function setBinaryState(id: string, state: Buffer, callback?: iobJS.SetStateCallback): void; + function setBinaryStateAsync(id: string, state: Buffer): iobJS.SetStatePromise; + + /** + * Returns the state with the given ID. + * If the adapter is configured to subscribe to all states on start, + * this can be called synchronously and immediately returns the state. + * Otherwise, you need to provide a callback. + */ + function getState(id: string, callback: iobJS.GetStateCallback): void; + function getState(id: string): iobJS.State | iobJS.AbsentState; + function getStateAsync(id: string): Promise>; + + /** + * Returns the binary state with the given ID. + * If the adapter is configured to subscribe to all states on start, + * this can be called synchronously and immediately returns the state. + * Otherwise, you need to provide a callback. + */ + function getBinaryState(id: string, callback: iobJS.GetStateCallback): void; + function getBinaryState(id: string): Buffer; + function getBinaryStateAsync(id: string): iobJS.GetBinaryStatePromise; + + /** + * Checks if the state with the given ID exists + */ + function existsState(id: string, callback: iobJS.ExistsStateCallback): void; + function existsState(id: string): boolean; + function existsStateAsync(id: string): Promise; + /** + * Checks if the object with the given ID exists + */ + function existsObject(id: string): boolean; + function existsObjectAsync(id: string): Promise; + + /** + * Returns the IDs of the states with the given name + * @param name Name of the state + * @param forceArray (optional) Ensures that the return value is always an array, even if only one ID was found. + */ + function getIdByName(name: string, forceArray?: boolean): string | string[]; + + /** + * Reads an object from the object db. + * @param enumName Which enum should be included in the returned object. `true` to return all enums. + */ + function getObject(id: T, enumName?: string | true): iobJS.ObjectIdToObjectType; + function getObject(id: T, callback: iobJS.GetObjectCallback): void; + function getObject(id: T, enumName: string | true, callback: iobJS.GetObjectCallback): void; + function getObjectAsync(id: T, enumName?: string | true): iobJS.GetObjectPromise; + + /** Creates or overwrites an object in the object db */ + function setObject(id: string, obj: iobJS.SettableObject, callback?: iobJS.SetObjectCallback): void; + function setObjectAsync(id: string, obj: iobJS.SettableObject): iobJS.SetObjectPromise; + /** Extend an object and create it if it might not exist */ + function extendObject(id: string, objPart: iobJS.PartialObject, callback?: iobJS.SetObjectCallback): void; + function extendObjectAsync(id: string, objPart: iobJS.PartialObject): iobJS.SetObjectPromise; + + /** Deletes an object in the object db */ + function deleteObject(id: string, callback?: ErrorCallback): void; + function deleteObject(id: string, recursive: boolean, callback?: ErrorCallback): void; + function deleteObjectAsync(id: string, recursive?: boolean): Promise; + + function getEnums(enumName?: string): any; + + /** + * Creates a state and the corresponding object under the javascript namespace. + * @param name The name of the state without the namespace + * @param initValue (optional) Initial value of the state + * @param forceCreation (optional) Override the state if it already exists + * @param common (optional) Common part of the state object + * @param native (optional) Native part of the state object + * @param callback (optional) Called after the state was created + */ + function createState(name: string, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: iobJS.StateValue, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: iobJS.StateValue, forceCreation: boolean, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: iobJS.StateValue, forceCreation: boolean, common: Partial, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: iobJS.StateValue, forceCreation: boolean, common: Partial, native: any, callback?: iobJS.SetStateCallback): void; + function createState(name: string, common: Partial, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: iobJS.StateValue, common: Partial, callback?: iobJS.SetStateCallback): void; + function createState(name: string, common: Partial, native: any, callback?: iobJS.SetStateCallback): void; + function createState(name: string, initValue: iobJS.StateValue, common: Partial, native: any, callback?: iobJS.SetStateCallback): void; + + function createStateAsync(name: string, initValue?: iobJS.StateValue, forceCreation?: boolean, common?: Partial, native?: any): iobJS.SetStatePromise; + function createStateAsync(name: string, common: Partial): iobJS.SetStatePromise; + function createStateAsync(name: string, common: Partial, native?: any): iobJS.SetStatePromise; + function createStateAsync(name: string, initValue: iobJS.StateValue, common: Partial): iobJS.SetStatePromise; + function createStateAsync(name: string, initValue: iobJS.StateValue, common: Partial, native?: any): iobJS.SetStatePromise; + + function createAlias(name: string, alias: string | iobJS.StateCommonAlias, callback?: iobJS.SetStateCallback): void; + function createAlias(name: string, alias: string | iobJS.StateCommonAlias, forceCreation: boolean, callback?: iobJS.SetStateCallback): void; + function createAlias(name: string, alias: string | iobJS.StateCommonAlias, forceCreation: boolean, common: Partial, callback?: iobJS.SetStateCallback): void; + function createAlias(name: string, alias: string | iobJS.StateCommonAlias, forceCreation: boolean, common: Partial, native: any, callback?: iobJS.SetStateCallback): void; + function createAlias(name: string, alias: string | iobJS.StateCommonAlias, common: Partial, callback?: iobJS.SetStateCallback): void; + function createAlias(name: string, alias: string | iobJS.StateCommonAlias, common: Partial, native: any, callback?: iobJS.SetStateCallback): void; + + function createAliasAsync(name: string, alias: string | iobJS.StateCommonAlias, forceCreation?: boolean, common?: Partial, native?: any): iobJS.SetStatePromise; + function createAliasAsync(name: string, alias: string | iobJS.StateCommonAlias, common: Partial): iobJS.SetStatePromise; + function createAliasAsync(name: string, alias: string | iobJS.StateCommonAlias, common: Partial, native?: any): iobJS.SetStatePromise; + + + + /** + * Deletes the state with the given ID + * @param callback (optional) Is called after the state was deleted (or not). + */ + function deleteState(id: string, callback?: GenericCallback): void; + function deleteStateAsync(id: string): Promise; + + /** + * Sends a message to a specific instance or all instances of some specific adapter. + * @param instanceName The instance to send this message to. + * If the ID of an instance is given (e.g. "admin.0"), only this instance will receive the message. + * If the name of an adapter is given (e.g. "admin"), all instances of this adapter will receive it. + * @param command (optional) Command name of the target instance. Default: "send" + * @param message The message (e.g., params) to send. + */ + function sendTo(instanceName: string, command: string, message: string | object, callback?: iobJS.MessageCallback | iobJS.MessageCallbackInfo): void; + function sendTo(instanceName: string, message: string | object, callback?: iobJS.MessageCallback | iobJS.MessageCallbackInfo): void; + function sendToAsync(instanceName: string, message: string | object): Promise; + function sendToAsync(instanceName: string, command: string, message: string | object): Promise; + + /** + * Sends a message to a specific instance or all instances of some specific adapter. + * @param host Host name. + * @param command Command name for the target host. + * @param message The message (e.g., params) to send. + */ + function sendToHost(host: string, command: string, message: string | object, callback?: iobJS.MessageCallback | iobJS.MessageCallbackInfo): void; + function sendToHostAsync(host: string, command: string, message: string | object): Promise; + + type CompareTimeOperations = + "between" | "not between" | + ">" | ">=" | "<" | "<=" | "==" | "<>" + ; + + /** + * Compares two or more times + * @param timeToCompare - The time to compare with startTime and/or endTime. If none is given, the current time is used + */ + function compareTime( + startTime: string | number | Date | iobJS.AstroDate, + endTime: string | number | Date | iobJS.AstroDate, + operation: CompareTimeOperations, + timeToCompare?: string | number | Date | iobJS.AstroDate, + ): boolean; + + /** Sets up a callback which is called when the script stops */ + function onStop(callback: (cb?: EmptyCallback) => void, timeout?: number): void; + + function formatValue(value: number | string, format?: any): string; + function formatValue(value: number | string, decimals: number, format?: any): string; + function formatDate(dateObj: string | Date | number, format: string, language?: string): string; + function formatDate(dateObj: string | Date | number, isDuration: boolean | string, format: string, language?: string): string; + + function getDateObject(date: number | string | Date): Date; + + /** + * Writes a file. + * @param id Name of the root directory. This should be the adapter instance, e.g. "admin.0" + * @param name File name + * @param data Contents of the file + * @param callback Is called when the operation has finished (successfully or not) + */ + function writeFile(id: string, name: string, data: Buffer | string, callback: ErrorCallback): void; + function writeFileAsync(id: string, name: string, data: Buffer | string): Promise; + + /** + * Reads a file. + * @param id Name of the root directory. This should be the adapter instance, e.g. "admin.0" + * @param name File name + * @param callback Is called when the operation has finished (successfully or not) + */ + function readFile(id: string, name: string, callback: iobJS.ReadFileCallback): void; + function readFileAsync(id: string, name: string): iobJS.ReadFilePromise; + + /** + * Deletes a file. + * @param id Name of the root directory. This should be the adapter instance, e.g. "admin.0" + * @param name File name + * @param callback Is called when the operation has finished (successfully or not) + */ + function unlink(id: string, name: string, callback: ErrorCallback): void; + function unlinkAsync(id: string, name: string): Promise; + + /** + * Deletes a file. + * @param id Name of the root directory. This should be the adapter instance, e.g. "admin.0" + * @param name File name + * @param callback Is called when the operation has finished (successfully or not) + */ + function delFile(id: string, name: string, callback: ErrorCallback): void; + function delFileAsync(id: string, name: string): Promise; + + function getHistory(instance: any, options: any, callback: any): any; + function getHistoryAsync(instance: any, options: any): Promise; + + /** + * Starts or restarts a script by name + * @param scriptName (optional) Name of the script. If none is given, the current script is (re)started. + */ + function runScript(scriptName?: string, callback?: ErrorCallback): boolean; + function runScriptAsync(scriptName?: string): Promise; + + /** + * Starts or restarts a script by name + * @param scriptName (optional) Name of the script. If none is given, the current script is (re)started. + * @param ignoreIfStarted If set to true, running scripts will not be restarted. + * @param callback (optional) Is called when the script has finished (successfully or not) + */ + function startScript(scriptName: string | undefined, ignoreIfStarted: boolean, callback?: GenericCallback): boolean; + function startScriptAsync(scriptName?: string | undefined, ignoreIfStarted?: boolean): Promise; + + /** + * Starts or restarts a script by name + * @param scriptName (optional) Name of the script. If none is given, the current script is (re)started. + * @param callback (optional) Is called when the script has finished (successfully or not) + */ + function startScript(scriptName?: string, callback?: GenericCallback): boolean; + /** + * Stops a script by name + * @param scriptName (optional) Name of the script. If none is given, the current script is stopped. + */ + function stopScript(scriptName: string | undefined, callback?: GenericCallback): boolean; + function stopScriptAsync(scriptName?: string): Promise; + + function isScriptActive(scriptName: string): boolean; + + /** Converts a value to an integer */ + function toInt(val: any): number; + /** Converts a value to a floating point number */ + function toFloat(val: any): number; + /** Converts a value to a boolean */ + function toBoolean(val: any): boolean; + + /** + * Digs in an object for the property value at the given path. + * @param obj The object to dig in + * @param path The path of the property to dig for in the given object + */ + function getAttr(obj: string | Record, path: string | string[]): any; + + /** + * Sends a message to another script. + * @param target Message name or target object + * @param data Any data, that should be sent to message bus + * @param options Actually only {timeout: X} is supported as option + * @param callback Callback to get the result from other script + * @return ID of the subscription. It could be used for un-subscribe. + */ + function messageTo(target: iobJS.MessageTarget | string, data: any, options?: any, callback?: SimpleCallback): iobJS.MessageSubscribeID; + function messageToAsync(target: iobJS.MessageTarget | string, data: any, options?: any): Promise; + + /** + * Process message from another script. + * @param message Message name + * @param callback Callback to send the result to another script + */ + function onMessage(message: string, callback?: SimpleCallback); + + /** + * Unregister onmessage handler + * @param id Message subscription id from onMessage or by message name + * @return true if subscription exists and was deleted. + */ + function onMessageUnregister(id: iobJS.MessageSubscribeID | string): boolean; + + /** + * Receives logs of specified severity level in a script. + * @param severity Severity level + * @param callback Callback to send the result to another script + */ + function onLog(severity: iobJS.LogLevel | "*", callback: SimpleCallback); + + /** + * Unsubscribe log handler. + * @param idOrCallbackOrSeverity Message subscription id from onLog or by callback function + * @return true if subscription exists and was deleted. + */ + function onLogUnregister(idOrCallbackOrSeverity: iobJS.MessageSubscribeID | SimpleCallback | iobJS.LogLevel | "*"): boolean; + + /** `await` this method to pause for the given number of milliseconds */ + function wait(ms: number): Promise; + + /** `await` this method to pause for the given number of milliseconds */ + function sleep(ms: number): Promise; +} diff --git a/ioBroker/NsPanelTs.ts b/ioBroker/NsPanelTs.ts index c65e43e1..3a064ac4 100644 --- a/ioBroker/NsPanelTs.ts +++ b/ioBroker/NsPanelTs.ts @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------- -TypeScript v4.3.3.30 zur Steuerung des SONOFF NSPanel mit dem ioBroker by @Armilar / @TT-Tom / @ticaki / @Sternmiere / @Britzelpuf / @ravenS0ne +TypeScript v4.3.3.31 zur Steuerung des SONOFF NSPanel mit dem ioBroker by @Armilar / @TT-Tom / @ticaki / @Sternmiere / @Britzelpuf / @ravenS0ne - abgestimmt auf TFT 53 / v4.3.3 / BerryDriver 9 / Tasmota 13.3.0 @joBr99 Projekt: https://github.com/joBr99/nspanel-lovelace-ui/tree/main/ioBroker NsPanelTs.ts (dieses TypeScript in ioBroker) Stable: https://github.com/joBr99/nspanel-lovelace-ui/blob/main/ioBroker/NsPanelTs.ts @@ -91,6 +91,11 @@ ReleaseNotes: - 02.01.2024 - v4.3.3.29 Add Tasmota Buzzer for NotifyPage - 02.02.2024 - v4.3.3.29 Fix ThermoPage -> UnSubScribsWatcher - 02.02.2024 - v4.3.3.30 Add stronger config type checks + - 03.02.2024 - v4.3.3.31 Remove: autoCreateAlias from cardMedia + - 03.02.2024 - v4.3.3.31 Remove: adapterPlayerInstance from every card except cardMedia + - 03.02.2024 - v4.3.3.31 [dev]: optional with type - cardMedia has adapterPlayerInstance all other not + - 03.02.2024 - v4.3.3.31 [dev]: add PlayerType some more work to do + - 03.02.2024 - v4.3.3.31 changed: adapterPlayerInstance instance 0-9 allowed. Always require a '.' at the end. Todo: - XX.XX.XXXX - v5.0.0 Change the bottomScreensaverEntity (rolling) if more than 6 entries are defined @@ -953,7 +958,7 @@ export const config: Config = { // _________________________________ DE: Ab hier keine Konfiguration mehr _____________________________________ // _________________________________ EN: No more configuration from here _____________________________________ -const scriptVersion: string = 'v4.3.3.30'; +const scriptVersion: string = 'v4.3.3.31'; const tft_version: string = 'v4.3.3'; const desired_display_firmware_version = 53; const berry_driver_version = 9; @@ -3022,7 +3027,9 @@ function HandleMessage(typ: string, method: string, page: number | undefined, wo } let pageItem: PageItem = findPageItem(tempId); if (pageItem !== undefined) { - SendToPanel(GenerateDetailPage(words[2], tempPageItem[1], pageItem, placeId)); + let temp: string | mediaOptional | undefined = tempPageItem[1] + if (isMediaOptional(temp)) SendToPanel(GenerateDetailPage(words[2], temp, pageItem, placeId)); + else SendToPanel(GenerateDetailPage(words[2], undefined, pageItem, placeId)); } } break; @@ -4286,288 +4293,290 @@ function GenerateThermoPage(page: PageThermo): Payload[] { if ((i_list.length - 3) != 0) { let i = 0; + switch (o.common.role) { + case 'thermostat': { - if (o.common.role == 'thermostat') { - - if (existsState(id + '.AUTOMATIC') && getState(id + '.AUTOMATIC').val != null) { - if (getState(id + '.AUTOMATIC').val) { - bt[i++] = Icons.GetIcon('alpha-a-circle') + '~' + rgb_dec565(On) + '~1~' + 'AUTT' + '~'; - statusStr = 'AUTO'; - } else { - bt[i++] = Icons.GetIcon('alpha-a-circle') + '~33840~1~' + 'AUTT' + '~'; - } - } - if (existsState(id + '.MANUAL') && getState(id + '.MANUAL').val != null) { - if (getState(id + '.MANUAL').val) { - bt[i++] = Icons.GetIcon('alpha-m-circle') + '~' + rgb_dec565(On) + '~1~' + 'MANT' + '~'; - statusStr = 'MANU'; - } else { - bt[i++] = Icons.GetIcon('alpha-m-circle') + '~33840~1~' + 'MANT' + '~'; - } - } - if (existsState(id + '.PARTY') && getState(id + '.PARTY').val != null) { - if (getState(id + '.PARTY').val) { - bt[i++] = Icons.GetIcon('party-popper') + '~' + rgb_dec565(On) + '~1~' + 'PART' + '~'; - statusStr = 'PARTY'; - } else { - bt[i++] = Icons.GetIcon('party-popper') + '~33840~1~' + 'PART' + '~'; - } - } - if (existsState(id + '.VACATION') && getState(id + '.VACATION').val != null) { - if (getState(id + '.VACATION').val) { - bt[i++] = Icons.GetIcon('palm-tree') + '~' + rgb_dec565(On) + '~1~' + 'VACT' + '~'; - statusStr = 'VAC'; - } else { - bt[i++] = Icons.GetIcon('palm-tree') + '~33840~1~' + 'VACT' + '~'; - } - } - if (existsState(id + '.BOOST') && getState(id + '.BOOST').val != null) { - if (getState(id + '.BOOST').val) { - bt[i++] = Icons.GetIcon('fast-forward-60') + '~' + rgb_dec565(On) + '~1~' + 'BOOT' + '~'; - statusStr = 'BOOST'; - } else { - bt[i++] = Icons.GetIcon('fast-forward-60') + '~33840~1~' + 'BOOT' + '~'; - } - } - - for (let i_index in i_list) { - let thermostatState = i_list[i_index].split('.'); - if ( - thermostatState[thermostatState.length - 1] != 'SET' && - thermostatState[thermostatState.length - 1] != 'ACTUAL' && - thermostatState[thermostatState.length - 1] != 'MODE' - ) { - i++; - - switch (thermostatState[thermostatState.length - 1]) { - case 'HUMIDITY': - if (existsState(id + '.HUMIDITY') && getState(id + '.HUMIDITY').val != null) { - if (parseInt(getState(id + '.HUMIDITY').val) < 40) { - bt[i - 1] = Icons.GetIcon('water-percent') + '~65504~1~' + 'HUM' + '~'; - } else if (parseInt(getState(id + '.HUMIDITY').val) < 30) { - bt[i - 1] = Icons.GetIcon('water-percent') + '~63488~1~' + 'HUM' + '~'; - } else if (parseInt(getState(id + '.HUMIDITY').val) >= 40) { - bt[i - 1] = Icons.GetIcon('water-percent') + '~2016~1~' + 'HUM' + '~'; - } else if (parseInt(getState(id + '.HUMIDITY').val) > 65) { - bt[i - 1] = Icons.GetIcon('water-percent') + '~65504~1~' + 'HUM' + '~'; - } else if (parseInt(getState(id + '.HUMIDITY').val) > 75) { - bt[i - 1] = Icons.GetIcon('water-percent') + '~63488~1~' + 'HUM' + '~'; - } - } else i--; - break; - case 'LOWBAT': - if (existsState(id + '.LOWBAT') && getState(id + '.LOWBAT').val != null) { - if (getState(id + '.LOWBAT').val) { - bt[i - 1] = Icons.GetIcon('battery-low') + '~63488~1~' + 'LBAT' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('battery-high') + '~2016~1~' + 'LBAT' + '~'; - } - } else i--; - break; - case 'MAINTAIN': - if (existsState(id + '.MAINTAIN') && getState(id + '.MAINTAIN').val != null) { - if (getState(id + '.MAINTAIN').val >> .1) { - bt[i - 1] = Icons.GetIcon('account-wrench') + '~60897~1~' + 'MAIN' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('account-wrench') + '~33840~1~' + 'MAIN' + '~'; - } - } else i--; - break; - case 'UNREACH': - if (existsState(id + '.UNREACH') && getState(id + '.UNREACH').val != null) { - if (getState(id + '.UNREACH').val) { - bt[i - 1] = Icons.GetIcon('wifi-off') + '~63488~1~' + 'WLAN' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('wifi') + '~2016~1~' + 'WLAN' + '~'; - } - } else i--; - break; - case 'POWER': - if (existsState(id + '.POWER') && getState(id + '.POWER').val != null) { - if (getState(id + '.POWER').val) { - bt[i - 1] = Icons.GetIcon('power-standby') + '~2016~1~' + 'POWER' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('power-standby') + '~33840~1~' + 'POWER' + '~'; - } - } else i--; - break; - case 'ERROR': - if (existsState(id + '.ERROR') && getState(id + '.ERROR').val != null) { - if (getState(id + '.ERROR').val) { - bt[i - 1] = Icons.GetIcon('alert-circle') + '~63488~1~' + 'ERR' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('alert-circle') + '~33840~1~' + 'ERR' + '~'; - } - } else i--; - break; - case 'WORKING': - if (existsState(id + '.WORKING') && getState(id + '.WORKING').val != null) { - if (getState(id + '.WORKING').val) { - bt[i - 1] = Icons.GetIcon('briefcase-check') + '~2016~1~' + 'WORK' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('briefcase-check') + '~33840~1~' + 'WORK' + '~'; - } - } else i--; - break; - case 'WINDOWOPEN': - if (existsState(id + '.WINDOWOPEN') && getState(id + '.WINDOWOPEN').val != null) { - if (getState(id + '.WINDOWOPEN').val) { - bt[i - 1] = Icons.GetIcon('window-open-variant') + '~63488~1~' + 'WIN' + '~'; - } else { - bt[i - 1] = Icons.GetIcon('window-closed-variant') + '~2016~1~' + 'WIN' + '~'; - } - } else i--; - break; - default: - i--; - break; - } - } - } - - for (let j = i; j < 9; j++) { - bt[j] = '~~~~'; - } - } - - if (o.common.role == 'airCondition') { - if (existsState(id + '.MODE') && getState(id + '.MODE').val != null) { - let Mode = getState(id + '.MODE').val - let States = getObject(id + '.MODE').common.states; - - let iconIndex: number = 1; - for(const statekey in States) { - let stateName: string = States[statekey]; - let stateKeyNumber: number = parseInt(statekey); - if(stateName == 'OFF' || stateKeyNumber > 6) { - continue; - } - if(stateKeyNumber == Mode) { - statusStr = stateName.replace('_', ' '); - } - - switch(stateName) { - case 'AUTO': - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[1] !== '') { - tempIcon = page.items[0].iconArray[1]; - } else { - tempIcon = 'air-conditioner'; - } - if(stateKeyNumber == Mode) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~1024~1~' + 'AUTO' + '~'; - } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'AUTO' + '~'; - } - break; - case 'COOL': - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[2] !== '') { - tempIcon = page.items[0].iconArray[2]; - } else { - tempIcon = 'snowflake'; - } - if(stateKeyNumber == Mode) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~11487~1~' + 'COOL' + '~'; - } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'COOL' + '~'; - } - break; - case 'HEAT': - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[3] !== '') { - tempIcon = page.items[0].iconArray[3]; - } else { - tempIcon = 'fire'; - } - if(stateKeyNumber == Mode) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~64512~1~' + 'HEAT' + '~'; - } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'HEAT' + '~'; - } - break; - case 'ECO': - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[4] !== '') { - tempIcon = page.items[0].iconArray[4]; - } else { - tempIcon = 'alpha-e-circle-outline'; - } - if(stateKeyNumber == Mode) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'ECO' + '~'; - } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'ECO' + '~'; - } - break; - case 'FAN_ONLY': - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[5] !== '') { - tempIcon = page.items[0].iconArray[5]; - } else { - tempIcon = 'fan'; - } - if(stateKeyNumber == Mode) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~11487~1~' + 'FAN_ONLY' + '~'; - } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'FAN_ONLY' + '~'; - } - break; - case 'DRY': - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[6] !== '') { - tempIcon = page.items[0].iconArray[6]; - } else { - tempIcon = 'water-percent'; - } - if(stateKeyNumber == Mode) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~60897~1~' + 'DRY' + '~'; - } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'DRY' + '~'; - } - break; - } - iconIndex++; - } - - if (iconIndex <= 7 && existsState(id + '.ECO') && getState(id + '.ECO').val != null) { - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[4] !== '') { - tempIcon = page.items[0].iconArray[4]; + if (existsState(id + '.AUTOMATIC') && getState(id + '.AUTOMATIC').val != null) { + if (getState(id + '.AUTOMATIC').val) { + bt[i++] = Icons.GetIcon('alpha-a-circle') + '~' + rgb_dec565(On) + '~1~' + 'AUTT' + '~'; + statusStr = 'AUTO'; } else { - tempIcon = 'alpha-e-circle-outline'; + bt[i++] = Icons.GetIcon('alpha-a-circle') + '~33840~1~' + 'AUTT' + '~'; } - if (getState(id + '.ECO').val && getState(id + '.ECO').val == 1) { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'ECO' + '~'; - statusStr = 'ECO'; + } + if (existsState(id + '.MANUAL') && getState(id + '.MANUAL').val != null) { + if (getState(id + '.MANUAL').val) { + bt[i++] = Icons.GetIcon('alpha-m-circle') + '~' + rgb_dec565(On) + '~1~' + 'MANT' + '~'; + statusStr = 'MANU'; } else { - bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'ECO' + '~'; + bt[i++] = Icons.GetIcon('alpha-m-circle') + '~33840~1~' + 'MANT' + '~'; + } + } + if (existsState(id + '.PARTY') && getState(id + '.PARTY').val != null) { + if (getState(id + '.PARTY').val) { + bt[i++] = Icons.GetIcon('party-popper') + '~' + rgb_dec565(On) + '~1~' + 'PART' + '~'; + statusStr = 'PARTY'; + } else { + bt[i++] = Icons.GetIcon('party-popper') + '~33840~1~' + 'PART' + '~'; + } + } + if (existsState(id + '.VACATION') && getState(id + '.VACATION').val != null) { + if (getState(id + '.VACATION').val) { + bt[i++] = Icons.GetIcon('palm-tree') + '~' + rgb_dec565(On) + '~1~' + 'VACT' + '~'; + statusStr = 'VAC'; + } else { + bt[i++] = Icons.GetIcon('palm-tree') + '~33840~1~' + 'VACT' + '~'; + } + } + if (existsState(id + '.BOOST') && getState(id + '.BOOST').val != null) { + if (getState(id + '.BOOST').val) { + bt[i++] = Icons.GetIcon('fast-forward-60') + '~' + rgb_dec565(On) + '~1~' + 'BOOT' + '~'; + statusStr = 'BOOST'; + } else { + bt[i++] = Icons.GetIcon('fast-forward-60') + '~33840~1~' + 'BOOT' + '~'; } - iconIndex++; } - if (iconIndex <= 7 && existsState(id + '.SWING') && getState(id + '.SWING').val != null) { - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[7] !== '') { - tempIcon = page.items[0].iconArray[7]; - } else { - tempIcon = 'swap-vertical-bold'; + for (let i_index in i_list) { + let thermostatState = i_list[i_index].split('.'); + if ( + thermostatState[thermostatState.length - 1] != 'SET' && + thermostatState[thermostatState.length - 1] != 'ACTUAL' && + thermostatState[thermostatState.length - 1] != 'MODE' + ) { + i++; + + switch (thermostatState[thermostatState.length - 1]) { + case 'HUMIDITY': + if (existsState(id + '.HUMIDITY') && getState(id + '.HUMIDITY').val != null) { + if (parseInt(getState(id + '.HUMIDITY').val) < 40) { + bt[i - 1] = Icons.GetIcon('water-percent') + '~65504~1~' + 'HUM' + '~'; + } else if (parseInt(getState(id + '.HUMIDITY').val) < 30) { + bt[i - 1] = Icons.GetIcon('water-percent') + '~63488~1~' + 'HUM' + '~'; + } else if (parseInt(getState(id + '.HUMIDITY').val) >= 40) { + bt[i - 1] = Icons.GetIcon('water-percent') + '~2016~1~' + 'HUM' + '~'; + } else if (parseInt(getState(id + '.HUMIDITY').val) > 65) { + bt[i - 1] = Icons.GetIcon('water-percent') + '~65504~1~' + 'HUM' + '~'; + } else if (parseInt(getState(id + '.HUMIDITY').val) > 75) { + bt[i - 1] = Icons.GetIcon('water-percent') + '~63488~1~' + 'HUM' + '~'; + } + } else i--; + break; + case 'LOWBAT': + if (existsState(id + '.LOWBAT') && getState(id + '.LOWBAT').val != null) { + if (getState(id + '.LOWBAT').val) { + bt[i - 1] = Icons.GetIcon('battery-low') + '~63488~1~' + 'LBAT' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('battery-high') + '~2016~1~' + 'LBAT' + '~'; + } + } else i--; + break; + case 'MAINTAIN': + if (existsState(id + '.MAINTAIN') && getState(id + '.MAINTAIN').val != null) { + if (getState(id + '.MAINTAIN').val >> .1) { + bt[i - 1] = Icons.GetIcon('account-wrench') + '~60897~1~' + 'MAIN' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('account-wrench') + '~33840~1~' + 'MAIN' + '~'; + } + } else i--; + break; + case 'UNREACH': + if (existsState(id + '.UNREACH') && getState(id + '.UNREACH').val != null) { + if (getState(id + '.UNREACH').val) { + bt[i - 1] = Icons.GetIcon('wifi-off') + '~63488~1~' + 'WLAN' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('wifi') + '~2016~1~' + 'WLAN' + '~'; + } + } else i--; + break; + case 'POWER': + if (existsState(id + '.POWER') && getState(id + '.POWER').val != null) { + if (getState(id + '.POWER').val) { + bt[i - 1] = Icons.GetIcon('power-standby') + '~2016~1~' + 'POWER' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('power-standby') + '~33840~1~' + 'POWER' + '~'; + } + } else i--; + break; + case 'ERROR': + if (existsState(id + '.ERROR') && getState(id + '.ERROR').val != null) { + if (getState(id + '.ERROR').val) { + bt[i - 1] = Icons.GetIcon('alert-circle') + '~63488~1~' + 'ERR' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('alert-circle') + '~33840~1~' + 'ERR' + '~'; + } + } else i--; + break; + case 'WORKING': + if (existsState(id + '.WORKING') && getState(id + '.WORKING').val != null) { + if (getState(id + '.WORKING').val) { + bt[i - 1] = Icons.GetIcon('briefcase-check') + '~2016~1~' + 'WORK' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('briefcase-check') + '~33840~1~' + 'WORK' + '~'; + } + } else i--; + break; + case 'WINDOWOPEN': + if (existsState(id + '.WINDOWOPEN') && getState(id + '.WINDOWOPEN').val != null) { + if (getState(id + '.WINDOWOPEN').val) { + bt[i - 1] = Icons.GetIcon('window-open-variant') + '~63488~1~' + 'WIN' + '~'; + } else { + bt[i - 1] = Icons.GetIcon('window-closed-variant') + '~2016~1~' + 'WIN' + '~'; + } + } else i--; + break; + default: + i--; + break; + } } - if (getState(id + '.POWER').val && getState(id + '.SWING').val == 1) { //0=ON oder .SWING = true - bt[7] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'SWING' + '~'; - } else { - bt[7] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'SWING' + '~'; - } - iconIndex++; } - // Power Icon zuletzt pruefen, damit der Mode ggf. mit OFF ueberschrieben werden kann - if (existsState(id + '.POWER') && getState(id + '.POWER').val != null) { - if (page.items[0].iconArray !== undefined && page.items[0].iconArray[0] !== '') { - tempIcon = page.items[0].iconArray[0]; - } else { - tempIcon = 'power-standby'; + for (let j = i; j < 9; j++) { + bt[j] = '~~~~'; + } + } + break; + case 'airCondition': { + if (existsState(id + '.MODE') && getState(id + '.MODE').val != null) { + let Mode = getState(id + '.MODE').val + let States = getObject(id + '.MODE').common.states; + + let iconIndex: number = 1; + for (const statekey in States) { + let stateName: string = States[statekey]; + let stateKeyNumber: number = parseInt(statekey); + if (stateName == 'OFF' || stateKeyNumber > 6) { + continue; + } + if (stateKeyNumber == Mode) { + statusStr = stateName.replace('_', ' '); + } + + switch (stateName) { + case 'AUTO': + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[1] !== '') { + tempIcon = page.items[0].iconArray[1]; + } else { + tempIcon = 'air-conditioner'; + } + if (stateKeyNumber == Mode) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~1024~1~' + 'AUTO' + '~'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'AUTO' + '~'; + } + break; + case 'COOL': + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[2] !== '') { + tempIcon = page.items[0].iconArray[2]; + } else { + tempIcon = 'snowflake'; + } + if (stateKeyNumber == Mode) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~11487~1~' + 'COOL' + '~'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'COOL' + '~'; + } + break; + case 'HEAT': + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[3] !== '') { + tempIcon = page.items[0].iconArray[3]; + } else { + tempIcon = 'fire'; + } + if (stateKeyNumber == Mode) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~64512~1~' + 'HEAT' + '~'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'HEAT' + '~'; + } + break; + case 'ECO': + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[4] !== '') { + tempIcon = page.items[0].iconArray[4]; + } else { + tempIcon = 'alpha-e-circle-outline'; + } + if (stateKeyNumber == Mode) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'ECO' + '~'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'ECO' + '~'; + } + break; + case 'FAN_ONLY': + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[5] !== '') { + tempIcon = page.items[0].iconArray[5]; + } else { + tempIcon = 'fan'; + } + if (stateKeyNumber == Mode) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~11487~1~' + 'FAN_ONLY' + '~'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'FAN_ONLY' + '~'; + } + break; + case 'DRY': + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[6] !== '') { + tempIcon = page.items[0].iconArray[6]; + } else { + tempIcon = 'water-percent'; + } + if (stateKeyNumber == Mode) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~60897~1~' + 'DRY' + '~'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'DRY' + '~'; + } + break; + } + iconIndex++; } - if (States[Mode] == 'OFF' || !getState(id + '.POWER').val) { - bt[0] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'POWER' + '~'; - statusStr = 'OFF'; + + if (iconIndex <= 7 && existsState(id + '.ECO') && getState(id + '.ECO').val != null) { + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[4] !== '') { + tempIcon = page.items[0].iconArray[4]; + } else { + tempIcon = 'alpha-e-circle-outline'; + } + if (getState(id + '.ECO').val && getState(id + '.ECO').val == 1) { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'ECO' + '~'; + statusStr = 'ECO'; + } else { + bt[iconIndex] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'ECO' + '~'; + } + iconIndex++; } - else { - bt[0] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'POWER' + '~'; + + if (iconIndex <= 7 && existsState(id + '.SWING') && getState(id + '.SWING').val != null) { + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[7] !== '') { + tempIcon = page.items[0].iconArray[7]; + } else { + tempIcon = 'swap-vertical-bold'; + } + if (getState(id + '.POWER').val && getState(id + '.SWING').val == 1) { //0=ON oder .SWING = true + bt[7] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'SWING' + '~'; + } else { + bt[7] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'SWING' + '~'; + } + iconIndex++; + } + + // Power Icon zuletzt pruefen, damit der Mode ggf. mit OFF ueberschrieben werden kann + if (existsState(id + '.POWER') && getState(id + '.POWER').val != null) { + if (page.items[0].iconArray !== undefined && page.items[0].iconArray[0] !== '') { + tempIcon = page.items[0].iconArray[0]; + } else { + tempIcon = 'power-standby'; + } + if (States[Mode] == 'OFF' || !getState(id + '.POWER').val) { + bt[0] = Icons.GetIcon(tempIcon) + '~35921~0~' + 'POWER' + '~'; + statusStr = 'OFF'; + } + else { + bt[0] = Icons.GetIcon(tempIcon) + '~2016~1~' + 'POWER' + '~'; + } } } } + break; } } @@ -4703,153 +4712,250 @@ function subscribeMediaSubscriptionsAlexaAdd(id: string): void { }); } -async function createAutoMediaAlias(id: string, mediaDevice: string, adapterPlayerInstance: string) { +async function createAutoMediaAlias (id: string, mediaDevice: string, adapterPlayerInstance: adapterPlayerInstanceType) { if (autoCreateAlias) { if (isSetOptionActive) { - if (adapterPlayerInstance == 'alexa2.0.') { - if (existsObject(id) == false){ - log('Alexa Alias ' + id + ' does not exist - will be created now', 'info'); + switch (adapterPlayerInstance) { + case "alexa2.0.": + case "alexa2.1.": + case "alexa2.2.": + case "alexa2.3.": + case "alexa2.4.": + case "alexa2.5.": + case "alexa2.6.": + case "alexa2.7.": + case "alexa2.8.": + case "alexa2.9.": { + if (existsObject(id) == false) { + log('Alexa Alias ' + id + ' does not exist - will be created now', 'info'); - let dpPath: string = adapterPlayerInstance + 'Echo-Devices.' + mediaDevice; - try { - setObject(id, {_id: id, type: 'channel', common: {role: 'media', name:'media'}, native: {}}); - await createAliasAsync(id + '.ACTUAL', dpPath + '.Player.volume', true, { type: 'number', role: 'value.volume', name: 'ACTUAL' }); - await createAliasAsync(id + '.ALBUM', dpPath + '.Player.currentAlbum', true, { type: 'string', role: 'media.album', name: 'ALBUM' }); - await createAliasAsync(id + '.ARTIST', dpPath + '.Player.currentArtist', true, { type: 'string', role: 'media.artist', name: 'ARTIST' }); - await createAliasAsync(id + '.TITLE', dpPath + '.Player.currentTitle', true, { type: 'string', role: 'media.title', name: 'TITLE' }); - await createAliasAsync(id + '.NEXT', dpPath + '.Player.controlNext', true, { type: 'boolean', role: 'button.next', name: 'NEXT' }); - await createAliasAsync(id + '.PREV', dpPath + '.Player.controlPrevious', true, { type: 'boolean', role: 'button.prev', name: 'PREV' }); - await createAliasAsync(id + '.PLAY', dpPath + '.Player.controlPlay', true, { type: 'boolean', role: 'button.play', name: 'PLAY' }); - await createAliasAsync(id + '.PAUSE', dpPath + '.Player.controlPause', true, { type: 'boolean', role: 'button.pause', name: 'PAUSE' }); - await createAliasAsync(id + '.STOP', dpPath + '.Commands.deviceStop', true, { type: 'boolean', role: 'button.stop', name: 'STOP' }); - await createAliasAsync(id + '.STATE', dpPath + '.Player.currentState', true, { type: 'boolean', role: 'media.state', name: 'STATE' }); - await createAliasAsync(id + '.VOLUME', dpPath + '.Player.volume', true, { type: 'number', role: 'level.volume', name: 'VOLUME' }); - await createAliasAsync(id + '.REPEAT', dpPath + '.Player.controlRepeat', true, { type: 'boolean', role: 'media.mode.repeat', name: 'REPEAT' }); - await createAliasAsync(id + '.SHUFFLE', dpPath + '.Player.controlShuffle', true, { type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE' }); - } catch (err: any) { - log('error at function createAutoMediaAlias Adapter Alexa2: ' + err.message, 'warn'); + const dpPath: string = adapterPlayerInstance + 'Echo-Devices.' + mediaDevice; + try { + setObject(id, {_id: id, type: 'channel', common: {role: 'media', name: 'media'}, native: {}}); + await createAliasAsync(id + '.ACTUAL', dpPath + '.Player.volume', true, {type: 'number', role: 'value.volume', name: 'ACTUAL'}); + await createAliasAsync(id + '.ALBUM', dpPath + '.Player.currentAlbum', true, {type: 'string', role: 'media.album', name: 'ALBUM'}); + await createAliasAsync(id + '.ARTIST', dpPath + '.Player.currentArtist', true, {type: 'string', role: 'media.artist', name: 'ARTIST'}); + await createAliasAsync(id + '.TITLE', dpPath + '.Player.currentTitle', true, {type: 'string', role: 'media.title', name: 'TITLE'}); + await createAliasAsync(id + '.NEXT', dpPath + '.Player.controlNext', true, {type: 'boolean', role: 'button.next', name: 'NEXT'}); + await createAliasAsync(id + '.PREV', dpPath + '.Player.controlPrevious', true, {type: 'boolean', role: 'button.prev', name: 'PREV'}); + await createAliasAsync(id + '.PLAY', dpPath + '.Player.controlPlay', true, {type: 'boolean', role: 'button.play', name: 'PLAY'}); + await createAliasAsync(id + '.PAUSE', dpPath + '.Player.controlPause', true, {type: 'boolean', role: 'button.pause', name: 'PAUSE'}); + await createAliasAsync(id + '.STOP', dpPath + '.Commands.deviceStop', true, {type: 'boolean', role: 'button.stop', name: 'STOP'}); + await createAliasAsync(id + '.STATE', dpPath + '.Player.currentState', true, {type: 'boolean', role: 'media.state', name: 'STATE'}); + await createAliasAsync(id + '.VOLUME', dpPath + '.Player.volume', true, {type: 'number', role: 'level.volume', name: 'VOLUME'}); + await createAliasAsync(id + '.REPEAT', dpPath + '.Player.controlRepeat', true, {type: 'boolean', role: 'media.mode.repeat', name: 'REPEAT'}); + await createAliasAsync(id + '.SHUFFLE', dpPath + '.Player.controlShuffle', true, {type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE'}); + } catch (err: any) { + log('error at function createAutoMediaAlias Adapter Alexa2: ' + err.message, 'warn'); + } } - } - //Add Alexa Datapoints > v4.3.3.18 - if (existsObject(id + '.DURATION') == false) { - let dpPath: string = adapterPlayerInstance + 'Echo-Devices.' + mediaDevice; - await createAliasAsync(id + '.DURATION', dpPath + '.Player.mediaLength', true, { type: 'string', role: 'media.duration.text', name: 'DURATION' }); - await createAliasAsync(id + '.ELAPSED', dpPath + '.Player.mediaProgressStr', true, { type: 'string', role: 'media.elapsed.text', name: 'ELAPSED' }); - } - } - - if (adapterPlayerInstance == 'spotify-premium.0.') { - if (existsObject(id) == false){ - log('Spotify Alias ' + id + ' does not exist - will be created now', 'info'); - - let dpPath: string = adapterPlayerInstance; - try { - setObject(id, {_id: id + 'player', type: 'channel', common: {role: 'media', name:'media'}, native: {}}); - await createAliasAsync(id + '.ACTUAL', dpPath + 'player.volume', true, { type: 'number', role: 'value.volume', name: 'ACTUAL' }); - await createAliasAsync(id + '.ALBUM', dpPath + 'player.album', true, { type: 'string', role: 'media.album', name: 'ALBUM' }); - await createAliasAsync(id + '.ARTIST', dpPath + 'player.artistName', true, { type: 'string', role: 'media.artist', name: 'ARTIST' }); - await createAliasAsync(id + '.TITLE', dpPath + 'player.trackName', true, { type: 'string', role: 'media.title', name: 'TITLE' }); - await createAliasAsync(id + '.CONTEXT_DESCRIPTION', dpPath + 'player.contextDescription', true, { type: 'string', role: 'media.station', name: 'CONTEXT_DESCRIPTION' }); - await createAliasAsync(id + '.NEXT', dpPath + 'player.skipPlus', true, { type: 'boolean', role: 'button.next', name: 'NEXT' }); - await createAliasAsync(id + '.PREV', dpPath + 'player.skipMinus', true, { type: 'boolean', role: 'button.prev', name: 'PREV' }); - await createAliasAsync(id + '.PLAY', dpPath + 'player.play', true, { type: 'boolean', role: 'button.play', name: 'PLAY' }); - await createAliasAsync(id + '.PAUSE', dpPath + 'player.pause', true, { type: 'boolean', role: 'button.pause', name: 'PAUSE' }); - await createAliasAsync(id + '.STOP', dpPath + 'player.pause', true, { type: 'boolean', role: 'button.stop', name: 'STOP' }); - await createAliasAsync(id + '.STATE', dpPath + 'player.isPlaying', true, { type: 'boolean', role: 'media.state', name: 'STATE' }); - await createAliasAsync(id + '.VOLUME', dpPath + 'player.volume', true, { type: 'number', role: 'level.volume', name: 'VOLUME' }); - await createAliasAsync(id + '.REPEAT', dpPath + 'player.repeat', true, { type: 'string', role: 'value', name: 'REPEAT' }); - await createAliasAsync(id + '.SHUFFLE', dpPath + 'player.shuffle', true, { type: 'string', role: 'value', name: 'SHUFFLE' }); - - } catch (err: any) { - log('error at function createAutoMediaAlias Adapter spotify-premium: ' + err.message, 'warn'); + //Add Alexa Datapoints > v4.3.3.18 + if (existsObject(id + '.DURATION') == false) { + let dpPath: string = adapterPlayerInstance + 'Echo-Devices.' + mediaDevice; + await createAliasAsync(id + '.DURATION', dpPath + '.Player.mediaLength', true, {type: 'string', role: 'media.duration.text', name: 'DURATION'}); + await createAliasAsync(id + '.ELAPSED', dpPath + '.Player.mediaProgressStr', true, {type: 'string', role: 'media.elapsed.text', name: 'ELAPSED'}); } + } - } + break; + case "sonos.0.": + case "sonos.1.": + case "sonos.2.": + case "sonos.3.": + case "sonos.4.": + case "sonos.5.": + case "sonos.6.": + case "sonos.7.": + case "sonos.8.": + case "sonos.9.": { + if (existsObject(id) == false) { + log('Sonos Alias ' + id + ' does not exist - will be created now', 'info'); - if (adapterPlayerInstance == 'sonos.0.') { - if (existsObject(id) == false){ - log('Sonos Alias ' + id + ' does not exist - will be created now', 'info'); - - let dpPath: string = adapterPlayerInstance + 'root.' + mediaDevice; - try { - setObject(id, {_id: id, type: 'channel', common: {role: 'media', name:'media'}, native: {}}); - await createAliasAsync(id + '.ACTUAL', dpPath + '.volume', true, { type: 'number', role: 'value.volume', name: 'ACTUAL' }); - await createAliasAsync(id + '.ALBUM', dpPath + '.current_album', true, { type: 'string', role: 'media.album', name: 'ALBUM' }); - await createAliasAsync(id + '.ARTIST', dpPath + '.current_artist', true, { type: 'string', role: 'media.artist', name: 'ARTIST' }); - await createAliasAsync(id + '.TITLE', dpPath + '.current_title', true, { type: 'string', role: 'media.title', name: 'TITLE' }); - await createAliasAsync(id + '.CONTEXT_DESCRIPTION', dpPath + '.current_station', true, { type: 'string', role: 'media.station', name: 'CONTEXT_DESCRIPTION' }); - await createAliasAsync(id + '.NEXT', dpPath + '.next', true, { type: 'boolean', role: 'button.next', name: 'NEXT' }); - await createAliasAsync(id + '.PREV', dpPath + '.prev', true, { type: 'boolean', role: 'button.prev', name: 'PREV' }); - await createAliasAsync(id + '.PLAY', dpPath + '.play', true, { type: 'boolean', role: 'button.play', name: 'PLAY' }); - await createAliasAsync(id + '.PAUSE', dpPath + '.pause', true, { type: 'boolean', role: 'button.pause', name: 'PAUSE' }); - await createAliasAsync(id + '.STOP', dpPath + '.stop', true, { type: 'boolean', role: 'button.stop', name: 'STOP' }); - await createAliasAsync(id + '.STATE', dpPath + '.state_simple', true, { type: 'boolean', role: 'media.state', name: 'STATE' }); - await createAliasAsync(id + '.VOLUME', dpPath + '.volume', true, { type: 'number', role: 'level.volume', name: 'VOLUME' }); - await createAliasAsync(id + '.REPEAT', dpPath + '.repeat', true, { type: 'number', role: 'media.mode.repeat', name: 'REPEAT' }); - await createAliasAsync(id + '.SHUFFLE', dpPath + '.shuffle', true, { type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE' }); - } catch (err: any) { - log('error at function createAutoMediaAlias Adapter sonos: ' + err.message, 'warn'); + const dpPath: string = adapterPlayerInstance + 'root.' + mediaDevice; + try { + setObject(id, {_id: id, type: 'channel', common: {role: 'media', name: 'media'}, native: {}}); + await createAliasAsync(id + '.ACTUAL', dpPath + '.volume', true, {type: 'number', role: 'value.volume', name: 'ACTUAL'}); + await createAliasAsync(id + '.ALBUM', dpPath + '.current_album', true, {type: 'string', role: 'media.album', name: 'ALBUM'}); + await createAliasAsync(id + '.ARTIST', dpPath + '.current_artist', true, {type: 'string', role: 'media.artist', name: 'ARTIST'}); + await createAliasAsync(id + '.TITLE', dpPath + '.current_title', true, {type: 'string', role: 'media.title', name: 'TITLE'}); + await createAliasAsync(id + '.CONTEXT_DESCRIPTION', dpPath + '.current_station', true, {type: 'string', role: 'media.station', name: 'CONTEXT_DESCRIPTION'}); + await createAliasAsync(id + '.NEXT', dpPath + '.next', true, {type: 'boolean', role: 'button.next', name: 'NEXT'}); + await createAliasAsync(id + '.PREV', dpPath + '.prev', true, {type: 'boolean', role: 'button.prev', name: 'PREV'}); + await createAliasAsync(id + '.PLAY', dpPath + '.play', true, {type: 'boolean', role: 'button.play', name: 'PLAY'}); + await createAliasAsync(id + '.PAUSE', dpPath + '.pause', true, {type: 'boolean', role: 'button.pause', name: 'PAUSE'}); + await createAliasAsync(id + '.STOP', dpPath + '.stop', true, {type: 'boolean', role: 'button.stop', name: 'STOP'}); + await createAliasAsync(id + '.STATE', dpPath + '.state_simple', true, {type: 'boolean', role: 'media.state', name: 'STATE'}); + await createAliasAsync(id + '.VOLUME', dpPath + '.volume', true, {type: 'number', role: 'level.volume', name: 'VOLUME'}); + await createAliasAsync(id + '.REPEAT', dpPath + '.repeat', true, {type: 'number', role: 'media.mode.repeat', name: 'REPEAT'}); + await createAliasAsync(id + '.SHUFFLE', dpPath + '.shuffle', true, {type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE'}); + } catch (err: any) { + log('error at function createAutoMediaAlias Adapter sonos: ' + err.message, 'warn'); + } } - } - //Add Sonos Datapoints > v4.3.3.15 - if (existsObject(id + '.QUEUE') == false) { - let dpPath: string = adapterPlayerInstance + 'root.' + mediaDevice; - await createAliasAsync(id + '.QUEUE', dpPath + '.queue', true, { type: 'string', role: 'state', name: 'QUEUE' }); - await createAliasAsync(id + '.DURATION', dpPath + '.current_duration_s', true, { type: 'string', role: 'media.duration.text', name: 'DURATION' }); - await createAliasAsync(id + '.ELAPSED', dpPath + '.current_elapsed_s', true, { type: 'string', role: 'media.elapsed.text', name: 'ELAPSED' }); - } - } - - if (adapterPlayerInstance.startsWith('volumio')) { - if (existsObject(id) == false){ - log('Volumio Alias ' + id + ' does not exist - will be created now', 'info'); - - let dpPath: string = adapterPlayerInstance; - try { - setObject(id, {_id: id, type: 'channel', common: {role: 'media', name:'media'}, native: {}}); - await createAliasAsync(id + '.ACTUAL', dpPath + 'playbackInfo.volume', true, { type: 'number', role: 'value.volume', name: 'ACTUAL' }); - await createAliasAsync(id + '.ALBUM', dpPath + 'playbackInfo.album', true, { type: 'string', role: 'media.album', name: 'ALBUM' }); - await createAliasAsync(id + '.ARTIST', dpPath + 'playbackInfo.artist', true, { type: 'string', role: 'media.artist', name: 'ARTIST' }); - await createAliasAsync(id + '.TITLE', dpPath + 'playbackInfo.title', true, { type: 'string', role: 'media.title', name: 'TITLE' }); - await createAliasAsync(id + '.NEXT', dpPath + 'player.next', true, { type: 'boolean', role: 'button.next', name: 'NEXT' }); - await createAliasAsync(id + '.PREV', dpPath + 'player.prev', true, { type: 'boolean', role: 'button.prev', name: 'PREV' }); - await createAliasAsync(id + '.PLAY', dpPath + 'player.play', true, { type: 'boolean', role: 'button.play', name: 'PLAY' }); - await createAliasAsync(id + '.PAUSE', dpPath + 'player.toggle', true, { type: 'boolean', role: 'button.pause', name: 'PAUSE' }); - await createAliasAsync(id + '.STOP', dpPath + 'player.stop', true, { type: 'boolean', role: 'button.stop', name: 'STOP' }); - await createAliasAsync(id + '.STATE', dpPath + 'playbackInfo.status', true, { type: 'boolean', role: 'media.state', name: 'STATE' }); - await createAliasAsync(id + '.VOLUME', dpPath + 'playbackInfo.volume', true, { type: 'number', role: 'level.volume', name: 'VOLUME' }); - await createAliasAsync(id + '.REPEAT', dpPath + 'playbackInfo.repeat', true, { type: 'number', role: 'media.mode.repeat', name: 'REPEAT' }); - await createAliasAsync(id + '.SHUFFLE', dpPath + 'queue.shuffle', true, { type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE' }); - await createAliasAsync(id + '.status', dpPath + 'playbackInfo.status', true, { type: 'string', role: 'media.state', name: 'status' }); - } catch (err: any) { - log('error function createAutoMediaAlias Adapter volumio: ' + err.message, 'warn'); + //Add Sonos Datapoints > v4.3.3.15 + if (existsObject(id + '.QUEUE') == false) { + let dpPath: string = adapterPlayerInstance + 'root.' + mediaDevice; + await createAliasAsync(id + '.QUEUE', dpPath + '.queue', true, {type: 'string', role: 'state', name: 'QUEUE'}); + await createAliasAsync(id + '.DURATION', dpPath + '.current_duration_s', true, {type: 'string', role: 'media.duration.text', name: 'DURATION'}); + await createAliasAsync(id + '.ELAPSED', dpPath + '.current_elapsed_s', true, {type: 'string', role: 'media.elapsed.text', name: 'ELAPSED'}); } + } - } + break; + case "spotify-premium.0.": + case "spotify-premium.1.": + case "spotify-premium.2.": + case "spotify-premium.3.": + case "spotify-premium.4.": + case "spotify-premium.5.": + case "spotify-premium.6.": + case "spotify-premium.7.": + case "spotify-premium.8.": + case "spotify-premium.9.": { + if (existsObject(id) == false) { + log('Spotify Alias ' + id + ' does not exist - will be created now', 'info'); - if (adapterPlayerInstance.startsWith('squeezeboxrpc')) { - if (existsObject(id) == false){ - log('Squeezebox Alias ' + id + ' does not exist - will be created now', 'info'); + const dpPath: string = adapterPlayerInstance; + try { + setObject(id, {_id: id + 'player', type: 'channel', common: {role: 'media', name: 'media'}, native: {}}); + await createAliasAsync(id + '.ACTUAL', dpPath + 'player.volume', true, {type: 'number', role: 'value.volume', name: 'ACTUAL'}); + await createAliasAsync(id + '.ALBUM', dpPath + 'player.album', true, {type: 'string', role: 'media.album', name: 'ALBUM'}); + await createAliasAsync(id + '.ARTIST', dpPath + 'player.artistName', true, {type: 'string', role: 'media.artist', name: 'ARTIST'}); + await createAliasAsync(id + '.TITLE', dpPath + 'player.trackName', true, {type: 'string', role: 'media.title', name: 'TITLE'}); + await createAliasAsync(id + '.CONTEXT_DESCRIPTION', dpPath + 'player.contextDescription', true, {type: 'string', role: 'media.station', name: 'CONTEXT_DESCRIPTION'}); + await createAliasAsync(id + '.NEXT', dpPath + 'player.skipPlus', true, {type: 'boolean', role: 'button.next', name: 'NEXT'}); + await createAliasAsync(id + '.PREV', dpPath + 'player.skipMinus', true, {type: 'boolean', role: 'button.prev', name: 'PREV'}); + await createAliasAsync(id + '.PLAY', dpPath + 'player.play', true, {type: 'boolean', role: 'button.play', name: 'PLAY'}); + await createAliasAsync(id + '.PAUSE', dpPath + 'player.pause', true, {type: 'boolean', role: 'button.pause', name: 'PAUSE'}); + await createAliasAsync(id + '.STOP', dpPath + 'player.pause', true, {type: 'boolean', role: 'button.stop', name: 'STOP'}); + await createAliasAsync(id + '.STATE', dpPath + 'player.isPlaying', true, {type: 'boolean', role: 'media.state', name: 'STATE'}); + await createAliasAsync(id + '.VOLUME', dpPath + 'player.volume', true, {type: 'number', role: 'level.volume', name: 'VOLUME'}); + await createAliasAsync(id + '.REPEAT', dpPath + 'player.repeat', true, {type: 'string', role: 'value', name: 'REPEAT'}); + await createAliasAsync(id + '.SHUFFLE', dpPath + 'player.shuffle', true, {type: 'string', role: 'value', name: 'SHUFFLE'}); - let dpPath: string = adapterPlayerInstance + '.Players.' + mediaDevice; - try { - setObject(id, {_id: id, type: 'channel', common: {role: 'media', name:'media'}, native: {}}); - await createAliasAsync(id + '.ALBUM', dpPath + '.Album', true, { type: 'string', role: 'media.album', name: 'ALBUM'}); - await createAliasAsync(id + '.ARTIST', dpPath + '.Artist', true, { type: 'string', role: 'media.artist', name: 'ARTIST'}); - await createAliasAsync(id + '.TITLE', dpPath + '.Title', true, { type: 'string', role: 'media.title', name: 'TITLE'}); - await createAliasAsync(id + '.NEXT', dpPath + '.btnForward', true, { type: 'boolean', role: 'button.forward', name: 'NEXT'}); - await createAliasAsync(id + '.PREV', dpPath + '.btnRewind', true, { type: 'boolean', role: 'button.reverse', name: 'PREV'}); - await createAliasAsync(id + '.PLAY', dpPath + '.state', true, { type: 'boolean', role: 'media.state', name: 'PLAY', alias: { id: dpPath + '.state', read: 'val === 1 ? true : false' }}); - await createAliasAsync(id + '.PAUSE', dpPath + '.state', true, { type: 'boolean', role: 'media.state', name: 'PAUSE', alias: { id: dpPath + '.state', read: 'val === 0 ? true : false'}}); - await createAliasAsync(id + '.STOP', dpPath + '.state', true, { type: 'boolean', role: 'media.state', name: 'STOP', alias: { id: dpPath + '.state', read: 'val === 0 ? true : false'}}); - await createAliasAsync(id + '.STATE', dpPath + '.Power', true, { type: 'number', role: 'switch', name: 'STATE'}); - await createAliasAsync(id + '.VOLUME', dpPath + '.Volume', true, { type: 'number', role: 'level.volume', name: 'VOLUME'}); - await createAliasAsync(id + '.VOLUME_ACTUAL', dpPath + '.Volume', true, { type: 'number', role: 'value.volume', name: 'VOLUME_ACTUAL'}); - await createAliasAsync(id + '.SHUFFLE', dpPath + '.PlaylistShuffle', true, { type: 'string', role: 'media.mode.shuffle', name: 'SHUFFLE', alias: { id: dpPath + '.PlaylistShuffle', read: 'val !== 0 ? \'on\' : \'off\'', write: 'val === \'off\' ? 0 : 1' }}); - await createAliasAsync(id + '.REPEAT', dpPath + '.PlaylistRepeat', true, {type: 'number', role: 'media.mode.repeat', name: 'REPEAT'}); - } catch (err: any) { - log('error at function createAutoMediaAlias Adapter Squeezebox: ' + err.message, 'warn'); + } catch (err: any) { + log('error at function createAutoMediaAlias Adapter spotify-premium: ' + err.message, 'warn'); + } } + + } + break; + case "volumio.0.": + case "volumio.1.": + case "volumio.2.": + case "volumio.3.": + case "volumio.4.": + case "volumio.5.": + case "volumio.6.": + case "volumio.7.": + case "volumio.8.": + case "volumio.9.": { + if (existsObject(id) == false) { + log('Volumio Alias ' + id + ' does not exist - will be created now', 'info'); + + const dpPath: string = adapterPlayerInstance; + try { + setObject(id, {_id: id, type: 'channel', common: {role: 'media', name: 'media'}, native: {}}); + await createAliasAsync(id + '.ACTUAL', dpPath + 'playbackInfo.volume', true, {type: 'number', role: 'value.volume', name: 'ACTUAL'}); + await createAliasAsync(id + '.ALBUM', dpPath + 'playbackInfo.album', true, {type: 'string', role: 'media.album', name: 'ALBUM'}); + await createAliasAsync(id + '.ARTIST', dpPath + 'playbackInfo.artist', true, {type: 'string', role: 'media.artist', name: 'ARTIST'}); + await createAliasAsync(id + '.TITLE', dpPath + 'playbackInfo.title', true, {type: 'string', role: 'media.title', name: 'TITLE'}); + await createAliasAsync(id + '.NEXT', dpPath + 'player.next', true, {type: 'boolean', role: 'button.next', name: 'NEXT'}); + await createAliasAsync(id + '.PREV', dpPath + 'player.prev', true, {type: 'boolean', role: 'button.prev', name: 'PREV'}); + await createAliasAsync(id + '.PLAY', dpPath + 'player.play', true, {type: 'boolean', role: 'button.play', name: 'PLAY'}); + await createAliasAsync(id + '.PAUSE', dpPath + 'player.toggle', true, {type: 'boolean', role: 'button.pause', name: 'PAUSE'}); + await createAliasAsync(id + '.STOP', dpPath + 'player.stop', true, {type: 'boolean', role: 'button.stop', name: 'STOP'}); + await createAliasAsync(id + '.STATE', dpPath + 'playbackInfo.status', true, {type: 'boolean', role: 'media.state', name: 'STATE'}); + await createAliasAsync(id + '.VOLUME', dpPath + 'playbackInfo.volume', true, {type: 'number', role: 'level.volume', name: 'VOLUME'}); + await createAliasAsync(id + '.REPEAT', dpPath + 'playbackInfo.repeat', true, {type: 'number', role: 'media.mode.repeat', name: 'REPEAT'}); + await createAliasAsync(id + '.SHUFFLE', dpPath + 'queue.shuffle', true, {type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE'}); + await createAliasAsync(id + '.status', dpPath + 'playbackInfo.status', true, {type: 'string', role: 'media.state', name: 'status'}); + } catch (err: any) { + log('error function createAutoMediaAlias Adapter volumio: ' + err.message, 'warn'); + } + } + + } + break; + case "squeezeboxrpc.0.": + case "squeezeboxrpc.1.": + case "squeezeboxrpc.2.": + case "squeezeboxrpc.3.": + case "squeezeboxrpc.4.": + case "squeezeboxrpc.5.": + case "squeezeboxrpc.6.": + case "squeezeboxrpc.7.": + case "squeezeboxrpc.8.": + case "squeezeboxrpc.9.": { + if (existsObject(id) == false) { + log('Squeezebox Alias ' + id + ' does not exist - will be created now', 'info'); + + const dpPath: string = adapterPlayerInstance + 'Players.' + mediaDevice; + try { + setObject(id, {_id: id, type: 'channel', common: {role: 'media', name: 'media'}, native: {}}); + await createAliasAsync(id + '.ALBUM', dpPath + '.Album', true, {type: 'string', role: 'media.album', name: 'ALBUM'}); + await createAliasAsync(id + '.ARTIST', dpPath + '.Artist', true, {type: 'string', role: 'media.artist', name: 'ARTIST'}); + await createAliasAsync(id + '.TITLE', dpPath + '.Title', true, {type: 'string', role: 'media.title', name: 'TITLE'}); + await createAliasAsync(id + '.NEXT', dpPath + '.btnForward', true, {type: 'boolean', role: 'button.forward', name: 'NEXT'}); + await createAliasAsync(id + '.PREV', dpPath + '.btnRewind', true, {type: 'boolean', role: 'button.reverse', name: 'PREV'}); + await createAliasAsync(id + '.PLAY', dpPath + '.state', true, {type: 'boolean', role: 'media.state', name: 'PLAY', alias: {id: dpPath + '.state', read: 'val === 1 ? true : false'}}); + await createAliasAsync(id + '.PAUSE', dpPath + '.state', true, {type: 'boolean', role: 'media.state', name: 'PAUSE', alias: {id: dpPath + '.state', read: 'val === 0 ? true : false'}}); + await createAliasAsync(id + '.STOP', dpPath + '.state', true, {type: 'boolean', role: 'media.state', name: 'STOP', alias: {id: dpPath + '.state', read: 'val === 0 ? true : false'}}); + await createAliasAsync(id + '.STATE', dpPath + '.Power', true, {type: 'number', role: 'switch', name: 'STATE'}); + await createAliasAsync(id + '.VOLUME', dpPath + '.Volume', true, {type: 'number', role: 'level.volume', name: 'VOLUME'}); + await createAliasAsync(id + '.VOLUME_ACTUAL', dpPath + '.Volume', true, {type: 'number', role: 'value.volume', name: 'VOLUME_ACTUAL'}); + await createAliasAsync(id + '.SHUFFLE', dpPath + '.PlaylistShuffle', true, {type: 'string', role: 'media.mode.shuffle', name: 'SHUFFLE', alias: {id: dpPath + '.PlaylistShuffle', read: 'val !== 0 ? \'on\' : \'off\'', write: 'val === \'off\' ? 0 : 1'}}); + await createAliasAsync(id + '.REPEAT', dpPath + '.PlaylistRepeat', true, {type: 'number', role: 'media.mode.repeat', name: 'REPEAT'}); + } catch (err: any) { + log('error at function createAutoMediaAlias Adapter Squeezebox: ' + err.message, 'warn'); + } + } + + } + break; + case "bosesoundtouch.0.": + case "bosesoundtouch.1.": + case "bosesoundtouch.2.": + case "bosesoundtouch.3.": + case "bosesoundtouch.4.": + case "bosesoundtouch.5.": + case "bosesoundtouch.6.": + case "bosesoundtouch.7.": + case "bosesoundtouch.8.": + case "bosesoundtouch.9.": { + if (existsObject(id) == false) { + log('bosesoundtouch Alias ' + id + ' does not exist - will be created now', 'info'); + + try { + let dpPath: string = adapterPlayerInstance + 'keys'; + await extendObjectAsync(id, {_id: id, type: 'channel', common: {role: 'media', name: 'media'}, native: {}}); + await createAliasAsync(id + '.ACTUAL', dpPath + '.volume', true, {type: 'number', role: 'value.volume', name: 'ACTUAL'}); + await createAliasAsync(id + '.VOLUME', dpPath + '.volume', true, {type: 'number', role: 'level.volume', name: 'VOLUME'}); + + dpPath = adapterPlayerInstance + 'nowPlaying'; + await createAliasAsync(id + '.ALBUM', dpPath + '.album', true, {type: 'string', role: 'media.album', name: 'ALBUM'}); + await createAliasAsync(id + '.ARTIST', dpPath + '.artist', true, {type: 'string', role: 'media.artist', name: 'ARTIST'}); + await createAliasAsync(id + '.TITLE', dpPath + '.track', true, {type: 'string', role: 'media.title', name: 'TITLE'}); + await createAliasAsync(id + '.DURATION', dpPath + '.total', true, {type: 'string', role: 'media.duration.text', name: 'DURATION'}); + await createAliasAsync(id + '.ELAPSED', dpPath + '.time', true, {type: 'string', role: 'media.elapsed.text', name: 'ELAPSED'}); + await createAliasAsync(id + '.REPEAT', dpPath + '.repeat', true, {type: 'boolean', role: 'media.mode.repeat', name: 'REPEAT'}); + await createAliasAsync(id + '.SHUFFLE', dpPath + '.shuffle', true, {type: 'boolean', role: 'media.mode.shuffle', name: 'SHUFFLE'}); + + dpPath = adapterPlayerInstance + 'keys'; + await createAliasAsync(id + '.STATE', dpPath + '.POWER', true, {type: 'boolean', role: 'media.state', name: 'STATE'}); + await createAliasAsync(id + '.NEXT', dpPath + '.NEXT_TRACK', true, {type: 'boolean', role: 'button.next', name: 'NEXT'}); + await createAliasAsync(id + '.PREV', dpPath + '.PREV_TRACK', true, {type: 'boolean', role: 'button.prev', name: 'PREV'}); + await createAliasAsync(id + '.PLAY', dpPath + '.PLAY', true, {type: 'boolean', role: 'button.play', name: 'PLAY'}); + await createAliasAsync(id + '.PAUSE', dpPath + '.PAUSE', true, {type: 'boolean', role: 'button.pause', name: 'PAUSE'}); + await createAliasAsync(id + '.STOP', dpPath + '.STOP', true, {type: 'boolean', role: 'button.stop', name: 'STOP'}); + } catch (err: any) { + log('error at function createAutoMediaAlias Adapter bosesoundtouch: ' + err.message, 'warn'); + } + } + break; + } + default: { + log(`Dont find adapterPlayerInstance: ${adapterPlayerInstance}!`, 'warn') } } } @@ -4868,7 +4974,7 @@ function GenerateMediaPage(page: PageMedia): Payload[] { if (!page.items[0].adapterPlayerInstance!) throw new Error('page.items[0].adapterPlayerInstance is undefined!') let vInstance = page.items[0].adapterPlayerInstance!; let v1Adapter = vInstance.split('.'); - let v2Adapter = v1Adapter[0]; + let v2Adapter:PlayerType = v1Adapter[0] as PlayerType; // Some magic to change the ID of the alias, since speakers are not a property but separate objects if(v2Adapter == 'squeezeboxrpc') { @@ -4887,11 +4993,10 @@ function GenerateMediaPage(page: PageMedia): Payload[] { } } - if (page.items[0].autoCreateALias) { - let vMediaDevice = (page.items[0].mediaDevice != undefined) ? page.items[0].mediaDevice : ''; - createAutoMediaAlias(id, vMediaDevice, page.items[0].adapterPlayerInstance!); - } - + let vMediaDevice = (page.items[0].mediaDevice != undefined) ? page.items[0].mediaDevice : ''; + if (!vMediaDevice) throw new Error(`Error in cardMedia! mediaDevice is empty! Page: ${JSON.stringify(page)}`); + createAutoMediaAlias(id, vMediaDevice, page.items[0].adapterPlayerInstance!); + // Leave the display on if the alwaysOnDisplay parameter is specified (true) if (page.type == 'cardMedia' && pageCounter == 0 && page.items[0].alwaysOnDisplay != undefined) { out_msgs.push({ payload: 'pageType~cardMedia' }); @@ -6253,83 +6358,90 @@ function HandleButtonEvent(words: any): void { case 'hue': toggleState(id + '.ON_ACTUAL'); case 'media': + if (!activePage || activePage.type != 'cardMedia') { + if (activePage) throw new Error(`Found channel role media for card: ${activePage.type} not allowed`) + else throw new Error(`Something went wrong! Active Page is empty!`); + } if (tempid[1] == undefined) { if (Debug) log('Logo click', 'info'); GeneratePage(activePage!); } else if (tempid[1] == 'repeat') { + let pageItemRepeat = findPageItem(id); - let adapterInstanceRepeat = pageItemRepeat.adapterPlayerInstance!; - let adapterRepeat = adapterInstanceRepeat.split('.'); - let deviceAdapterRP = adapterRepeat[0]; - - switch (deviceAdapterRP) { - case 'spotify-premium': - let stateSpotifyRepeat = getState(id + '.REPEAT').val - if (stateSpotifyRepeat == 'none') { - setIfExists(id + '.REPEAT', 'all'); - } else if (stateSpotifyRepeat == 'all') { - setIfExists(id + '.REPEAT', 'one'); - } else if (stateSpotifyRepeat == 'one') { - setIfExists(id + '.REPEAT', 'none'); - } - GeneratePage(activePage!); - break; - case 'sonos': - let stateSonosRepeat = getState(id + '.REPEAT').val - if (stateSonosRepeat == 0) { - setIfExists(id + '.REPEAT', 1); - } else if (stateSonosRepeat == 1) { - setIfExists(id + '.REPEAT', 2); - } else if (stateSonosRepeat == 2) { - setIfExists(id + '.REPEAT', 0); - } - GeneratePage(activePage!); - break; - case 'alexa2': - try { - setIfExists(id + '.REPEAT', !getState(id + '.REPEAT').val); - } catch (err: any) { - log('ALEXA2: Repeat kann nicht verändert werden', 'warn'); - } - GeneratePage(activePage!); - break; - case 'volumio': - let urlString: string = `${getState(adapterInstanceRepeat+'info.host').val}/api/commands/?cmd=repeat`; - axios.get(urlString, { headers: { 'User-Agent': 'ioBroker' } }) - .then(async function (response) { - if (response.status === 200) { - if (Debug) { - log(response.data, 'info'); - } - GeneratePage(activePage!); - } else { - log('Axios Status - adapterInstanceRepeat: ' + response.state, 'warn'); - } - }) - .catch(function (error) { - log(error, 'warn'); - }); - break; - case 'squeezeboxrpc': - try { - switch(getState(id + '.REPEAT').val) { - case 0: - setIfExists(id + '.REPEAT', 1); - GeneratePage(activePage!); - break; - case 1: - setIfExists(id + '.REPEAT', 2) - GeneratePage(activePage!); - break; - case 2: - setIfExists(id + '.REPEAT', 0); - GeneratePage(activePage!); - break; + if (isPageMediaItem(pageItemRepeat)) { + let adapterInstanceRepeat = pageItemRepeat.adapterPlayerInstance; + let adapterRepeat = adapterInstanceRepeat.split('.'); + const deviceAdapterRP: PlayerType = adapterRepeat[0] as PlayerType; + + switch (deviceAdapterRP) { + case 'spotify-premium': + let stateSpotifyRepeat = getState(id + '.REPEAT').val + if (stateSpotifyRepeat == 'none') { + setIfExists(id + '.REPEAT', 'all'); + } else if (stateSpotifyRepeat == 'all') { + setIfExists(id + '.REPEAT', 'one'); + } else if (stateSpotifyRepeat == 'one') { + setIfExists(id + '.REPEAT', 'none'); } - } catch (err: any) { - log('Squeezebox: Repeat kann nicht verändert werden', 'warn'); - } - break; + GeneratePage(activePage!); + break; + case 'sonos': + let stateSonosRepeat = getState(id + '.REPEAT').val + if (stateSonosRepeat == 0) { + setIfExists(id + '.REPEAT', 1); + } else if (stateSonosRepeat == 1) { + setIfExists(id + '.REPEAT', 2); + } else if (stateSonosRepeat == 2) { + setIfExists(id + '.REPEAT', 0); + } + GeneratePage(activePage!); + break; + case 'alexa2': + try { + setIfExists(id + '.REPEAT', !getState(id + '.REPEAT').val); + } catch (err: any) { + log('ALEXA2: Repeat kann nicht verändert werden', 'warn'); + } + GeneratePage(activePage!); + break; + case 'volumio': + let urlString: string = `${getState(adapterInstanceRepeat + 'info.host').val}/api/commands/?cmd=repeat`; + axios.get(urlString, {headers: {'User-Agent': 'ioBroker'}}) + .then(async function (response) { + if (response.status === 200) { + if (Debug) { + log(response.data, 'info'); + } + GeneratePage(activePage!); + } else { + log('Axios Status - adapterInstanceRepeat: ' + response.state, 'warn'); + } + }) + .catch(function (error) { + log(error, 'warn'); + }); + break; + case 'squeezeboxrpc': + try { + switch (getState(id + '.REPEAT').val) { + case 0: + setIfExists(id + '.REPEAT', 1); + GeneratePage(activePage!); + break; + case 1: + setIfExists(id + '.REPEAT', 2) + GeneratePage(activePage!); + break; + case 2: + setIfExists(id + '.REPEAT', 0); + GeneratePage(activePage!); + break; + } + } catch (err: any) { + log('Squeezebox: Repeat kann nicht verändert werden', 'warn'); + } + break; + } } } } @@ -6478,58 +6590,63 @@ function HandleButtonEvent(words: any): void { break; case 'media-pause': let pageItemTemp = findPageItem(id); - let adaInstanceSplit = pageItemTemp.adapterPlayerInstance!.split('.'); - if (adaInstanceSplit[0] == 'squeezeboxrpc') { - let adapterPlayerInstanceStateSeceltor: string = [pageItemTemp.adapterPlayerInstance, 'Players', pageItemTemp.mediaDevice, 'state'].join('.'); - if (Debug) log('HandleButtonEvent media-pause Squeezebox-> adapterPlayerInstanceStateSeceltor: ' + adapterPlayerInstanceStateSeceltor, 'info'); - let stateVal = getState(adapterPlayerInstanceStateSeceltor).val; - if (stateVal == 0) { - setState(adapterPlayerInstanceStateSeceltor, 1); - } else if (stateVal == 1) { - setState(adapterPlayerInstanceStateSeceltor, 0); - } else if (stateVal == null) { - setState(adapterPlayerInstanceStateSeceltor, 1); - } - } else { - if (Debug) log('HandleButtonEvent media-pause -> .STATE Value: ' + getState(id + '.STATE').val, 'info'); - if (getState(id + '.STATE').val === true) { - setIfExists(id + '.PAUSE', true); + if (isPageMediaItem(pageItemTemp)) { + let adaInstanceSplit = pageItemTemp.adapterPlayerInstance!.split('.'); + if (adaInstanceSplit[0] == 'squeezeboxrpc') { + let adapterPlayerInstanceStateSeceltor: string = [pageItemTemp.adapterPlayerInstance, 'Players', pageItemTemp.mediaDevice, 'state'].join('.'); + if (Debug) log('HandleButtonEvent media-pause Squeezebox-> adapterPlayerInstanceStateSeceltor: ' + adapterPlayerInstanceStateSeceltor, 'info'); + let stateVal = getState(adapterPlayerInstanceStateSeceltor).val; + if (stateVal == 0) { + setState(adapterPlayerInstanceStateSeceltor, 1); + } else if (stateVal == 1) { + setState(adapterPlayerInstanceStateSeceltor, 0); + } else if (stateVal == null) { + setState(adapterPlayerInstanceStateSeceltor, 1); + } } else { - setIfExists(id + '.PLAY', true); + if (Debug) log('HandleButtonEvent media-pause -> .STATE Value: ' + getState(id + '.STATE').val, 'info'); + if (getState(id + '.STATE').val === true) { + setIfExists(id + '.PAUSE', true); + } else { + setIfExists(id + '.PLAY', true); + } } + GeneratePage(activePage!); } - GeneratePage(activePage!); break; case 'media-next': setIfExists(id + '.NEXT', true); GeneratePage(activePage!); break; case 'media-shuffle': - if ((findPageItem(id).adapterPlayerInstance!).startsWith("volumio")) { - findPageItem(id).playList = []; break; - } //Volumio: empty playlist $uha-20230103 - if ((findPageItem(id).adapterPlayerInstance!).startsWith("spotify")) { - if (getState(id + '.SHUFFLE').val == 'off') { - setIfExists(id + '.SHUFFLE', 'on'); - } else { - setIfExists(id + '.SHUFFLE', 'off'); + const tempPage = findPageItem(id); + if (isPageMediaItem(tempPage)) { + if (tempPage.adapterPlayerInstance.startsWith("volumio")) { + findPageItem(id).playList = []; break; + } //Volumio: empty playlist $uha-20230103 + if ((tempPage.adapterPlayerInstance).startsWith("spotify")) { + if (getState(id + '.SHUFFLE').val == 'off') { + setIfExists(id + '.SHUFFLE', 'on'); + } else { + setIfExists(id + '.SHUFFLE', 'off'); + } } - } - if ((findPageItem(id).adapterPlayerInstance!).startsWith("alexa")) { - if (getState(id + '.SHUFFLE').val == false) { - setIfExists(id + '.SHUFFLE', true); - } else { - setIfExists(id + '.SHUFFLE', false); + if ((tempPage.adapterPlayerInstance).startsWith("alexa")) { + if (getState(id + '.SHUFFLE').val == false) { + setIfExists(id + '.SHUFFLE', true); + } else { + setIfExists(id + '.SHUFFLE', false); + } } - } - if ((findPageItem(id).adapterPlayerInstance!).startsWith("sonos")) { - if (getState(id + '.SHUFFLE').val == false) { - setIfExists(id + '.SHUFFLE', true); - } else { - setIfExists(id + '.SHUFFLE', false); + if ((tempPage.adapterPlayerInstance).startsWith("sonos")) { + if (getState(id + '.SHUFFLE').val == false) { + setIfExists(id + '.SHUFFLE', true); + } else { + setIfExists(id + '.SHUFFLE', false); + } } + GeneratePage(activePage!); } - GeneratePage(activePage!); break; case 'volumeSlider': pageCounter = -1; @@ -6544,49 +6661,56 @@ function HandleButtonEvent(words: any): void { break; case 'mode-speakerlist': let pageItem = findPageItem(id); - let adapterInstance = pageItem.adapterPlayerInstance!; - let adapter = adapterInstance!.split('.'); - let deviceAdapter = adapter[0]; + if (isPageMediaItem(pageItem)) { + let adapterInstance = pageItem.adapterPlayerInstance!; + let adapter = adapterInstance!.split('.'); + const deviceAdapter: PlayerType = adapter[0] as PlayerType; - switch (deviceAdapter) { - case 'spotify-premium': - let strDevicePI = pageItem.speakerList![words[4]]; - let strDeviceID = spotifyGetDeviceID(strDevicePI); - setState(adapterInstance + 'devices.' + strDeviceID + ".useForPlayback", true); - break; - case 'alexa2': - let i_list = Array.prototype.slice.apply($('[state.id="' + adapterInstance + 'Echo-Devices.*.Info.name"]')); - for (let i_index in i_list) { - let i = i_list[i_index]; - if ((getState(i).val) === pageItem.speakerList![words[4]]) { - if (Debug) log('HandleButtonEvent mode-Speakerlist Alexa2: ' + getState(i).val + ' - ' + pageItem.speakerList![words[4]], 'info'); - let deviceId = i; - deviceId = deviceId.split('.'); - setIfExists(adapterInstance + 'Echo-Devices.' + pageItem.mediaDevice + '.Commands.textCommand', 'Schiebe meine Musik auf ' + pageItem.speakerList![words[4]]); - pageItem.mediaDevice = deviceId[3]; - } - } - break; - case 'sonos': - break; - case 'chromecast': - break; - case 'squeezeboxrpc': - pageItem.mediaDevice = pageItem.speakerList![words[4]]; - break; - } - pageCounter = 0; - GeneratePage(activePage!); - setTimeout(async function () { - pageCounter = 1; + switch (deviceAdapter) { + case 'spotify-premium': + let strDevicePI = pageItem.speakerList![words[4]]; + let strDeviceID = spotifyGetDeviceID(strDevicePI); + setState(adapterInstance + 'devices.' + strDeviceID + ".useForPlayback", true); + break; + case 'alexa2': + let i_list = Array.prototype.slice.apply($('[state.id="' + adapterInstance + 'Echo-Devices.*.Info.name"]')); + for (let i_index in i_list) { + let i = i_list[i_index]; + if ((getState(i).val) === pageItem.speakerList![words[4]]) { + if (Debug) log('HandleButtonEvent mode-Speakerlist Alexa2: ' + getState(i).val + ' - ' + pageItem.speakerList![words[4]], 'info'); + let deviceId = i; + deviceId = deviceId.split('.'); + setIfExists(adapterInstance + 'Echo-Devices.' + pageItem.mediaDevice + '.Commands.textCommand', 'Schiebe meine Musik auf ' + pageItem.speakerList![words[4]]); + pageItem.mediaDevice = deviceId[3]; + } + } + break; + case 'sonos': + break; + /*case 'chromecast': + break;*/ + case 'squeezeboxrpc': + pageItem.mediaDevice = pageItem.speakerList![words[4]]; + break; + case "volumio": + break; + case "bosesoundtouch": + break; + } + pageCounter = 0; GeneratePage(activePage!); - }, 3000); + setTimeout(async function () { + pageCounter = 1; + GeneratePage(activePage!); + }, 3000); + } break; case 'mode-playlist': let pageItemPL = findPageItem(id); + if (!isPageMediaItem(pageItemPL)) break; let adapterInstancePL = pageItemPL.adapterPlayerInstance!; let adapterPL = adapterInstancePL.split('.'); - let deviceAdapterPL = adapterPL[0]; + const deviceAdapterPL: PlayerType = adapterPL[0] as PlayerType; switch (deviceAdapterPL) { case 'spotify-premium': @@ -6629,6 +6753,10 @@ function HandleButtonEvent(words: any): void { case 'squeezeboxrpc': setState([pageItemPL.adapterPlayerInstance, 'Players', pageItemPL.mediaDevice, 'cmdPlayFavorite'].join('.'), words[4]); break; + case "bosesoundtouch": + break; + default: + log('Hello Mr. Developer u miss in mode-playlist something!', 'warn') } pageCounter = 0; GeneratePage(activePage!); @@ -6639,9 +6767,10 @@ function HandleButtonEvent(words: any): void { break; case 'mode-tracklist': let pageItemTL = findPageItem(id); + if (!isPageMediaItem(pageItemTL)) break; let adapterInstanceTL = pageItemTL.adapterPlayerInstance!; let adapterTL = adapterInstanceTL.split('.'); - let deviceAdapterTL = adapterTL[0]; + const deviceAdapterTL: PlayerType = adapterTL[0] as PlayerType; switch (deviceAdapterTL) { case 'spotify-premium': @@ -6673,6 +6802,10 @@ function HandleButtonEvent(words: any): void { //@ts-ignore Fehler kommt von findPageItem in vscode setState([pageItemPL.adapterPlayerInstance, 'Players', pageItemPL.mediaDevice, 'PlaylistCurrentIndex'].join('.'), words[4]); break; + case "bosesoundtouch": + break; + default: + log('Hello Mr. Developer u miss in mode-tracklist something!', 'warn') } pageCounter = 0; GeneratePage(activePage!); @@ -6683,6 +6816,7 @@ function HandleButtonEvent(words: any): void { break; case 'mode-repeat': let pageItemRP = findPageItem(id); + if (!isPageMediaItem(pageItemRP)) break; let adapterInstanceRP = pageItemRP.adapterPlayerInstance!; let adapterRP = adapterInstanceRP.split('.'); let deviceAdapterRP = adapterRP[0]; @@ -6712,6 +6846,7 @@ function HandleButtonEvent(words: any): void { break; case 'mode-seek': let pageItemSeek = findPageItem(id); + if (!isPageMediaItem(pageItemSeek)) break; let adapterInstanceSK = pageItemSeek.adapterPlayerInstance!; let adapterSK = adapterInstanceSK.split('.'); let deviceAdapterSK = adapterSK[0]; @@ -6732,6 +6867,7 @@ function HandleButtonEvent(words: any): void { break; case 'mode-crossfade': let pageItemCrossfade = findPageItem(id); + if (!isPageMediaItem(pageItemCrossfade)) break; let adapterInstanceCF = pageItemCrossfade.adapterPlayerInstance!; let adapterCF = adapterInstanceCF.split('.'); let deviceAdapterCF = adapterCF[0]; @@ -6756,6 +6892,7 @@ function HandleButtonEvent(words: any): void { break; case 'mode-favorites': let pageItemFav = findPageItem(id); + if (!isPageMediaItem(pageItemFav)) break; if (Debug) log(getState(pageItemFav.adapterPlayerInstance + 'root.' + pageItemFav.mediaDevice + '.favorites_set').val, 'info'); let favListArray = getState(pageItemFav.adapterPlayerInstance + 'root.' + pageItemFav.mediaDevice + '.favorites_list_array').val; setState(pageItemFav.adapterPlayerInstance + 'root.' + pageItemFav.mediaDevice + '.favorites_set', favListArray[words[4]]); @@ -6771,7 +6908,8 @@ function HandleButtonEvent(words: any): void { break; case 'media-OnOff': let pageItemTem = findPageItem(id); - let adaInstanceSpli = pageItemTem.adapterPlayerInstance!.split('.'); + if (!isPageMediaItem(pageItemTem)) break; + let adaInstanceSpli = pageItemTem.adapterPlayerInstance.split('.'); if (adaInstanceSpli[0] == 'squeezeboxrpc') { let adapterPlayerInstancePowerSelector: string = [pageItemTem.adapterPlayerInstance, 'Players', pageItemTem.mediaDevice, 'Power'].join('.'); let stateVal = getState(adapterPlayerInstancePowerSelector).val; @@ -7089,7 +7227,7 @@ function GetNavigationString(pageId: number): string { return ''; } -function GenerateDetailPage(type: string, optional: string | undefined, pageItem: PageItem, placeId: number | undefined): Payload[] { +function GenerateDetailPage(type: string, optional: mediaOptional | undefined, pageItem: PageItem, placeId: number | undefined): Payload[] { if (Debug) log('GenerateDetailPage Übergabe Type: ' + type + ' - optional: ' + optional + ' - pageItem.id: ' + pageItem.id, 'info'); try { let out_msgs: Array = []; @@ -7106,7 +7244,9 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem let switchVal = '0'; let brightness = 0; - if (o.common.role == 'light' || o.common.role == 'socket') { + switch (o.common.role) { + case 'light': + case 'socket': { if (existsState(id + '.GET')) { val = getState(id + '.GET').val; RegisterDetailEntityWatcher(id + '.GET', pageItem, type, placeId); @@ -7152,9 +7292,9 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem + effect_supported }); } - + break; // Dimmer - if (o.common.role == 'dimmer') { + case 'dimmer': { if (existsState(id + '.ON_ACTUAL')) { val = getState(id + '.ON_ACTUAL').val; RegisterDetailEntityWatcher(id + '.ON_ACTUAL', pageItem, type, placeId); @@ -7209,9 +7349,9 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem + effect_supported }); } - + break; // HUE-Licht - if (o.common.role == 'hue') { + case 'hue': { if (existsState(id + '.ON_ACTUAL')) { val = getState(id + '.ON_ACTUAL').val; @@ -7281,9 +7421,9 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem + effect_supported }); } - + break; // RGB-Licht - if (o.common.role == 'rgb') { + case 'rgb': { if (existsState(id + '.ON_ACTUAL')) { val = getState(id + '.ON_ACTUAL').val; @@ -7352,9 +7492,9 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem + effect_supported }); } - + break; // RGB-Licht-einzeln (HEX) - if (o.common.role == 'rgbSingle') { + case 'rgbSingle': { if (existsState(id + '.ON_ACTUAL')) { val = getState(id + '.ON_ACTUAL').val; @@ -7428,9 +7568,9 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem + effect_supported }); } - + break; // Farbtemperatur (CT) - if (o.common.role == 'ct') { + case 'ct': { if (existsState(id + '.ON')) { val = getState(id + '.ON').val; @@ -7492,6 +7632,8 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem + effect_supported }); } + break; + } } if (type == 'popupShutter') { @@ -7774,228 +7916,229 @@ function GenerateDetailPage(type: string, optional: string | undefined, pageItem let actualState: any = ''; let optionalString: string = 'Kein Eintrag'; let mode: string = ''; - - let vTempAdapter = (pageItem.adapterPlayerInstance!).split('.'); - let vAdapter = vTempAdapter[0]; - if (optional == 'seek') { - let actualStateTemp: number = getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.seek').val; - if (actualStateTemp >= 95) { - actualState = '100%'; - } else if (actualStateTemp >= 85) { - actualState = '90%'; - } else if (actualStateTemp >= 75) { - actualState = '80%'; - } else if (actualStateTemp >= 65) { - actualState = '70%'; - } else if (actualStateTemp >= 55) { - actualState = '60%'; - } else if (actualStateTemp >= 45) { - actualState = '50%'; - } else if (actualStateTemp >= 35) { - actualState = '40%'; - } else if (actualStateTemp >= 25) { - actualState = '30%'; - } else if (actualStateTemp >= 15) { - actualState = '20%'; - } else if (actualStateTemp >= 5) { - actualState = '10%'; - } else if (actualStateTemp >= 0) { - actualState = '0%'; - } - if (vAdapter == 'sonos') { - optionalString = '0%?10%?20%?30%?40%?50%?60%?70%?80%?90%?100%'; - } - mode = 'seek'; - } else if (optional == 'crossfade') { - if (existsObject(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.crossfade')) { - let actualStateTemp: boolean = getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.crossfade').val; - if (actualStateTemp) { - actualState = findLocale('media', 'on'); - } else { - actualState = findLocale('media', 'off'); + if (isPageMediaItem(pageItem)) { + const vTempAdapter = (pageItem.adapterPlayerInstance!).split('.'); + const vAdapter: PlayerType = vTempAdapter[0] as PlayerType; + if (optional == 'seek') { + let actualStateTemp: number = getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.seek').val; + if (actualStateTemp >= 95) { + actualState = '100%'; + } else if (actualStateTemp >= 85) { + actualState = '90%'; + } else if (actualStateTemp >= 75) { + actualState = '80%'; + } else if (actualStateTemp >= 65) { + actualState = '70%'; + } else if (actualStateTemp >= 55) { + actualState = '60%'; + } else if (actualStateTemp >= 45) { + actualState = '50%'; + } else if (actualStateTemp >= 35) { + actualState = '40%'; + } else if (actualStateTemp >= 25) { + actualState = '30%'; + } else if (actualStateTemp >= 15) { + actualState = '20%'; + } else if (actualStateTemp >= 5) { + actualState = '10%'; + } else if (actualStateTemp >= 0) { + actualState = '0%'; } - } - if (vAdapter == 'sonos') { - optionalString = findLocale('media', 'on') + '?' + findLocale('media', 'off'); - } - mode = 'crossfade'; - } else if (optional == 'speakerlist') { - if (vAdapter == 'spotify-premium') { - if (existsObject(pageItem.adapterPlayerInstance + 'player.device.name')) { - actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'player.device.name').val); + if (vAdapter == 'sonos') { + optionalString = '0%?10%?20%?30%?40%?50%?60%?70%?80%?90%?100%'; } - } else if (vAdapter == 'alexa2') { - if (existsObject(pageItem.adapterPlayerInstance + 'player.device.name')) { + mode = 'seek'; + } else if (optional == 'crossfade') { + if (existsObject(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.crossfade')) { + let actualStateTemp: boolean = getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.crossfade').val; + if (actualStateTemp) { + actualState = findLocale('media', 'on'); + } else { + actualState = findLocale('media', 'off'); + } + } + if (vAdapter == 'sonos') { + optionalString = findLocale('media', 'on') + '?' + findLocale('media', 'off'); + } + mode = 'crossfade'; + } else if (optional == 'speakerlist') { + if (vAdapter == 'spotify-premium') { + if (existsObject(pageItem.adapterPlayerInstance + 'player.device.name')) { + actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'player.device.name').val); + } + } else if (vAdapter == 'alexa2') { + if (existsObject(pageItem.adapterPlayerInstance + 'player.device.name')) { + //Todo Richtiges Device finden + actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'Echo-Devices.' + pageItem.mediaDevice + '.Info.name').val); + } + } else if (vAdapter == 'squeezeboxrpc') { + actualState = pageItem.mediaDevice; + } + let tempSpeakerList: string[] = []; + for (let i = 0; i < pageItem.speakerList!.length; i++) { + tempSpeakerList[i] = formatInSelText(pageItem.speakerList![i]).trim(); + } + optionalString = pageItem.speakerList != undefined ? tempSpeakerList.join('?') : ''; + mode = 'speakerlist'; + } else if (optional == 'playlist') { + if (vAdapter == 'spotify-premium') { + if (existsObject(pageItem.adapterPlayerInstance + 'player.playlist.name')) { + actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'player.playlist.name').val); + } + let tempPlayList: string[] = []; + for (let i = 0; i < pageItem.playList!.length; i++) { + tempPlayList[i] = formatInSelText(pageItem.playList![i]); + } + optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' + } else if (vAdapter == 'alexa2') { //Todo Richtiges Device finden - actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'Echo-Devices.' + pageItem.mediaDevice + '.Info.name').val); + actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'Echo-Devices.' + pageItem.mediaDevice + '.Player.currentAlbum').val); + + let tPlayList: any = [] + for (let i = 0; i < pageItem.playList!.length; i++) { + if (Debug) log('function GenerateDetailPage role:media -> Playlist ' + pageItem.playList![i], 'info'); + let tempItem = pageItem.playList![i].split('.'); + tPlayList[i] = tempItem[1]; + } + + let tempPlayList: string[] = []; + for (let i = 0; i < tPlayList.length; i++) { + tempPlayList[i] = formatInSelText(tPlayList[i]); + } + optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' + } else if (vAdapter == 'sonos') { + if (Debug) log(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.playlist_set', 'info'); + if (existsObject(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.playlist_set')) { + actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.playlist_set').val); + } + let tempPlayList: string[] = []; + for (let i = 0; i < pageItem.playList!.length; i++) { + tempPlayList[i] = formatInSelText(pageItem.playList![i]); + } + optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' + } else if (vAdapter == 'volumio') { /* Volumio: limit 900 chars */ + actualState = ''; //todo: no actual playlistname saving + let tempPlayList: string[] = []; let tempPll = 0; + for (let i = 0; i < pageItem.playList!.length; i++) { + tempPll += pageItem.playList![i].length; if (tempPll > 900) break; + tempPlayList[i] = formatInSelText(pageItem.playList![i]); + } + optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' + } else if (vAdapter == 'squeezeboxrpc') { + // Playlist browsing not supported by squeezeboxrpc adapter. But Favorites can be used + actualState = ''; // Not supported by squeezeboxrpc adapter + let tempPlayList: string[] = []; + let pathParts: Array = pageItem.adapterPlayerInstance!.split('.'); + for (let favorite_index = 0; favorite_index < 45; favorite_index++) { + let favorite_name_selector: string = [pathParts[0], pathParts[1], 'Favorites', favorite_index, 'Name'].join('.'); + if (!existsObject(favorite_name_selector)) { + break; + } + let favoritename: string = getState(favorite_name_selector).val; + tempPlayList.push(formatInSelText(favoritename)); + } + optionalString = tempPlayList.length > 0 ? tempPlayList.join('?') : ''; } - } else if (vAdapter == 'squeezeboxrpc') { - actualState = pageItem.mediaDevice; - } - let tempSpeakerList: string[] = []; - for (let i = 0; i < pageItem.speakerList!.length; i++) { - tempSpeakerList[i] = formatInSelText(pageItem.speakerList![i]).trim(); - } - optionalString = pageItem.speakerList != undefined ? tempSpeakerList.join('?') : ''; - mode = 'speakerlist'; - } else if (optional == 'playlist') { - if (vAdapter == 'spotify-premium') { - if (existsObject(pageItem.adapterPlayerInstance + 'player.playlist.name')) { - actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'player.playlist.name').val); + mode = 'playlist'; + } else if (optional == 'tracklist') { + actualState = ''; + /* Volumio: works for files */ + if (vAdapter == 'volumio') { + actualState = getState(pageItem.id + '.TITLE').val; + globalTracklist = pageItem.globalTracklist; + } else if (vAdapter == 'squeezeboxrpc') { + actualState = getState(pageItem.id + '.TITLE').val; + } else if (vAdapter == 'sonos') { + actualState = getState(pageItem.id + '.TITLE').val; + } else { + actualState = getState(pageItem.adapterPlayerInstance + 'player.trackName').val; } - let tempPlayList: string[] = []; - for (let i = 0; i < pageItem.playList!.length; i++) { - tempPlayList[i] = formatInSelText(pageItem.playList![i]); - } - optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' - } else if (vAdapter == 'alexa2') { - //Todo Richtiges Device finden - actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'Echo-Devices.' + pageItem.mediaDevice + '.Player.currentAlbum').val); - - let tPlayList: any = [] - for (let i = 0; i < pageItem.playList!.length; i++) { - if (Debug) log('function GenerateDetailPage role:media -> Playlist ' + pageItem.playList![i], 'info'); - let tempItem = pageItem.playList![i].split('.'); - tPlayList[i] = tempItem[1]; - } - - let tempPlayList: string[] = []; - for (let i = 0; i < tPlayList.length; i++) { - tempPlayList[i] = formatInSelText(tPlayList[i]); - } - optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' - } else if (vAdapter == 'sonos') { - if (Debug) log(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.playlist_set', 'info'); - if (existsObject(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.playlist_set')) { - actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.playlist_set').val); - } - let tempPlayList: string[] = []; - for (let i = 0; i < pageItem.playList!.length; i++) { - tempPlayList[i] = formatInSelText(pageItem.playList![i]); - } - optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' - } else if (vAdapter == 'volumio') { /* Volumio: limit 900 chars */ - actualState = ''; //todo: no actual playlistname saving - let tempPlayList: string[] = []; let tempPll = 0; - for (let i = 0; i < pageItem.playList!.length; i++) { - tempPll += pageItem.playList![i].length; if (tempPll > 900) break; - tempPlayList[i] = formatInSelText(pageItem.playList![i]); - } - optionalString = pageItem.playList != undefined ? tempPlayList.join('?') : '' - } else if(vAdapter == 'squeezeboxrpc') { - // Playlist browsing not supported by squeezeboxrpc adapter. But Favorites can be used - actualState = ''; // Not supported by squeezeboxrpc adapter - let tempPlayList: string[] = []; - let pathParts: Array = pageItem.adapterPlayerInstance!.split('.'); - for (let favorite_index=0; favorite_index < 45; favorite_index++) { - let favorite_name_selector: string = [pathParts[0], pathParts[1], 'Favorites', favorite_index, 'Name'].join('.'); - if(!existsObject(favorite_name_selector)) { + actualState = (actualState.replace('?', '')).split(' -'); + actualState = actualState[0].split(" ("); + actualState = formatInSelText(actualState[0]); + if (Debug) log(actualState, 'info'); + if (Debug) log(globalTracklist, 'info'); + //Limit 900 characters, then memory overflow --> Shorten as much as possible + let temp_array: any[] = []; + //let trackArray = (function () { try {return JSON.parse(getState(pageItem.adapterPlayerInstance + 'player.playlist.trackListArray').val);} catch(e) {return {};}})(); + for (let track_index = 0; track_index < 45; track_index++) { + let temp_cut_array = getAttr(globalTracklist, track_index + '.title'); + /* Volumio: @local/NAS no title -> name */ + if (temp_cut_array == undefined) { + temp_cut_array = getAttr(globalTracklist, track_index + '.name'); + } + if (Debug) log('function GenerateDetailPage role:media tracklist -> ' + temp_cut_array, 'info'); + if (temp_cut_array != undefined) { + temp_cut_array = (temp_cut_array.replace('?', '')).split(' -'); + temp_cut_array = temp_cut_array[0].split(" ("); + temp_cut_array = temp_cut_array[0]; + if (String(temp_cut_array[0]).length >= 22) { + temp_array[track_index] = temp_cut_array.substring(0, 20) + '..'; + } else { + temp_array[track_index] = temp_cut_array.substring(0, 23); + } + } + else { break; } - let favoritename: string = getState(favorite_name_selector).val; - tempPlayList.push(formatInSelText(favoritename)); } - optionalString = tempPlayList.length > 0 ? tempPlayList.join('?') : ''; - } - mode = 'playlist'; - } else if (optional == 'tracklist') { - actualState = ''; - /* Volumio: works for files */ - if (vAdapter == 'volumio') { - actualState = getState(pageItem.id + '.TITLE').val; - globalTracklist = pageItem.globalTracklist; - }else if(vAdapter == 'squeezeboxrpc') { - actualState = getState(pageItem.id + '.TITLE').val; - }else if(vAdapter == 'sonos') { - actualState = getState(pageItem.id + '.TITLE').val; - } else { - actualState = getState(pageItem.adapterPlayerInstance + 'player.trackName').val; - } - actualState = (actualState.replace('?','')).split(' -'); - actualState = actualState[0].split(" ("); - actualState = formatInSelText(actualState[0]); - if (Debug) log(actualState, 'info'); - if (Debug) log(globalTracklist, 'info'); - //Limit 900 characters, then memory overflow --> Shorten as much as possible - let temp_array: any[] = []; - //let trackArray = (function () { try {return JSON.parse(getState(pageItem.adapterPlayerInstance + 'player.playlist.trackListArray').val);} catch(e) {return {};}})(); - for (let track_index=0; track_index < 45; track_index++) { - let temp_cut_array = getAttr(globalTracklist, track_index + '.title'); - /* Volumio: @local/NAS no title -> name */ - if (temp_cut_array == undefined) { - temp_cut_array = getAttr(globalTracklist, track_index + '.name'); - } - if (Debug) log('function GenerateDetailPage role:media tracklist -> ' + temp_cut_array, 'info'); - if (temp_cut_array != undefined) { - temp_cut_array = (temp_cut_array.replace('?','')).split(' -'); - temp_cut_array = temp_cut_array[0].split(" ("); - temp_cut_array = temp_cut_array[0]; - if (String(temp_cut_array[0]).length >= 22) { - temp_array[track_index] = temp_cut_array.substring(0,20) + '..'; - } else { - temp_array[track_index] = temp_cut_array.substring(0,23); - } + let tempTrackList: string[] = []; + for (let i = 0; i < temp_array.length; i++) { + tempTrackList[i] = formatInSelText(temp_array[i]); } - else { - break; - } - } - let tempTrackList: string[] = []; - for (let i = 0; i < temp_array.length; i++) { - tempTrackList[i] = formatInSelText(temp_array[i]); - } - optionalString = pageItem.playList != undefined ? tempTrackList.join('?') : '' - mode = 'tracklist'; - } else if (optional == 'equalizer') { - if (pageItem.id == undefined) throw new Error ('Missing pageItem.id in equalizer!'); - let lastIndex = (pageItem.id.split('.')).pop(); + optionalString = pageItem.playList != undefined ? tempTrackList.join('?') : '' + mode = 'tracklist'; + } else if (optional == 'equalizer') { + if (pageItem.id == undefined) throw new Error('Missing pageItem.id in equalizer!'); + let lastIndex = (pageItem.id.split('.')).pop(); - if (existsObject(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode') == false || - existsObject(NSPanel_Path + 'Media.Player.' + lastIndex + '.Speaker') == false) { - createState(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode', { type: 'string' }); - createState(NSPanel_Path + 'Media.Player.' + lastIndex + '.Speaker', { type: 'string' }); - } - - actualState = '' - if (getState(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode').val != null) { - actualState = formatInSelText(getState(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode').val); - } - - let tempEQList: string[] = []; - for (let i = 0; i < pageItem.equalizerList!.length; i++) { - tempEQList[i] = formatInSelText(pageItem!.equalizerList![i]); + if (existsObject(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode') == false || + existsObject(NSPanel_Path + 'Media.Player.' + lastIndex + '.Speaker') == false) { + createState(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode', {type: 'string'}); + createState(NSPanel_Path + 'Media.Player.' + lastIndex + '.Speaker', {type: 'string'}); + } + + actualState = '' + if (getState(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode').val != null) { + actualState = formatInSelText(getState(NSPanel_Path + 'Media.Player.' + lastIndex + '.EQ.activeMode').val); + } + + let tempEQList: string[] = []; + for (let i = 0; i < pageItem.equalizerList!.length; i++) { + tempEQList[i] = formatInSelText(pageItem!.equalizerList![i]); + } + + optionalString = pageItem.equalizerList != undefined ? tempEQList.join('?') : ''; + mode = 'equalizer'; + } else if (optional == 'repeat') { + actualState = getState(pageItem.adapterPlayerInstance + 'player.repeat').val; + optionalString = pageItem.repeatList!.join('?'); + mode = 'repeat'; + } else if (optional == 'favorites') { + if (Debug) log(getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.favorites_set').val, 'info') + actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.favorites_set').val); + + let tempFavList: string[] = []; + let favList = getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.favorites_list_array').val; + for (let i = 0; i < favList.length; i++) { + tempFavList[i] = formatInSelText(favList[i]); + } + optionalString = tempFavList != undefined ? tempFavList.join('?') : ''; + mode = 'favorites'; } - optionalString = pageItem.equalizerList != undefined ? tempEQList.join('?') : ''; - mode = 'equalizer'; - } else if (optional == 'repeat') { - actualState = getState(pageItem.adapterPlayerInstance + 'player.repeat').val; - optionalString = pageItem.repeatList!.join('?'); - mode = 'repeat'; - } else if (optional == 'favorites') { - if (Debug) log(getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.favorites_set').val, 'info') - actualState = formatInSelText(getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.favorites_set').val); - - let tempFavList:string [] = []; - let favList = getState(pageItem.adapterPlayerInstance + 'root.' + pageItem.mediaDevice + '.favorites_list_array').val; - for (let i = 0; i < favList.length; i++) { - tempFavList[i] = formatInSelText(favList[i]); - } - optionalString = tempFavList != undefined ? tempFavList.join('?') : ''; - mode = 'favorites'; + let tempId = placeId != undefined ? placeId : id; + + out_msgs.push({ + payload: 'entityUpdateDetail2' + '~' //entityUpdateDetail + + tempId + '?' + optional + '~~' //{entity_id} + + rgb_dec565(HMIOn) + '~' //{icon_color}~ + + mode + '~' + + actualState + '~' + + optionalString + }); + GeneratePage(activePage!); } - - let tempId = placeId != undefined ? placeId : id; - - out_msgs.push({ - payload: 'entityUpdateDetail2' + '~' //entityUpdateDetail - + tempId + '?' + optional + '~~' //{entity_id} - + rgb_dec565(HMIOn) + '~' //{icon_color}~ - + mode + '~' - + actualState + '~' - + optionalString - }); - GeneratePage(activePage!); } else if (o.common.role == 'buttonSensor') { let actualValue: string = ''; @@ -9406,8 +9549,8 @@ type PageThermo = { type PageMedia = { type: 'cardMedia', - items: PageItem[], -} & Omit + items: PageMediaItem[], +} & Omit type PageAlarm = { type: 'cardAlarm', @@ -9434,7 +9577,16 @@ type PageChart = { items: PageItem[], } & Omit -type PageItem = { +type PageItem = PageBaseItem | PageMediaItem + +function isPageMediaItem(F: PageItem | PageMediaItem):F is PageMediaItem { + return (F as PageMediaItem).adapterPlayerInstance !== undefined +} +type PageMediaItem = { + adapterPlayerInstance: adapterPlayerInstanceType, +} & PageBaseItem + +type PageBaseItem = { id?: string | null, icon?: string, icon2?: string, @@ -9462,7 +9614,7 @@ type PageItem = { navigate?: boolean, colormode?: string, colorScale?: any, - adapterPlayerInstance?: string, + //adapterPlayerInstance?: adapterPlayerInstanceType, mediaDevice?: string, targetPage?: string, speakerList?: string[], @@ -9568,3 +9720,38 @@ type IconScaleElement = { val_max:number, val_best?: number } +/** we need this to have a nice order when using switch() */ +type adapterPlayerInstanceType = + 'alexa2.0.' | 'alexa2.1.'| 'alexa2.2.' | 'alexa2.3.' | 'alexa2.4.' | 'alexa2.5.' | 'alexa2.6.' | 'alexa2.7.' | 'alexa2.8.' | 'alexa2.9.' +| 'sonos.0.' | 'sonos.1.' | 'sonos.2.' | 'sonos.3.' | 'sonos.4.' | 'sonos.5.' | 'sonos.6.' | 'sonos.7.' | 'sonos.8.' | 'sonos.9.' +| 'spotify-premium.0.' | 'spotify-premium.1.' | 'spotify-premium.2.' | 'spotify-premium.3.' | 'spotify-premium.4.' | 'spotify-premium.5.' | 'spotify-premium.6.' | 'spotify-premium.7.' | 'spotify-premium.8.' | 'spotify-premium.9.' +| 'volumio.0.' | 'volumio.1.' | 'volumio.2.' | 'volumio.3.' |'volumio.4.' | 'volumio.5.' | 'volumio.6.' | 'volumio.7.' | 'volumio.8.' | 'volumio.9.' +| 'squeezeboxrpc.0.' | 'squeezeboxrpc.1.' | 'squeezeboxrpc.2.' | 'squeezeboxrpc.3.' | 'squeezeboxrpc.4.' | 'squeezeboxrpc.5.' | 'squeezeboxrpc.6.' | 'squeezeboxrpc.7.' | 'squeezeboxrpc.8.' | 'squeezeboxrpc.9.' +| 'bosesoundtouch.0.' | 'bosesoundtouch.1.' | 'bosesoundtouch.2.' | 'bosesoundtouch.3.' |'bosesoundtouch.4.' | 'bosesoundtouch.5.' | 'bosesoundtouch.6.' | 'bosesoundtouch.7.' | 'bosesoundtouch.8.' | 'bosesoundtouch.9.' + +type PlayerType = 'alexa2' | 'sonos' | 'spotify-premium' | 'volumio' | 'squeezeboxrpc' | 'bosesoundtouch' + +type notSortedPlayerType = `${PlayerType}.0.` | `${PlayerType}.1.` | `${PlayerType}.2.` | `${PlayerType}.3.` | `${PlayerType}.4.` | `${PlayerType}.5.` | `${PlayerType}.6.` | `${PlayerType}.7.` | `${PlayerType}.8.` | `${PlayerType}.9.` + +/** check if adapterPlayerInstanceType has all Playertypes */ +function checkSortedPlayerType(F: notSortedPlayerType) { + const test: adapterPlayerInstanceType = F; +} + +type mediaOptional = 'seek' | 'crossfade' | 'speakerlist' | 'playlist' | 'tracklist' | 'equalizer' | 'repeat' | 'favorites' + +function isMediaOptional(F: string | mediaOptional): F is mediaOptional { + switch(F as mediaOptional) { + case "seek": + case "crossfade": + case "speakerlist": + case "playlist": + case "tracklist": + case "equalizer": + case "repeat": + case "favorites": + return true; + default: + return false + } +} \ No newline at end of file diff --git a/ioBroker/tsconfig.json b/ioBroker/tsconfig.json new file mode 100644 index 00000000..ef388040 --- /dev/null +++ b/ioBroker/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "noEmit": true, + "allowJs": true, + "checkJs": true, + "module": "commonjs", + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "strict": true, + "noImplicitReturns": true, + "noImplicitAny": false, + "target": "ES2022", + "typeRoots": [ + ".iobroker/types", + "node_modules/@types" + ] + }, + "include": [ + "**/*.js", + "**/*.ts", + "./.iobroker/types/javascript.d.ts" + ], + "exclude": [ + "node_modules/**" + ] +} \ No newline at end of file