web
You’re offline. This is a read only version of the page.
close
Support Portal

picoScan150: Field Evaluation and Perpendicular Distance API Possibilites

Configure, manage, and monitor picoScan150 field evaluation zones via REST and CoLa APIs, including group setup, polygon geometry and activation.
Related Products
picoScan100

Table of Contents

Firmware compatibility Tested against picoScan150 firmware V2.3.3. The API may change in future firmware releases.
Introduction

1 - Introduction

What is Field Evaluation?

Field Evaluation checks whether scan points fall inside a defined polygon zone. Each scan yields a binary state - FREE or INFRINGED - that can drive a digital output, be read over the API, or be pushed over a WebSocket.

What is Field Evaluation with Teach-In?

Field Evaluation with Teach-In runs the same group/output machinery as ordinary Field Evaluation, but the detection contour is learned from the live scan rather than drawn as a fixed polygon.

What is perpendicular distance evaluation?

Perpendicular Distance measures the signed perpendicular distance from the object entering the field to a reference line.


Field Evaluation

2 - Typical Workflow: Field Evaluation

The following workflow is an example of how an API interaction can look like.

1 CREATE      Define evaluation 'TriangleEval' (a triangular detection zone), place it in a
              group, route that group to digital output InOut1, then save to the device.
2 USE         Read the live group state (configured / active / inactive) and the current
              FREE or INFRINGED evaluation result.
3 CHANGE      Raise the minimum detectable object size from 100 mm to 200 mm, then save.
4 RESHAPE     Replace the triangular zone with a square zone, then save.
5 ACTIVATE    Switch the group's activation to TELEGRAM, save, then turn the
              group on and read back its state and detection result.
6 DEACTIVATE  Turn the group off and confirm it is now inactive.
7 DELETE      Restore the original group mapping and output wiring, clear the evaluation
              slot, save, and verify the group returns to NOT_CONFIGURED.
Command / variableDescriptionRESTCoLa ACoLa B
Eval001–048Detection parameters for one evaluation slot (mode, minimum object size, timing).
EvaluationGroupMappingGroups the evaluation and sets its activation type (ALWAYS at creation, switched to TELEGRAM for Activate/Deactivate).
SetFieldEvaluationContourWrites the detection polygon (zone shape) for an evaluation's volume.
GetFieldEvaluationContourReads back the current detection polygon.
PortConfigurationWires a digital output pin to an evaluation's INFRINGED signal.
ActivateEvaluationGroupTurns a TELEGRAM-activated group on or off at runtime (no authentication); has no effect on an ALWAYS group.
FieldEvaluationGroupStateRead-only group state (NOT_CONFIGURED / ACTIVE / INACTIVE).
FieldEvaluationResultRead-only per-evaluation result (FREE / INFRINGED / DETECTING_…).
WriteEeprom / mEEwriteallPersists the current RAM configuration to non-volatile memory.

2.1 Use a group and change the activation method

A group lives in EvaluationGroupMapping. Activation type 1=ALWAYS, 2=INPUTS, 3=TELEGRAM. The group is created with Activation.Type = 1 (ALWAYS) so it is active from CREATE through RESHAPE without needing a telegram. Only at the ACTIVATE step (2.3) is the mapping rewritten to switch the same group to Activation.Type = 3 (TELEGRAM) — ActivateEvaluationGroup has no effect on an ALWAYS group.

REST:   PUT  /api/EvaluationGroupMapping   (creation — ALWAYS)
        {"data":{"EvaluationGroupMapping":[{"Name":"TriangleGroup","Id":1,
        "Activation":{"Type":1,"InitialActivationStateTelegram":false},"EvalIds":[1]}]}}
        PUT  /api/EvaluationGroupMapping   (before ACTIVATE — switch to TELEGRAM)
        {"data":{"EvaluationGroupMapping":[{"Name":"TriangleGroup","Id":1,
        "Activation":{"Type":3,"InitialActivationStateTelegram":false},"EvalIds":[1]}]}}
CoLa A: sWN EvaluationGroupMapping 1 D TriangleGroup 1 1 2 2 2 2 2 2 2 2 0 1 1   (ALWAYS, type=1)
        sWN EvaluationGroupMapping 1 D TriangleGroup 1 3 2 2 2 2 2 2 2 2 0 1 1   (TELEGRAM, type=3)
        RX: sWA EvaluationGroupMapping

2.2 Change geometry and detection settings

The Eval00N struct holds mode, minimum object size and timing. The contour is written separately with SetFieldEvaluationContour (write the eval slot first or it returns error 1).

REST:   PUT  /api/Eval001   (min-obj 100 mm)
        {"data":{"Eval001":{"Name":"TriangleEval",
        "EvaluationParamsCommon":[{"Mode":0,"MinimumObjectSizeHorizontal":100}],"VolumeList":[1]}}}
        POST /api/SetFieldEvaluationContour   (triangle (0,0)(2000,-1000)(2000,1000))
        {"data":{"EvaluationId":1,"Points":[{"X":0,"Y":0},{"X":2000,"Y":-1000},{"X":2000,"Y":1000}],
        "LowerZLimit":0,"UpperZLimit":0}}
CoLa A: sWN Eval001 1 C TriangleEval 1 1 0 64 0 0 0 0 64 64 64 0 7D0 0 0 0 0 0 1 1
        sMN SetFieldEvaluationContour 1 3 0 0 7D0 FFFFFC18 7D0 3E8 0 0
        RX: sAN SetFieldEvaluationContour 0      (0 = OK)

2.3 Activate / deactivate a group

The group must first be switched from ALWAYS to TELEGRAM (2.1) and persisted — only then does ActivateEvaluationGroup (no auth) have any effect, otherwise it always reports Success = false and the group can never be turned off.

REST:   POST /api/ActivateEvaluationGroup {"data":{"List":[{"GroupId":1,"Activate":true}]}}
CoLa A: sMN ActivateEvaluationGroup 1 1 1   →   sAN ActivateEvaluationGroup 1 1

2.4 Read state and scan results

VariableContents
FieldEvaluationGroupState48× 0/1/2 (NOT_CONFIGURED/ACTIVE/INACTIVE)
FieldEvaluationResult48× state: 2=FREE, 4=INFRINGED, 3/5 transitions

WebSocket: the same result struct is pushed continuously when subscribed.

To confirm the polygon was stored correctly, read it back with GetFieldEvaluationContour. It returns the point list for an evaluation slot, so this example calls it right after CREATE and again after RESHAPE to verify the contour took effect.

REST:   POST /api/GetFieldEvaluationContour {"data":{"EvaluationId":1}}
        → {"Contour":[{"EvaluationId":1,"Points":[{"X":0,"Y":0}, … ]}]}
CoLa A: sMN GetFieldEvaluationContour 1
        → sAN GetFieldEvaluationContour 0 1 3 0 0 7D0 FFFFFC18 7D0 3E8   (flag, EvalId, PointCount, x y …)

2.5 Delete an evaluation

REST:   1 PUT  /api/EvaluationGroupMapping {"data":{"EvaluationGroupMapping":[]}}   (clear all groups)
        2 PUT  /api/Eval001 {"data":{"Eval001":{"Name":"","VolumeList":[]}}}        (reset eval slot)
        3 POST /api/WriteEeprom                                                     → group becomes NOT_CONFIGURED
CoLa A: 1 sWN EvaluationGroupMapping 0          (clear all groups)
        2 sWN Eval001 1 0 1 1 0 64 0 0 0 0 64 64 64 0 7D0 0 0 0 0 0 0   (VolumeList=0)
        3 sMN mEEwriteall                       → group becomes NOT_CONFIGURED

2.6 Assign outputs (PortConfiguration)

REST:   PUT  /api/PortConfiguration
        {"data":{"PortConfiguration":[{"PortType":1,"Name":"TriangleOut",
        "OutputSettings":{"Logic":1,"Sources":[{"Name":"EG01"}]}}, … remaining ports]}}
CoLa A: sWN PortConfiguration 1 B TriangleOut 0 A 1 0 0 1 0 0 C8 1 1 0 0 1 EG01 0 0 0 0 0 0 0 ...
        RX: sWA PortConfiguration   (all ports; OUTPUT→EG01 goes HIGH on INFRINGED)

Field Evaluation (Teach-In)

3 - Typical Workflow: Field Evaluation (Teach-In)

The following workflow is an example of how an API interaction can look like..

1 CREATE      Define evaluation e.g. 'TeachInEval' in CONTOUR mode, define the Region of Interest
              for Teach-In, place it in a group, route the evaluation to digital
              output InOut2, run Teach-In (tolerance 250 mm), then save to the device.
2 USE         Read the live group state, the FREE / INFRINGED evaluation result, and the
              TeachInContourValid flag.
3 CHANGE      Raise the minimum detectable object size from 100 mm to 200 mm, then save.
4 RESHAPE     Re-teach the contour with a wider tolerance (250 mm -> 400 mm), then save.
5 ACTIVATE    Switch the group's activation to TELEGRAM, save, then turn the
              group on and read back its state and detection result.
6 DEACTIVATE  Turn the group off and confirm it is now inactive.
7 DELETE      Restore the original group mapping and output wiring, clear the evaluation
              slot, save, and verify the group returns to NOT_CONFIGURED.
Command / variableDescriptionRESTCoLa ACoLa B
Eval001–048 (Mode = CONTOUR)Detection parameters for one evaluation slot, using the learned-contour mode.
SetFieldEvaluationContour (Region of Interest)Writes the Region of Interest inside which Teach-In learns the contour — not the detection contour itself.
GetFieldEvaluationContourReads back the Region of Interest polygon — not the learned contour.
TeachInToleranceFront/BackTolerance distance in front of / behind the taught object, in mm.
TeachInMinimumTimeMs, TeachInActivationInputMinimum settle time before teaching; input pin that can trigger Teach-In (0 = software-only).
StartTeachIn / StopTeachInStarts and stops the Teach-In learning sequence.
TeachInContourValidRead-only flag — 1 once the last Teach-In produced a valid contour.
EvaluationGroupMapping, PortConfigurationGroups the evaluation and wires its output, same as Field Evaluation.
ActivateEvaluationGroup, FieldEvaluationGroupState/ResultRuntime activation and state/result read-back, same as Field Evaluation.
WriteEeprom / mEEwriteallPersists the current RAM configuration to non-volatile memory.

3.1 CONTOUR evaluation and the Region of Interest for Teach-In

The eval slot is written exactly like a Field Evaluation slot but with Mode = 2 (CONTOUR). The polygon supplied to SetFieldEvaluationContour is not the detection contour - it is the Region of Interest inside which Teach-In learns the reference contour. It is written once in CREATE and never reshaped (GetFieldEvaluationContour reads this region back, not the taught contour).

REST:   PUT  /api/Eval002   (CONTOUR, min-obj 100 mm)
        {"data":{"Eval002":{"Name":"TeachInEval",
        "EvaluationParamsCommon":[{"Mode":2,"MinimumObjectSizeHorizontal":100}],"VolumeList":[2]}}}
        POST /api/SetFieldEvaluationContour   (Region of Interest, square)
        {"data":{"EvaluationId":2,"Points":[{"X":0,"Y":-1000},{"X":2000,"Y":-1000},
        {"X":2000,"Y":1000},{"X":0,"Y":1000}],"LowerZLimit":0,"UpperZLimit":0}}
CoLa A: sWN Eval002 1 B TeachInEval 2 1 2 64 0 0 0 0 64 64 64 0 7D0 0 0 0 0 0 1 2
        sMN SetFieldEvaluationContour 2 4 0 FFFFFC18 7D0 FFFFFC18 7D0 3E8 0 3E8 0 0

3.2 Run Teach-In (Settings -> Start -> wait -> Stop)

Teach-In is global (no per-slot binding). Write the tolerance / minimum-time / activation-input settings, start, wait a fixed 3 s, then stop.

REST:   PUT  /api/TeachInToleranceFront {"data":{"TeachInToleranceFront":250}}
        PUT  /api/TeachInToleranceBack  {"data":{"TeachInToleranceBack":250}}
        PUT  /api/TeachInMinimumTimeMs  {"data":{"TeachInMinimumTimeMs":500}}
        PUT  /api/TeachInActivationInput {"data":{"TeachInActivationInput":0}}
        POST /api/StartTeachIn   ->  (wait 3 s)  ->  POST /api/StopTeachIn
        GET  /api/TeachInContourValid   (1 = valid, log-only)
CoLa A: sWN TeachInToleranceFront FA   sWN TeachInToleranceBack FA
        sWN TeachInMinimumTimeMs 1F4   sWN TeachInActivationInput 0
        sMN StartTeachIn   ->  (wait 3 s)  ->  sMN StopTeachIn
        sRN TeachInContourValid  ->  sRA TeachInContourValid 1

3.3 Reshape = re-teach with a wider tolerance

“Changing the polygon” is done by re-running Teach-In with a different tolerance (250 mm -> 400 mm). The Region of Interest is left untouched.

3.4 Activate / deactivate, read state, delete

Identical to Field Evaluation: the group is created ALWAYS-active and switched to TELEGRAM before ACTIVATE (Field Evaluation §2.1), ActivateEvaluationGroup (no auth) then turns it on/off, FieldEvaluationGroupState / FieldEvaluationResult read state, and DELETE restores the saved mapping / ports and clears Eval002 back to NOT_CONFIGURED.

Region of Interest, not detection contour The polygon written with SetFieldEvaluationContour only bounds where Teach-In operates. The actual detection contour is the taught reference; verify a successful teach with TeachInContourValid, not GetFieldEvaluationContour.

Perpendicular Distance

4 - Typical Workflow: Perpendicular Distance

The following workflow is an example of how an API interaction can look like.

1 CREATE      Switch the group slot to perpendicular-distance mode, write the
              evaluation (object detection), write its detection field directly
              to Volume00N, set the reference line, then place it in a group
              (Activation = ALWAYS) that references it and save to the device.
              (Perpendicular distance is NOT wired to a digital output.)
2 USE         Read the live group state and the perpendicularDistanceResult (per-target
              minimum / maximum distance to the line).
3 CHANGE      Move the reference line (1000 mm → 2000 mm), then save.
4 RESHAPE     Re-orient the reference line from forward (1,0) to lateral (0,1), then save.
5 ACTIVATE    Switch the group's activation to TELEGRAM, save, then turn the
              group on and read back its state and distance result.
6 DEACTIVATE  Turn the group off and confirm it is now inactive.
7 DELETE      Restore the group type and mapping, reset the field and line, save, and
              verify the group returns to NOT_CONFIGURED.
Command / variableDescriptionRESTCoLa ACoLa B
EvaluationGroupType (write)Switches a group slot between Field Evaluation (0) and Perpendicular Distance (1).
Eval001–048 (object detection)Detection parameters for the object-detection part of the PD evaluation (mode, minimum object size, timing).
Volume001–048 (detection field, direct write)The detection polygon itself — write it directly here for PD; SetFieldEvaluationContour returns OK but has proven unreliable for PD slots.
perpDist001–048The reference line: in-plane normal vector plus a signed perpendicular distance.
EvaluationGroupMappingGroups the evaluation and sets its activation type (ALWAYS at creation, switched to TELEGRAM for Activate/Deactivate).
ActivateEvaluationGroupTurns a TELEGRAM-activated group on or off at runtime (no authentication).
perpendicularDistanceResultRead-only per-target minimum/maximum distance to the reference line.

4.1 Use a group and change the activation method

A Perpendicular Distance group references its evaluation slot via EvalIds (e.g. [3]); without it the group is empty and the result stays blank. The group is created with Activation.Type = 1 (ALWAYS) so it is active from CREATE through RESHAPE without needing a telegram. Only at the ACTIVATE step (4.3) is the mapping rewritten to switch the same group to Activation.Type = 3 (TELEGRAM) — ActivateEvaluationGroup has no effect on an ALWAYS group.

REST:   PUT  /api/EvaluationGroupMapping   (creation — ALWAYS)
        {"data":{"EvaluationGroupMapping":[{"Name":"LineGroup","Id":3,
        "Activation":{"Type":1,"InitialActivationStateTelegram":false},"EvalIds":[3]}]}}
        PUT  /api/EvaluationGroupMapping   (before ACTIVATE — switch to TELEGRAM)
        {"data":{"EvaluationGroupMapping":[{"Name":"LineGroup","Id":3,
        "Activation":{"Type":3,"InitialActivationStateTelegram":false},"EvalIds":[3]}]}}
CoLa A: sWN EvaluationGroupMapping 1 9 LineGroup 3 1 2 2 2 2 2 2 2 2 0 1 3   (ALWAYS, type=1)
        sWN EvaluationGroupMapping 1 9 LineGroup 3 3 2 2 2 2 2 2 2 2 0 1 3   (TELEGRAM, type=3)
        RX: sWA EvaluationGroupMapping

4.1a Write the evaluation and its detection field

A Perpendicular Distance evaluation needs a detection field exactly like a Field Evaluation: write Eval00N with VolumeList=[slot], then write the polygon directly to Volume00N.

REST:   PUT  /api/Eval003   {"data":{"Eval003":{"Name":"LineRef",
        "EvaluationParamsCommon":[{"Mode":0,"MinimumObjectSizeHorizontal":200}],"VolumeList":[3]}}}
        PUT  /api/Volume003   (square (0,-1000)(2000,-1000)(2000,1000)(0,1000), direct write)
        {"data":{"Volume003":{"Version":1,"Name":"","ID":3,"ExtrudedPolygonList":[{"Version":1,
        "LowerZLimit":0,"UpperZLimit":0,"Points":[{"x":0,"y":-1000},{"x":2000,"y":-1000},
        {"x":2000,"y":1000},{"x":0,"y":1000}]}]}}}
CoLa A: sWN Eval003 1 7 LineRef 3 1 0 C8 0 0 0 0 32 32 32 0 7D0 0 0 0 0 0 1 3
        sWN Volume003 1 0 3 1 1 0 0 4 0 FFFFFC18 7D0 FFFFFC18 7D0 3E8 0 3E8
        RX: sWA Volume003

4.2 Define and change the reference line

REST:   PUT  /api/perpDist003   (forward line, in-plane normal (1,0) @ 1000 mm)
        {"data":{"perpDist003":{"Normal":{"X":1.0,"Y":0.0,"Z":0.0},"Distance":1000}}}
        PUT  /api/perpDist003   (reshape → lateral normal (0,1))
        {"data":{"perpDist003":{"Normal":{"X":0.0,"Y":1.0,"Z":0.0},"Distance":1000}}}
CoLa A: sWN perpDist003 3F800000 00000000 00000000 3E8 0 0 1 0 FFF85EE0 7A120 1 0   (forward @ 1000 mm)
        sWN perpDist003 00000000 3F800000 00000000 3E8 0 0 1 0 FFF85EE0 7A120 1 0   (lateral normal)

4.3 Activate / deactivate

The group must first be switched from ALWAYS to TELEGRAM (4.1) and persisted — only then does ActivateEvaluationGroup (no auth) have any effect, otherwise it always reports Success = false and the group can never be turned off.

4.4 Read results

REST:   GET  /api/perpendicularDistanceResult   (empty list when no targets on the line)
CoLa A: sRN perpendicularDistanceResult → timestamp, count, per-eval min/max distance + position

4.5 Delete an evaluation

REST:   1 PUT  /api/EvaluationGroupMapping {"data":{"EvaluationGroupMapping":[]}}
        2 PUT  /api/Eval003 {"data":{"Eval003":{"Name":"","VolumeList":[]}}}        (reset eval slot)
        3 PUT  /api/Volume003 {"data":{"Volume003":{"ExtrudedPolygonList":[]}}}     (reset field)
        4 PUT  /api/perpDist003 {"data":{"perpDist003":{ … zero line … }}}
        5 PUT  /api/EvaluationGroupType {"data":{"EvaluationGroupType":[0, … all 0 …]}}   (back to Field Evaluation)
        6 POST /api/WriteEeprom → NOT_CONFIGURED
CoLa A: 1 sWN EvaluationGroupMapping 0
        2 sWN Eval003 1 0 3 ...                (reset eval slot, VolumeList=0)
        3 sWN Volume003 1 0 3 0                (reset field, empty polygon list)
        4 sWN perpDist003 ...0...      (zero line)
        5 sWN EvaluationGroupType ...all 0...   (back to Field Evaluation)
        6 sMN mEEwriteall → NOT_CONFIGURED

Persistence

5 - Persist to Non-Volatile Memory

RAM changes survive reboot only after sMN mEEwriteall (CoLa A/B) or POST /api/WriteEeprom (REST).

REST:   POST /api/WriteEeprom   →   status 0
CoLa A: sMN mEEwriteall          →   sAN mEEwriteall 1

Reference

6 - AI Agent Context (AGENT.md)

The full machine-oriented reference (encoding rules, every variable, pitfalls) lives in AGENT.md.

# AGENT.md - field-eval-config

> AI agent handover document - picoScan150 Field Evaluation via API (CoLa A/B + REST).
> Read before editing any file in this workspace.

---

## 1. Device & Connection

| Property | Value |
| --- | --- |
| Default device IP | `192.168.0.1` |
| Firmware tested | picoScan150 v2.3.3 |
| REST base URL | `http://192.168.0.1/api/` |
| CoLa A port | 2111 |
| CoLa B port | 2112 |

---

## 2. Authentication

| Level | CoLa A command | Hash |
| --- | --- | --- |
| Service (L4) | `sMN SetAccessMode 4 81BE23AA` | `81BE23AA` |
| Authorized Client (L3) | `sMN SetAccessMode 3 F4724744` | `F4724744` |

Logout CoLa A: `sMN Run` → `sAN Run 1`.

REST auth: username `Service`, password `servicelevel` - challenge-response (SHA-256).
Write operations require at least Authorized Client (L3); use Service (L4) to be safe.

---

## 3. CoLa A Encoding Rules

| Type | Encoding | Example |
| --- | --- | --- |
| UInt / UDInt | hex, no prefix, no leading zeros | 200 → `C8`, 2000 → `7D0` |
| DInt (signed 32-bit) | 8-digit 2's-complement hex | -1000 → `FFFFFC18`, 2000 → `7D0` |
| Bool | `0` or `1` | |
| Enum8 | decimal | BLANKING=0, REFLECTOR_ONLY=1, CONTOUR=2 |
| String (variable length) | `hex_len SPACE chars` | `8 Triangle` |
| String (fixed length) | chars only, no length prefix | `DRDY`, `EG01` |
| Array (variable length) | decimal count then elements | `2 val1 val2` |
| Array (fixed length) | elements only, no count prefix | Activation_t.Inputs (8 values) |

---

## 4. Key CoLa A Variables

### 4.1 Eval001–Eval048 (AuthorizedClient write)

One slot per evaluation case. Slot N references Volume N.

**Field order:**

```text
sWN Eval002
  <Version:UInt>
  <NameLen:hex> <Name:str>
  <ID:UInt>
  <EvalParamsCount=1>
  <Mode:Enum8>                          0=BLANKING 1=REFLECTOR_ONLY 2=CONTOUR
  <MinObjSizeH:UDInt mm>                e.g. C8=200mm 64=100mm
  <OcclusionHandling:Bool>
  <TreatMissingAsInfringed:Bool>
  <TreatGlareAsInfringed:Bool>          always 0
  <ManipulationProtection:Bool>
  <TimeManipulationProtection:UDInt ms> e.g. 32=50ms 64=100ms
  <TimeInfringedState:UDInt ms>
  <Reserved2:UDInt>                     always 0
  <RadialSeparationEnable:Bool>
  <RadialSeparationDistance:UDInt mm>   e.g. 7D0=2000mm
  <Reserved3:UDInt>                     always 0
  <Reserved4:UDInt>                     always 0
  <EasyTeachCount=0>
  <DynamicFieldCount=0>
  <ReservedParamsCount=0>
  <VolumeListCount=1>
  <VolumeId:UInt>                       same as slot number
```

**Example** (slot 2, "Zone2", BLANKING, 200 mm, 50 ms):

```text
sWN Eval002 1 5 Zone2 2 1 0 C8 0 0 0 0 32 32 32 0 7D0 0 0 0 0 0 1 2
-> sWA Eval002
```

**Reset** (clear VolumeList, empty name):

```text
sWN Eval002  1  0  2  1  0 C8 0 0 0 0 32 32 32 0 7D0 0 0  0 0 0  0
```

### 4.2 SetFieldEvaluationContour (AuthorizedClient)

```text
sMN SetFieldEvaluationContour <EvalId> <PointCount> <x1> <y1> ... <xN> <yN> <LowerZ> <UpperZ>
```

All coordinates are hex (DInt). ErrorCodes: 0=OK, 1=INVALID_EVALUATION_ID, 2=INVALID_POLYGON, 3=INVALID_ZLIM.

Error 1 means `Eval00N.VolumeList` is empty - write the Eval slot first.

**Example** (slot 2, 2 m × 2 m rectangle, 2D):

```text
TX: sMN SetFieldEvaluationContour 2 4 0 FFFFFC18 7D0 FFFFFC18 7D0 3E8 0 3E8 0 0
RX: sAN SetFieldEvaluationContour 0
```

> **Unreliable for Perpendicular Distance slots.** `SetFieldEvaluationContour` returns
> `ErrorCode 0` for a Perpendicular Distance slot, but has proven unreliable there in
> practice - write `Volume00N` directly instead (§4.2b). Field Evaluation slots are unaffected.

### 4.2b Volume00N (AuthorizedClient write) - direct field write

`SetFieldEvaluationContour` is a convenience method that writes this very variable.
For **Perpendicular Distance** slots, write it directly instead - verified on FW 2.3.3
against the live device (§4.2).

```text
sWN Volume00N
  <Version:UInt=1>
  <NameLen:hex=0>                        name is always empty in practice
  <ID:UInt>                              arbitrary id - slot number by convention
  <PolygonCount:decimal>                 normally 1
  <PolyVersion:UInt=1>
  <LowerZLimit:DInt mm>
  <UpperZLimit:DInt mm>
  <PointCount:decimal>
  <x1:DInt> <y1:DInt> ... <xN:DInt> <yN:DInt>
```

**Example** (slot 3, 2 m × 2 m square):

```text
TX: sWN Volume003 1 0 3 1 1 0 0 4 0 FFFFFC18 7D0 FFFFFC18 7D0 3E8 0 3E8
RX: sWA Volume003
```

**Reset** (empty polygon list):

```text
sWN Volume003 1 0 3 0
```

REST: `GET`/`POST /api/Volume00N` -
`{"Version":1,"Name":"","ID":<id>,"ExtrudedPolygonList":[{"Version":1,"LowerZLimit":0,"UpperZLimit":0,"Points":[{"x":..,"y":..}, ...]}]}`.
Same slot N as `Eval00N.VolumeList`.

### 4.3 EvaluationGroupMapping (AuthorizedClient write)

Variable-length array of up to 48 group structs. **The entire array is replaced on every write** - always include all groups you want to keep.

**Group struct field order:**

```text
<NameLen:hex> <Name:str>
<Id:UInt>
<Activation.Type:Enum8>                  0=DEACTIVATED 1=ALWAYS 2=INPUTS 3=TELEGRAM
<Activation.Inputs[8]>                   fixed length, NO count prefix; 0=INACTIVE 1=ACTIVE 2=DONT_CARE
<Activation.InitialStateTelegram:Bool>
<EvalIds.count:decimal>
<EvalId1:UInt> [<EvalId2> ...]           a Perpendicular Distance group references its eval slot here too - see §12.5
```

**Example** (2 TELEGRAM groups):

```text
TX: sWN EvaluationGroupMapping 2 8 Triangle 1 3 2 2 2 2 2 2 2 2 0 1 1 6 Square 2 3 2 2 2 2 2 2 2 2 0 1 2
RX: sWA EvaluationGroupMapping
```

### 4.4 ActivateEvaluationGroup (no auth required)

```text
TX: sMN ActivateEvaluationGroup <count> <gid1> <act1> [<gid2> <act2> ...]
RX: sAN ActivateEvaluationGroup <count> <Success1> [<Success2> ...]
```

act: 1=on, 0=off. ALWAYS-type groups return Success=0 (they are auto-active) - not an error.

### 4.5 mEEwriteall (Service L4)

```text
TX: sMN mEEwriteall
RX: sAN mEEwriteall 1
```

Persists all RAM state to EEPROM. After the call the device re-initializes - wait 1 s before reading state. **Write the complete EvaluationGroupMapping before calling** - unlisted Eval slots have their VolumeList cleared.

### 4.6 FieldEvaluationGroupState (read-only)

```text
sRN FieldEvaluationGroupState
-> sRA FieldEvaluationGroupState <s1> ... <s48>
   0=NOT_CONFIGURED  1=ACTIVE  2=INACTIVE
```

### 4.7 FieldEvaluationResult (read-only)

```text
sRN FieldEvaluationResult
-> sRA FieldEvaluationResult <Version> <Timestamp> <State1> <PosCount1> ...
   State: 0=NOT_CONFIGURED 1=INACTIVE 2=FREE 3=DETECTING_INFRINGED 4=INFRINGED 5=DETECTING_FREE
```

Version and Timestamp are the first two tokens. Positions array is always empty in FW 2.3.3.

### 4.8 PortConfiguration (AuthorizedClient write)

Fixed-length array of port structs (no count prefix) - always write the complete array. Accessible via **CoLa A, CoLa B, and REST** (GET/POST `/api/PortConfiguration`).

Source names: `EG01`–`EG48` (evaluation N's output signal), `DRDY` (data-ready signal). Outputs are assigned to **evaluations**, not to groups: the output goes HIGH when that **evaluation** is INFRINGED.

**CoLa A token formats:**

```text
OUTPUT (1 source, 25 tokens):
  1 <NameLen> <Name> 0 A 1 0 0  1 0 0 C8 1 1 0 0  1 <SrcName> 0 0 0  0 0 0 0

INPUT (no sources, 21 tokens - WRITE requires 21; readback returns only 20):
  0 <NameLen> <Name> 0 A 1 0 0  0 0 0 C8 1 1 0 0  0 0 0 0 0
```

**REST body** (per port, OUTPUT example wired to group 1):

```json
{
  "PortType": 1, "Name": "Triangle",
  "InputSettings": {"Logic": 0, "Debounce": 10, "Sensitivity": 1, "Reserved1": 0, "Reserved2": 0},
  "OutputSettings": {
    "Logic": 1, "OutputMode": 0, "RestartType": 0, "RestartTime": 200,
    "RestartInput": 1, "Combination": 1, "Reserved3": 0, "Reserved4": 0,
    "Sources": [{"Name": "EG01", "Invert": false, "Reserved5": 0, "Reserved6": 0}]
  },
  "EncoderPortType": 0, "Reserved8": 0, "Reserved9": 0, "Reserved10": 0
}
```

INPUT ports use `"Sources": []`. PortType: 0=INPUT, 1=OUTPUT, 2=ENCODER.

**Verified CoLa A example** (InOut1→EG01, InOut2→EG02, InOut7/8→DRDY):

```text
sWN PortConfiguration
  1 8 Triangle 0 A 1 0 0 1 0 0 C8 1 1 0 0 1 EG01 0 0 0 0 0 0 0
  1 6 Square   0 A 1 0 0 1 0 0 C8 1 1 0 0 1 EG02 0 0 0 0 0 0 0
  0 6 InOut3   0 A 1 0 0 0 0 0 C8 1 1 0 0 0 0 0 0 0
  0 6 InOut4   0 A 1 0 0 0 0 0 C8 1 1 0 0 0 0 0 0 0
  0 6 InOut5   0 A 1 0 0 0 0 0 C8 1 1 0 0 0 0 0 0 0
  0 6 InOut6   0 A 1 0 0 0 0 0 C8 1 1 0 0 0 0 0 0 0
  1 6 InOut7   0 A 1 0 0 1 0 0 C8 1 1 0 0 1 DRDY 0 0 0 0 0 0 0
  1 6 InOut8   0 A 1 0 0 1 0 0 C8 1 1 0 0 1 DRDY 0 0 0 0 0 0 0
-> sWA PortConfiguration
```

---

## 5. REST API

REST exposes **every** variable needed for the full Field Evaluation / Perpendicular
Distance workflow - no CoLa fallback is required. The variable endpoints below are
read/written via `RESTClient.readVariable` / `writeVariable` (GET/POST `/api/<Var>`):

| Endpoint | Access |
| --- | --- |
| `Eval001–Eval048` | GET + POST (AuthorizedClient) - `VolumeList` is a flat int list |
| `EvaluationGroupMapping` | GET + POST (AuthorizedClient) - use `InitialActivationStateTelegram` |
| `EvaluationGroupType` | GET + POST (AuthorizedClient) - 48-int array, 1 = Perpendicular Distance |
| `perpDist001–048` | GET + POST (AuthorizedClient) |
| `PortConfiguration` | GET + POST (AuthorizedClient) |
| `SetFieldEvaluationContour` | POST (AuthorizedClient) - Field Evaluation only; unreliable for Perpendicular Distance, use `Volume00N` instead (§4.2b) |
| `Volume001–Volume048` | GET + POST (AuthorizedClient) - direct field write, required for Perpendicular Distance (§4.2b) |
| `ActivateEvaluationGroup` | POST (no auth) |
| `FieldEvaluationGroupState`, `FieldEvaluationResult`, `perpendicularDistanceResult` | GET |
| `WriteEeprom` | POST (= `mEEwriteall`, Service) - no request body |

> REST is CoLa-free and writes Eval slots, mapping, group type,
> and perpDist slots entirely over REST. Verified on picoScan150 FW2.3.3.

---

## 6. Field Evaluation - Creation Workflow (CoLa A)

```text
1. sWN Eval00N  ...                     (Service L4)
2. sMN SetFieldEvaluationContour N ...  (Service L4)
3. sWN EvaluationGroupMapping ...       (Service L4 - include ALL groups)
4. sMN ActivateEvaluationGroup ...      (no auth - only affects TELEGRAM groups, see below)
5. sWN PortConfiguration ...            (Service L4 - optional: assign output pin)
6. sMN mEEwriteall                      (Service L4)
   → wait 1 s before reading state
```

Step 3's `Activation.Type` decides whether step 4 is needed at all: `1` (ALWAYS) makes
the group active as soon as it's persisted, with no telegram required; `3` (TELEGRAM)
leaves it INACTIVE until step 4 turns it on. The example scripts use ALWAYS at
creation and only switch to TELEGRAM right before demonstrating Activate/Deactivate -
see §12.7 for the full rationale.

---

## 7. Field Evaluation - Delete Workflow

```text
1. sMN ActivateEvaluationGroup 1 <N> 0         (no auth)
2. sWN EvaluationGroupMapping <count-1> ...    (omit the deleted group)
3. sWN Eval00N  1  0  N  1  0 C8 0 0 0 0 32 32 32 0 7D0 0 0  0 0 0  0
4. sMN mEEwriteall
```

---

## 9. Sensor Coordinate System

```text
Sensor at origin.  x = depth (forward), y = lateral (left/right).  All values in mm.

  y=-1000 ← sensor → y=+1000
               │
             x=0
               │
            x=2000  (2 m forward)
```

---

## 10. Detection Modes (Eval00N.Mode)

| Value | Name | Trigger |
| --- | --- | --- |
| `0` | BLANKING | Object ≥ MinimumObjectSizeHorizontal inside zone → INFRINGED. Most common. |
| `1` | REFLECTOR_ONLY | Only high-reflectivity objects (retroreflectors) trigger INFRINGED. |
| `2` | CONTOUR | Deviation from Teach-in reference exceeds tolerance → INFRINGED. Requires valid contour. |

Fields not exposed in SOPASair GUI - always write as shown:

| Field | Value |
| --- | --- |
| `TreatGlareAsInfringed` | `0` |
| `Reserved2 / Reserved3 / Reserved4` | `0` |
| `EasyTeachParams` | count=0 (unless CONTOUR mode with Teach-in) |
| `DynamicFieldParams / ReservedParams` | count=0 |

---

## 11. Teach-in (CONTOUR Mode)

### 11.1 Variables

| Variable | Type | Default | Notes |
| --- | --- | --- | --- |
| `TeachInToleranceFront` | UDInt mm | 250 | Distance in front of object |
| `TeachInToleranceBack` | UDInt mm | 250 | Distance behind object |
| `TeachInMinimumTimeMs` | UDInt ms | 250 | Range 100–60000 |
| `TeachInActivationInput` | UInt 0–8 | 0 | 0 = software-only |
| `TeachInActive` | Bool (read) | - | 1 while running |
| `TeachInContourValid` | Bool (read) | - | 1 after successful StopTeachIn |

### 11.2 CoLa A Sequence

```text
sWN TeachInToleranceFront FA    → sWA TeachInToleranceFront   (250 mm = 0xFA)
sWN TeachInToleranceBack  FA    → sWA TeachInToleranceBack
sWN TeachInMinimumTimeMs  1F4   → sWA TeachInMinimumTimeMs    (500 ms = 0x1F4)
sWN TeachInActivationInput 0    → sWA TeachInActivationInput
sMN StartTeachIn                → sAN StartTeachIn
sRN TeachInActive               → sRA TeachInActive 1         (running)
  … poll every ~500 ms …
sRN TeachInActive               → sRA TeachInActive 0         (done)
sMN StopTeachIn                 → sAN StopTeachIn
sRN TeachInContourValid         → sRA TeachInContourValid 1   (success)
sMN mEEwriteall                 → sAN mEEwriteall 1
```

REST equivalents: `POST /api/StartTeachIn`, `POST /api/StopTeachIn` (auth required).

### 11.3 Field Evaluation via Teach-In - Workflow

A Field Evaluation whose detection contour is **learned** by Teach-In instead of drawn
as a fixed polygon. Same group/output machinery as §6/§7, with three differences:

1. The eval slot uses **`Mode = 2` (CONTOUR)** (§10), otherwise identical to a BLANKING
   slot (`VolumeList=[N]`, `EasyTeach=0`).
2. `SetFieldEvaluationContour` writes the **Region of Interest for Teach-In** - the area
   *inside which* Teach-In learns the reference contour. It is **not** the detection
   contour. Written once at CREATE, never reshaped. `GetFieldEvaluationContour` reads this
   region back, **not** the taught contour, so it cannot verify a teach.
3. The contour is produced/refreshed by running **Teach-In** (§11.2). Teach-In is
   **global** - no per-slot binding.

**Reference workflow** (slot 2 / group 2, `TeachInEval` / `TeachInGroup`, output InOut2 → EG02):

```text
1. CREATE     sWN Eval002 ... (Mode=2 CONTOUR)        (Service L4)
              sMN SetFieldEvaluationContour 2 ...     (Region of Interest, e.g. square)
              sWN EvaluationGroupMapping ...           (ALWAYS, type=1 - include ALL groups)
              sWN PortConfiguration ...                (InOut2 → EG02)
              Teach-In #1 (tolerance 250 mm)           (§11.2 sequence)
              sMN mEEwriteall
2. USE        sRN FieldEvaluationGroupState / FieldEvaluationResult / TeachInContourValid
3. CHANGE     sWN Eval002 ... (MinimumObjectSizeHorizontal 100 → 200 mm); mEEwriteall
4. RESHAPE    Teach-In #2 (tolerance 400 mm); mEEwriteall   ← "change the polygon" = re-teach
5. ACTIVATE   sWN EvaluationGroupMapping ... (TELEGRAM, type=3); mEEwriteall
              sMN ActivateEvaluationGroup 1 2 1        (no auth)
6. DEACTIVATE sMN ActivateEvaluationGroup 1 2 0
7. DELETE     restore mapping/ports, reset Eval002, mEEwriteall   (as §7)
```

**Teach-In sequence used here** (simplified from §11.2 - fixed wait, no polling):
set `TeachInToleranceFront`/`TeachInToleranceBack` (250 → 400 mm), `TeachInMinimumTimeMs`
(500, must stay < 3000 so the wait completes the teach), `TeachInActivationInput` (0,
software-only) → `StartTeachIn` → **wait 3 s** → `StopTeachIn` → read `TeachInContourValid`.
`TeachInContourValid` is **log-only** - the example never fails on an invalid contour
(an automated run may have no physical object present).

Implemented protocol-pure in `field_evaluation_teach_in/{cola_a,cola_b,rest}.py`.

---

## 12. Perpendicular Distance Evaluation

### 12.1 Overview

Switch a group slot from FIELD_EVALUATION to PERPENDICULAR_DISTANCE via `EvaluationGroupType`. Perpendicular Distance groups measure the signed perpendicular distance from detected objects to a reference line. picoScan150 is a 2D LiDAR, so the reference is a line in the scan plane - not a 3D plane - and there is no height/offset (z) dimension. Results are in `perpendicularDistanceResult`, not `FieldEvaluationResult`.

A Perpendicular Distance evaluation has **two parts**, exactly as shown in the SOPAS UI:

| Part | Variable | Holds |
|---|---|---|
| Object detection | `Eval00N` + `Volume00N` | detection params (BLANKING mode, `MinimumObjectSizeHorizontal`, `TimeForInfringedState`) **and a detection field**: `VolumeList=[slot]` plus a polygon written directly to `Volume00N` (§4.2b) - `SetFieldEvaluationContour` returns `ErrorCode 0` but has proven unreliable for Perpendicular Distance slots in practice |
| Reference line | `perpDist00N` | line orientation (in-plane normal) + signed distance |

The group ties them together by referencing the evaluation slot via `EvalIds=[slot]`. **Omitting `Eval00N`, leaving `EvalIds` empty, or skipping the detection field (empty `VolumeList` / no contour) leaves the group with no usable evaluation: the device flags `DS_RecoverableError` and `perpendicularDistanceResult` stays empty.** A Perpendicular Distance evaluation needs a drawn field exactly like a Field Evaluation does.

> **Perpendicular Distance is not wired to digital outputs.** It is read from `perpendicularDistanceResult`; it is not assigned to a PortConfiguration output source.

**Perpendicular Distance groups must occupy their own slots - they cannot share with Field Evaluation groups.**

### 12.2 EvaluationGroupType

Write all 48 values **without a count prefix** (despite FixedLength=False in the CID - a count token causes `sFA 5`):

```text
TX: sWN EvaluationGroupType 0 0 1 1 0 0 ... (48 values, no count prefix)
RX: sWA EvaluationGroupType
```

REST: `GET` / `POST /api/EvaluationGroupType` - 48-int array, value 1 = Perpendicular Distance. Writable over REST (see §5); CoLa A and CoLa B (native binary) also write it.

### 12.3 perpDist001–048

Writable over CoLa A, CoLa B (native binary struct), and REST (`GET` / `POST /api/perpDist00N`).

```text
sWN perpDist003
  <nx:Real>          IEEE 754 float, 8-char hex (1.0 = 3F800000) - in-scan-plane x
  <ny:Real>          in-scan-plane y
  <nz:Real>          always 0 - picoScan150 is 2D (no out-of-plane component)
  <lineDist:DInt mm>     perpendicular distance of the line; e.g. 3E8=1000mm  FFFFF830=-2000mm
  <noDataMode:Enum8>     0=ZERO 1=HOLD 2=USER_VALUE
  <substDist:DInt mm>    substitution value when noDataMode=USER_VALUE
  <threshCount=1>
  <enableThresh:Bool>
  <minThresh:DInt mm>
  <maxThresh:DInt mm>
  <resCount=1>
  <resDummy=0>
```

IEEE 754 encoding: single-precision float → 4 bytes big-endian → 8 uppercase hex chars.

**Example** (forward reference line at 1000 mm, threshold disabled):

```text
TX: sWN perpDist003 3F800000 00000000 00000000 3E8 0 0 1 0 FFF85EE0 7A120 1 0
RX: sWA perpDist003
```

### 12.4 perpendicularDistanceResult (read-only)

```text
CoLa A: sRN perpendicularDistanceResult
→ sRA perpendicularDistanceResult <timestamp> <count>
     [<evalId> <minDist> <maxDist> <minX> <minY> <minZ> <maxX> <maxY> <maxZ>] ...

REST: GET /api/perpendicularDistanceResult
```

`evaluations` is empty when no object is detected near the reference line - expected, not an error.

### 12.5 EvaluationGroupMapping for Perpendicular Distance groups

A Perpendicular Distance group references its evaluation slot via `EvalIds=[slot]`, exactly like a Field Evaluation group. The evaluation itself is built like a Field Evaluation too: the `Eval00N` object-detection entry references its volume (`VolumeList=[slot]`) and that volume's polygon is written directly to `Volume00N` (§4.2b) - **not** via `SetFieldEvaluationContour`, which has proven unreliable for Perpendicular Distance slots even though it returns `ErrorCode 0`. The slot is additionally paired with a `perpDist00N` reference line.

```text
sWN EvaluationGroupMapping 4
  8 Triangle 1 3 2 2 2 2 2 2 2 2 0 1 1   ← Field Evaluation: count=1, eval_id=1
  6 Square   2 3 2 2 2 2 2 2 2 2 0 1 2   ← Field Evaluation: count=1, eval_id=2
  7 Line1m   3 1 2 2 2 2 2 2 2 2 0 1 3   ← Perpendicular Distance: ALWAYS (type=1) at creation, count=1, eval_id=3 (Eval003 + perpDist003)
  7 Line2m   4 3 2 2 2 2 2 2 2 2 0 1 4   ← Perpendicular Distance: TELEGRAM (type=3), count=1, eval_id=4
```

A complete Perpendicular Distance evaluation requires, for its slot: `EvaluationGroupType[slot]=1`, `Eval00N` (object detection, `VolumeList=[slot]`), `Volume00N` (the field, written directly - §4.2b), and `perpDist00N` (reference line) - plus the group entry above. Skipping the field (empty `VolumeList` or empty `Volume00N`) holds the device in `DS_RecoverableError` and yields an empty result; leaving `EvalIds` empty (`count=0`) produces a group with no evaluation inside it.

### 12.6 Verified Perpendicular Distance workflow and per-step device-status checking

The example scripts mirror the GUI workflow that is known to work: **add a group → add an evaluation (`Eval00N`, `VolumeList=[slot]`) → write the field directly to `Volume00N` (§4.2b) → set the reference line (`perpDist00N`) → map the group → persist (`mEEwriteall`)**. With the field written, every step keeps the device at `DeviceStatus = 3` (DS_NormalOperation) and the result populates with the evaluation. Without the field, the device drops to `DeviceStatus = 7` (DS_RecoverableError).

All example scripts read `DeviceStatus` (`DevSta`) after every step and flag any value other than 3 as an anomaly (the transient 7 the device reports for ~1 s right after `mEEwriteall` is polled out). A clean run reports `Anomalies: none`.

### 12.7 Group activation in the example scripts - ALWAYS first, TELEGRAM only for Activate/Deactivate

All three example demos - Field Evaluation, Field Evaluation via Teach-In, and
Perpendicular Distance (all 9 scripts: `rest.py`/`cola_a.py`/`cola_b.py` × 3 folders) -
follow the same group-activation pattern. CREATE writes the group with
`Activation.Type = 1` (ALWAYS), so it is active throughout CREATE/USE/CHANGE/RESHAPE
without needing any telegram control. Only at step 5 (ACTIVATE) do they rewrite
`EvaluationGroupMapping`, switching the same group to `Activation.Type = 3` (TELEGRAM,
`InitialActivationStateTelegram = false`) and persist - the group goes INACTIVE at
that point - before calling `ActivateEvaluationGroup` (§4.4) to turn it back on.
`ActivateEvaluationGroup` has no effect on an ALWAYS group (`Success = false`, "not an
error", but the group also cannot be turned off), so the switch to TELEGRAM is required
before Activate/Deactivate can be demonstrated.

`EvaluationGroupMapping` is a single array covering **all** groups configured on the
device, not just the demo's own group. Every write that touches it (both the ALWAYS
write at CREATE and the TELEGRAM write at ACTIVATE) merges the demo's group into the
mapping captured at step 0 (SAVE STATE), rather than replacing the array outright -
otherwise a group configured via the GUI (or any other slot) would be silently deleted.
The merge helpers are `cmd_merge_mapping_add_group()` (CoLa A, ASCII token-splicing) and
`pack_merge_mapping_add_group()` (CoLa B, binary struct-splicing); the REST scripts do
the equivalent merge inline against the JSON array read at step 0.
Keywords:
field evaluation, picoScan150, CoLa API, REST API, evaluation groups, zone detection, contour polygon, sensor configuration, LiDAR scanning, real-time results