Even more documentation

This commit is contained in:
Daniel Kluge
2023-11-01 23:44:43 +01:00
parent 86dcd8d2f8
commit 591cf6662b
10 changed files with 506 additions and 8 deletions
+35
View File
@@ -5,14 +5,29 @@ import Traverser from "./helper/traverser";
import AIDParser from "./parser/AIDParser";
import { SubmodelElementCollection } from "@aas-core-works/aas-core3.0-typescript/dist/types/types";
/**
* The AIMC Mapper.
* @remarks
* This class is used to map properties t endpoints.
*/
export default class AIMCMapper {
/**
* The map.
*/
private map: AIMCMap = new Map();
/**
* Creates the mapper.
* @param env The AAS Environment
*/
public constructor(private readonly env: types.Environment) {
this.generate();
}
/**
* Generates the map from the AAS Environment.
*/
private generate(): void {
const aimc = AIMCParser.parse(this.env);
if (aimc === null) return;
@@ -51,14 +66,29 @@ export default class AIMCMapper {
}
}
/**
*
* @returns The complete map
*/
public getMap(): AIMCMap {
return this.map;
}
/**
* Get endpoint for a specific Element
* @param element Element
* @returns Endpoint description or undefined if not found
*/
public get(element: types.Class): ConnectionConfiguration | undefined {
return this.map.get(element);
}
/**
* Returns all endpoints for a idShort
* Can return multiple as idShorts are not necessarily unique
* @param idShort idShort of the element
* @returns Endpoints descriptions of elements with that idShort
*/
public getByIdShort(idShort: string): ConnectionConfiguration[] {
const result = [];
for (const [e, cc] of this.map.entries()) {
@@ -67,6 +97,11 @@ export default class AIMCMapper {
return result;
}
/**
* Get an element by its endpoint
* @param path Absolute endpoint path
* @returns Elements that use that path
*/
public reverseGet(path: string): types.Class[] {
const result = [];
+21
View File
@@ -2,7 +2,18 @@
import { readdirSync, readFileSync } from "fs";
import { types, jsonization } from "@aas-core-works/aas-core3.0-typescript";
/**
* Helper class to import AAS-Environments from files.
*/
export default class FileImporter {
/**
* Read an AAS-Environment from a file.
* @param path Filepath
* @returns Environment
* @throws Error if file is not a JSON file
* @throws Any IO error on file read operation
*/
public static readAASByPath(path: string): types.Environment {
if (!path.endsWith(".json")) throw new Error("File must be a JSON file");
@@ -15,6 +26,11 @@ export default class FileImporter {
return aasJson.mustValue();
}
/**
* Import all AAS-Environments from a directory.
* @param path Directory path
* @returns Environments
*/
public static readAllAASFromPath(path: string): types.Environment[] {
return FileImporter.getAllAASFilePaths(path).map(file => {
@@ -27,6 +43,11 @@ export default class FileImporter {
}).filter(aas => aas !== null) as types.Environment[];
}
/**
* Get all JSON files from a directory.
* @param aasPath Directory path
* @returns Paths of the JSON files
*/
private static getAllAASFilePaths(aasPath: string): string[] {
const files = readdirSync(aasPath);
return files.filter(file => file.endsWith(".json")).map(file => `${aasPath}/${file}`);
+63 -2
View File
@@ -3,28 +3,58 @@ import { types } from "@aas-core-works/aas-core3.0-typescript";
import { RelationshipElement } from "@aas-core-works/aas-core3.0-typescript/dist/types/types";
import { ResolvedRelationshipElement } from "../types/common";
/**
* Keytypes which are globally identifiable.
*/
const GLOBALLY_IDENTIFIABLES = [types.KeyTypes.GlobalReference, types.KeyTypes.AssetAdministrationShell, types.KeyTypes.ConceptDescription, types.KeyTypes.Identifiable, types.KeyTypes.Submodel]
/**
* Helper to traverse the AAS-Environment.
*/
export default class Traverser {
/**
* Find an AAS by its ID.
* @param environment Environment to search in
* @param id ID
* @returns AAS or null if not found
*/
public static findAASById(environment: types.Environment, id: string): types.AssetAdministrationShell | null {
if (environment.assetAdministrationShells === null) return null;
return (environment.assetAdministrationShells.find(aas => aas.id === id)) ?? null;
}
/**
* Find a Submodel by its ID.
* @param environment Environment to search in
* @param id ID
* @returns Submodel or null if not found
*/
public static findSMById(environment: types.Environment, id: string): types.Submodel | null {
if (environment.submodels === null) return null;
return (environment.submodels.find(sm => sm.id === id)) ?? null;
}
/**
* Find Submodel by its idShort.
* @param environment Environment to search in
* @param id idShort
* @returns Submodel or null if not found
*/
public static findSMByIdShort(environment: types.Environment, id: string): types.Submodel | null {
if (environment.submodels === null) return null;
return (environment.submodels.find(sm => sm.idShort === id)) ?? null;
}
/**
* Resolve a reference to an element.
* @param env Environment to search in
* @param ref Reference
* @returns Element or null if not found
*/
public static resolveReference(env: types.Environment, ref: types.Reference): types.Class | null {
if (ref.type === types.ReferenceTypes.ExternalReference) return null; // Not implemented
@@ -42,6 +72,12 @@ export default class Traverser {
return current;
}
/**
* Resolve both references of a relationship element
* @param env Environment to search in
* @param relationship Relationship element
* @returns Resolved relationship element or null if not found
*/
public static resolveRelationship(env: types.Environment, relationship: RelationshipElement): ResolvedRelationshipElement | null {
const first = Traverser.resolveReference(env, relationship.first);
const second = Traverser.resolveReference(env, relationship.second);
@@ -50,9 +86,15 @@ export default class Traverser {
return { first, second };
}
public static traverseByShortIds(start: types.Class, shortIds: string[]): types.Class | null {
/**
* Traverse downwards from a start element by using idShorts.
* @param start Start element
* @param idShorts Array of idShorts to traverse by
* @returns Target element at the end of the path or null if not found
*/
public static traverseByShortIds(start: types.Class, idShorts: string[]): types.Class | null {
let current: types.Class | null = start;
for (const shortId of shortIds) {
for (const shortId of idShorts) {
if (current === null) break;
current = Traverser.findChildByIdShort(current, shortId);
@@ -61,12 +103,25 @@ export default class Traverser {
return current;
}
/**
* Get element by using idShorts from an submodel.
* @param env Environment to search in
* @param submodelOrIdShort Submodel element or Submodel idShort
* @param idShorts idShorts to traverse by
* @returns Target element at the end of the path or null if not found
*/
public static getElementByIdPath(env: types.Environment, submodelOrIdShort: string | types.Submodel, idShorts: string[]): types.Class | null {
const sm = typeof submodelOrIdShort === "string" ? Traverser.findSMByIdShort(env, submodelOrIdShort) : submodelOrIdShort;
if (sm === null) return null;
return Traverser.traverseByShortIds(sm, idShorts);
}
/**
* Find an element using a function to check.
* @param start Start element
* @param checkFunction Function to find the element. First element which evaluates to true using this function will be returned.
* @returns Element or null if not found
*/
public static findElement(start: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null {
for (const element of start.descend()) {
if (checkFunction(element)) return element;
@@ -75,6 +130,12 @@ export default class Traverser {
return null;
}
/**
* Find a child element by its idShort.
* @param start Start element
* @param idShort idShort to search for
* @returns Child element or null if not found
*/
public static findChildByIdShort(start: types.Class, idShort: string): types.Class | null {
for (const child of start.descendOnce()) {
if ((child as any).idShort === idShort) return child;
+99 -1
View File
@@ -7,48 +7,146 @@ type ConnectionType = "ON_DEMAND" | "PERMANENT";
export type OnEventCallback = (response: any) => void
/**
* Abstract class for an connector to an asset.
* This should be used as base class for your own connectors.
*/
export default abstract class InterfaceConnectionObject<ConfigInterface> {
/**
* A name for your connector.
* @remarks
* Currently not used.
*/
public static readonly connectorName: string;
/**
* Which protocols are supported by your connector.
* This is really important later so the AAS can decide which connector to use!
*/
public static readonly uriProtocol: string[]|string;
/**
* The type of connection your connector uses.
* @remarks
* Currently not used.
*/
public static readonly connectionType: ConnectionType;
/**
* Whether your connector supports subscriptions.
* @remarks
* Currently not used.
*/
public static readonly supportsSubscriptions: boolean;
/**
* A store for all observers and their callbacks.
*/
protected readonly observerStore: Record<any, ((value: any) => void)[]> = {};
/**
* A store for all event subscriptions and their callbacks.
*/
protected readonly eventSubStore: Record<any, ((value: any) => void)[]> = {};
/**
* A store for all async action handles and their results.
*/
protected readonly asyncActionStateStore: Record<any, {finished: boolean, result: any}> = {};
/**
* Creates the connector.
* @param connectionParameter Connection parameter for your connector.
* @param endpointMetadata {@link EndpointMetadata}
* @param mapper {@link AIMCMapper}
* @param onConnectorEvent Callback when an event is received.
*/
public constructor(
protected readonly connectionParameter: ConfigInterface,
public readonly endpointMetadata: EndpointMetadata,
protected readonly mapper: AIMCMapper,
protected readonly onConnectorEvent: OnEventCallback) {}
/**
* This should connect the Connector to the asset.
* @returns Whether the connection was successful.
*/
public abstract connect(): boolean;
/**
* This should disconnect the Connector from the asset (if it even is connected).
*/
public abstract disconnect(): void;
/**
* Read a property value from the asset.
* @param prop Property
* @returns Value of property casted to the type it says it should be.
*/
public abstract readProperty(prop: types.Property): void;
/**
* Write a property value to the asset.
* @param prop Property
* @param value Value to write.
*/
public abstract writeProperty(prop: types.Property, value: any): void;
/**
* Observe a property value from the asset.
* @param prop Property
* @param callback Callback to call when the property changes.
* @returns Whether the creation of an observer was successful.
*/
public abstract observeProperty(prop: types.Property, callback: (value: any) => void): boolean;
/**
* Call an action on the asset synchronously.
* @param action Action
* @param args Arguments
* @returns Return value of the action.
*/
public abstract callActionSync(action: types.Operation, args: Record<string, any>): any;
/**
* Call an action on the asset asynchronously.
* @param action Action
* @param args Arguments
* @returns Handle for the async action.
*/
public abstract callActionAsync(action: types.Operation, args: Record<string, any>): string | null;
public readAsyncActionState(handle: string): any {
/**
* Read the state of an async action.
* @param handle Handle of the async action.
* @returns Whether the async action is finished.
*/
public readAsyncActionState(handle: string): boolean {
return this.asyncActionStateStore[handle].finished;
}
/**
* Read the result of an async action.
* @param handle Handle of the async action.
* @returns Result of the async action.
*/
public readAsyncActionResponse(handle: string): any {
return this.asyncActionStateStore[handle].result;
};
/**
* Subscribe to an event.
* @param event Event
* @param callback Callback to call when the event occurs.
* @returns Whether the subscription was successful.
*/
public abstract subscribeEvent(event: types.BasicEventElement, callback: (event: types.BasicEventElement) => void): boolean;
/**
* Unsubscribe from an event.
* @param event Event
*/
public abstract unsubscribeEvent(event: types.BasicEventElement): void;
/**
* Generate a handle for an async action.
* @returns Handle
*/
protected generateAsyncHandle() {
const handle = v4();
this.asyncActionStateStore[handle] = {finished: false, result: null};
+56 -2
View File
@@ -27,15 +27,46 @@ type InterfaceConnectionEntry<ConfigInterface> = {
config: ConfigInterface
}
/**
* The core of the library.
* @remarks
* Here are all Interface Servers and Connectors are created and managed.
* Also every request is handled here.
*/
export default class MultiMessageBroker {
private static instance: MultiMessageBroker|null = null;
/**
* Whether the broker is prepared.
*/
private prepared: boolean = false;
/**
* All registered AASs.
*/
private aasRegistrations: AASRegistrationPrepared[] = [];
/**
* All registered Interface Connectors.
*/
private interfaceConnections: InterfaceConnectionEntry<any>[] = [];
public constructor() {}
private constructor() {}
public registerAAS<T>(registration: AASRegistration): void {
/**
* Get the singleton instance of the broker.
*/
public static getInstance(): MultiMessageBroker {
if (this.instance === null) this.instance = new MultiMessageBroker();
return this.instance;
}
/**
* Register an AAS.
* @remarks
* This will also create the {@link AIMCMapper} for the AAS
* @param registration AAS registration
*/
public registerAAS(registration: AASRegistration): void {
this.aasRegistrations.push({
...registration,
serverInstances: [],
@@ -44,10 +75,20 @@ export default class MultiMessageBroker {
});
}
/**
* Register an Interface Connector.
* @param interfaceConnectionEntry Interface connector registration
* @typeParam T - The config interface for your interface connector.
*/
public registerInterfaceConnection<T>(interfaceConnectionEntry: InterfaceConnectionEntry<T>): void {
this.interfaceConnections.push(interfaceConnectionEntry);
}
/**
* Prepare the broker.
* @remarks
* This will create instances of the {@link AASInterfaceServer} and {@link InterfaceConnectionObject} classes.
*/
public prepare(): void {
for (const registration of this.aasRegistrations) {
@@ -92,6 +133,9 @@ export default class MultiMessageBroker {
this.prepared = true;
}
/**
* Start the broker.
*/
public start(): void {
if (!this.prepared) this.prepare();
@@ -101,6 +145,12 @@ export default class MultiMessageBroker {
}
}
/**
* Callback on an server request
* @param request Request
* @param registration AAS Registration
* @returns Value or success state or nothing based on the request type
*/
private onInterfaceRequest(request: Request, registration: AASRegistrationPrepared): any {
const getConnector = (target: types.Class) => {
@@ -162,6 +212,10 @@ export default class MultiMessageBroker {
}
}
/**
* Callback on a connector event.
* @param response
*/
private onConnectorEvent(response: any) {
for (const aas of this.aasRegistrations) {
for (const server of aas.serverInstances ?? []) {
+38 -1
View File
@@ -1,18 +1,55 @@
import { types } from "@aas-core-works/aas-core3.0-typescript";
import type { Request, OnRequestCallback } from "./types/requests";
import type { OnRequestCallback } from "./types/requests";
/**
* Abstract class for an interface server.
* This should be used as base class for your own interface servers!
*
* @typeParam ConfigInterface - The config interface for your interface server.
*/
export default abstract class AASInterfaceServer<ConfigInterface> {
/**
* A name for your interface server.
* @remarks
* Currently unused
*/
public static readonly serverInterfaceName: string;
/**
* Whether your interface server supports subscriptions.
* @remarks
* Currently unused
*/
public static readonly supportsSubscriptions: boolean;
/**
* Crate the interface server.
* @param config Interface server config
* @param aas AAS Environment for the server
* @param onRequestCallback Callback when a request is received and parsed
*/
constructor(
protected readonly config: ConfigInterface,
protected readonly aas: types.Environment,
protected readonly onRequestCallback: OnRequestCallback) {}
/**
* Prepare the interface server.
* @remarks
* Here you can create routes, listeners, callbacks,...
*/
public abstract prepare(): void;
/**
* Run the interface server.
*/
public abstract run(): void;
/**
* Stop the interface server.
*/
public abstract stop(): void;
/**
* Notify observers/subscribers about an event.
* @param event Event to notify the interface server about
*/
public abstract notify(event: any): void;
}