Any recommendations on a MQTT client for Elixir?

Hello,

Is there a good mqtt client library for Elixir? Getting a bit disillusioned, none of the options seem very good.

  • GitHub - gausby/tortoise: A MQTT Client written in Elixir - initially seemed very good. Particularly with the rewrite mqtt-5 support - which I suspect is in the mqtt-5 branch. But has numerous bugs, particularly when mqtt server goes down (pub requests can hang - hanging my application) or if trying to dynamically subscribe to topics at run-time. These errors aren’t always easy to reproduce on demand, and at one stage I was told that there were fundamental design issues. Was looking forward to the rewrite, but development has stalled, and status unclear. I don’t blame the author in anyway (I have a lot of projects in this state myself). Would consider contributing to mqtt-5 branch development if this is the best option.

  • GitHub - brianbinbin/exmqtt: Elixir MQTT v5.0 Client - no development for 15 months. But does support MQTT 5 - according to description at least.

  • A number of other options that look hopeful, but looks like development has also stalled.

  • Maybe I should be looking at erlang solutions? e.g. GitHub - emqx/emqtt: Erlang MQTT v5.0 Client looks like it is actively maintained.

So wondering what the best option forward is. Examples:

  • Take over the development of the mqtt-5 branch on tortoise.
  • Take over another project.
  • Learn about MQTT-5 and start my own project from scratch?
  • Write Elixir wrapper for Erlang solution.

I tend to favor the last option right now. Unless of course somebody has already written such a wrapper.

But would appreciate any opinions,

3 Likes

GitHub - ryanwinchester/exmqtt: Elixir wrapper for emqx/emqtt - interesting. No development for 2 years. But might be really good starting point.

1 Like

Hmmm. Something weird with emqtt. exmqtt a lot of nice logic to handle gentle back-offs with retries if there is a connection fault. But none of this actually gets used.

Still not 100% clear what is going on. It appears that emqtt.connect(conn_pid) |> IO.inspect() returns and will print the error result. But if I have a call to IO.puts("xxxx") immediately following, it will never get called. Then the genserver process gets recreated 4 times.

Initially I thought maybe an exception is being generated and silently discarded. But I can’t see any evidence of this actually happening.

I think that the genserver process is silently getting killed by another process immediately after an error. And the supervisor recreates it 4 times, before the supervisor dies. Which is standard supervisor behavior. But the supervisor doesn’t print any messages about the process having died. But still is my most likely theory. Maybe the supervisor sees it as a normal exit, not an error exit, so doesn’t print anything.

If correct, this is a bit awkward, I want to process the errors myself, and not have my genserver killed.

If anybody else is interested in testing, I can publish my test code and it should be easy to reproduce.

1 Like

My bad. Need to call:

Process.flag(:trap_exit, true)

Duh!

I would go with emqtt.
There seems to be zero documentation on the web how to use it with Elixir.
We should change that.

Here is a starting point. (connects to test.mosquitto.org with no auth and subscribes to # so you get lots of messages)

deps (latest - 1.4.0 - seems to be out of sync with the docs in readme)

{:emqtt, github: "emqx/emqtt", tag: "v1.2.0"},

some code

defmodule MqttHowto do
  def hello do
    opts = [
      clientid: "my_client_id",
      host: 'test.mosquitto.org',
      port: 1883
    ]

    {:ok, pid} = :emqtt.start_link(opts)
    {:ok, _} = :emqtt.connect(pid)
    {:ok, _, _} = :emqtt.subscribe(pid, %{}, [{"#", []}])

    recv()
  end

  def recv() do
    receive do
      message -> IO.inspect(message)
    end

    recv()
  end
end

yes please do that

See GitHub - brianmay/exmqtt: Elixir wrapper for emqx/emqtt for what I have done so far.

Basically a fork for the existing exmqtt, with some improvements. Including writing a simple test client. Which is a layer on top of emqtt. Which seems to be the recommended approach based on feedback so far.

Going to have to rename the project so I have the option to upload to hex.pm - this name is already taken by what looks like a different project. Thinking maybe mqtt_potion or something.

Progress: receiving messages works, reconnect appears to work if server goes down. But not tested any of the publish or subscribe methods.

Also the disconnect method is perhaps a bit misleading, it is more like a ‘reconnect’ I think (not tested).

Probably should test what happens if the client makes requests and the connection is offline. Ideally this shouldn’t kill the genserver and error returned to client (if sync request).

Pull requests welcome :slight_smile:

Looks like my first attempt to build emqtt in github actions was a little bit less then successful.

[ 83%] Building C object CMakeFiles/quicer_static.dir/c_src/quicer_listener.c.o
cc1: error: too many filenames given.  Type cc1 --help for usage
cc1: error: too many filenames given.  Type cc1 --help for usage
cc1: fatal error: CMakeFiles/quicer_static.dir/c_src/quicer_queue.c.d: No such file or directory
compilation terminated.

Huh? Weird.

Do you think its worth the effort to build a wrapper?
Obviously there is very low demand for an Elixir MQTT-client.
You are forking a repo that received one like in two years…

I’d use emqtt directly. Some documentation how to do that would be really helpful.

I think there are Two questions here, which I will answer:

  1. Why create a separate project?

I will face the same challenges regardless if I add the code directly to my project or not. Plus this way I am more likely to be able to get help, then if it it integrated into my project with numerous external dependencies. And I get to reuse the same code in my other projects.

There is at least one other open source software project I use that uses tortoise. Would be good to move these projects to something better supported. In fact based on the tortoise bug reports I see, I think there are a number of people who incorrectly feel that is the only option available. So demand might be higher then you think.

  1. Why base on project that is 2 years old?

The code structure + my changes is very similar to what I would be used anyway, and similar to what my existing code already expects (being based on tortoise).

While your code certainly looks simple, I believe it wouldn’t try to gracefully reconnect if there is a connection error. In fact a connection error will kill the process that called hello - as I discovered earlier - because the emqtt process is linked, when the connection dies, it dies, which will in turn kill the calling process. As these are long running processes, that is important for me, and where most of the complexity lies.

Yes better documentation would be very useful. e.g. Took me ages to work out how to get mqtt over TLS working with peer verification enabled.

2 Likes

I wonder if emqtt supports IPv6? My suspicion is maybe not. As I believe my hostname I provided was getting translated to an IPv4 address, which is why the TLS peer verification wasn’t working.

Later: Looks like this issue was closed without actually fixing the problem :frowning:

I understand your points.
Sure not a bad thing for the community to have a wrapper of emqtt.
Still I’d rather use emqtt directly before pulling in a dependency.

The code I posted is just the first step, but it will surely be way less than 500loc.

It’s not directly used in :gen_udp.connect options (see: emqtt_sock.erl) but you can try to override the default configuration:

https://erlang.org/doc/apps/erts/inet_cfg.html

(never done this)

The next step will be to try to get this working in Github CI. This is going to be a bit useless if it breaks all my CI tests :frowning:

On the topic of IPv6, looks like I need to set:

tcp_opts: [
    :inet6
],

Which is one of the opts supported by emqtt. It annoys me that this isn’t the default in erlang - it should be the default to try IPv6 then IPv4 - but I guess we are stuck with legacy code and the need to provide a stable API.

I might as well describe my TLS solution. I need to set:

      ssl_opts: [
        server_name_indication: 'hostname.example.org',
        verify: :verify_peer,
        cacertfile: mqtt_ca_cert_file
      ],

If I don’t set the server_name_indication to the hostname (must be a charlist!), the hostname verification of the certificate fails. The documentation seems to indicate that this should default to the hostname supplied to the connect call - Erlang -- ssl - which I thought meant it should be OK if I don’t supply it. But after using the debugger on the code (and only vaguely understanding it) I strongly suspect the supplied hostname is the IP address, which - as per the documentation (and common sense!) won’t work.

Oh there is definitely demand for an elixir MQTT client.
I tried several days to get a solution that connects to AWS IOT.

I tried tortoise and the erlang one.
The problem was always SSL on the beam. Never managed to get it working.
But this was in OTP 20 and there were some problems in SSL/crypto around that release I think.

Any ideas of the build failures? What seems weird is that it starts off compiling files fine. Then it gets to a point where it won’t compile the file even when repeatedly re-tried.

Tried a number of things, all of them failed. Running out of ideas.

Just noticed somebody opened a bug report. Hmmm. Wonder if this was in response to this post…

Will see if I can add details.

Hmmm. Eventually I probably will want to try the latest version. But I don’t see anything like a changelog documenting breaking changes.

Any ideas what changes I will need to make?

LATER: on second thoughts the changes don’t look particularly significant:

I just tried my code with

{:emqtt, github: "emqx/emqtt", tag: "1.4.2"},

and it works. I took no notes (bad) so I don’t know what the problem was.

About your build problems: works for me, so it must work in Github Actions (when you use the same toolchain, check all versions.).

Latest theory is that it might be related to the cmake version version. Downgrading to an earlier cmake seems to fix it.

Seems like the challenges are

  • IPv6
  • different encryption and authentication methods

mosquitto has multiple servers running at test.mosquitto.org providing these methods:

The server listens on the following ports:

1883 : MQTT, unencrypted, unauthenticated
1884 : MQTT, unencrypted, authenticated
8883 : MQTT, encrypted, unauthenticated
8884 : MQTT, encrypted, client certificate required
8885 : MQTT, encrypted, authenticated
8887 : MQTT, encrypted, server certificate deliberately expired
… snip: MQTT over websockets

I’d like to get them all working with emqtt, for this I installed mosquitto-clients as a reference implementation:

sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa
sudo apt-get update
sudo apt-get install mosquitto mosquitto-clients

after downloading the provided cert and generating my own (see https://test.mosquitto.org/) I can successfully test all of them but those that are authenticated, because I’m missing the credentials, anybody know what to use?


mosquitto_sub -h test.mosquitto.org -p 1883 -t "#"
# TODO - whats the password? --- mosquitto_sub --pw ??? -h test.mosquitto.org -p 1884 -t "#"
mosquitto_sub --cafile mosquitto.org.crt -h test.mosquitto.org -p 8883 -t "#"
mosquitto_sub --cafile mosquitto.org.crt --cert client.crt --key client.key -h test.mosquitto.org -p 8884 -t "#"

EDIT:

emqtt-cli looks almost the same as mosquitto-sub

./emqtt sub -h test.mosquitto.org -p 1883 -t "#"
./emqtt sub --enable-ssl --CAfile mosquitto.org.crt -h test.mosquitto.org -p 8883 -t "#"
./emqtt sub --enable-ssl --CAfile mosquitto.org.crt --cert client.crt --key client.key -h test.mosquitto.org -p 8884 -t "#"