Packages

Pure-Elixir EtherCAT master built on OTP. Declarative bus configuration, cyclic process data exchange, CoE SDO transfers, and distributed clocks.

Current section

Files

Jump to
ethercat lib ethercat simulator slave profile digital_io.ex
Raw

lib/ethercat/simulator/slave/profile/digital_io.ex

defmodule EtherCAT.Simulator.Slave.Profile.DigitalIO do
@moduledoc false
@default_output_pdo 0x1600
@default_input_pdo 0x1A00
def spec(opts) do
mode = Keyword.get(opts, :mode, :image)
channel_count = Keyword.get(opts, :channels, 8)
direction = Keyword.get(opts, :direction, :io)
{pdo_entries, signals, output_size, input_size, mirror_output_to_input?} =
case mode do
:channels ->
channel_layout(direction, channel_count, opts)
:image ->
{image_pdo_entries(), image_signal_specs(), 1, 1, true}
end
%{
profile: :digital_io,
vendor_id: Keyword.get(opts, :vendor_id, 0x0000_0ACE),
product_code: Keyword.get(opts, :product_code, 0x0000_1601),
revision: Keyword.get(opts, :revision, 0x0000_0001),
serial_number: Keyword.get(opts, :serial_number, 0x0000_0001),
esc_type: Keyword.get(opts, :esc_type, 0x11),
fmmu_count: Keyword.get(opts, :fmmu_count, 4),
sm_count: Keyword.get(opts, :sm_count, 4),
output_phys: 0x1100,
output_size: output_size,
input_phys: 0x1180,
input_size: input_size,
mirror_output_to_input?: mirror_output_to_input?,
pdo_entries: pdo_entries,
mailbox_config: %{recv_offset: 0, recv_size: 0, send_offset: 0, send_size: 0},
objects: %{},
dc_capable?: false,
signals: signals,
behavior: __MODULE__
}
end
def channel_signal_specs(direction, channel_count, opts \\ []) do
channel_layout(direction, channel_count, opts)
|> elem(1)
end
def pdo_entries(direction, channel_count, opts \\ []) do
channel_layout(direction, channel_count, opts)
|> elem(0)
end
def signal_specs, do: image_signal_specs()
defp image_signal_specs do
%{
out: %{
direction: :output,
pdo_index: @default_output_pdo,
bit_offset: 0,
bit_size: 8,
type: :u8,
label: "Output",
group: :outputs
},
in: %{
direction: :input,
pdo_index: @default_input_pdo,
bit_offset: 0,
bit_size: 8,
type: :u8,
label: "Input",
group: :inputs
}
}
end
defp image_pdo_entries do
[
%{index: @default_output_pdo, direction: :output, sm_index: 2, bit_size: 8},
%{index: @default_input_pdo, direction: :input, sm_index: 3, bit_size: 8}
]
end
defp channel_layout(direction, channel_count, opts)
when direction in [:input, :output, :io] and is_integer(channel_count) and
channel_count > 0 do
input_names = Keyword.get(opts, :input_names, default_names(channel_count))
output_names = Keyword.get(opts, :output_names, default_names(channel_count))
input_pdo_base = Keyword.get(opts, :input_pdo_base, @default_input_pdo)
output_pdo_base = Keyword.get(opts, :output_pdo_base, @default_output_pdo)
input_signals = build_channel_signals(:input, input_names, input_pdo_base)
output_signals = build_channel_signals(:output, output_names, output_pdo_base)
{pdo_entries, signals, output_size, input_size} =
case direction do
:input ->
{build_channel_pdos(:input, input_pdo_base, input_names), input_signals, 0,
bit_bytes(length(input_names))}
:output ->
{build_channel_pdos(:output, output_pdo_base, output_names), output_signals,
bit_bytes(length(output_names)), 0}
:io ->
{build_channel_pdos(:output, output_pdo_base, output_names) ++
build_channel_pdos(:input, input_pdo_base, input_names),
Map.merge(output_signals, input_signals), bit_bytes(length(output_names)),
bit_bytes(length(input_names))}
end
mirror_output_to_input? =
direction == :io and Keyword.get(opts, :mirror_output_to_input?, true)
{pdo_entries, signals, output_size, input_size, mirror_output_to_input?}
end
defp build_channel_pdos(direction, base, names) do
sm_index =
case direction do
:output -> 2
:input -> 3
end
names
|> Enum.with_index()
|> Enum.map(fn {_name, index} ->
%{index: base + index, direction: direction, sm_index: sm_index, bit_size: 1}
end)
end
defp build_channel_signals(direction, names, pdo_base) do
names
|> Enum.with_index()
|> Enum.into(%{}, fn {name, index} ->
{name,
%{
direction: direction,
pdo_index: pdo_base + index,
bit_offset: lsb_first_bit_offset(index),
bit_size: 1,
type: :bool,
label: default_label(direction, index + 1),
group: default_group(direction)
}}
end)
end
defp default_names(channel_count) do
Enum.map(1..channel_count, &String.to_atom("ch#{&1}"))
end
defp default_label(:input, channel), do: "Input #{channel}"
defp default_label(:output, channel), do: "Output #{channel}"
defp default_group(:input), do: :inputs
defp default_group(:output), do: :outputs
defp bit_bytes(bit_count) do
div(bit_count + 7, 8)
end
defp lsb_first_bit_offset(index) do
div(index, 8) * 8 + (7 - rem(index, 8))
end
def init(_definition), do: %{}
end