# PLC Algorithm Design - Lighting and Water Boiler Control

## Overviewp

This document defines the PLC algorithms for the home automation system:
1. **Lighting Control** - Improved logic with command-based control for Home Assistant and toggle-based for Zigbee switches
2. **Water Boiler Control** - Simple ON/OFF with safety features (time limit, emergency stop)

## System Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          CODESYS PLC (Raspberry Pi)                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │
│  │   NVL_Receiver  │    │    MainTask     │    │    NVL_Sender       │ │
│  │   (from Node-RED)│    │    (4ms cycle)  │    │   (to Node-RED)     │ │
│  └────────┬────────┘    └────────┬────────┘    └──────────┬──────────┘ │
│           │                      │                        │             │
│           ▼                      ▼                        ▲             │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                          PLC_PRG                                │   │
│  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │   │
│  │  │   Lights    │  │   Boiler    │  │   Safety_Monitor        │  │   │
│  │  │  Program    │  │  Program    │  │      (future)           │  │   │
│  │  └──────┬──────┘  └──────┬──────┘  └─────────────────────────┘  │   │
│  └─────────┼────────────────┼──────────────────────────────────────┘   │
│            │                │                                          │
│            ▼                ▼                                          │
│  ┌─────────────────────────────────────────────────────────────────┐   │
│  │                     EtherCAT I/O                                │   │
│  │  ┌──────────────┐              ┌──────────────────────────────┐ │   │
│  │  │  EL1809      │              │   Output Module              │ │   │
│  │  │  16x DI 24V  │              │   16x DO (Relays)            │ │   │
│  │  │  (Switches)  │              │   (Lights + Boiler)          │ │   │
│  │  └──────────────┘              └──────────────────────────────┘ │   │
│  └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘
                    │                           │
                    ▼                           ▼
          ┌─────────────────┐         ┌─────────────────┐
          │  Physical       │         │  Physical       │
          │  Switches       │         │  Relays         │
          └─────────────────┘         └─────────────────┘
```

---

## Part 1: Lighting Control Algorithm

### 1.1 Design Goals

- **Command-based control** for Home Assistant (explicit ON/OFF)
- **Toggle-based control** for Zigbee switches (edge detection)
- **State synchronization** between PLC and Home Assistant
- **Relay status feedback** for actual state monitoring

### 1.2 Data Structures

#### Input Structure (from Node-RED/Home Assistant)

```iec
TYPE struct_light_commands :
STRUCT
    // Home Assistant Commands (momentary pulses)
    ha_on: BOOL;          // HA command: Turn ON (edge detected)
    ha_off: BOOL;         // HA command: Turn OFF (edge detected)
    
    // Zigbee Switch Input (toggle trigger)
    zigbee_toggle: BOOL;  // Zigbee button press (edge detected)
    
    // Global Commands
    all_on: BOOL;         // Global: All lights ON
    all_off: BOOL;        // Global: All lights OFF
END_STRUCT
END_TYPE

TYPE struct_room_commands :
STRUCT
    l_1: struct_light_commands;
    l_2: struct_light_commands;
    l_3: struct_light_commands;
    l_4: struct_light_commands;
    l_5: struct_light_commands;
    l_6: struct_light_commands;
    
    // Room-level global commands
    room_all_on: BOOL;
    room_all_off: BOOL;
END_STRUCT
END_TYPE
```

#### Output Structure (to Node-RED/Home Assistant)

```iec
TYPE struct_light_status :
STRUCT
    state: BOOL;          // Control output (to relay)
    relay_status: BOOL;   // Actual relay state (read back)
END_STRUCT
END_TYPE

TYPE struct_room_status :
STRUCT
    l_1: struct_light_status;
    l_2: struct_light_status;
    l_3: struct_light_status;
    l_4: struct_light_status;
    l_5: struct_light_status;
    l_6: struct_light_status;
END_STRUCT
END_TYPE
```

### 1.3 Function Block: fb_lightControl

Individual light control with command/toggle support.

```iec
FUNCTION_BLOCK fb_lightControl
VAR_INPUT
    // Commands
    ha_on: BOOL;              // Home Assistant ON command
    ha_off: BOOL;             // Home Assistant OFF command
    zigbee_toggle: BOOL;      // Zigbee toggle trigger
    all_on: BOOL;             // Global all-on command
    all_off: BOOL;            // Global all-off command
    
    // Status feedback
    relay_feedback: BOOL;     // Actual relay state (from EtherCAT)
END_VAR
VAR_OUTPUT
    output: BOOL;             // Control output to relay
    status: BOOL;             // Actual status (for feedback)
END_VAR
VAR
    // Edge detection
    r_trig_ha_on: R_TRIG;
    r_trig_ha_off: R_TRIG;
    r_trig_zigbee: R_TRIG;
    r_trig_all_on: R_TRIG;
    r_trig_all_off: R_TRIG;
    
    // Internal state
    light_state: BOOL := FALSE;
END_VAR
```

#### Algorithm Logic

```iec
// =====================================================
// fb_lightControl - Implementation
// =====================================================
// Priority (highest to lowest):
// 1. Global all_off  - Forces all lights OFF
// 2. Global all_on   - Forces all lights ON
// 3. HA OFF command  - Explicit OFF
// 4. HA ON command   - Explicit ON
// 5. Zigbee toggle   - Toggle current state
// =====================================================

// Edge detection for all commands
r_trig_ha_on(CLK := ha_on);
r_trig_ha_off(CLK := ha_off);
r_trig_zigbee(CLK := zigbee_toggle);
r_trig_all_on(CLK := all_on);
r_trig_all_off(CLK := all_off);

// State machine with priority handling
IF r_trig_all_off.Q THEN
    // Priority 1: Global OFF (highest priority)
    light_state := FALSE;
    
ELSIF r_trig_all_on.Q THEN
    // Priority 2: Global ON
    light_state := TRUE;
    
ELSIF r_trig_ha_off.Q THEN
    // Priority 3: HA OFF command
    light_state := FALSE;
    
ELSIF r_trig_ha_on.Q THEN
    // Priority 4: HA ON command
    light_state := TRUE;
    
ELSIF r_trig_zigbee.Q THEN
    // Priority 5: Zigbee toggle
    light_state := NOT light_state;
END_IF;

// Output assignment
output := light_state;

// Status feedback from actual relay
status := relay_feedback;
```

### 1.4 Function Block: fb_roomLights

Room-level lighting control with 6 lights per room.

```iec
FUNCTION_BLOCK fb_roomLights
VAR_INPUT
    commands: struct_room_commands;
    relay_feedback: ARRAY[1..6] OF BOOL;  // Actual relay states
END_VAR
VAR_OUTPUT
    outputs: ARRAY[1..6] OF BOOL;         // Control outputs
    status: struct_room_status;
END_VAR
VAR
    lights: ARRAY[1..6] OF fb_lightControl;
    i: INT;
END_VAR
```

#### Algorithm Logic

```iec
// =====================================================
// fb_roomLights - Implementation
// =====================================================

// Light 1
lights[1](
    ha_on := commands.l_1.ha_on,
    ha_off := commands.l_1.ha_off,
    zigbee_toggle := commands.l_1.zigbee_toggle,
    all_on := commands.room_all_on OR commands.l_1.all_on,
    all_off := commands.room_all_off OR commands.l_1.all_off,
    relay_feedback := relay_feedback[1]
);
outputs[1] := lights[1].output;
status.l_1.state := lights[1].output;
status.l_1.relay_status := lights[1].status;

// Light 2
lights[2](
    ha_on := commands.l_2.ha_on,
    ha_off := commands.l_2.ha_off,
    zigbee_toggle := commands.l_2.zigbee_toggle,
    all_on := commands.room_all_on OR commands.l_2.all_on,
    all_off := commands.room_all_off OR commands.l_2.all_off,
    relay_feedback := relay_feedback[2]
);
outputs[2] := lights[2].output;
status.l_2.state := lights[2].output;
status.l_2.relay_status := lights[2].status;

// Light 3
lights[3](
    ha_on := commands.l_3.ha_on,
    ha_off := commands.l_3.ha_off,
    zigbee_toggle := commands.l_3.zigbee_toggle,
    all_on := commands.room_all_on OR commands.l_3.all_on,
    all_off := commands.room_all_off OR commands.l_3.all_off,
    relay_feedback := relay_feedback[3]
);
outputs[3] := lights[3].output;
status.l_3.state := lights[3].output;
status.l_3.relay_status := lights[3].status;

// Light 4
lights[4](
    ha_on := commands.l_4.ha_on,
    ha_off := commands.l_4.ha_off,
    zigbee_toggle := commands.l_4.zigbee_toggle,
    all_on := commands.room_all_on OR commands.l_4.all_on,
    all_off := commands.room_all_off OR commands.l_4.all_off,
    relay_feedback := relay_feedback[4]
);
outputs[4] := lights[4].output;
status.l_4.state := lights[4].output;
status.l_4.relay_status := lights[4].status;

// Light 5
lights[5](
    ha_on := commands.l_5.ha_on,
    ha_off := commands.l_5.ha_off,
    zigbee_toggle := commands.l_5.zigbee_toggle,
    all_on := commands.room_all_on OR commands.l_5.all_on,
    all_off := commands.room_all_off OR commands.l_5.all_off,
    relay_feedback := relay_feedback[5]
);
outputs[5] := lights[5].output;
status.l_5.state := lights[5].output;
status.l_5.relay_status := lights[5].status;

// Light 6
lights[6](
    ha_on := commands.l_6.ha_on,
    ha_off := commands.l_6.ha_off,
    zigbee_toggle := commands.l_6.zigbee_toggle,
    all_on := commands.room_all_on OR commands.l_6.all_on,
    all_off := commands.room_all_off OR commands.l_6.all_off,
    relay_feedback := relay_feedback[6]
);
outputs[6] := lights[6].output;
status.l_6.state := lights[6].output;
status.l_6.relay_status := lights[6].status;
```

---

## Part 2: Water Boiler Control Algorithm

### 2.1 Design Goals

Based on your requirements:
- **Simple ON/OFF control** via relay output only
- **Maximum heating time limit** (safety feature)
- **Emergency stop button** support
- **State monitoring and feedback**

### 2.2 Data Structures

#### Boiler Commands (from Node-RED/Home Assistant)

```iec
TYPE struct_boiler_commands :
STRUCT
    // Control Commands
    ha_on: BOOL;              // Home Assistant: Turn ON
    ha_off: BOOL;             // Home Assistant: Turn OFF
    schedule_on: BOOL;        // Scheduled ON (from automation)
    schedule_off: BOOL;       // Scheduled OFF (from automation)
    
    // Safety Input
    emergency_stop: BOOL;     // Emergency stop (physical button or HA)
    
    // Configuration (can be set via Node-RED)
    max_on_time_minutes: INT; // Maximum ON time in minutes (default: 480 = 8 hours)
END_STRUCT
END_TYPE
```

#### Boiler Status (to Node-RED/Home Assistant)

```iec
TYPE struct_boiler_status :
STRUCT
    // State
    state: BOOL;              // Current boiler state (ON/OFF)
    relay_output: BOOL;       // Actual relay output state
    
    // Runtime Info
    on_time_minutes: INT;     // Current ON time in minutes
    remaining_minutes: INT;   // Remaining time before auto-shutoff
    
    // Safety Status
    emergency_active: BOOL;   // Emergency stop is active
    time_limit_reached: BOOL; // Max time limit was reached
    
    // Error Handling
    error_state: BOOL;        // Error detected
    error_code: INT;          // Error code (0=none, 1=emergency, 2=time limit)
END_STRUCT
END_TYPE
```

### 2.3 Function Block: fb_waterBoiler

Simple ON/OFF boiler control with time limit and emergency stop.

```iec
FUNCTION_BLOCK fb_waterBoiler
VAR_INPUT
    // Control Commands
    ha_on: BOOL;              // Home Assistant ON command
    ha_off: BOOL;             // Home Assistant OFF command
    schedule_on: BOOL;        // Scheduled ON
    schedule_off: BOOL;       // Scheduled OFF
    
    // Safety
    emergency_stop: BOOL;     // Emergency stop input
    
    // Configuration
    max_on_time: TIME := T#8H; // Maximum ON time (default 8 hours)
END_VAR
VAR_OUTPUT
    // Outputs
    relay_control: BOOL;      // Control output to relay
    
    // Status
    state: BOOL;              // Current state
    on_time_seconds: DINT;    // Current ON time in seconds
    remaining_seconds: DINT;  // Remaining time in seconds
    
    // Safety Status
    emergency_active: BOOL;   // Emergency stop active
    time_limit_reached: BOOL; // Time limit was reached
    error_state: BOOL;        // Error flag
    error_code: INT;          // Error code
END_VAR
VAR
    // Internal State
    internal_state: BOOL := FALSE;
    last_state: BOOL := FALSE;
    
    // Edge Detection
    r_trig_ha_on: R_TRIG;
    r_trig_ha_off: R_TRIG;
    r_trig_schedule_on: R_TRIG;
    r_trig_schedule_off: R_TRIG;
    r_trig_emergency: R_TRIG;
    f_trig_emergency: F_TRIG;
    
    // Timers
    on_timer: TON;            // Counts ON time
    
    // Time tracking
    max_on_seconds: DINT;
END_VAR
```

### 2.4 Algorithm Logic

```iec
// =====================================================
// fb_waterBoiler - Implementation
// =====================================================
// Priority (highest to lowest):
// 1. Emergency Stop       - Immediate shutdown
// 2. Time Limit Exceeded  - Auto shutdown
// 3. OFF Commands         - Turn off
// 4. ON Commands          - Turn on (if safe)
// =====================================================

// Calculate max time in seconds
max_on_seconds := TIME_TO_DINT(max_on_time) / 1000;

// Edge detection for commands
r_trig_ha_on(CLK := ha_on);
r_trig_ha_off(CLK := ha_off);
r_trig_schedule_on(CLK := schedule_on);
r_trig_schedule_off(CLK := schedule_off);
r_trig_emergency(CLK := emergency_stop);
f_trig_emergency(CLK := emergency_stop);

// =====================================================
// SAFETY CHECKS (highest priority)
// =====================================================

// Priority 1: Emergency Stop
IF emergency_stop THEN
    internal_state := FALSE;
    emergency_active := TRUE;
    error_state := TRUE;
    error_code := 1;  // Emergency stop active
    
// Priority 2: Time Limit Check
ELSIF on_timer.Q THEN
    internal_state := FALSE;
    time_limit_reached := TRUE;
    error_state := TRUE;
    error_code := 2;  // Time limit exceeded

// =====================================================
// CONTROL LOGIC (only when safe)
// =====================================================
ELSE
    // Clear safety flags when emergency released
    IF f_trig_emergency.Q THEN
        emergency_active := FALSE;
        error_state := FALSE;
        error_code := 0;
    END_IF;
    
    // Clear time limit flag when boiler turned off
    IF NOT internal_state THEN
        time_limit_reached := FALSE;
        IF error_code = 2 THEN
            error_state := FALSE;
            error_code := 0;
        END_IF;
    END_IF;
    
    // Priority 3: OFF Commands (HA OFF or Schedule OFF)
    IF r_trig_ha_off.Q OR r_trig_schedule_off.Q THEN
        internal_state := FALSE;
        
    // Priority 4: ON Commands (HA ON or Schedule ON)
    ELSIF (r_trig_ha_on.Q OR r_trig_schedule_on.Q) AND NOT time_limit_reached THEN
        internal_state := TRUE;
    END_IF;
END_IF;

// =====================================================
// TIMER MANAGEMENT
// =====================================================

// ON timer - counts total ON time
on_timer(
    IN := internal_state AND NOT emergency_active,
    PT := max_on_time
);

// Calculate current ON time in seconds
IF internal_state THEN
    on_time_seconds := TIME_TO_DINT(on_timer.ET) / 1000;
    remaining_seconds := max_on_seconds - on_time_seconds;
    IF remaining_seconds < 0 THEN
        remaining_seconds := 0;
    END_IF;
ELSE
    on_time_seconds := 0;
    remaining_seconds := max_on_seconds;
END_IF;

// =====================================================
// OUTPUT ASSIGNMENT
// =====================================================

// Relay control - only ON when state is ON and no emergency
relay_control := internal_state AND NOT emergency_active;

// State output
state := internal_state;
```

### 2.5 State Diagram

```
                    ┌─────────────────────────────────────────┐
                    │           EMERGENCY STOP                │
                    │         (highest priority)              │
                    │    Sets: emergency_active = TRUE        │
                    │          error_code = 1                 │
                    │          relay_control = FALSE          │
                    └─────────────────────────────────────────┘
                                       │
                    ┌──────────────────┴──────────────────┐
                    ▼                                     │
              ┌───────────┐                               │
              │           │                               │
    START ───►│    OFF    │◄──────────────────────────────┤
              │           │                               │
              └─────┬─────┘                               │
                    │                                     │
                    │ ON Command                          │
                    │ (ha_on OR schedule_on)              │
                    │ AND NOT emergency_stop              │
                    │ AND NOT time_limit_reached          │
                    ▼                                     │
              ┌───────────┐                               │
              │           │     Time Limit                │
              │    ON     │─────Exceeded──────────────────┘
              │           │     (on_timer.Q)
              └─────┬─────┘
                    │
                    │ OFF Command
                    │ (ha_off OR schedule_off)
                    │
                    └──────────────────────────────────────►OFF
```

---

## Part 3: Main Program Integration

### 3.1 PLC_PRG Structure

```iec
PROGRAM PLC_PRG
VAR
    // =====================================================
    // LIGHTING CONTROL
    // =====================================================
    // Room instances
    masterBedroom: fb_roomLights;
    masterBathroom: fb_roomLights;
    bedroom_1: fb_roomLights;
    bedroom_2: fb_roomLights;
    bathroom: fb_roomLights;
    guest_wc: fb_roomLights;
    kitchen: fb_roomLights;
    pantry: fb_roomLights;
    livingRoom: fb_roomLights;
    diningRoom: fb_roomLights;
    entrance: fb_roomLights;
    hallway: fb_roomLights;
    veranda: fb_roomLights;
    front: fb_roomLights;
    back: fb_roomLights;
    side: fb_roomLights;
    
    // =====================================================
    // WATER BOILER CONTROL
    // =====================================================
    waterBoiler: fb_waterBoiler;
    
    // =====================================================
    // GLOBAL COMMANDS
    // =====================================================
    global_all_lights_off: BOOL;
    global_all_lights_on: BOOL;
END_VAR
```

### 3.2 Main Program Logic

```iec
// =====================================================
// PLC_PRG - Main Program Implementation
// =====================================================

// =====================================================
// SECTION 1: LIGHTING CONTROL
// =====================================================

// Master Bedroom
masterBedroom(
    commands := NVL_Receiver.masterBedroom,
    relay_feedback := EtherCAT_Outputs.masterBedroom_relay
);
NVL_Sender.l_masterBedroom := masterBedroom.status;
EtherCAT_Outputs.masterBedroom := masterBedroom.outputs;

// ... (repeat for all rooms)

// =====================================================
// SECTION 2: WATER BOILER CONTROL
// =====================================================

waterBoiler(
    ha_on := NVL_Receiver.boiler.ha_on,
    ha_off := NVL_Receiver.boiler.ha_off,
    schedule_on := NVL_Receiver.boiler.schedule_on,
    schedule_off := NVL_Receiver.boiler.schedule_off,
    emergency_stop := NVL_Receiver.boiler.emergency_stop 
                      OR DI_Emergency_Stop,  // Physical button OR remote
    max_on_time := T#8H
);

// Map outputs
EtherCAT_Outputs.boiler_relay := waterBoiler.relay_control;

// Status to Node-RED
NVL_Sender.boiler_status.state := waterBoiler.state;
NVL_Sender.boiler_status.relay_output := waterBoiler.relay_control;
NVL_Sender.boiler_status.on_time_minutes := DINT_TO_INT(waterBoiler.on_time_seconds / 60);
NVL_Sender.boiler_status.remaining_minutes := DINT_TO_INT(waterBoiler.remaining_seconds / 60);
NVL_Sender.boiler_status.emergency_active := waterBoiler.emergency_active;
NVL_Sender.boiler_status.time_limit_reached := waterBoiler.time_limit_reached;
NVL_Sender.boiler_status.error_state := waterBoiler.error_state;
NVL_Sender.boiler_status.error_code := waterBoiler.error_code;
```

---

## Part 4: I/O Mapping

### 4.1 EtherCAT Digital Outputs

| Address | Function | Description |
|---------|----------|-------------|
| 16#1A00 - 16#1A0E | Lighting | Room light relays (existing) |
| 16#1A0F | Boiler | Water boiler relay (new) |

### 4.2 EtherCAT Digital Inputs (if using physical emergency stop)

| Address | Function | Description |
|---------|----------|-------------|
| 16#1900 - 16#190F | Switches | Room physical switches (existing) |
| TBD | Emergency | Boiler emergency stop button (optional) |

---

## Part 5: Network Variables

### 5.1 NVL_Sender (PLC → Node-RED)

```iec
VAR_GLOBAL
    // Lighting status (existing rooms)
    l_masterBedroom: struct_room_status;
    l_masterBathroom: struct_room_status;
    l_bedroom_1: struct_room_status;
    l_bedroom_2: struct_room_status;
    l_bathroom: struct_room_status;
    l_guestWc: struct_room_status;
    l_kitchen: struct_room_status;
    l_pantry: struct_room_status;
    l_livingRoom: struct_room_status;
    l_dinningRoom: struct_room_status;
    l_entrance: struct_room_status;
    l_hallway: struct_room_status;
    l_outVeranda: struct_room_status;
    l_outFront: struct_room_status;
    l_outBack: struct_room_status;
    l_outSide: struct_room_status;
    
    // Boiler status (NEW)
    boiler_status: struct_boiler_status;
END_VAR
```

### 5.2 NVL_Receiver (Node-RED → PLC)

```iec
VAR_GLOBAL
    // Lighting commands (existing rooms)
    masterBedroom: struct_room_commands;
    masterBathroom: struct_room_commands;
    bedroom_1: struct_room_commands;
    bedroom_2: struct_room_commands;
    bathroom: struct_room_commands;
    guestWc: struct_room_commands;
    kitchen: struct_room_commands;
    pantry: struct_room_commands;
    livingRoom: struct_room_commands;
    dinningRoom: struct_room_commands;
    entrance: struct_room_commands;
    hallway: struct_room_commands;
    outVeranda: struct_room_commands;
    outFront: struct_room_commands;
    outBack: struct_room_commands;
    outSide: struct_room_commands;
    
    // Boiler commands (NEW)
    boiler: struct_boiler_commands;
END_VAR
```

---

## Part 6: Error Codes Reference

### Boiler Error Codes

| Code | Name | Description | Action |
|------|------|-------------|--------|
| 0 | No Error | Normal operation | None |
| 1 | Emergency Stop | Emergency stop activated | Release emergency stop, then send ON command |
| 2 | Time Limit | Maximum ON time exceeded | Send OFF command, then ON again to reset |

---

## Part 7: Testing Checklist

### Lighting Tests

- [ ] HA ON command turns light ON
- [ ] HA OFF command turns light OFF
- [ ] Zigbee toggle switches light state
- [ ] All-off command turns all lights OFF
- [ ] All-on command turns all lights ON
- [ ] Relay status feedback is accurate
- [ ] Multiple commands in sequence work correctly

### Boiler Tests

- [ ] HA ON command starts boiler
- [ ] HA OFF command stops boiler
- [ ] Schedule ON command works
- [ ] Schedule OFF command works
- [ ] Emergency stop immediately stops boiler
- [ ] Emergency stop prevents ON commands
- [ ] Releasing emergency stop allows normal operation
- [ ] Time limit auto-shutoff works (test with short time)
- [ ] ON time counter is accurate
- [ ] Remaining time is accurate
- [ ] Error codes are set correctly
- [ ] Status feedback to HA is correct

---

## Implementation Notes

1. **Backward Compatibility**: The new lighting structure can coexist with existing code during migration.

2. **Emergency Stop**: Can be either a physical button (EtherCAT input) or a virtual command from Home Assistant/Node-RED.

3. **Time Limit Configuration**: Default is 8 hours but can be changed via `max_on_time` parameter or Node-RED configuration.

4. **Task Configuration**: Run on EtherCAT_Task (4ms cycle) for responsive control.

---

**Document Version**: 1.0  
**Created**: 2026-02-07  
**Status**: Design Complete - Ready for Implementation
