s7webserverapi
Version:
Unofficial Simatic-S7-Webserver JSON-RPC-API Client for S7-1200/1500 PLCs
207 lines (206 loc) • 13.2 kB
TypeScript
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, TicketID, WebAppBrowseResourcesResponse, WebAppBrowseResponse, 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<Function>;
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>, existingHttpClient?: RxJSHttpClient, ticketApiUrl?: string);
uploadFileToWebApp(application_name: string, filename: string, media_type: string, data: string, isProtected?: boolean, etag?: string, last_modified?: string | Date): Observable<boolean>;
protected getXAuthHeader(): {
"Content-Type": string;
"X-Auth-Token": string;
};
protected postRequest(url: string, headers: any, body: any, binary?: boolean): Observable<any>;
uploadToTicket(ticketId: any, data: TicketID): Observable<boolean>;
webAppCreate(name: string, enabled?: boolean): Observable<boolean>;
webAppDelete(name: string): Observable<boolean>;
webAppRename(name: string, new_name: string): Observable<true>;
webAppBrowse(name?: string): Observable<WebAppBrowseResponse>;
webAppSetState(name: string, enabled: boolean): Observable<boolean>;
webAppSetDefaultPage(name: string, resource_name: string): Observable<boolean>;
webAppSetNotFoundPage(name: string, resource_name: string): Observable<boolean>;
webAppBrowseResources(app_name: string, resource_name?: string): Observable<WebAppBrowseResourcesResponse>;
webAppCreateResource(app_name: string, resource_name: string, media_type: string, isProtected?: boolean, etag?: string, last_modified?: string | Date): Observable<TicketID>;
webAppDeleteResource(app_name: string, resource_name: string): Observable<boolean>;
webAppRenameResource(app_name: string, resource_name: string, resource_new_name: string): Observable<boolean>;
webAppDownloadResource(app_name: string, resource_name: string): Observable<TicketID>;
webAppSetResourceVisibility(app_name: string, resource_name: string, is_protected: boolean): Observable<boolean>;
webAppSetResourceETag(app_name: string, resource_name: string, etag: string): Observable<boolean>;
webAppSetResourceMediaType(app_name: string, resource_name: string, media_type: string): Observable<boolean>;
webAppSetResourceModificationTime(app_name: string, resource_name: string, last_modified: string | Date): Observable<boolean>;
webAppSetVersion(app_name: string, version: string): Observable<boolean>;
webAppSetUrlRedirectMode(app_name: string, redirect_mode: string): Observable<boolean>;
protected getFileDownloadTicket(path: string): Observable<string>;
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;
protected getAnyRPCMethodObject(method: string, params: any, id?: string): {
jsonrpc: string;
method: string;
params: any;
id: string;
};
protected debugLog(...arg0: any[]): void;
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 | {
code: RPCErrorCode;
message: string;
}>;
}