Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 16 additions & 43 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,27 @@
### [FIX] `axdev` password guard contradicted the secrets complexity policy
### [COMPONENTS.COGNEX.VISION] AxoVisionProNet — TCP/.NET alternative to the PROFINET AxoVisionPro

**Note:** Bug fix in `src/axopen.dev`. Branch: `feat/axdev-user-secrets-loader`.
**Note:** Additive change. New component `AxoVisionProNet` in `src/components.cognex.vision/ctrl/src/AxoVisionProNet/`, its .NET twin + TCP protocol stack in `src/components.cognex.vision/src/AXOpen.Components.Cognex.Vision/AxoVisonProNet/`, a Blazor proxy view, and full showcase/doc wiring. No public-API removal; existing `AxoVisionPro` (PROFINET) is unchanged. Branch: `1104-new-featureaxovisionpro-alternative`.

- fix: `AXOpen.Dev.Validation.PasswordValidator` no longer rejects `$ & ( ) *`. These are endorsed by the set-time complexity policy (`configure-secrets.sh` requires a special char from `!@#$%^&*()_+-=`), so a password that satisfied the complexity rule was then rejected at use time by `axdev alf` / `axdev all` with "The PASSWORD contains problematic characters." The blocklist now keeps only genuinely-dangerous shell metacharacters (`` ` \ " ' | ; < > ? [ ] { } `` and whitespace) — safe because arguments reach apax/openssl via CliWrap (no shell). Error message and `PasswordValidatorTests` updated.

**Impact:** `apax alf` / `apax all` accept the same passwords the secrets-setup flow accepts; no more spurious rejection of compliant passwords.

**Testing:** `dotnet test src/axopen.dev/AXOpen.Dev.Tests` — 180 passed.

### [BUILD] `axdev` loads dotnet user-secrets at startup

**Note:** Developer-CLI enhancement in `src/axopen.dev`. No PLC source change, no public-API removal. Branch: `feat/axdev-user-secrets-loader`.

- feat: `AXOpen.Dev.Secrets.UserSecretsLoader` reads dotnet user-secrets into the process environment so PLC verbs resolve `AX_TARGET_PWD` / `AX_USERNAME` without a prior `source load-secrets.sh`. It locates the twin project's `<UserSecretsId>` (probes `../axpansion/twin` then `.`, overridable via the `AX_SECRETS_PROJECT` environment variable), reads its `secrets.json` from the OS user-secrets root, and flattens nested keys with the standard `key:subkey` convention.
- feat: New shared entry point `AxdevApp.Run(args)` calls `UserSecretsLoader.Load()` then builds and runs the command app. Both the packed `dotnet axdev` tool (`AXOpen.Dev.Tool/Program.cs`) and the in-repo dispatcher (`src/scripts/dev.cs`) now call `AxdevApp.Run` instead of `AxdevApp.Build().Run`.
- test: `AXOpen.Dev.Tests/Secrets/UserSecretsLoaderTests.cs` covers apply-from-store, existing-env-wins precedence, missing project / missing store / malformed JSON / no-`UserSecretsId` no-ops, the `AX_SECRETS_PROJECT` override, and nested-key flattening.

**Impact:**
- The template's credential UX (per-project `dotnet user-secrets`) is preserved while removing the bash `source load-secrets.sh` step, so apax verbs can call `axdev` directly on any platform.
- Precedence is non-surprising: an already-set environment variable (or apax variable) always wins over the secrets store; an explicit `-p/--password` still overrides everything in `PlcCommandSettings.ResolvePassword`.

**Risks/Review:**
- Secret loading is best-effort: a missing twin project, missing store, or unreadable JSON is a silent no-op, and the per-command argument guards still report any genuinely missing credential.

**Testing:**
- `dotnet test src/axopen.dev/AXOpen.Dev.Tests` — full suite green (176 passed), including the 8 new `UserSecretsLoaderTests`.

### [BUILD] Dependency-maintenance tooling + AXSharp `0.47.0-alpha.484` bump

**Note:** Build/CI tooling and dependency maintenance. No public-API change, no PLC source change. Branch: `deps-update`.

- feat: `scripts/update-latest-deps.ps1` — bumps all non-AXSharp dependencies (NuGet + npm) to their latest stable versions, sharing common helpers via `scripts/_deps-common.ps1`.
- feat: `scripts/update-vulnerable-deps.ps1` — scans npm and NuGet dependencies for known vulnerabilities and emits a report.
- chore: AXSharp packages bumped to `0.47.0-alpha.484` in `Directory.Packages.props`, with transitive dependencies reconciled. `.config/dotnet-tools.json` updated to match.
- chore: Added `.claude/skills/update-axsharp-version/SKILL.md` — skill for updating AXSharp and Inxton.Operon package versions.
- chore: Removed obsolete `package.json` / `package-lock.json` files across `src/components.abb.robotics`, `src/components.abstractions`, `src/data`, `src/data/src/AXOpen.Data.Blazor`, `src/inspectors`, and a stray `apax.yml`, to clean up the project structure.
- chore: `develop` branch GitVersion mode changed to `ContinuousDeployment`.
- chore: Styling dependencies refreshed (`src/styling/src/package.json` / lock; `momentum.css` regenerated).
- feat: `AxoVisionProNet` (`AXOpen.Components.Cognex.Vision`) — drives a Cognex VisionPro PC over a TCP/.NET channel instead of a PROFINET IO frame. The PLC `Invoke()`s `AxoRemoteTask`s (`Trigger`, `InspectionResult`, `SetRecipe`, `SendSpecificData`, `ReceiveSpecificData`, `TriggerWithSpecificData`, `SendSpecificDataAndTypes`) whose handlers run on the .NET twin; `Restore` is a local `AxoTask`. Use this variant when the camera PC is reachable over the network but is not wired as a PROFINET device.
- feat: `Control` (writable: `TriggerId`, `PartId`, `VariantId`) carries the per-request parameters; read-only `Config` holds the task supervision timers (`InfoTime` 5 s, `ErrorTime` 10 s, `TaskTimeout` 50 s); `Status` surfaces `Accepted`, `TriggerId`, `ErrorCode`, `ActionDescription`, `ErrorDescription`, `RejectReason`. Each remote task is checked for `HasRemoteException` per cycle and its `ErrorDetails` copied into `Status.ErrorDescription`; `Restore()` clears `ErrorCode` and re-arms the tasks. `SendSpecificDataAndTypes` is gated to manual control (commissioning only); `TriggerWithSpecificData` is disabled while `Trigger`/`SendSpecificData`/`SetRecipe` are busy.
- feat: .NET twin TCP protocol stack under `AxoVisonProNet/VisionProtocol/` — `VisionTcpClient` (async connect/send/receive loop with connect-timeout and cancellation), `VisionEnvelope` + `EnvelopeMessages` (framing), and `VisionTypedPayloadSerializer` (typed payload encode/decode). The socket is opened once at host start-up via `InitializeVisionClientAsync(host, port)` on the twin; `AxoVisionProNetSpecificDataContainer` exchanges the typed specific-data payloads.
- feat: `AxoVisionProNetView` (`AXOpen.Components.Cognex.Vision.blazor`) — dedicated proxy view on `AxoComponentContainerView` with `AxoVisionProNetStatusView` / `AxoVisionProNetCommandView` / `AxoVisionProNetSpotView` derivatives, so `RenderableContentControl` auto-selects the dedicated rendering by `Presentation`.
- feat: Showcase — `AxoVisionProNet_Example` (`src/showcase/app/src/components.cognex.vision/Documentation/AxoVisionProNet.st`) demonstrates the sequencer workflow, `TriggerWithSpecificData`, manual/commissioning send, and error recovery; wired into `CognexVision.st`, the Blazor `CognexVision.razor` page, `Program.cs` (`InitializeVisionClientAsync`), and the search registry.
- docs: Added `src/components.cognex.vision/docs/AxoVisionProNet.md` (CONTROLLER / .NET TWIN / BLAZOR tabs), linked it from `toc.yml`, referenced both VisionPro variants in `README.md`, and bumped `src/components.cognex.vision/docs/CHANGELOG.md` + `GitVersion.yml` to `0.57.0`.

**Impact:**
- Routine dependency bumps and vulnerability scanning are now scriptable and reproducible.
- AXSharp consumers build against `0.47.0-alpha.484`.
- Dead npm lockfiles no longer pollute the tree or trigger spurious tooling.
- Applications can integrate a Cognex VisionPro inspection over plain TCP without provisioning a PROFINET device, while keeping the same component-level operate/monitor/spot UX as the PROFINET `AxoVisionPro`.
- Remote-task exceptions are surfaced on `Status.ErrorDescription`/`ErrorCode` and cleared deterministically through `Restore()`.

**Risks/Review:**
- Dependency version bumps can introduce behavioural drift; verify a full `dotnet build` and the styling render after pulling.
- The .NET twin source folder is spelled `AxoVisonProNet` (missing the `i`) while the PLC `ctrl/` and Blazor folders use the correct `AxoVisionProNet`. Class/type names are correct so it compiles; the folder name is a cosmetic inconsistency.
- No automated test coverage was added: the TCP protocol classes (`VisionTcpClient`, `VisionEnvelope`, `VisionTypedPayloadSerializer`) have no xUnit tests in `tests/AXOpen.Components.Cognex.Vision.Tests`, and the ST trigger→result→restore flow has no AxUnit case in `ctrl/test/tests.st`.
- `InitializeVisionClientAsync` must be called by the host before the component runs, otherwise the remote tasks report "REMOTE TASK IS NOT INITIALIZED". The showcase `Program.cs` shows the required wiring (hardcoded demo endpoint `192.168.100.142:8500`).

**Testing:**
- Run `scripts/update-latest-deps.ps1` and `scripts/update-vulnerable-deps.ps1` end-to-end (exit code 0).
- `dotnet build` from solution root succeeds against the bumped package set.
- `apax ib` in `src/components.cognex.vision/ctrl` — PLC library + twin compile (`dotnet ixc`).
- `dotnet build src/components.cognex.vision/src/AXOpen.Components.Cognex.Vision.blazor` — proxy view compiles (verified: 0 errors).
- Showcase `Pages/components-cognex-vision/Documentation/CognexVision.razor`, AxoVisionProNet tab — exercise the sequencer, commissioning send, and error-recovery scenarios against a reachable VisionPro endpoint.

### [CORE] AxoSequencer step-timeout alarm — does not fall after timeout clears

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
USING AXOpen.Core;
USING AXOpen.Messaging;
USING AXOpen.Messaging.Static;
USING AXOpen.Components.Abstractions;
USING Siemens.Simatic.Hardware.Utilities;
USING Siemens.Simatic.MemoryAccess;
USING AXOpen.Timers;

NAMESPACE AXOpen.Components.Cognex.Vision
{S7.extern=ReadWrite}
{#ix-prop: public string DeviceIpAddress}
{#ix-prop: public string Proxy}
CLASS AxoVisionProNet EXTENDS AXOpen.Core.AxoComponent

VAR PUBLIC // TASKS
{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#Restore#>"}
RestoreTask : AXOpen.Core.AxoTask;

{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#Trigger#>"}
TriggerTask : AXOpen.Core.AxoRemoteTask;

{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#InspectionResult#>"}
InspectionResultTask : AXOpen.Core.AxoRemoteTask;

{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#SetRecipe#>"}
SetRecipeTask : AXOpen.Core.AxoRemoteTask;

{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#SendSpecificData#>"}
SendSpecificDataTask : AXOpen.Core.AxoRemoteTask;




{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#ReceiveSpecificData#>"}
ReceiveSpecificDataTask : AXOpen.Core.AxoRemoteTask;

{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#TriggerWithSpecificData#>"}
TriggerWithSpecificDataTask : AXOpen.Core.AxoRemoteTask;


{#ix-attr:[Container(Layout.Wrap)]}
{#ix-attr:[ComponentDetails("Tasks")]}
{#ix-set:AttributeName = "<#SendSpecificDataAndTypes#>"}
SendSpecificDataAndTypesTask : AXOpen.Core.AxoRemoteTask;

END_VAR

VAR PUBLIC // CONFIG
{#ix-attr:[Container(Layout.Stack)]}
{#ix-attr:[ComponentDetails("Config")]}
{#ix-attr:[ReadOnly()]}
Config : AxoVisionProNet_Config;
END_VAR

VAR PUBLIC // CONTROL
{#ix-attr:[Container(Layout.Stack)]}
{#ix-attr:[ComponentDetails("Control")]}
Control : AxoVisionProNet_Control;
END_VAR

VAR PUBLIC // STATUS
{#ix-attr:[Container(Layout.Stack)]}
{#ix-attr:[ComponentDetails("Status")]}
Status : AxoVisionProNet_Component_Status;
Messenger : AXOpen.Messaging.Static.AxoMessenger;
TaskMessenger : AXOpen.Messaging.Static.AxoMessenger;
END_VAR



VAR PRIVATE
_progress : INT;
_infoTimer : AXOpen.Timers.OnDelayTimer;
_errorTimer : AXOpen.Timers.OnDelayTimer;

END_VAR

///<summary>
/// Runs tasks and logic of this component.
/// >[!IMPORTANT] This method must or one of its overloads be called cyclically.
///</summary>
METHOD PUBLIC OVERRIDE Run
VAR_INPUT
inParent : IAxoObject;

END_VAR
VAR_OUTPUT

END_VAR


SUPER.Run(inParent);

Messenger.Serve(THIS);

RestoreTask.Run(THIS);


TriggerTask.Execute(THIS);
if TriggerTask.HasRemoteException and not TriggerTask.IsBusy() then
Status.ErrorDescription := TriggerTask.ErrorDetails;
END_IF;
InspectionResultTask.Execute(THIS);
if InspectionResultTask.HasRemoteException and not InspectionResultTask.IsBusy() then
Status.ErrorDescription := InspectionResultTask.ErrorDetails;
END_IF;
SetRecipeTask.Execute(THIS);
if SetRecipeTask.HasRemoteException and not SetRecipeTask.IsBusy() then
Status.ErrorDescription := SetRecipeTask.ErrorDetails;
END_IF;
SendSpecificDataTask.Execute(THIS); if SendSpecificDataTask.HasRemoteException and not SendSpecificDataTask.IsBusy() then
Status.ErrorDescription := SendSpecificDataTask.ErrorDetails;
END_IF;
ReceiveSpecificDataTask.Execute(THIS);
if ReceiveSpecificDataTask.HasRemoteException and not ReceiveSpecificDataTask.IsBusy() then
Status.ErrorDescription := ReceiveSpecificDataTask.ErrorDetails;
END_IF;
TriggerWithSpecificDataTask.SetIsDisabled(TriggerTask.IsBusy() OR SendSpecificDataTask.IsBusy() OR SetRecipeTask.IsBusy());
TriggerWithSpecificDataTask.Execute(THIS);
IF TriggerWithSpecificDataTask.HasRemoteException and not TriggerWithSpecificDataTask.IsBusy() then
Status.ErrorDescription := TriggerWithSpecificDataTask.ErrorDetails;
END_IF;
SendSpecificDataAndTypesTask.SetIsDisabled(not THIS.IsManuallyControllable()); // This task is only for commissioning purposes, so it can only be executed in manual mode.
SendSpecificDataAndTypesTask.Execute(THIS);
IF SendSpecificDataAndTypesTask.HasRemoteException and not SendSpecificDataAndTypesTask.IsBusy() then
Status.ErrorDescription := SendSpecificDataAndTypesTask.ErrorDetails;
END_IF;


//*************RESTORE********************
RestoreTask.SetIsDisabled(FALSE);
IF RestoreTask.Execute(THIS) THEN
THIS.Restore();
Status.ActionDescription := '<#Component restored#>';
END_IF;
//****************************************




END_METHOD

METHOD PUBLIC Trigger : IAxoTaskState
Trigger := TriggerTask.Invoke(THIS);
END_METHOD

METHOD PUBLIC InspectionResult : IAxoTaskState
InspectionResult := InspectionResultTask.Invoke(THIS);
END_METHOD

METHOD PUBLIC SetRecipe : IAxoTaskState
SetRecipe := SetRecipeTask.Invoke(THIS);
END_METHOD

METHOD PUBLIC SendSpecificData : IAxoTaskState
SendSpecificData := SendSpecificDataTask.Invoke(THIS);
END_METHOD

METHOD PUBLIC ReceiveSpecificData : IAxoTaskState
ReceiveSpecificData := ReceiveSpecificDataTask.Invoke(THIS);
END_METHOD
METHOD PUBLIC TriggerWithSpecificData : IAxoTaskState
TriggerWithSpecificData := TriggerWithSpecificDataTask.Invoke(THIS);
END_METHOD
METHOD PUBLIC SendSpecificDataAndTypes : IAxoTaskState
SendSpecificDataAndTypes := SendSpecificDataAndTypesTask.Invoke(THIS);
END_METHOD

METHOD PROTECTED OVERRIDE ManualControl
THIS._isManuallyControllable := TRUE;


END_METHOD
METHOD PRIVATE CallTimers
VAR_INPUT
signal : BOOL;
END_VAR

_infoTimer.OnDelay(THIS, signal AND Config.InfoTime > LT#0S ,Config.InfoTime);
_errorTimer.OnDelay(THIS, signal AND Config.ErrorTime > LT#0S , Config.ErrorTime);
END_METHOD

///<summary>
/// Restores this component into intial state.
///</summary>
METHOD PUBLIC OVERRIDE Restore
VAR
_index : INT;
END_VAR



TriggerTask.Restore();
SetRecipeTask.Restore();
SendSpecificDataTask.Restore();
SendSpecificDataAndTypesTask.Restore();
InspectionResultTask.Restore();
ReceiveSpecificDataTask.Restore();
TriggerWithSpecificDataTask.Restore();
Status.ActionDescription := '';
Status.ErrorDescription := '';
Status.Accepted := FALSE;
Status.ErrorCode := 0;
Status.RejectReason := '';
RestoreTask.DoneWhen(TRUE);

END_METHOD
END_CLASS
END_NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
NAMESPACE AXOpen.Components.Cognex.Vision
{S7.extern=ReadWrite}
CLASS PUBLIC AxoVisionProNetSpecificData

END_CLASS
END_NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
NAMESPACE AXOpen.Components.Cognex.Vision
{S7.extern=ReadWrite}
{#ix-generic:<TOnline, TPlain>}
CLASS PUBLIC AxoVisionProNetSpecificDataContainer

END_CLASS
END_NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
NAMESPACE AXOpen.Components.Cognex.Vision
{S7.extern=ReadWrite}
{#ix-attr:[Container(Layout.Stack)]}
CLASS PUBLIC AxoVisionProNet_Component_Status

VAR PUBLIC

ActionDescription : String;
ErrorDescription : string;
Accepted : BOOL;
TriggerId : INT;
ErrorCode : INT;
RejectReason : STRING;
END_VAR
END_CLASS
END_NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
NAMESPACE AXOpen.Components.Cognex.Vision
{S7.extern=ReadWrite}
{#ix-attr:[Container(Layout.Stack)]}
CLASS PUBLIC AxoVisionProNet_Config
VAR PUBLIC
{#ix-set:AttributeName = "<#Info time#>"}
InfoTime : LTIME := LT#5S;
{#ix-set:AttributeName = "<#Error time#>"}
ErrorTime : LTIME := LT#10S;
{#ix-set:AttributeName = "<#Task timeout#>"}
TaskTimeout : LTIME := LT#50S;

END_VAR
END_CLASS
END_NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
NAMESPACE AXOpen.Components.Cognex.Vision
{S7.extern=ReadWrite}
{#ix-attr:[Container(Layout.Stack)]}
CLASS PUBLIC AxoVisionProNet_Control
VAR PUBLIC
TriggerId : INT;
PartId : STRING;
VariantId : STRING;
END_VAR
END_CLASS
END_NAMESPACE
Loading
Loading