# EtherCAT Introduction

```elixir
Mix.install([
  {:ethercat, github: "sid2baker/ethercat", override: true},
  {:kino_ethercat, path: "/home/n0gg1n/Development/Work/opencode/kino_ethercat"}
])
```

## The Ring

One master. Three slaves.

```mermaid
%%{init: {'deterministicIds': true, 'deterministicIDSeed': 'ethercat-intro-ring'}}%%
flowchart LR
  M[Master] --> C

  subgraph S[Slaves]
    C[Coupler]
    I[Inputs]
    O[Outputs]
    C --> I
    I --> O
  end
```

## One Cycle

The master uses cyclic EtherCAT frames.

```mermaid
%%{init: {'deterministicIds': true, 'deterministicIDSeed': 'ethercat-intro-cycle'}}%%
sequenceDiagram
  participant M as Master
  participant R as EtherCAT ring
  M->>R: cyclic EtherCAT frame
  R-->>M: inputs + working counter
  M->>M: check the cycle
```

## Start The Simulator

Evaluate the simulator cell, then follow the short instructions in the `Introduction` tab.

<!-- livebook:{"attrs":"eyJjb25uZWN0aW9ucyI6W3sic291cmNlX2lkIjoiMyIsInNvdXJjZV9zaWduYWwiOiJjaDEiLCJ0YXJnZXRfaWQiOiIyIiwidGFyZ2V0X3NpZ25hbCI6ImNoMSJ9XSwiZXhwZXJ0X21vZGUiOmZhbHNlLCJzZWxlY3RlZCI6W3siZHJpdmVyIjoiS2lub0V0aGVyQ0FULkRyaXZlci5FSzExMDAiLCJpZCI6IjEiLCJuYW1lIjoiY291cGxlciJ9LHsiZHJpdmVyIjoiS2lub0V0aGVyQ0FULkRyaXZlci5FTDE4MDkiLCJpZCI6IjIiLCJuYW1lIjoiaW5wdXRzIn0seyJkcml2ZXIiOiJLaW5vRXRoZXJDQVQuRHJpdmVyLkVMMjgwOSIsImlkIjoiMyIsIm5hbWUiOiJvdXRwdXRzIn1dfQ","chunks":null,"kind":"Elixir.KinoEtherCAT.SmartCells.Simulator","livebook_object":"smart_cell"} -->

```elixir
alias EtherCAT.Simulator
alias EtherCAT.Simulator.Slave

simulator_ip = {127, 0, 0, 2}

_ = Simulator.stop()

devices = [
  Slave.from_driver(KinoEtherCAT.Driver.EK1100, name: :coupler),
  Slave.from_driver(KinoEtherCAT.Driver.EL1809, name: :inputs),
  Slave.from_driver(KinoEtherCAT.Driver.EL2809, name: :outputs)
]

{:ok, _supervisor} = Simulator.start(devices: devices, udp: [ip: simulator_ip, port: 34980])

:ok = Slave.connect({:outputs, :ch1}, {:inputs, :ch1})

Kino.Layout.tabs(
  Introduction: KinoEtherCAT.introduction(),
  Simulator: KinoEtherCAT.simulator(),
  Faults: KinoEtherCAT.simulator_faults()
)
```

```mermaid
%%{init: {'deterministicIds': true, 'deterministicIDSeed': 'ethercat-intro-loopback'}}%%
flowchart LR
  O[outputs.ch1] --> I[inputs.ch1]
```

This is the small path we will use later:

* `outputs.ch1 -> inputs.ch1`

## Start The Master

Now let the setup flow create the real master session.

<!-- livebook:{"attrs":"eyJhd2FpdF9sb2NrIjpmYWxzZSwiZGNfY3ljbGVfbnMiOjEwMDAwMDAwLCJkY19lbmFibGVkIjpmYWxzZSwiZG9tYWlucyI6W3siY3ljbGVfdGltZV9tcyI6MTAsImN5Y2xlX3RpbWVfdXMiOjEwMDAwLCJpZCI6Im1haW4iLCJtaXNzX3RocmVzaG9sZCI6MTAwMH1dLCJob3N0IjoiMTI3LjAuMC4yIiwiaW50ZXJmYWNlIjoiZW5wMHMzMWY2IiwibG9ja190aHJlc2hvbGRfbnMiOjEwMCwibG9ja190aW1lb3V0X21zIjo1MDAwLCJwb3J0IjozNDk4MCwic2xhdmVzIjpbeyJkaXNjb3ZlcmVkX25hbWUiOiJjb3VwbGVyIiwiZG9tYWluX2lkIjoiIiwiZHJpdmVyIjoiIiwibmFtZSI6ImNvdXBsZXIiLCJwcm9kdWN0X2NvZGUiOjcyMTAwOTQ2LCJzdGF0aW9uIjo0MDk2LCJ2ZW5kb3JfaWQiOjJ9LHsiZGlzY292ZXJlZF9uYW1lIjoiaW5wdXRzIiwiZG9tYWluX2lkIjoibWFpbiIsImRyaXZlciI6Iktpbm9FdGhlckNBVC5Ecml2ZXIuRUwxODA5IiwibmFtZSI6InNsYXZlXzEiLCJwcm9kdWN0X2NvZGUiOjExODU2Njk5NCwic3RhdGlvbiI6NDA5NywidmVuZG9yX2lkIjoyfSx7ImRpc2NvdmVyZWRfbmFtZSI6Im91dHB1dHMiLCJkb21haW5faWQiOiJtYWluIiwiZHJpdmVyIjoiS2lub0V0aGVyQ0FULkRyaXZlci5FTDI4MDkiLCJuYW1lIjoic2xhdmVfMiIsInByb2R1Y3RfY29kZSI6MTg0MTAyOTk0LCJzdGF0aW9uIjo0MDk4LCJ2ZW5kb3JfaWQiOjJ9XSwidHJhbnNwb3J0IjoidWRwIiwidHJhbnNwb3J0X21vZGUiOiJtYW51YWwiLCJ3YXJtdXBfY3ljbGVzIjowfQ","chunks":null,"kind":"Elixir.KinoEtherCAT.SmartCells.Setup","livebook_object":"smart_cell"} -->

```elixir
alias EtherCAT.Slave.Config, as: SlaveConfig
alias EtherCAT.Domain.Config, as: DomainConfig
udp_host = {127, 0, 0, 2}
udp_bind_ip = {127, 0, 0, 1}
# Persisted from live bus discovery.
# The notebook now boots the master from a single static EtherCAT.start/1 call.
_ = EtherCAT.stop()

setup_result =
  with :ok <-
         EtherCAT.start(
           transport: :udp,
           host: udp_host,
           port: 34980,
           bind_ip: udp_bind_ip,
           frame_timeout_ms: 10,
           domains: [%DomainConfig{id: :main, cycle_time_us: 10000, miss_threshold: 1000}],
           dc: nil,
           slaves: [
             %SlaveConfig{name: :coupler, target_state: :op},
             %SlaveConfig{
               name: :slave_1,
               driver: KinoEtherCAT.Driver.EL1809,
               process_data: {:all, :main},
               target_state: :op
             },
             %SlaveConfig{
               name: :slave_2,
               driver: KinoEtherCAT.Driver.EL2809,
               process_data: {:all, :main},
               target_state: :op
             }
           ]
         ),
       :ok <- EtherCAT.await_running(),
       :ok <- EtherCAT.await_operational() do
    :ok
  end

case setup_result do
  :ok ->
    Kino.Layout.tabs(
      Master: KinoEtherCAT.master(),
      "Task Manager": KinoEtherCAT.diagnostics()
    )

  {:error, reason} ->
    _ = EtherCAT.stop()

    Kino.Markdown.new("""
    ## EtherCAT setup failed

    `#{inspect(reason)}`

    The generated setup cell kept the notebook running instead of crashing.
    Re-run the cell once the bus is stable. Configuration timeouts usually mean one or more slaves did not reach the requested state in time.
    """)
end
```

<!-- livebook:{"attrs":"eyJzZWxlY3RlZCI6W3siYml0X3NpemUiOjEsImRlZmF1bHRfd2lkZ2V0Ijoic3dpdGNoIiwiZGlyZWN0aW9uIjoib3V0cHV0IiwiZG9tYWluIjoibWFpbiIsImtleSI6InNsYXZlXzIuY2gxIiwibGFiZWwiOm51bGwsInNpZ25hbCI6ImNoMSIsInNpZ25hbF9pbmRleCI6MCwic2xhdmUiOiJzbGF2ZV8yIiwic2xhdmVfaW5kZXgiOjIsIndpZGdldCI6ImF1dG8ifSx7ImJpdF9zaXplIjoxLCJkZWZhdWx0X3dpZGdldCI6ImxlZCIsImRpcmVjdGlvbiI6ImlucHV0IiwiZG9tYWluIjoibWFpbiIsImtleSI6InNsYXZlXzEuY2gxIiwibGFiZWwiOm51bGwsInNpZ25hbCI6ImNoMSIsInNpZ25hbF9pbmRleCI6MCwic2xhdmUiOiJzbGF2ZV8xIiwic2xhdmVfaW5kZXgiOjEsIndpZGdldCI6ImF1dG8ifV19","chunks":null,"kind":"Elixir.KinoEtherCAT.SmartCells.Visualizer","livebook_object":"smart_cell"} -->

```elixir
widgets = [
  KinoEtherCAT.Widgets.switch(:slave_2, :ch1),
  KinoEtherCAT.Widgets.led(:slave_1, :ch1)
]

case widgets do
  [] -> Kino.nothing()
  [widget] -> widget
  _ -> Kino.Layout.grid(widgets, columns: 2)
end
|> Kino.render()

Kino.nothing()
```

## Try ch1

Now change `outputs.ch1` through the normal EtherCAT path.

When you toggle the output, `inputs.ch1` should change too.

```mermaid
%%{init: {'deterministicIds': true, 'deterministicIDSeed': 'ethercat-intro-ch1-sequence'}}%%
sequenceDiagram
  participant M as Master
  participant O as outputs.ch1
  participant I as inputs.ch1
  M->>O: write output bit
  O->>I: loopback wiring
  I-->>M: input bit in next cycle
```

```elixir
Kino.Layout.grid(
  [
    KinoEtherCAT.Widgets.switch(:slave_2, :ch1, label: "outputs.ch1"),
    KinoEtherCAT.Widgets.led(:slave_1, :ch1, label: "inputs.ch1")
  ],
  columns: 2
)
```
