Elixir client for MeshCore companion radios.
This is a port of the Python library
meshcore_py: same features, same
function names, with an OTP/Elixir flavour. Connect over serial, TCP or
BLE, run commands, and react to events.
Quick start
{:ok, mc} = Meshcore.create_serial("/dev/ttyUSB0")
{:ok, contacts} =
case Meshcore.Commands.get_contacts(mc) do
{:ok, event} -> {:ok, event.payload}
{:error, event} -> raise "failed: #{inspect(event.payload)}"
end
contact = contacts |> Map.values() |> hd()
{:ok, _sent} = Meshcore.Commands.send_msg(mc, contact, "Hello from Elixir!")
Meshcore.disconnect(mc)Mapping from Python
| Python | Elixir |
|---|---|
await MeshCore.create_serial(port) | Meshcore.create_serial(port) |
await mc.commands.send_msg(c, "hi") | Meshcore.Commands.send_msg(mc, c, "hi") |
result.type == EventType.ERROR | {:error, %Meshcore.Event{}} |
mc.subscribe(EventType.ACK, cb, filt) | Meshcore.subscribe(mc, :ack, attribute_filters: filt) |
await mc.wait_for_event(...) | Meshcore.wait_for_event(mc, type, opts) |
mc.contacts, mc.self_info | Meshcore.contacts(mc), Meshcore.self_info(mc) |
| timeouts in seconds | timeouts in milliseconds |
See the Migrating from Python guide for the complete list.
Events
Events arrive as messages: {:meshcore, subscription_ref, %Meshcore.Event{}}.
{:ok, sub} = Meshcore.subscribe(mc, :contact_msg_recv)
receive do
{:meshcore, ref, %Meshcore.Event{payload: %{text: text}}} when ref == sub.ref ->
IO.puts(text)
endSupervision
Put a connection in your own supervision tree with child_spec/1:
children = [
{Meshcore,
name: MyApp.Radio,
transport: {Meshcore.Transport.TCP, host: "192.168.1.100", port: 5000},
auto_reconnect: true}
]and use the registered name as mc everywhere.
Summary
Functions
Whether contacts are refreshed automatically on adverts and path updates.
Child specification for supervision trees. Restarts only after abnormal
exits (a disconnect/1 is final).
Connects (when started with connect: false) and runs the app-start
handshake. Returns {:ok, self_info_event} or {:error, reason}.
Whether the transport is connected.
The known contacts, %{public_key_hex => contact}.
true when a push notification suggests the contact list is out of date.
Connects over BLE and performs the handshake.
Connects over a serial port and performs the handshake.
Connects over TCP and performs the handshake.
Default command timeout in milliseconds.
Disconnects from the device and stops the connection process, emitting a
:disconnected event (reason: "manual_disconnect") first.
Loads the contact list if it is empty – or, with follow set, if it is
marked dirty – and returns whether a refresh was requested.
Forgets all pending contacts.
Finds a contact by (case-insensitive, possibly partial) hex public key prefix.
Finds a contact by (case-insensitive) advertised name.
Contacts advertised but not added (manual-add mode), %{public_key_hex => contact}.
Removes and returns a pending contact by full hex public key.
The device's :self_info payload (empty map before the handshake).
Enables or disables automatic contact refresh.
Enables decryption of group-channel packets seen in the RX log. Needed to
annotate :channel_msg_recv events with :path, :snr, :rssi and
:recv_time.
Sets the default command timeout in milliseconds.
Starts pulling queued messages whenever the device announces them
(:messages_waiting), and once immediately. The messages show up as
:contact_msg_recv / :channel_msg_recv events for subscribers.
Starts a connection process, linked to the caller – the supervised variant of
the create_* functions.
Synchronously stops the connection process without emitting events.
Stops automatic message fetching.
Subscribes a process to events.
The last device time seen in a :current_time event (0 if never).
Cancels a subscription. Messages already in the mailbox stay there.
Waits for an event of type (or any of a list of types) matching
:attribute_filters.
Types
@type mc() :: GenServer.server()
Functions
Whether contacts are refreshed automatically on adverts and path updates.
@spec child_spec(keyword()) :: Supervisor.child_spec()
Child specification for supervision trees. Restarts only after abnormal
exits (a disconnect/1 is final).
@spec connect(mc()) :: {:ok, Meshcore.Event.t()} | {:error, term()}
Connects (when started with connect: false) and runs the app-start
handshake. Returns {:ok, self_info_event} or {:error, reason}.
Whether the transport is connected.
The known contacts, %{public_key_hex => contact}.
true when a push notification suggests the contact list is out of date.
Connects over BLE and performs the handshake.
BLE needs a platform adapter (see Meshcore.Transport.BLE.Adapter), passed as
:adapter. Options: :adapter (required), :address (BLE address or part of
the advertised name; nil scans for a MeshCore* device), :pin (pair
after connecting), :adapter_opts, plus the options of create_tcp/3.
Connects over a serial port and performs the handshake.
Requires the optional :circuits_uart dependency. If the device does not
answer, the connection is retried once with the DTR line inverted (some
boards reset on DTR).
Options: :baudrate (default 115200), :dtr (true), :rts (false),
:cx_delay (ms, 100), plus the options of create_tcp/3.
@spec create_tcp(String.t() | :inet.ip_address(), :inet.port_number(), keyword()) :: {:ok, pid()} | {:error, term()}
Connects over TCP and performs the handshake.
Returns {:ok, mc} or {:error, reason}. The connection process is started
under Meshcore.ConnectionSupervisor and is not restarted when it
crashes – use child_spec/1 for that.
Options: :debug, :only_error, :default_timeout, :auto_reconnect,
:max_reconnect_attempts (see start_link/1), :connect_timeout.
@spec default_timeout(mc()) :: pos_integer()
Default command timeout in milliseconds.
@spec disconnect(mc()) :: :ok
Disconnects from the device and stops the connection process, emitting a
:disconnected event (reason: "manual_disconnect") first.
Loads the contact list if it is empty – or, with follow set, if it is
marked dirty – and returns whether a refresh was requested.
@spec flush_pending_contacts(mc()) :: :ok
Forgets all pending contacts.
Finds a contact by (case-insensitive, possibly partial) hex public key prefix.
Finds a contact by (case-insensitive) advertised name.
Contacts advertised but not added (manual-add mode), %{public_key_hex => contact}.
Removes and returns a pending contact by full hex public key.
The device's :self_info payload (empty map before the handshake).
Enables or disables automatic contact refresh.
Enables decryption of group-channel packets seen in the RX log. Needed to
annotate :channel_msg_recv events with :path, :snr, :rssi and
:recv_time.
@spec set_default_timeout(mc(), pos_integer()) :: :ok
Sets the default command timeout in milliseconds.
@spec start_auto_message_fetching(mc()) :: :ok
Starts pulling queued messages whenever the device announces them
(:messages_waiting), and once immediately. The messages show up as
:contact_msg_recv / :channel_msg_recv events for subscribers.
@spec start_link(keyword()) :: GenServer.on_start()
Starts a connection process, linked to the caller – the supervised variant of
the create_* functions.
Unlike create_tcp/3 and friends it does not block on the device: with
connect: true (default) it connects in the background, retrying according to
:auto_reconnect. Use connect/1 to wait for the handshake.
Options:
:transport(required) –{module, options}implementingMeshcore.Transport:name– register the process under this name:connect– connect right after starting (defaulttrue):auto_reconnect– reconnect with exponential backoff (1s, 2s, 4s, 8s…) after the link is lost (defaultfalse):max_reconnect_attempts– default3:default_timeout– command timeout in ms (default15_000):debug/:only_error– raise or lower the log level of this connection (equivalent to Python's flags)
@spec stop(mc()) :: :ok
Synchronously stops the connection process without emitting events.
@spec stop_auto_message_fetching(mc()) :: :ok
Stops automatic message fetching.
@spec subscribe( mc(), Meshcore.Event.type() | [Meshcore.Event.type()] | nil | :all, keyword() ) :: {:ok, Meshcore.Subscription.t()}
Subscribes a process to events.
type is an event type (Meshcore.Event.type/0), a list of types, or nil
/ :all for everything. Matching events are sent to the subscriber as
{:meshcore, ref, %Meshcore.Event{}}, ref being subscription.ref.
Options:
:attribute_filters– map (or keyword list) of attributes the event must carry, e.g.%{channel_idx: 1}or%{pubkey_prefix: "a1b2c3d4e5f6"}:pid– the subscriber (default: the calling process)
Subscriptions end when the subscriber exits or on unsubscribe/1.
@spec time(mc()) :: non_neg_integer()
The last device time seen in a :current_time event (0 if never).
@spec unsubscribe(Meshcore.Subscription.t()) :: :ok
Cancels a subscription. Messages already in the mailbox stay there.
@spec wait_for_event(mc(), Meshcore.Event.type() | [Meshcore.Event.type()], keyword()) :: {:ok, Meshcore.Event.t()} | {:error, :timeout | :disconnected}
Waits for an event of type (or any of a list of types) matching
:attribute_filters.
Returns {:ok, event} or {:error, :timeout}.
Options: :attribute_filters, :timeout (ms, default: the connection's
default timeout).