UNPKG

s7webserverapi

Version:

Unofficial Simatic-S7-Webserver JSON-RPC-API Client for S7-1200/1500 PLCs

172 lines (171 loc) 10.7 kB
import { Observable, ReplaySubject, Subject } from "rxjs"; import { BrowseFilesParams, BrowseTicketsParams, BrowseTicketsResult, CacheMethod, CloseTicketParams, CloseTicketResult, DownloadFileParams, FileBrowseResult, FlattenKeys, GetCertificateUrlParams, GetPermissionsParams, GetPermissionsResult, LoginParams, PingParams, PlcPermissions, ReadParams, ReadResult, RPCErrorCode, RPCMethodObject, RPCMethods, RPCResponse, RPCResults, S7DataTypes, S7JsonClient, S7WebserverClientConfig, WriteParams, WriteResult } from "../util/types"; import { RxJSHttpClient } from "rxjs-http-client"; import { GetTransaction, GetTransactionHandler, WriteTransaction, WriteTransactionHandler } from "./WriteTransaction"; import { CacheStructure } from "./CacheStructure"; import { SubscriberTrie } from "./Trie"; export declare class S7WebserverClient<T = "Structureless"> implements S7JsonClient<T> { protected baseUrl: string; protected config: S7WebserverClientConfig<T>; protected http: RxJSHttpClient; protected connectionErrorObservable: Subject<VoidFunction>; onPlcConnectionReady: ReplaySubject<boolean>; protected loaded: boolean; protected user: string; protected token: string; protected permissionMap: Map<PlcPermissions, boolean>; protected permissionsSubject: ReplaySubject<PlcPermissions[]>; protected lastPollingTime: Date; protected pollingDelay: number; protected slowPollingMode: boolean; protected readStack: FlattenKeys<T>[]; protected lastReadStack: FlattenKeys<T>[]; protected cache: CacheStructure<T>; protected pollErrorSubject: Subject<string>; protected errorSubscriber: number; protected writeTransactionHandler: WriteTransactionHandler<T>; protected getTransactionHandler: GetTransactionHandler<T>; protected subscriberCountMap: Map<FlattenKeys<T>, number>; protected rpcRequestHashedKeyMap: Map<number, string>; protected getRequestLoadedSubject: Subject<boolean>; /** * Because we use flattened keys, we use a Trie-Datastructure to keep track of the subscribers. * When a leaf changes its value we can use the prefix-property of the trie to also call all the parents of the leaf. * * @protected * @type {SubscriberTrie<typeof data>} */ protected subscriberTrie: SubscriberTrie<T>; /** * Subscriber trie for non cached values. * * @protected * @type {SubscriberTrie<typeof data>} */ protected ignoreCacheSubscriberTrie: SubscriberTrie<T>; protected browseFilesMap: Map<string, Subject<FileBrowseResult[]>>; protected downloadFileMap: Map<string, Subject<string>>; protected browseTicketsCounter: number; protected browseTicketsMap: Map<string, Subject<BrowseTicketsResult>>; protected closeTicketMap: Map<string, Subject<CloseTicketResult>>; protected localStorage?: Storage; protected pollTimeout: NodeJS.Timeout | undefined; protected ticketApiUrl: string; constructor(baseUrl: string, config: S7WebserverClientConfig<T>, ticketApiUrl?: string); protected getFileDownloadTicket(path: string): Observable<string>; protected downloadTicket(ticketId: string, type?: 'text' | 'arrayBuffer' | 'json'): Observable<string>; downloadFile(path: string, binary?: boolean): Observable<string>; downloadFolder(folderPath: string): Observable<(FileBrowseResult & { data: string; })[]>; browsePath(path: string): Observable<FileBrowseResult[]>; closeAllTickets(): Observable<boolean>; browseTickets(): Observable<BrowseTicketsResult>; closeTicket(ticketId: string): Observable<CloseTicketResult>; get onPollError(): Observable<unknown>; start(): Observable<boolean>; protected checkStoredToken(): Observable<string | undefined>; /** * Sets the current permissions and updates the permissions subject so other components can get a live-update of the permissions. * E.g. if the user logs out, we may want to redirect them if theyre currently on a page they shouldnt access. * @param permissions */ protected setCurrentPermissions(permissions: GetPermissionsResult): void; getRPCMethodObject(method: RPCMethods.BrowseTickets, params: BrowseTicketsParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.CloseTicket, params: CloseTicketParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.Ping, params: PingParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.BrowseFiles, params: BrowseFilesParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.DownloadFile, params: DownloadFileParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.Read, params: ReadParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.Login, params: LoginParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.GetCertificateUrl, params: GetCertificateUrlParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.Write, params: WriteParams, id?: string): RPCMethodObject; getRPCMethodObject(method: RPCMethods.GetPermissions, params: GetPermissionsParams, id?: string): RPCMethodObject; initPLCPoll(): void; protected hashRPCMethods(set: Set<RPCMethodObject>): void; protected getHashedId(humanReadableId: string): number; protected handleRPCResponse(responses: RPCResponse<RPCResults>[]): void; protected handleRPCResponseRead(key: FlattenKeys<T>, reponse: RPCResponse<ReadResult>): void; protected handleRPCResponseWrite(key: FlattenKeys<T>, response: RPCResponse<WriteResult>, writeTransactionId: number): void; /** * Or better, maybe call the subscriber on error. Maybe create a error-subject that can be subscribed to and display the error somewhere in the UI. * @param id rpc-id * @param error RPC-Error Object */ protected handleRPCResponseError(id: string, error: { code: RPCErrorCode; message: string; }): void; /** * This is the polling-cycle that will be called recursivley. It collects all the RPC-Methods that need to be called. * It collects one time get- and write-requests and subscriber-requests. It then sends the requests to the PLC and collects them. * * If an network-error occurs, the connectionErrorObservable is called with a callback function. This is primarily used to retry connection attempts. Currently we display a error-toast with a retry button that calls this callback function. * * * @returns */ pollData(once?: boolean): void; protected recalculatePollingDelay(): void; protected exponentialMovingAverage(): void; protected clampPollingDelay(): void; collectFileRPCMethodObject(objectSet: Set<RPCMethodObject>): void; collectTicketRPCMethodObjects(objectSet: Set<RPCMethodObject>): void; /** * Collects all the different RPC-Methods that should be called on a polling-cycle. * */ protected collectRPCMethodObjects(): Set<RPCMethodObject>; protected collectWriteRPCMethodObjects(objectSet: Set<RPCMethodObject>): void; protected collectSubscribeRPCMethodObjects(objectSet: Set<RPCMethodObject>): void; /** * When calling the get-Function, we just return the Subject in the subscriber-Trie and return the value once. * Then we append the hmi-key value to our read-Stack. Which we then collect here, by filling in the children keys and calling the get-Method on the server for the key. Later we identify the results and write it into the cache, which then triggers the subject. * * In case we never successfully read, we save the lastReadStack and delete it after a successful polling-cycle. That way, if we retry the connection, we do not lose the read information. * @param objectSet */ protected collectGetRPCMethodObjects(objectSet: Set<RPCMethodObject>): void; protected collectChildrenKeys(key: FlattenKeys<T>, objectSet: Set<RPCMethodObject>, method: RPCMethods.Read | RPCMethods.Write, depth: number, value?: S7DataTypes, transaction?: WriteTransaction<T> | GetTransaction<T>): void; protected _collectChildrenKeys(wholeKey: FlattenKeys<T>, objectSet: Set<RPCMethodObject>, ref: S7DataTypes, newKey: string, method: RPCMethods.Read | RPCMethods.Write, depth: number, value?: S7DataTypes, transaction?: WriteTransaction<T> | GetTransaction<T>): void; /** * Turns a HMI-Key into a PLC-Key. Given the information about the mapping of the HMI to the PLC. * We also have to replace the Index-signatures for arrays with the correct syntax for the PLC (basically just wrapping the index in square bracktes and removing the dot before the square bracket) someObject.somearray.0 -> someObject.somearray[0] * @param key Hmi-Key * @returns */ protected hmiKeyToPlcKey(key: FlattenKeys<T>): string; /** * Inserts the PLC-Prefix based on the configured mapping. * @param key Hmi-Key * @returns PLC-Key */ protected insertPrefixMapping(key: FlattenKeys<T>): string; /** * This function should be the last function that is called in the collection of RPC Methods. * If the Set is empty by the time it reaches here, a ping gets added and a "slow Mode" is activated. * When we dont subscribe to any values, we dont need to poll the PLC as often as usual and clamp the polling delay to a higher value. Because the only things we need to poll are errors and the ping basically. * * @param objectSet Object set that stores the RPC-Methods */ protected collectStaticRPCMethodObjects(objectSet: Set<RPCMethodObject>): void; /** * Calls the get-Function for each configured initial-Cache key. * @returns */ protected loadInitialCacheData(): void; protected hmiKeysLoaded(key: FlattenKeys<T> | FlattenKeys<T>[]): boolean; protected toggleBackSlowMode(): void; get<K = S7DataTypes>(key: FlattenKeys<T> | FlattenKeys<T>[], cacheMode?: CacheMethod, depth?: number): Observable<K>; protected concatenateCacheFromKeys(keys: FlattenKeys<T>[]): S7DataTypes; write<K = S7DataTypes>(key: FlattenKeys<T>, value: K): Observable<S7DataTypes>; protected initDefaultErrorHandler(): void; subscribe<K = S7DataTypes>(key: FlattenKeys<T> | FlattenKeys<T>[], ignoreCache?: boolean): Observable<{ value: K; changedKey: string; }>; get currentUser(): string; can(permission: PlcPermissions): boolean; getPermissionsUpdates(): Observable<PlcPermissions[]>; login(user?: string, password?: string): Observable<true | number>; }