diff --git a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts
index e18075b9..77fc0d23 100644
--- a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts
+++ b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/control-scheme-view-io-list.component.ts
@@ -55,7 +55,11 @@ export class ControlSchemeViewIoListComponent {
this.treeControl.dataNodes = this.dataSource.data;
if (!this.initialExpansionDone) {
this.initialExpansionDone = true;
- this.treeControl.expandAll();
+ this.treeControl.dataNodes
+ .filter((node) => node.initiallyExpanded)
+ .forEach((node) => {
+ this.treeControl.expand(node);
+ });
}
}
diff --git a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html
index c8040f50..74b08ca8 100644
--- a/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html
+++ b/src/app/control-schemes/control-scheme-page/control-scheme-view-io-list/io-tree-node/io-tree-node.component.html
@@ -3,6 +3,17 @@
[ioType]="node.ioType"
[isConnected]="node.isConnected"
>
+
+
+
+ {{ node.runningTask | portCommandTaskSummary | ngrxPush }}
+
+
+
+
+ {{ node.lastExecutedTask | portCommandTaskSummary | ngrxPush }}
+
+
): ControlSchemeViewIoTreeNode {
const ioId = attachedIosIdFn({ hubId: hubConfig.hubId, portId });
const io = iosEntities[ioId];
@@ -66,6 +70,9 @@ function createIoTreeNode(
const useDecelerationProfile = bindings.filter((b) => b.useDecelerationProfile && b.portId === portId && b.hubId === hubConfig.hubId)
.length > 0;
+ const runningTask = portTasksModelDictionary[hubPortTasksIdFn({ hubId: hubConfig.hubId, portId })]?.runningTask ?? undefined;
+ const lastExecutedTask = portTasksModelDictionary[hubPortTasksIdFn({ hubId: hubConfig.hubId, portId })]?.lastExecutedTask ?? undefined;
+
return {
path: `${parentPath}.${portId}`,
nodeType: ControlSchemeNodeTypes.Io,
@@ -78,7 +85,10 @@ function createIoTreeNode(
accelerationTimeMs: portConfig?.accelerationTimeMs ?? 0,
useDecelerationProfile,
decelerationTimeMs: portConfig?.decelerationTimeMs ?? 0,
- children: []
+ runningTask,
+ lastExecutedTask,
+ children: [],
+ initiallyExpanded: false
};
}
@@ -100,7 +110,8 @@ function createBindingTreeNode(
binding,
controlSchemeId,
ioHasNoRequiredCapabilities,
- children: []
+ children: [],
+ initiallyExpanded: false
};
}
@@ -113,6 +124,7 @@ export const CONTROL_SCHEME_VIEW_SELECTORS = {
ATTACHED_IO_MODES_SELECTORS.selectEntities,
ATTACHED_IO_PORT_MODE_INFO_SELECTORS.selectEntities,
PORT_TASKS_SELECTORS.selectLastExecutedBindingIds,
+ PORT_TASKS_SELECTORS.selectEntities,
(
scheme: ControlSchemeModel | undefined,
hubEntities: Dictionary,
@@ -120,7 +132,8 @@ export const CONTROL_SCHEME_VIEW_SELECTORS = {
iosEntities: Dictionary,
ioSupportedModesEntities: Dictionary,
portModeInfoEntities: Dictionary,
- lastExecutedTasksBindingIds: ReadonlySet
+ lastExecutedTasksBindingIds: ReadonlySet,
+ portCommandTasksEntities: Dictionary,
): ControlSchemeViewHubTreeNode[] => {
if (!scheme) {
return [];
@@ -178,7 +191,8 @@ export const CONTROL_SCHEME_VIEW_SELECTORS = {
binding.portId,
scheme.bindings,
scheme.portConfigs,
- schemeId
+ schemeId,
+ portCommandTasksEntities
);
hubIosViewMap.set(ioId, ioViewModel);
diff --git a/src/app/control-schemes/control-scheme-page/types.ts b/src/app/control-schemes/control-scheme-page/types.ts
index fc3fcbc3..a308d7a0 100644
--- a/src/app/control-schemes/control-scheme-page/types.ts
+++ b/src/app/control-schemes/control-scheme-page/types.ts
@@ -1,5 +1,5 @@
import { HubType, IOType } from '@nvsukhanov/rxpoweredup';
-import { ControlSchemeBinding } from '@app/store';
+import { ControlSchemeBinding, PortCommandTask } from '@app/store';
export enum ControlSchemeNodeTypes {
Hub = 'Hub',
@@ -15,6 +15,7 @@ export type ControlSchemeViewBindingTreeNodeData = {
readonly binding: ControlSchemeBinding;
readonly ioHasNoRequiredCapabilities: boolean;
readonly children: [];
+ readonly initiallyExpanded: boolean;
};
export type ControlSchemeViewIoTreeNode = {
@@ -29,7 +30,10 @@ export type ControlSchemeViewIoTreeNode = {
readonly accelerationTimeMs: number;
readonly useDecelerationProfile: boolean;
readonly decelerationTimeMs: number;
+ readonly runningTask?: PortCommandTask;
+ readonly lastExecutedTask?: PortCommandTask;
readonly children: ControlSchemeViewBindingTreeNodeData[];
+ readonly initiallyExpanded: boolean;
};
export type ControlSchemeViewHubTreeNode = {
@@ -44,6 +48,7 @@ export type ControlSchemeViewHubTreeNode = {
readonly hasCommunication: boolean; // TODO: remove, may impact performance, Use ad-hoc selector instead
readonly isConnected: boolean;
readonly children: ControlSchemeViewIoTreeNode[];
+ readonly initiallyExpanded: boolean;
};
export type ControlSchemeViewTreeNode = ControlSchemeViewHubTreeNode
diff --git a/src/app/control-schemes/port-command-task-summary/index.ts b/src/app/control-schemes/port-command-task-summary/index.ts
new file mode 100644
index 00000000..0e8978aa
--- /dev/null
+++ b/src/app/control-schemes/port-command-task-summary/index.ts
@@ -0,0 +1 @@
+export * from './port-command-task-summary.pipe';
diff --git a/src/app/control-schemes/port-command-task-summary/linear-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/linear-port-command-task-summary-builder.service.ts
new file mode 100644
index 00000000..7b871e18
--- /dev/null
+++ b/src/app/control-schemes/port-command-task-summary/linear-port-command-task-summary-builder.service.ts
@@ -0,0 +1,21 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { TranslocoService } from '@ngneat/transloco';
+import { SetLinearSpeedTaskPayload } from '@app/store';
+
+@Injectable({ providedIn: 'root' })
+export class LinearPortCommandTaskSummaryBuilderService {
+ constructor(
+ private readonly translocoService: TranslocoService
+ ) {
+ }
+
+ public build(
+ payload: SetLinearSpeedTaskPayload
+ ): Observable {
+ if (payload.power !== 0 && payload.speed === 0) {
+ return this.translocoService.selectTranslate('controlScheme.linearBrakeTaskSummary');
+ }
+ return this.translocoService.selectTranslate('controlScheme.linearTaskSummary', payload);
+ }
+}
diff --git a/src/app/control-schemes/port-command-task-summary/port-command-task-summary.pipe.ts b/src/app/control-schemes/port-command-task-summary/port-command-task-summary.pipe.ts
new file mode 100644
index 00000000..cac262d4
--- /dev/null
+++ b/src/app/control-schemes/port-command-task-summary/port-command-task-summary.pipe.ts
@@ -0,0 +1,53 @@
+import { Pipe, PipeTransform } from '@angular/core';
+import { Observable, filter, switchMap } from 'rxjs';
+import { Store } from '@ngrx/store';
+import { ATTACHED_IO_PROPS_SELECTORS, AttachedIoPropsModel, PortCommandTask, PortCommandTaskType } from '@app/store';
+
+import { LinearPortCommandTaskSummaryBuilderService } from './linear-port-command-task-summary-builder.service';
+import { ServoPortCommandTaskSummaryBuilderService } from './servo-port-command-task-summary-builder.service';
+import { SetAnglePortCommandTaskSummaryBuilderService } from './set-angle-port-command-task-summary-builder.service';
+import { StepperPortCommandTaskSummaryBuilderService } from './stepper-port-command-task-summary-builder.service';
+
+@Pipe({
+ standalone: true,
+ name: 'portCommandTaskSummary',
+ pure: true
+})
+export class PortCommandTaskSummaryPipe implements PipeTransform {
+ constructor(
+ private readonly linearPortCommandTaskSummaryBuilder: LinearPortCommandTaskSummaryBuilderService,
+ private readonly setAnglePortCommandTaskSummaryBuilder: SetAnglePortCommandTaskSummaryBuilderService,
+ private readonly servoPortCommandTaskSummaryBuilder: ServoPortCommandTaskSummaryBuilderService,
+ private readonly stepperPortCommandTaskSummaryBuilder: StepperPortCommandTaskSummaryBuilderService,
+ private readonly store: Store
+ ) {
+ }
+
+ public transform(
+ portCommandTask: PortCommandTask
+ ): Observable {
+ const payload = portCommandTask.payload;
+ switch (payload.taskType) {
+ case PortCommandTaskType.SetSpeed:
+ return this.linearPortCommandTaskSummaryBuilder.build(payload);
+ case PortCommandTaskType.SetAngle:
+ return this.store.select(ATTACHED_IO_PROPS_SELECTORS.selectById(portCommandTask)).pipe(
+ filter((ioProps): ioProps is AttachedIoPropsModel => !!ioProps),
+ switchMap((ioProps) => this.setAnglePortCommandTaskSummaryBuilder.build(
+ ioProps,
+ payload
+ ))
+ );
+ case PortCommandTaskType.Servo:
+ return this.store.select(ATTACHED_IO_PROPS_SELECTORS.selectById(portCommandTask)).pipe(
+ filter((ioProps): ioProps is AttachedIoPropsModel => !!ioProps),
+ switchMap((ioProps) => this.servoPortCommandTaskSummaryBuilder.build(
+ ioProps,
+ payload
+ ))
+ );
+ case PortCommandTaskType.Stepper:
+ return this.stepperPortCommandTaskSummaryBuilder.build(payload);
+ }
+ }
+}
diff --git a/src/app/control-schemes/port-command-task-summary/servo-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/servo-port-command-task-summary-builder.service.ts
new file mode 100644
index 00000000..2aeccc91
--- /dev/null
+++ b/src/app/control-schemes/port-command-task-summary/servo-port-command-task-summary-builder.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { TranslocoService } from '@ngneat/transloco';
+import { AttachedIoPropsModel, ServoTaskPayload } from '@app/store';
+
+@Injectable({ providedIn: 'root' })
+export class ServoPortCommandTaskSummaryBuilderService {
+ constructor(
+ private readonly translocoService: TranslocoService
+ ) {
+ }
+
+ public build(
+ attachedIoProps: AttachedIoPropsModel,
+ payload: ServoTaskPayload
+ ): Observable {
+ const angle = (attachedIoProps.motorEncoderOffset ?? 0) + payload.angle;
+ return this.translocoService.selectTranslate('controlScheme.servoTaskSummary', { angle });
+ }
+}
diff --git a/src/app/control-schemes/port-command-task-summary/set-angle-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/set-angle-port-command-task-summary-builder.service.ts
new file mode 100644
index 00000000..b5cee322
--- /dev/null
+++ b/src/app/control-schemes/port-command-task-summary/set-angle-port-command-task-summary-builder.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { TranslocoService } from '@ngneat/transloco';
+import { AttachedIoPropsModel, SetAngleTaskPayload } from '@app/store';
+
+@Injectable({ providedIn: 'root' })
+export class SetAnglePortCommandTaskSummaryBuilderService {
+ constructor(
+ private readonly translocoService: TranslocoService
+ ) {
+ }
+
+ public build(
+ attachedIoProps: AttachedIoPropsModel,
+ payload: SetAngleTaskPayload
+ ): Observable {
+ const angle = (attachedIoProps.motorEncoderOffset ?? 0) + payload.angle;
+ return this.translocoService.selectTranslate('controlScheme.setAngleTaskSummary', { angle });
+ }
+}
diff --git a/src/app/control-schemes/port-command-task-summary/stepper-port-command-task-summary-builder.service.ts b/src/app/control-schemes/port-command-task-summary/stepper-port-command-task-summary-builder.service.ts
new file mode 100644
index 00000000..12a6cf80
--- /dev/null
+++ b/src/app/control-schemes/port-command-task-summary/stepper-port-command-task-summary-builder.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { TranslocoService } from '@ngneat/transloco';
+import { StepperTaskPayload } from '@app/store';
+
+@Injectable({ providedIn: 'root' })
+export class StepperPortCommandTaskSummaryBuilderService {
+ constructor(
+ private readonly translocoService: TranslocoService
+ ) {
+ }
+
+ public build(
+ payload: StepperTaskPayload
+ ): Observable {
+ return this.translocoService.selectTranslate('controlScheme.stepperTaskSummary', payload);
+ }
+}
diff --git a/src/app/store/selectors/attached-io-props.selectors.ts b/src/app/store/selectors/attached-io-props.selectors.ts
index d2a0f428..5339558d 100644
--- a/src/app/store/selectors/attached-io-props.selectors.ts
+++ b/src/app/store/selectors/attached-io-props.selectors.ts
@@ -7,6 +7,12 @@ export const ATTACHED_IO_PROPS_SELECTORS = {
ATTACHED_IO_PROPS_FEATURE.selectAttachedIoPropsState,
ATTACHED_IO_PROPS_ENTITY_ADAPTER.getSelectors().selectEntities,
),
+ selectById: (
+ q: { hubId: string; portId: number }
+ ) => createSelector(
+ ATTACHED_IO_PROPS_SELECTORS.selectEntities,
+ (entities) => entities[hubAttachedIoPropsIdFn(q)]
+ ),
selectMotorEncoderOffset: (
q: { hubId: string; portId: number }
) => createSelector(
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 7ddbf6f1..6b58163a 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -111,19 +111,24 @@
"runningDetailsTotalTasksExecuted": "Total tasks executed:",
"runningDetailsLastTenTasksAverageExecutionTime": "Last 10 tasks average execution time (ms):",
"setAngleInput": "Set angle",
- "setAngleOutputConfigurationAngle": "Target angle",
+ "setAngleOutputConfigurationAngle": "Target angle (°)",
+ "setAngleTaskSummary": "Set angle: { angle }°",
"linearInputSpeed": "Speed",
"linearInputBrake": "Brake",
"linearOutputConfigurationTreatAsSwitch": "Treat button as a switch",
+ "linearTaskSummary": "Speed: { speed }",
+ "linearBrakeTaskSummary": "Brake",
"servoInput": "Servo control",
"servoOutputConfigurationCalibrate": "Calibrate",
- "servoOutputConfigurationCenterOffset": "Center offset (deg)",
- "servoOutputConfigurationAngleRange": "Servo range (deg)",
+ "servoOutputConfigurationCenterOffset": "Center offset (°)",
+ "servoOutputConfigurationAngleRange": "Servo range (°)",
"servoCalibrationIsInProgress": "Servo calibration is in progress",
"servoCalibrationError": "An error occurred during servo calibration",
"servoCalibrationCancel": "Cancel",
+ "servoTaskSummary": "Servo: { angle }°",
"stepperInput": "Make step",
- "stepperOutputConfigurationDegree": "Angle (deg)",
+ "stepperOutputConfigurationDegree": "Angle (°)",
+ "stepperTaskSummary": "Step: { degree }°",
"outputSpeedControlTitle": "Speed",
"outputPowerControlTitle": "Power",
"outputInvertControlTitle": "Invert",