diff --git a/code/package-lock.json b/code/package-lock.json index be5dd1d..1ada898 100644 --- a/code/package-lock.json +++ b/code/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "axios": "^1.7.2", + "cors": "^2.8.5", "express": "^4.19.2", "json-ignore": "^0.4.0", "sdp-transform": "^2.14.2", @@ -646,6 +647,18 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1474,6 +1487,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -2887,6 +2908,15 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3470,6 +3500,11 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", diff --git a/code/package.json b/code/package.json index a94dec0..cccb4f3 100644 --- a/code/package.json +++ b/code/package.json @@ -23,6 +23,7 @@ "license": "ISC", "dependencies": { "axios": "^1.7.2", + "cors": "^2.8.5", "express": "^4.19.2", "json-ignore": "^0.4.0", "sdp-transform": "^2.14.2", diff --git a/code/src/NCModel/Blocks.ts b/code/src/NCModel/Blocks.ts index dce2a42..ec95352 100644 --- a/code/src/NCModel/Blocks.ts +++ b/code/src/NCModel/Blocks.ts @@ -6,20 +6,34 @@ import { INotificationContext } from '../SessionManager'; import { myIdDecorator, NcBlockMemberDescriptor, + NcBulkValuesHolder, NcClassDescriptor, NcElementId, NcMethodDescriptor, NcMethodStatus, NcObject, + NcObjectPropertiesHolder, + NcObjectPropertiesSetValidation, NcParameterDescriptor, + NcPropertyChangeType, NcPropertyConstraints, NcPropertyDescriptor, - NcTouchpoint } from './Core'; + NcPropertyId, + NcPropertyRestoreNotice, + NcPropertyRestoreNoticeType, + NcPropertyValueHolder, + NcRestoreMode, + NcRestoreValidationStatus, + NcTouchpoint, + RestoreArguments } from './Core'; +import { ExampleControl } from './Features'; export class NcBlock extends NcObject { public static staticClassID: number[] = [ 1, 1 ]; + public static readonly RootOid: number = 1; + @myIdDecorator('1p1') public override classID: number[] = NcBlock.staticClassID; @@ -31,6 +45,10 @@ export class NcBlock extends NcObject public memberObjects: NcObject[]; + protected maxMembers: number | null; + + protected rootContext: IRootContext | null; + public constructor( oid: number, constantOid: boolean, @@ -42,9 +60,16 @@ export class NcBlock extends NcObject enabled: boolean, memberObjects: NcObject[], description: string, - notificationContext: INotificationContext) + notificationContext: INotificationContext, + rootContext: IRootContext | null, + maxMembers: number | null = null, + isRebuildable: boolean = false) { - super(oid, constantOid, ownerObject, role, userLabel, touchpoints, runtimePropertyConstraints, description, notificationContext); + super(oid, constantOid, ownerObject, role, userLabel, touchpoints, runtimePropertyConstraints, description, notificationContext, isRebuildable); + + this.maxMembers = maxMembers; + + this.rootContext = rootContext; this.enabled = enabled; this.memberObjects = memberObjects; @@ -93,7 +118,7 @@ export class NcBlock extends NcObject return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); } - public override InvokeMethod(socket: WebSocketConnection, oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue + public override InvokeMethod(oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue { if(oid == this.oid) { @@ -303,7 +328,7 @@ export class NcBlock extends NcObject return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'Invalid arguments provided'); } default: - return super.InvokeMethod(socket, oid, methodId, args, handle); + return super.InvokeMethod(oid, methodId, args, handle); } } @@ -388,10 +413,13 @@ export class NcBlock extends NcObject return currentClassDescriptor; } - public UpdateMembers(memberObjects: NcObject[]) + public UpdateMembers(memberObjects: NcObject[], notify: boolean = false) { this.memberObjects = memberObjects; this.members = this.memberObjects.map(x => x.GenerateMemberDescriptor()); + + if(notify) + this.notificationContext.NotifyPropertyChanged(this.oid, new NcElementId(2, 2), NcPropertyChangeType.ValueChanged, this.members, null); } public GenerateMemberDescriptors(recurse: boolean) : NcBlockMemberDescriptor[] @@ -463,6 +491,63 @@ export class NcBlock extends NcObject return new Array(); } + public FindMemberByRolePath(rolePath: string[]) : NcObject | null + { + if(rolePath.length == 1 && rolePath[0] == this.role) + { + return this; + } + else if(rolePath.length > 1 && rolePath[0] == this.role) + { + let childRole = rolePath[1]; + if(this.memberObjects != null) + { + let member = this.memberObjects.find(e => e.role === childRole); + if(member) + { + if(rolePath.length == 2) + { + return member; + } + else if(member instanceof NcBlock) + { + let furtherPath = rolePath.splice(1); + return member.FindMemberByRolePath(furtherPath); + } + else + return null; + } + else + return null; + } + else + return null; + } + else + return null; + } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let holders = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(2, 1), "enabled", "NcBoolean", true, this.enabled), + new NcPropertyValueHolder(new NcPropertyId(2, 2), "members", "NcBlockMemberDescriptor", true, this.members) + ], this.isRebuildable) + ]; + + holders[0].values = holders[0].values.concat(super.GetAllProperties(recurse)[0].values); + + if(recurse) + { + this.memberObjects.forEach(member => { + holders = holders.concat(member.GetAllProperties(recurse)); + }); + } + + return holders + } + public GenerateMemberDescriptorsByRole(role: string, caseSensitive: boolean, matchWholeString: boolean, recurse: boolean) : NcBlockMemberDescriptor[] { if(this.memberObjects != null) @@ -528,12 +613,144 @@ export class NcBlock extends NcObject else return new Array() } + + public GetRolePathUrls(): string[] + { + let urls = new Array(); + + urls = urls.concat(this.GetRolePathUrl()); + + this.memberObjects.forEach(member => { + if(member instanceof NcBlock) + urls = urls.concat(member.GetRolePathUrls()); + else + urls = urls.concat(member.GetRolePathUrl()); + }); + + return urls; + } + + public GetRolePathForMember(role: string): string[] + { + return this.GetRolePath().concat(role); + } + + public ReconstructMembers(members: NcBlockMemberDescriptor[], dataSet: NcBulkValuesHolder, applyChanges: Boolean = true) : [NcPropertyRestoreNotice | null, NcObjectPropertiesSetValidation[]] + { + //Left intentionally empty as "virtual" so that rebuildable blocks override this with the desired behaviour + return [null, []]; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => + { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + + if(restoreArguments.restoreMode == NcRestoreMode.Rebuild) + { + if(propertyId != '1p6' && propertyId != '2p2') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else + { + //Perform further validation + if(propertyId == '2p2') + { + //Structural changes to the members + let membersData = propertyData.value as NcBlockMemberDescriptor[]; + if(membersData) + { + //TODO: Might need to include the list of members in the outcomes of ReconstructMembers so that we can skip them from further restore actions because they would have already been applied when creating the member objects + + let outcomes = this.ReconstructMembers(membersData, restoreArguments.dataSet, applyChanges); + if(outcomes[0] != null) + myNotices.push(outcomes[0]); + + validationEntries = validationEntries.concat(outcomes[1]); + } + else + { + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + `Cannot reconstruct members because the members data is null`)); + console.log(`Cannot reconstruct members because the members data is null`); + } + } + else if(applyChanges) + { + //Perform further validations + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + } + } + else + { + if(propertyId == '2p2') + { + if(this.isRebuildable) + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched unless restoreMode is changed to Rebuild")); + else + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + } + else if(propertyId != '1p6') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validations + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + if(restoreArguments.recurse) + { + this.memberObjects.forEach(member => { + validationEntries = validationEntries.concat(member.Restore(restoreArguments, applyChanges)); + }); + } + + return validationEntries; + } +} + +export interface IRootContext +{ + AllocateOid(path: string) : number } -export class RootBlock extends NcBlock +export class RootBlock extends NcBlock implements IRootContext { + private oidAllocations: { [id: number] : string; } = {}; + public constructor( - oid: number, constantOid: boolean, ownerObject: NcObject | null, role: string, @@ -543,10 +760,11 @@ export class RootBlock extends NcBlock enabled: boolean, memberObjects: NcObject[], description: string, - notificationContext: INotificationContext) + notificationContext: INotificationContext, + maxMembers: number | null = null) { super( - oid, + NcBlock.RootOid, constantOid, ownerObject, role, @@ -556,7 +774,19 @@ export class RootBlock extends NcBlock enabled, memberObjects, description, - notificationContext); + notificationContext, + null, + maxMembers); + + this.oidAllocations[NcBlock.RootOid] = this.GetRolePathUrl(); + } + + public AllocateOid(path: string) : number + { + //Generate new oid + let newOid = Object.keys(this.oidAllocations).length + 1; + this.oidAllocations[newOid] = path; + return newOid; } public ProcessMessage(msg: string, socket: WebSocketConnection) @@ -678,12 +908,12 @@ export class RootBlock extends NcBlock else { if(commandMsg.oid == this.oid) - return this.InvokeMethod(socket, commandMsg.oid, commandMsg.methodId, commandMsg.arguments, commandMsg.handle); + return this.InvokeMethod(commandMsg.oid, commandMsg.methodId, commandMsg.arguments, commandMsg.handle); else if(this.memberObjects != null) { let member = this.FindNestedMember(commandMsg.oid); if(member) - return member.InvokeMethod(socket, commandMsg.oid, commandMsg.methodId, commandMsg.arguments, commandMsg.handle); + return member.InvokeMethod(commandMsg.oid, commandMsg.methodId, commandMsg.arguments, commandMsg.handle); else return new CommandResponseError(commandMsg.handle, NcMethodStatus.BadOid, "OID could not be found"); } @@ -705,4 +935,180 @@ export class RootBlock extends NcBlock return true; return false; } +} + +export class ExampleControlsBlock extends NcBlock +{ + public constructor( + oid: number, + constantOid: boolean, + ownerObject: NcObject | null, + role: string, + userLabel: string, + touchpoints: NcTouchpoint[] | null, + runtimePropertyConstraints: NcPropertyConstraints[] | null, + enabled: boolean, + memberObjects: NcObject[], + description: string, + notificationContext: INotificationContext, + rootContext: IRootContext | null, + maxMembers: number | null = null, + isRebuildable: boolean) + { + super( + oid, + constantOid, + ownerObject, + role, + userLabel, + touchpoints, + runtimePropertyConstraints, + enabled, + memberObjects, + description, + notificationContext, + rootContext, + maxMembers, + isRebuildable); + } + + public override ReconstructMembers(members: NcBlockMemberDescriptor[], dataSet: NcBulkValuesHolder, applyChanges: Boolean = true) : [NcPropertyRestoreNotice | null, NcObjectPropertiesSetValidation[]] + { + let blockMembersNotice: NcPropertyRestoreNotice | null = null; + + let validationEntries = new Array(); + + console.log(`Reconstructing members, count: ${members.length}, applyChanges: ${applyChanges}`); + + let controlMembers: ExampleControl[] = []; + + members.forEach(member => { + if(member.classId.join() == ExampleControl.staticClassID.join()) + { + if(this.maxMembers != null) + { + if(controlMembers.length < this.maxMembers) + { + if(this.rootContext != null) + { + let memberRolepath = this.GetRolePathForMember(member.role); + + let setValidation: NcObjectPropertiesSetValidation; + + let memberRestoreData = dataSet.values.find(f => f.path.join('.') == memberRolepath.join('.')) + if(memberRestoreData) + setValidation = this.ValidateMemberDatasetChunk(memberRestoreData); + else + setValidation = new NcObjectPropertiesSetValidation(memberRolepath, NcRestoreValidationStatus.Ok, [], "No dataset information passed but object was created successfully with defaults") + + validationEntries = validationEntries.concat(setValidation); + + if(applyChanges) + { + const exampleControl = new ExampleControl( + this.rootContext.AllocateOid(memberRolepath.join('.')), + true, + this, + member.role, + member.userLabel ?? member.role, + [], + null, + true, + "Example control worker", + this.notificationContext, + true, + memberRestoreData); + + controlMembers.push(exampleControl); + } + } + } + else + { + blockMembersNotice = new NcPropertyRestoreNotice(new NcPropertyId(2, 2), "members", NcPropertyRestoreNoticeType.Warning, `Member [${member.role}] can't be constructed because it goes over the maximum block members limit of ${this.maxMembers}`); + console.log(`Member [${member.role}] can't be constructed because it goes over the maximum block members limit of ${this.maxMembers}`); + } + } + else + { + if(this.rootContext != null) + { + let memberRolepath = this.GetRolePathForMember(member.role); + + let setValidation: NcObjectPropertiesSetValidation; + + let memberRestoreData = dataSet.values.find(f => f.path.join('.') == memberRolepath.join('.')) + if(memberRestoreData) + setValidation = this.ValidateMemberDatasetChunk(memberRestoreData); + else + setValidation = new NcObjectPropertiesSetValidation(memberRolepath, NcRestoreValidationStatus.Ok, [], "No dataset information passed but object was created successfully with defaults") + + validationEntries = validationEntries.concat(setValidation); + + if(applyChanges) + { + const exampleControl = new ExampleControl( + this.rootContext.AllocateOid(memberRolepath.join('.')), + true, + this, + member.role, + member.userLabel ?? member.role, + [], + null, + true, + "Example control worker", + this.notificationContext, + true, + memberRestoreData); + + controlMembers.push(exampleControl); + } + } + } + } + else + { + blockMembersNotice = new NcPropertyRestoreNotice(new NcPropertyId(2, 2), "members", NcPropertyRestoreNoticeType.Warning, `Member [${member.role}] can't be constructed because it has an invalid classId of ${member.classId.join('.')}`); + console.log(`Member [${member.role}] can't be constructed because it has an invalid classId of ${member.classId.join('.')}`); + } + }); + + if(applyChanges) + this.UpdateMembers(controlMembers, true); + + return [blockMembersNotice, validationEntries]; + } + + public ValidateMemberDatasetChunk(holder: NcObjectPropertiesHolder) : NcObjectPropertiesSetValidation + { + let myNotices = new Array(); + + holder.values.forEach(propertyData => + { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + switch(propertyId) + { + case '1p6': + case '2p1': + case '3p1': + case '3p2': + case '3p3': + case '3p4': + case '3p5': + case '3p9': + case '3p10': + case '3p11': + case '3p12': + case '3p13': + { + //TODO: Perform further validations + } + break; + default: + myNotices.push(new NcPropertyRestoreNotice(propertyData.id, propertyData.name, NcPropertyRestoreNoticeType.Warning, "Property can't be changed and will receive a default value")); + } + }); + + return new NcObjectPropertiesSetValidation(holder.path, NcRestoreValidationStatus.Ok, myNotices, null) + } } \ No newline at end of file diff --git a/code/src/NCModel/Core.ts b/code/src/NCModel/Core.ts index a8ab1aa..3e53aa7 100644 --- a/code/src/NCModel/Core.ts +++ b/code/src/NCModel/Core.ts @@ -1,6 +1,5 @@ import { jsonIgnoreReplacer, jsonIgnore } from 'json-ignore'; import { CommandResponseError, CommandResponseNoValue, CommandResponseWithValue } from '../NCProtocol/Commands'; -import { WebSocketConnection } from '../Server'; import { INotificationContext } from '../SessionManager'; export function myIdDecorator(identity: string) { @@ -49,6 +48,8 @@ export abstract class NcObject public ownerObject: NcObject | null; + protected isRebuildable: boolean; + public constructor( oid: number, constantOid: boolean, @@ -58,7 +59,8 @@ export abstract class NcObject touchpoints: NcTouchpoint[] | null, runtimePropertyConstraints: NcPropertyConstraints[] | null, description: string, - notificationContext: INotificationContext) + notificationContext: INotificationContext, + isRebuildable: boolean = false) { this.oid = oid; this.constantOid = constantOid; @@ -70,6 +72,8 @@ export abstract class NcObject this.runtimePropertyConstraints = runtimePropertyConstraints; this.description = description; this.notificationContext = notificationContext; + + this.isRebuildable = isRebuildable; } //'1m1' @@ -135,13 +139,42 @@ export abstract class NcObject return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); } - public InvokeMethod(socket: WebSocketConnection, oid: number, methodId: NcElementId, args: { [key: string]: any } | null, handle: number) : CommandResponseNoValue + public InvokeMethod(oid: number, methodId: NcElementId, args: { [key: string]: any } | null, handle: number) : CommandResponseNoValue { if(oid == this.oid) { let key: string = `${methodId.level}m${methodId.index}`; switch(key) { + case '1m1': //Get + { + if(args != null && + 'id' in args) + { + let propertyId = args['id'] as NcElementId; + if(propertyId) + return this.Get(this.oid, propertyId, handle); + else + return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'Invalid id argument provided'); + } + else + return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'Invalid arguments provided'); + } + case '1m2': //Set + { + if(args != null && + 'id' in args && + 'value' in args) + { + let propertyId = args['id'] as NcElementId; + if(propertyId) + return this.Set(this.oid, propertyId, args['value'], handle); + else + return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'Invalid id argument provided'); + } + else + return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'Invalid arguments provided'); + } case '1m3': //GetSequenceItem { if(args != null && @@ -343,6 +376,22 @@ export abstract class NcObject ); } + public GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + return [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(1, 1), "classId", "NcClassId", true, this.classID), + new NcPropertyValueHolder(new NcPropertyId(1, 2), "oid", "NcOid", true, this.oid), + new NcPropertyValueHolder(new NcPropertyId(1, 3), "constantOid", "NcBoolean", true, this.constantOid), + new NcPropertyValueHolder(new NcPropertyId(1, 4), "owner", "NcOid", true, this.owner), + new NcPropertyValueHolder(new NcPropertyId(1, 5), "role", "NcString", true, this.role), + new NcPropertyValueHolder(new NcPropertyId(1, 6), "userLabel", "NcString", false, this.userLabel), + new NcPropertyValueHolder(new NcPropertyId(1, 7), "touchpoints", "NcTouchpoint", true, this.touchpoints), + new NcPropertyValueHolder(new NcPropertyId(1, 8), "runtimePropertyConstraints", "NcPropertyConstraints", true, this.runtimePropertyConstraints), + ], this.isRebuildable) + ]; + } + public GetRolePath(): string[] { let rolePath: string[] = []; @@ -354,6 +403,13 @@ export abstract class NcObject return rolePath; } + + public GetRolePathUrl(): string + { + return this.GetRolePath().join('.') + "/"; + } + + public abstract Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] } export class NcElementId extends BaseType @@ -379,6 +435,34 @@ export class NcElementId extends BaseType ], null, null, "Class element id which contains the level and index"); } + public static ToPropertyId(id: string) : NcPropertyId | null + { + let split = id.split('p'); + if(split.length == 2) + return new NcPropertyId(Number(split[0] || 0), Number(split[1] || 0)); + else + return null; + } + + public static ToMethodId(id: string) : NcMethodId | null + { + let split = id.split('m'); + if(split.length == 2) + return new NcMethodId(Number(split[0] || 0), Number(split[1] || 0)); + else + return null; + } + + public static ToPropertyString(id: NcElementId) : string + { + return `${id.level}p${id.index}`; + } + + public static ToMethodString(id: NcElementId) : string + { + return `${id.level}m${id.index}`; + } + public ToJson() { return JSON.stringify(this, jsonIgnoreReplacer); @@ -1809,4 +1893,294 @@ export class NcDatatypeDescriptorEnum extends NcDatatypeDescriptor { return JSON.stringify(this, jsonIgnoreReplacer); } -} \ No newline at end of file +} + +export class NcPropertyValueHolder extends BaseType +{ + public id: NcPropertyId; + public name: string; + public typeName: string | null; + public isReadOnly: boolean; + public value: any; + + public constructor( + id: NcPropertyId, + name: string, + typeName: string | null, + isReadOnly: boolean, + value: any) + { + super(); + + this.id = id; + this.name = name; + this.typeName = typeName; + this.isReadOnly = isReadOnly; + this.value = value; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + return new NcDatatypeDescriptorStruct("NcPropertyValueHolder", [ + new NcFieldDescriptor("id", "NcPropertyId", false, false, null, "Property id"), + new NcFieldDescriptor("name", "NcString", false, false, null, "Property name"), + new NcFieldDescriptor("typeName", "NcName", true, false, null, "Property type name. If null it means the type is any"), + new NcFieldDescriptor("isReadOnly", "NcBoolean", false, false, null, "Is the property ReadOnly?"), + new NcFieldDescriptor("value", null, true, false, null, "Property value"), + ], null, null, "Property value holder descriptor"); + } +} + +export class NcObjectPropertiesHolder extends BaseType +{ + public path: string[]; + public dependencyPaths: string[][] + public values: NcPropertyValueHolder[]; + public isRebuildable: boolean; + + public constructor( + path: string[], + dependencyPaths: string[][], + values: NcPropertyValueHolder[], + isRebuildable: boolean) + { + super(); + + this.path = path; + this.dependencyPaths = dependencyPaths; + this.values = values; + this.isRebuildable = isRebuildable; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + return new NcDatatypeDescriptorStruct("NcObjectPropertiesHolder", [ + new NcFieldDescriptor("path", "NcRolePath", false, false, null, "Object role path"), + new NcFieldDescriptor("dependencyPaths", "NcRolePath", false, true, null, "Sequence of role paths which are a dependency for this object (helpful to inform clients which objects need to be restored together)"), + new NcFieldDescriptor("values", "NcPropertyValueHolder", false, true, null, "Object properties values"), + new NcFieldDescriptor("isRebuildable", "NcBoolean", false, false, null, "Describes if the object is rebuildable"), + ], null, null, "Object properties holder descriptor"); + } +} + +export class NcBulkValuesHolder extends BaseType +{ + public validationFingerprint: string | null; + public values: NcObjectPropertiesHolder[]; + + public constructor( + validationFingerprint: string | null, + values: NcObjectPropertiesHolder[]) + { + super(); + + this.validationFingerprint = validationFingerprint; + this.values = values; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + return new NcDatatypeDescriptorStruct("NcBulkValuesHolder", [ + new NcFieldDescriptor("validationFingerprint", "NcString", true, false, null, "Optional vendor specific fingerprinting mechanism used for validation purposes"), + new NcFieldDescriptor("values", "NcObjectPropertiesHolder", false, true, null, "Values by rolePath") + ], null, null, "Bulk values holder descriptor"); + } +} + +export class NcMethodResultBulkValuesHolder extends NcMethodResult +{ + public value: NcBulkValuesHolder; + + public constructor( + status: NcMethodStatus, + value: NcBulkValuesHolder) + { + super(status); + + this.value = value; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + let currentClassDescriptor = new NcDatatypeDescriptorStruct("NcMethodResultBulkValuesHolder", [ + new NcFieldDescriptor("value", "NcBulkValuesHolder", false, false, null, "Bulk values holder value") + ], "NcMethodResult", null, "Bulk values holder result") + + if(includeInherited) + { + let baseDescriptor = super.GetTypeDescriptor(includeInherited); + + let baseDescriptorStruct = baseDescriptor as NcDatatypeDescriptorStruct; + if(baseDescriptorStruct) + currentClassDescriptor.fields = currentClassDescriptor.fields.concat(baseDescriptorStruct.fields); + } + + return currentClassDescriptor; + } +} + +export class ConfigApiValue +{ + public value: any; + + constructor( + value: any) + { + this.value = value; + } +} + +export class ConfigApiArguments +{ + public arguments: { [key: string]: any } | null; + + constructor( + configArguments: { [key: string]: any } | null) + { + this.arguments = configArguments; + } +} + +export class RestoreBody +{ + public arguments: RestoreArguments; + + constructor( + restoreArguments: RestoreArguments) + { + this.arguments = restoreArguments; + } +} + +export enum NcRestoreMode +{ + Modify = 0, + Rebuild = 1 +} + +export class RestoreArguments +{ + public dataSet: NcBulkValuesHolder; + public recurse: boolean; + public restoreMode: NcRestoreMode; + + constructor( + dataSet: NcBulkValuesHolder, + recurse: boolean, + restoreMode: NcRestoreMode) + { + this.dataSet = dataSet; + this.recurse = recurse; + this.restoreMode = restoreMode; + } +} + +export enum NcRestoreValidationStatus +{ + Ok = 200, + Failed = 400, + NotFound = 404, + DeviceError = 500 +} + +export enum NcPropertyRestoreNoticeType +{ + Warning = 300, + Error = 400 +} + +export class NcPropertyRestoreNotice extends BaseType +{ + public id: NcPropertyId; + public name: string; + public noticeType: NcPropertyRestoreNoticeType; + public noticeMessage: string; + + public constructor( + id: NcPropertyId, + name: string, + noticeType: NcPropertyRestoreNoticeType, + noticeMessage: string) + { + super(); + + this.id = id; + this.name = name; + this.noticeType = noticeType; + this.noticeMessage = noticeMessage; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + return new NcDatatypeDescriptorStruct("NcPropertyRestoreNotice", [ + new NcFieldDescriptor("id", "NcPropertyId", false, false, null, "Property id"), + new NcFieldDescriptor("name", "NcName", false, false, null, "Property name"), + new NcFieldDescriptor("noticeType", "NcPropertyRestoreNoticeType", false, false, null, "Property restore notice type"), + new NcFieldDescriptor("noticeMessage", "NcString", false, false, null, "Property restore notice message") + ], null, null, "Property restore notice descriptor"); + } +} + +export class NcObjectPropertiesSetValidation extends BaseType +{ + public path: string[]; + public status: NcRestoreValidationStatus; + public notices: NcPropertyRestoreNotice[]; + public statusMessage: string | null; + + public constructor( + path: string[], + status: NcRestoreValidationStatus, + notices: NcPropertyRestoreNotice[], + statusMessage: string | null) + { + super(); + + this.path = path; + this.status = status; + this.notices = notices; + this.statusMessage = statusMessage; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + return new NcDatatypeDescriptorStruct("NcObjectPropertiesSetValidation", [ + new NcFieldDescriptor("path", "NcRolePath", false, false, null, "Object role path"), + new NcFieldDescriptor("status", "NcRestoreValidationStatus", false, false, null, "Validation status"), + new NcFieldDescriptor("notices", "NcPropertyRestoreNotice", false, true, null, "Validation property notices"), + new NcFieldDescriptor("statusMessage", "NcString", true, false, null, "Validation status message"), + ], null, null, "Object properties bulk set validation"); + } +} + +export class NcMethodResultObjectPropertiesSetValidation extends NcMethodResult +{ + public value: NcObjectPropertiesSetValidation[]; + + public constructor( + status: NcMethodStatus, + value: NcObjectPropertiesSetValidation[]) + { + super(status); + + this.value = value; + } + + public static override GetTypeDescriptor(includeInherited: boolean): NcDatatypeDescriptor + { + let currentClassDescriptor = new NcDatatypeDescriptorStruct("NcMethodResultObjectPropertiesSetValidation", [ + new NcFieldDescriptor("value", "NcObjectPropertiesSetValidation", false, true, null, "Object properties set path validations") + ], "NcMethodResult", null, "Object properties bulk set validation result") + + if(includeInherited) + { + let baseDescriptor = super.GetTypeDescriptor(includeInherited); + + let baseDescriptorStruct = baseDescriptor as NcDatatypeDescriptorStruct; + if(baseDescriptorStruct) + currentClassDescriptor.fields = currentClassDescriptor.fields.concat(baseDescriptorStruct.fields); + } + + return currentClassDescriptor; + } +} diff --git a/code/src/NCModel/Features.ts b/code/src/NCModel/Features.ts index 20d0f2a..8e344d2 100644 --- a/code/src/NCModel/Features.ts +++ b/code/src/NCModel/Features.ts @@ -1,6 +1,5 @@ import { jsonIgnoreReplacer, jsonIgnore } from 'json-ignore'; import { CommandResponseError, CommandResponseNoValue, CommandResponseWithValue } from '../NCProtocol/Commands'; -import { WebSocketConnection } from '../Server'; import { INotificationContext } from '../SessionManager'; import { BaseType, @@ -14,13 +13,21 @@ import { NcMethodResult, NcMethodStatus, NcObject, + NcObjectPropertiesHolder, + NcObjectPropertiesSetValidation, NcParameterConstraintsNumber, NcParameterConstraintsString, NcParameterDescriptor, NcPropertyChangeType, NcPropertyConstraints, NcPropertyDescriptor, - NcTouchpoint } from './Core'; + NcPropertyId, + NcPropertyRestoreNotice, + NcPropertyRestoreNoticeType, + NcPropertyValueHolder, + NcRestoreValidationStatus, + NcTouchpoint, + RestoreArguments} from './Core'; export abstract class NcWorker extends NcObject { @@ -42,9 +49,10 @@ export abstract class NcWorker extends NcObject runtimePropertyConstraints: NcPropertyConstraints[] | null, enabled: boolean, description: string, - notificationContext: INotificationContext) + notificationContext: INotificationContext, + isRebuildable: boolean = false) { - super(oid, constantOid, ownerObject, role, userLabel, touchpoints, runtimePropertyConstraints, description, notificationContext); + super(oid, constantOid, ownerObject, role, userLabel, touchpoints, runtimePropertyConstraints, description, notificationContext, isRebuildable); this.enabled = enabled; } @@ -111,6 +119,19 @@ export abstract class NcWorker extends NcObject return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(2, 1), "enabled", "NcBoolean", false, this.enabled) + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } } export class GainControl extends NcWorker @@ -203,6 +224,49 @@ export class GainControl extends NcWorker return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(3, 1), "gainValue", "NcFloat32", false, this.gainValue) + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + if(propertyId != '1p6' && propertyId != '3p1') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } } export class NcIdentBeacon extends NcWorker @@ -295,6 +359,48 @@ export class NcIdentBeacon extends NcWorker return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(3, 1), "active", "NcBoolean", false, this.active) + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + if(propertyId != '1p6' && propertyId != '3p1') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } } enum NcOverallStatus @@ -411,6 +517,50 @@ export class NcStatusMonitor extends NcWorker return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(3, 1), "overallStatus", "NcOverallStatus", true, this.overallStatus), + new NcPropertyValueHolder(new NcPropertyId(3, 2), "overallStatusMessage", "NcString", true, this.overallStatusMessage), + new NcPropertyValueHolder(new NcPropertyId(3, 3), "statusReportingDelay", "NcUint32", false, this.statusReportingDelay) + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + if(NcElementId.ToPropertyString(propertyData.id) != '1p6') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } } enum NcLinkStatus @@ -808,7 +958,7 @@ export class NcReceiverMonitor extends NcStatusMonitor return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); } - public override InvokeMethod(socket: WebSocketConnection, oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue + public override InvokeMethod(oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue { if(oid == this.oid) { @@ -827,7 +977,7 @@ export class NcReceiverMonitor extends NcStatusMonitor this.ResetSynchronizationSourceChanges(); return new CommandResponseNoValue(handle, NcMethodStatus.OK); default: - return super.InvokeMethod(socket, oid, methodId, args, handle); + return super.InvokeMethod(oid, methodId, args, handle); } } @@ -890,6 +1040,30 @@ export class NcReceiverMonitor extends NcStatusMonitor return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(4, 1), "linkStatus", "NcLinkStatus", true, this.linkStatus), + new NcPropertyValueHolder(new NcPropertyId(4, 2), "linkStatusMessage", "NcString", true, this.linkStatusMessage), + new NcPropertyValueHolder(new NcPropertyId(4, 3), "connectionStatus", "NcConnectionStatus", true, this.connectionStatus), + new NcPropertyValueHolder(new NcPropertyId(4, 4), "connectionStatusMessage", "NcString", true, this.connectionStatusMessage), + new NcPropertyValueHolder(new NcPropertyId(4, 5), "externalSynchronizationStatus", "NcSynchronizationStatus", true, this.externalSynchronizationStatus), + new NcPropertyValueHolder(new NcPropertyId(4, 6), "externalSynchronizationStatusMessage", "NcString", true, this.externalSynchronizationStatusMessage), + new NcPropertyValueHolder(new NcPropertyId(4, 7), "synchronizationSourceId", "NcString", true, this.synchronizationSourceId), + new NcPropertyValueHolder(new NcPropertyId(4, 8), "synchronizationSourceChanges", "NcUint64", true, this.synchronizationSourceChanges), + new NcPropertyValueHolder(new NcPropertyId(4, 9), "streamStatus", "NcStreamStatus", true, this.streamStatus), + new NcPropertyValueHolder(new NcPropertyId(4, 10), "streamStatusMessage", "NcString", true, this.streamStatusMessage), + new NcPropertyValueHolder(new NcPropertyId(4, 11), "autoResetPacketCounters", "NcBoolean", false, this.autoResetPacketCounters), + new NcPropertyValueHolder(new NcPropertyId(4, 12), "autoResetSynchronizationSourceChanges", "NcBoolean", false, this.autoResetSynchronizationSourceChanges) + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } } enum ExampleEnum @@ -993,9 +1167,11 @@ export class ExampleControl extends NcWorker runtimePropertyConstraints: NcPropertyConstraints[] | null, enabled: boolean, description: string, - notificationContext: INotificationContext) + notificationContext: INotificationContext, + isRebuildable: boolean = false, + dataSet: NcObjectPropertiesHolder | null = null) { - super(oid, constantOid, ownerObject, role, userLabel, touchpoints, runtimePropertyConstraints, enabled, description, notificationContext); + super(oid, constantOid, ownerObject, role, userLabel, touchpoints, runtimePropertyConstraints, enabled, description, notificationContext, isRebuildable); this.enumProperty = ExampleEnum.Undefined; this.stringProperty = "test"; @@ -1010,6 +1186,45 @@ export class ExampleControl extends NcWorker this.enumSequence = [ ExampleEnum.Alpha, ExampleEnum.Gamma ]; this.numberSequence = [ 0, 50, 88]; this.objectSequence = [ new ExampleDataType(ExampleEnum.Alpha, "example", 50, false), new ExampleDataType(ExampleEnum.Gamma, "different", 75, true) ]; + + if(dataSet != null) + { + this.InitialiseFromDataset(dataSet); + console.log(`ExampleControl object [${this.role}] constructed from a dataSet`); + } + else + console.log(`ExampleControl object [${this.role}] constructed with defaults`); + } + + private InitialiseFromDataset(dataSet: NcObjectPropertiesHolder) + { + dataSet.values.forEach(propertyData => + { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + switch(propertyId) + { + case '3p1': + this.enumProperty = propertyData.value; + case '3p2': + this.stringProperty = propertyData.value; + case '3p3': + this.numberProperty = propertyData.value; + case '3p4': + this.booleanProperty = propertyData.value; + case '3p5': + this.objectProperty = propertyData.value; + case '3p9': + this.stringSequence = propertyData.value; + case '3p10': + this.booleanSequence = propertyData.value; + case '3p11': + this.enumSequence = propertyData.value; + case '3p12': + this.numberSequence = propertyData.value; + case '3p13': + this.objectSequence = propertyData.value; + } + }); } //'1m1' @@ -1116,7 +1331,7 @@ export class ExampleControl extends NcWorker return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); } - public override InvokeMethod(socket: WebSocketConnection, oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue + public override InvokeMethod(oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue { if(oid == this.oid) { @@ -1630,7 +1845,7 @@ export class ExampleControl extends NcWorker return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'Invalid arguments provided'); } default: - return super.InvokeMethod(socket, oid, methodId, args, handle); + return super.InvokeMethod(oid, methodId, args, handle); } } @@ -1685,4 +1900,64 @@ export class ExampleControl extends NcWorker return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), + [ + this.ownerObject?.GetRolePath() ?? [] + ], + [ + new NcPropertyValueHolder(new NcPropertyId(3, 1), "enumProperty", "ExampleEnum", false, this.enumProperty), + new NcPropertyValueHolder(new NcPropertyId(3, 2), "stringProperty", "NcString", false, this.stringProperty), + new NcPropertyValueHolder(new NcPropertyId(3, 3), "numberProperty", "NcUint64", false, this.numberProperty), + new NcPropertyValueHolder(new NcPropertyId(3, 4), "booleanProperty", "NcBoolean", false, this.booleanProperty), + new NcPropertyValueHolder(new NcPropertyId(3, 5), "objectProperty", "ExampleDataType", false, this.objectProperty), + new NcPropertyValueHolder(new NcPropertyId(3, 6), "methodNoArgsCount", "NcUint64", true, this.methodNoArgsCount), + new NcPropertyValueHolder(new NcPropertyId(3, 7), "methodSimpleArgsCount", "NcUint64", true, this.methodSimpleArgsCount), + new NcPropertyValueHolder(new NcPropertyId(3, 8), "methodObjectArgCount", "NcUint64", true, this.methodObjectArgCount), + new NcPropertyValueHolder(new NcPropertyId(3, 9), "stringSequence", "NcString", false, this.stringSequence), + new NcPropertyValueHolder(new NcPropertyId(3, 10), "booleanSequence", "NcBoolean", false, this.booleanSequence), + new NcPropertyValueHolder(new NcPropertyId(3, 11), "enumSequence", "ExampleEnum", false, this.enumSequence), + new NcPropertyValueHolder(new NcPropertyId(3, 12), "numberSequence", "NcUint64", false, this.numberSequence), + new NcPropertyValueHolder(new NcPropertyId(3, 13), "objectSequence", "ExampleDataType", false, this.objectSequence), + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + if(propertyId != '1p6' && propertyId != '3p1' && propertyId != '3p2' && propertyId != '3p3' && propertyId != '3p4' && propertyId != '3p5' && + propertyId != '3p9' && propertyId != '3p10' && propertyId != '3p11' && propertyId != '3p12' && propertyId != '3p13') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } } \ No newline at end of file diff --git a/code/src/NCModel/Managers.ts b/code/src/NCModel/Managers.ts index f4686ba..b6ad3f5 100644 --- a/code/src/NCModel/Managers.ts +++ b/code/src/NCModel/Managers.ts @@ -1,13 +1,13 @@ import { jsonIgnoreReplacer, jsonIgnore } from 'json-ignore'; import { CommandResponseError, CommandResponseNoValue, CommandResponseWithValue } from '../NCProtocol/Commands'; import { NcPropertyChangedEventData } from '../NCProtocol/Notifications'; -import { WebSocketConnection } from '../Server'; import { INotificationContext } from '../SessionManager'; import { NcBlock } from './Blocks'; import { BaseType, myIdDecorator, NcBlockMemberDescriptor, + NcBulkValuesHolder, NcClassDescriptor, NcDatatypeDescriptor, NcDatatypeDescriptorEnum, @@ -24,14 +24,18 @@ import { NcMethodId, NcMethodResult, NcMethodResultBlockMemberDescriptors, + NcMethodResultBulkValuesHolder, NcMethodResultClassDescriptor, NcMethodResultDatatypeDescriptor, NcMethodResultError, NcMethodResultId, NcMethodResultLength, + NcMethodResultObjectPropertiesSetValidation, NcMethodResultPropertyValue, NcMethodStatus, NcObject, + NcObjectPropertiesHolder, + NcObjectPropertiesSetValidation, NcParameterConstraints, NcParameterConstraintsNumber, NcParameterConstraintsString, @@ -42,12 +46,17 @@ import { NcPropertyConstraintsString, NcPropertyDescriptor, NcPropertyId, + NcPropertyRestoreNotice, + NcPropertyRestoreNoticeType, + NcPropertyValueHolder, + NcRestoreValidationStatus, NcTouchpoint, NcTouchpointNmos, NcTouchpointNmosChannelMapping, NcTouchpointResource, NcTouchpointResourceNmos, - NcTouchpointResourceNmosChannelMapping} from './Core'; + NcTouchpointResourceNmosChannelMapping, + RestoreArguments} from './Core'; import { ExampleDataType, ExampleControl, GainControl, NcIdentBeacon, NcReceiverMonitor, NcWorker, NcStatusMonitor, NcMethodResultCounters, NcCounter } from './Features'; export abstract class NcManager extends NcObject @@ -382,6 +391,58 @@ export class NcDeviceManager extends NcManager return currentClassDescriptor; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(3, 1), "ncVersion", "NcVersionCode", true, this.ncVersion), + new NcPropertyValueHolder(new NcPropertyId(3, 2), "manufacturer", "NcManufacturer", true, this.manufacturer), + new NcPropertyValueHolder(new NcPropertyId(3, 3), "product", "NcProduct", true, this.product), + new NcPropertyValueHolder(new NcPropertyId(3, 4), "serialNumber", "NcString", true, this.serialNumber), + new NcPropertyValueHolder(new NcPropertyId(3, 5), "userInventoryCode", "NcString", false, this.userInventoryCode), + new NcPropertyValueHolder(new NcPropertyId(3, 6), "deviceName", "NcString", false, this.deviceName), + new NcPropertyValueHolder(new NcPropertyId(3, 7), "deviceName", "NcString", false, this.deviceName), + new NcPropertyValueHolder(new NcPropertyId(3, 8), "operationalState", "NcDeviceOperationalState", true, this.operationalState), + new NcPropertyValueHolder(new NcPropertyId(3, 9), "resetCause", "NcResetCause", true, this.resetCause), + new NcPropertyValueHolder(new NcPropertyId(3, 10), "message", "NcString", true, this.message), + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + if(propertyId != '1p6' && propertyId != '3p5' && propertyId != '3p6' && propertyId != '3p7') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } } export class NcClassManager extends NcManager @@ -442,7 +503,7 @@ export class NcClassManager extends NcManager return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); } - public override InvokeMethod(socket: WebSocketConnection, oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue + public override InvokeMethod(oid: number, methodId: NcElementId, args: { [key: string]: any; } | null, handle: number): CommandResponseNoValue { if(oid == this.oid) { @@ -648,7 +709,7 @@ export class NcClassManager extends NcManager return new CommandResponseError(handle, NcMethodStatus.InvalidRequest, 'No type name has been provided'); } default: - return super.InvokeMethod(socket, oid, methodId, args, handle); + return super.InvokeMethod(oid, methodId, args, handle); } } @@ -701,7 +762,8 @@ export class NcClassManager extends NcManager '1.2.2.1': NcReceiverMonitor.GetClassDescriptor(false), '1.3': NcManager.GetClassDescriptor(false), '1.3.1': NcDeviceManager.GetClassDescriptor(false), - '1.3.2': NcClassManager.GetClassDescriptor(false) + '1.3.2': NcClassManager.GetClassDescriptor(false), + '1.3.3': NcBulkPropertiesManager.GetClassDescriptor(false) }; return register; @@ -724,6 +786,7 @@ export class NcClassManager extends NcManager case '1.3': return NcManager.GetClassDescriptor(true); case '1.3.1': return NcDeviceManager.GetClassDescriptor(true); case '1.3.2': return NcClassManager.GetClassDescriptor(true); + case '1.3.3': return NcBulkPropertiesManager.GetClassDescriptor(true); default: return null; } } @@ -879,7 +942,28 @@ export class NcClassManager extends NcManager 'NcRegex': new NcDatatypeDescriptorTypeDef("NcRegex", "NcString", false, null, "Regex pattern"), 'NcPropertyConstraints': NcPropertyConstraints.GetTypeDescriptor(false), 'NcPropertyConstraintsNumber': NcPropertyConstraintsNumber.GetTypeDescriptor(false), - 'NcPropertyConstraintsString': NcPropertyConstraintsString.GetTypeDescriptor(false) + 'NcPropertyConstraintsString': NcPropertyConstraintsString.GetTypeDescriptor(false), + 'NcBulkValuesHolder': NcBulkValuesHolder.GetTypeDescriptor(false), + 'NcMethodResultBulkValuesHolder': NcMethodResultBulkValuesHolder.GetTypeDescriptor(false), + 'NcMethodResultObjectPropertiesSetValidation': NcMethodResultObjectPropertiesSetValidation.GetTypeDescriptor(false), + 'NcObjectPropertiesHolder': NcObjectPropertiesHolder.GetTypeDescriptor(false), + 'NcObjectPropertiesSetValidation': NcObjectPropertiesSetValidation.GetTypeDescriptor(false), + 'NcPropertyRestoreNotice': NcPropertyRestoreNotice.GetTypeDescriptor(false), + 'NcPropertyValueHolder': NcPropertyValueHolder.GetTypeDescriptor(false), + 'NcPropertyRestoreNoticeType': new NcDatatypeDescriptorEnum("NcPropertyRestoreNoticeType", [ + new NcEnumItemDescriptor("Warning", 300, "Warning property restore notice"), + new NcEnumItemDescriptor("Error", 400, "Error property restore notice") + ], null, "Property restore notice type enumeration"), + 'NcRestoreMode': new NcDatatypeDescriptorEnum("NcRestoreMode", [ + new NcEnumItemDescriptor("Modify", 0, "Restore mode is Modify"), + new NcEnumItemDescriptor("Rebuild", 1, "Restore mode is Rebuild") + ], null, "Restore mode enumeration"), + 'NcRestoreValidationStatus': new NcDatatypeDescriptorEnum("NcRestoreValidationStatus", [ + new NcEnumItemDescriptor("Ok", 200, "Restore was successful"), + new NcEnumItemDescriptor("Failed", 400, "Restore failed"), + new NcEnumItemDescriptor("NotFound", 404, "Restore failed because the role path is not found in the device model or the device cannot create the role path from the data set"), + new NcEnumItemDescriptor("DeviceError", 500, "Restore failed due to an internal device error preventing the restore from happening"), + ], null, "Restore validation status enumeration"), }; return register; @@ -922,11 +1006,18 @@ export class NcClassManager extends NcManager case 'NcMethodResultId': return NcMethodResultId.GetTypeDescriptor(true); case 'NcMethodResultLength': return NcMethodResultLength.GetTypeDescriptor(true); case 'NcMethodResultCounters': return NcMethodResultCounters.GetTypeDescriptor(true); + case 'NcBulkValuesHolder': return NcBulkValuesHolder.GetTypeDescriptor(true); + case 'NcMethodResultBulkValuesHolder': return NcMethodResultBulkValuesHolder.GetTypeDescriptor(true); + case 'NcMethodResultObjectPropertiesSetValidation': return NcMethodResultObjectPropertiesSetValidation.GetTypeDescriptor(true); + case 'NcObjectPropertiesHolder': return NcObjectPropertiesHolder.GetTypeDescriptor(true); + case 'NcObjectPropertiesSetValidation': return NcObjectPropertiesSetValidation.GetTypeDescriptor(true); + case 'NcPropertyRestoreNotice': return NcPropertyRestoreNotice.GetTypeDescriptor(true); + case 'NcPropertyValueHolder': return NcPropertyValueHolder.GetTypeDescriptor(true); default: return this.dataTypesRegister[name]; } } - private GetClassDescriptor(identity: number[], includeInherited: boolean) : NcClassDescriptor | null + public GetClassDescriptor(identity: number[], includeInherited: boolean) : NcClassDescriptor | null { if(includeInherited) return this.GenerateClassDescriptorWithInheritedElements(identity); @@ -938,11 +1029,182 @@ export class NcClassManager extends NcManager } } - private GetTypeDescriptor(name: string, includeInherited: boolean) : NcDatatypeDescriptor | null + public GetTypeDescriptor(name: string, includeInherited: boolean) : NcDatatypeDescriptor | null { if(includeInherited) return this.GenerateTypeDescriptorWithInheritedElements(name); else return this.dataTypesRegister[name]; } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [ + new NcPropertyValueHolder(new NcPropertyId(3, 1), "controlClasses", "NcClassDescriptor", true, this.controlClasses), + new NcPropertyValueHolder(new NcPropertyId(3, 2), "dataTypes", "NcDatatypeDescriptor", true, this.dataTypes) + ], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + if(NcElementId.ToPropertyString(propertyData.id) != '1p6') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } +} + +export class NcBulkPropertiesManager extends NcManager +{ + public static staticClassID: number[] = [ 1, 3, 3 ]; + + @myIdDecorator('1p1') + public override classID: number[] = NcBulkPropertiesManager.staticClassID; + + public static staticRole: string = "BulkPropertiesManager"; + + public constructor( + oid: number, + constantOid: boolean, + ownerObject: NcObject | null, + userLabel: string, + touchpoints: NcTouchpoint[] | null, + runtimePropertyConstraints: NcPropertyConstraints[] | null, + description: string, + notificationContext: INotificationContext) + { + super(oid, constantOid, ownerObject, NcBulkPropertiesManager.staticRole, userLabel, touchpoints, runtimePropertyConstraints, description, notificationContext); + } + + //'1m1' + public override Get(oid: number, propertyId: NcElementId, handle: number) : CommandResponseNoValue + { + if(oid == this.oid) + { + let key: string = `${propertyId.level}p${propertyId.index}`; + + return super.Get(oid, propertyId, handle); + } + + return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); + } + + //'1m2' + public override Set(oid: number, id: NcElementId, value: any, handle: number) : CommandResponseNoValue + { + if(oid == this.oid) + { + let key: string = `${id.level}p${id.index}`; + + return super.Set(oid, id, value, handle); + } + + return new CommandResponseError(handle, NcMethodStatus.BadOid, 'OID could not be found'); + } + + public static override GetClassDescriptor(includeInherited: boolean): NcClassDescriptor + { + let currentClassDescriptor = new NcClassDescriptor(`${NcBulkPropertiesManager.name} class descriptor`, + NcBulkPropertiesManager.staticClassID, NcBulkPropertiesManager.name, NcBulkPropertiesManager.staticRole, + [], + [ + new NcMethodDescriptor(new NcElementId(3, 1), "GetPropertiesByPath", "NcMethodResultBulkValuesHolder", [ + new NcParameterDescriptor("path", "NcRolePath", false, false, null, "The target role path"), + new NcParameterDescriptor("recurse", "NcBoolean", false, false, null, "If true will return properties on specified path and all the nested paths") + ], "Get bulk object properties by given path"), + new NcMethodDescriptor(new NcElementId(3, 2), "ValidateSetPropertiesByPath", "NcMethodResultObjectPropertiesSetValidation", [ + new NcParameterDescriptor("dataSet", "NcBulkValuesHolder", false, false, null, "The values offered (this may include read-only values and also paths which are not the target role path)"), + new NcParameterDescriptor("path", "NcRolePath", false, false, null, "The target role path"), + new NcParameterDescriptor("recurse", "NcBoolean", false, false, null, "If true will validate properties on target path and all the nested paths"), + new NcParameterDescriptor("restoreMode", "NcRestoreMode", false, false, null, "Defines the restore mode to be applied") + ], "Validate bulk properties for setting by given paths"), + new NcMethodDescriptor(new NcElementId(3, 3), "SetPropertiesByPath", "NcMethodResultObjectPropertiesSetValidation", [ + new NcParameterDescriptor("dataSet", "NcBulkValuesHolder", false, false, null, "The values offered (this may include read-only values and also paths which are not the target role path)"), + new NcParameterDescriptor("path", "NcRolePath", false, false, null, "The target role path"), + new NcParameterDescriptor("recurse", "NcBoolean", false, false, null, "If true will set properties on target path and all the nested paths"), + new NcParameterDescriptor("restoreMode", "NcRestoreMode", false, false, null, "Defines the restore mode to be applied") + ], "Set bulk properties by given paths"), + ], + [] + ); + + if(includeInherited) + { + let baseDescriptor = super.GetClassDescriptor(includeInherited); + + currentClassDescriptor.properties = currentClassDescriptor.properties.concat(baseDescriptor.properties); + currentClassDescriptor.methods = currentClassDescriptor.methods.concat(baseDescriptor.methods); + currentClassDescriptor.events = currentClassDescriptor.events.concat(baseDescriptor.events); + } + + return currentClassDescriptor; + } + + public override GetAllProperties(recurse: boolean) : NcObjectPropertiesHolder[] + { + let properties = [ + new NcObjectPropertiesHolder(this.GetRolePath(), [], [], this.isRebuildable) + ]; + + properties[0].values = properties[0].values.concat(super.GetAllProperties(recurse)[0].values); + + return properties; + } + + public override Restore(restoreArguments: RestoreArguments, applyChanges: Boolean) : NcObjectPropertiesSetValidation[] + { + let validationEntries = new Array(); + + let myRestoreData = restoreArguments.dataSet.values.find(f => f.path.join('.') == this.GetRolePath().join('.')) + if(myRestoreData) + { + let myNotices = new Array(); + + myRestoreData.values.forEach(propertyData => { + let propertyId = NcElementId.ToPropertyString(propertyData.id); + if(propertyId != '1p6') + myNotices.push(new NcPropertyRestoreNotice( + propertyData.id, + propertyData.name, + NcPropertyRestoreNoticeType.Warning, + "Property cannot be changed and will be left untouched")); + else if(applyChanges) + { + //Perform further validation + this.Set(this.oid, propertyData.id, propertyData.value, 0); + } + }); + + validationEntries.push(new NcObjectPropertiesSetValidation(this.GetRolePath(), NcRestoreValidationStatus.Ok, myNotices, myNotices.length > 0 ? 'Some properties have notices' : null)); + } + + return validationEntries; + } } diff --git a/code/src/NCProtocol/Commands.ts b/code/src/NCProtocol/Commands.ts index d755054..d3295fc 100644 --- a/code/src/NCProtocol/Commands.ts +++ b/code/src/NCProtocol/Commands.ts @@ -166,4 +166,4 @@ export class ProtocolError extends ProtocolWrapper { return JSON.stringify(this, jsonIgnoreReplacer); } -} \ No newline at end of file +} diff --git a/code/src/NmosDevice.ts b/code/src/NmosDevice.ts index a99cb21..2179612 100644 --- a/code/src/NmosDevice.ts +++ b/code/src/NmosDevice.ts @@ -64,7 +64,8 @@ export class NmosDevice extends NmosResource this.controls = [ new NmosControl(`http://${address}:${port}/x-nmos/connection/v1.1/`, 'urn:x-nmos:control:sr-ctrl/v1.1'), new NmosControl(`http://${address}:${port}/x-nmos/connection/v1.0/`, 'urn:x-nmos:control:sr-ctrl/v1.0'), - new NmosControl(`ws://${address}:${port}/x-nmos/ncp/v1.0/connect`, 'urn:x-nmos:control:ncp/v1.0') + new NmosControl(`ws://${address}:${port}/x-nmos/ncp/v1.0/connect`, 'urn:x-nmos:control:ncp/v1.0'), + new NmosControl(`http://${address}:${port}/x-nmos/configuration/v1.0/`, 'urn:x-nmos:control:configuration/v1.0'), ]; this.type = 'urn:x-nmos:device:generic'; diff --git a/code/src/Server.ts b/code/src/Server.ts index d0785c7..336d05b 100644 --- a/code/src/Server.ts +++ b/code/src/Server.ts @@ -12,9 +12,9 @@ import { RegistrationClient } from './RegistrationClient'; import { NmosReceiverVideo } from './NmosReceiverVideo'; import { NmosReceiverActiveRtp } from './NmosReceiverActiveRtp'; import { SessionManager } from './SessionManager'; -import { NcBlock, RootBlock } from './NCModel/Blocks'; +import { ExampleControlsBlock, NcBlock, RootBlock } from './NCModel/Blocks'; import { NcClassManager, NcDeviceManager } from './NCModel/Managers'; -import { NcMethodStatus, NcTouchpointNmos, NcTouchpointResourceNmos } from './NCModel/Core'; +import { ConfigApiArguments, ConfigApiValue, NcBulkValuesHolder, NcMethodResultBulkValuesHolder, NcMethodResultObjectPropertiesSetValidation, NcMethodStatus, NcTouchpointNmos, NcTouchpointResourceNmos, RestoreBody } from './NCModel/Core'; import { ExampleControl, GainControl, NcIdentBeacon, NcReceiverMonitor } from './NCModel/Features'; import { ProtocolError, ProtocolSubscription } from './NCProtocol/Commands'; import { MessageType, ProtocolWrapper } from './NCProtocol/Core'; @@ -70,7 +70,6 @@ try const sessionManager = new SessionManager(config.notify_without_subscriptions); const rootBlock = new RootBlock( - 1, true, null, 'root', @@ -83,7 +82,7 @@ try sessionManager); const deviceManager = new NcDeviceManager( - 2, + rootBlock.AllocateOid(rootBlock.GetRolePathForMember(NcDeviceManager.staticRole).join('.')), true, rootBlock, 'Device manager', @@ -93,7 +92,7 @@ try sessionManager); const classManager = new NcClassManager( - 3, + rootBlock.AllocateOid(rootBlock.GetRolePathForMember(NcClassManager.staticRole).join('.')), true, rootBlock, 'Class manager', @@ -102,20 +101,8 @@ try "The class manager offers access to control class and data type descriptors", sessionManager); - const exampleControl = new ExampleControl( - 111, - true, - rootBlock, - 'ExampleControl', - 'Example control worker', - [], - null, - true, - "Example control worker", - sessionManager); - const stereoGainBlock = new NcBlock( - 31, + rootBlock.AllocateOid(rootBlock.GetRolePathForMember('stereo-gain').join('.')), true, rootBlock, 'stereo-gain', @@ -125,10 +112,11 @@ try true, [], "Stereo gain block", - sessionManager); + sessionManager, + rootBlock); const channelGainBlock = new NcBlock( - 21, + rootBlock.AllocateOid(stereoGainBlock.GetRolePathForMember('channel-gain').join('.')), true, stereoGainBlock, 'channel-gain', @@ -138,19 +126,20 @@ try true, [], "Channel gain block", - sessionManager); + sessionManager, + rootBlock); - let leftGain = new GainControl(22, true, channelGainBlock, "left-gain", "Left gain", [], null, true, 0, "Left channel gain", sessionManager); - let rightGain = new GainControl(23, true, channelGainBlock, "right-gain", "Right gain", [], null, true, 0, "Right channel gain", sessionManager); + let leftGain = new GainControl(rootBlock.AllocateOid(channelGainBlock.GetRolePathForMember('left-gain').join('.')), true, channelGainBlock, 'left-gain', 'Left gain', [], null, true, 0, 'Left channel gain', sessionManager); + let rightGain = new GainControl(rootBlock.AllocateOid(channelGainBlock.GetRolePathForMember('right-gain').join('.')), true, channelGainBlock, 'right-gain', 'Right gain', [], null, true, 0, 'Right channel gain', sessionManager); channelGainBlock.UpdateMembers([ leftGain, rightGain ]); - let masterGain = new GainControl(24, true, stereoGainBlock, "master-gain", "Master gain", [], null, true, 0, "Master gain", sessionManager); + let masterGain = new GainControl(rootBlock.AllocateOid(stereoGainBlock.GetRolePathForMember('master-gain').join('.')), true, stereoGainBlock, 'master-gain', 'Master gain', [], null, true, 0, 'Master gain', sessionManager); stereoGainBlock.UpdateMembers([ channelGainBlock, masterGain ]); const receiversBlock = new NcBlock( - 10, + rootBlock.AllocateOid(rootBlock.GetRolePathForMember('receivers').join('.')), true, rootBlock, 'receivers', @@ -160,10 +149,11 @@ try true, [], "Receivers block", - sessionManager); + sessionManager, + rootBlock); const receiverMonitor = new NcReceiverMonitor( - 11, + rootBlock.AllocateOid(receiversBlock.GetRolePathForMember('monitor-01').join('.')), true, receiversBlock, 'monitor-01', @@ -178,9 +168,40 @@ try receiversBlock.UpdateMembers([ receiverMonitor ]); - const identBeacon = new NcIdentBeacon(51, true, rootBlock, "IdentBeacon", "Identification beacon", [], null, true, false, "Identification beacon", sessionManager); + const identBeacon = new NcIdentBeacon(rootBlock.AllocateOid(rootBlock.GetRolePathForMember('IdentBeacon').join('.')), true, rootBlock, 'IdentBeacon', 'Identification beacon', [], null, true, false, 'Identification beacon', sessionManager); - rootBlock.UpdateMembers([ deviceManager, classManager, receiversBlock, stereoGainBlock, exampleControl, identBeacon ]); + const exampleControlsBlock = new ExampleControlsBlock( + rootBlock.AllocateOid(rootBlock.GetRolePathForMember('example-controls').join('.')), + true, + rootBlock, + 'example-controls', + 'Example controls', + null, + null, + true, + [], + "Example controls block", + sessionManager, + rootBlock, + 2, + true); + + const exampleControl = new ExampleControl( + rootBlock.AllocateOid(exampleControlsBlock.GetRolePathForMember('example-control-01').join('.')), + true, + exampleControlsBlock, + 'example-control-01', + 'Example control worker 01', + [], + null, + true, + "Example control worker", + sessionManager, + true); + + exampleControlsBlock.UpdateMembers([ exampleControl ]); + + rootBlock.UpdateMembers([ deviceManager, classManager, receiversBlock, stereoGainBlock, exampleControlsBlock, identBeacon ]); async function doAsync () { await registrationClient.RegisterOrUpdateResource('node', myNode); @@ -193,7 +214,10 @@ try //initialize the Express HTTP listener const app = application(); - app.use(application.json()); + var cors = require('cors'); + app.use(cors()); + app.use(application.json({ limit: '50mb' })); + app.use(application.urlencoded({ extended: true })); //initialize server const server = http.createServer(application); @@ -329,7 +353,8 @@ try res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify([ 'node/', - 'connection/' + 'connection/', + 'configuration/' ])); }) @@ -494,6 +519,263 @@ try else res.sendStatus(404); }) + + //IS-14 paths + + app.get('/x-nmos/configuration', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify([ 'v1.0/' ])); + }) + + app.get('/x-nmos/configuration/:version', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify([ + 'rolePaths/' + ])); + }) + + app.get('/x-nmos/configuration/:version/rolePaths', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let response = rootBlock.GetRolePathUrls(); + + res.send(JSON.stringify(response.sort((a, b) => (a > b ? -1 : 1)))); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + res.send(JSON.stringify([ + 'bulkProperties/', + 'descriptor/', + 'methods/', + 'properties/' + ])); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/descriptor/', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + res.send(JSON.stringify(classManager.GetClassDescriptor(member.classID, true), jsonIgnoreReplacer)); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/methods/', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + res.send(JSON.stringify(classManager.GetClassDescriptor(member.classID, true)?.methods.map(({ id }) => `${id.level}m${id.index}/`).sort((a, b) => (a > b ? 1 : -1)), jsonIgnoreReplacer)); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/properties/', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + res.send(JSON.stringify(classManager.GetClassDescriptor(member.classID, true)?.properties.map(({ id }) => `${id.level}p${id.index}/`).sort((a, b) => (a > b ? 1 : -1)), jsonIgnoreReplacer)); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/properties/:propertyId', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let property = classManager.GetClassDescriptor(member.classID, true)?.properties.find(f => req.params.propertyId == `${f.id.level}p${f.id.index}`); + if(property) + res.send(JSON.stringify([ + 'descriptor/', + 'value/' + ])); + else + res.sendStatus(404); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/properties/:propertyId/descriptor', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let property = classManager.GetClassDescriptor(member.classID, true)?.properties.find(f => req.params.propertyId == `${f.id.level}p${f.id.index}`); + if(property?.typeName) + res.send(JSON.stringify(classManager.GetTypeDescriptor(property.typeName, true), jsonIgnoreReplacer)); + else + res.sendStatus(404); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/properties/:propertyId/value', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let property = classManager.GetClassDescriptor(member.classID, true)?.properties.find(f => req.params.propertyId == `${f.id.level}p${f.id.index}`); + if(property) + res.send(JSON.stringify(member.Get(member.oid, property.id, 0).result, jsonIgnoreReplacer)); + else + res.sendStatus(404); + } + else + res.sendStatus(404); + }) + + app.put('/x-nmos/configuration/:version/rolePaths/:rolePath/properties/:propertyId/value', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let apiValue = req.body as ConfigApiValue; + + console.log(`Property PUT ${req.url}`); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let property = classManager.GetClassDescriptor(member.classID, true)?.properties.find(f => req.params.propertyId == `${f.id.level}p${f.id.index}`); + if(property) + res.send(JSON.stringify(member.Set(member.oid, property.id, apiValue.value, 0).result, jsonIgnoreReplacer)); + else + res.sendStatus(404); + } + else + res.sendStatus(404); + }); + + app.patch('/x-nmos/configuration/:version/rolePaths/:rolePath/methods/:methodId', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let apiArguments = req.body as ConfigApiArguments; + + console.log(`Method PATCH ${req.url}`); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let method = classManager.GetClassDescriptor(member.classID, true)?.methods.find(f => req.params.methodId == `${f.id.level}m${f.id.index}`); + if(method) + res.send(JSON.stringify(member.InvokeMethod(member.oid, method.id, apiArguments.arguments, 0).result, jsonIgnoreReplacer)); + else + res.sendStatus(404); + } + else + res.sendStatus(404); + }) + + app.get('/x-nmos/configuration/:version/rolePaths/:rolePath/bulkProperties', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let recurse: boolean = req.query.recurse === 'false' ? false : true; + + console.log(`BulkProperties GET ${req.url}, recurse: ${recurse}`); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let response = new NcMethodResultBulkValuesHolder( + NcMethodStatus.OK, new NcBulkValuesHolder("AMWA NMOS Device Control Mock Application|v1.0", + member.GetAllProperties(recurse))); + + res.send(JSON.stringify(response, jsonIgnoreReplacer)); + } + else + res.sendStatus(404); + }) + + app.patch('/x-nmos/configuration/:version/rolePaths/:rolePath/bulkProperties', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let restore = req.body as RestoreBody; + + console.log(`BulkProperties PATCH ${req.url}`); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let response = new NcMethodResultObjectPropertiesSetValidation( + NcMethodStatus.OK, + member.Restore(restore.arguments, false)); + + res.send(JSON.stringify(response, jsonIgnoreReplacer)); + } + else + res.sendStatus(404); + }) + + app.put('/x-nmos/configuration/:version/rolePaths/:rolePath/bulkProperties', function (req, res) { + res.setHeader('Content-Type', 'application/json'); + + let restore = req.body as RestoreBody; + + console.log(`BulkProperties PUT ${req.url}`); + + let rolePath = req.params.rolePath.split('.'); + + let member = rootBlock.FindMemberByRolePath(rolePath); + if(member) + { + let response = new NcMethodResultObjectPropertiesSetValidation( + NcMethodStatus.OK, + member.Restore(restore.arguments, true)); + + res.send(JSON.stringify(response, jsonIgnoreReplacer)); + } + else + res.sendStatus(404); + }) + + app.use((req, res, next) => { + //This applied to any invalid path + + res.set({ 'content-type': 'application/json; charset=utf-8' }); + res.status(404).send(''); + }) //start our server server.listen(config.port, () => {